diff mbox series

[4/4] ACPI: NHLT: Add query functions

Message ID 20230712091048.2545319-5-cezary.rojewski@intel.com
State Superseded
Headers show
Series ACPI: NHLT: Access and query helpers | expand

Commit Message

Cezary Rojewski July 12, 2023, 9:10 a.m. UTC
With iteration helpers added there is a room for more complex query
tasks which are commonly performed by sound drivers. Implement them in
common API so that a unified mechanism is available for all of them.

While the acpi_nhlt_endpoint_dmic_count() stands out a bit, it is a
critical component for any AudioDSP driver to know how many digital
microphones it is dealing with. There is no one perfect method, but the
best one available is provided.

Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
 drivers/acpi/nhlt.c | 183 ++++++++++++++++++++++++++++++++++++++++++++
 include/acpi/nhlt.h |  54 +++++++++++++
 2 files changed, 237 insertions(+)

Comments

Cezary Rojewski July 17, 2023, 8:29 a.m. UTC | #1
On 2023-07-12 5:48 PM, Andy Shevchenko wrote:
> On Wed, Jul 12, 2023 at 11:10:48AM +0200, Cezary Rojewski wrote:

...

>> +bool acpi_nhlt_endpoint_match(const struct acpi_nhlt_endpoint *ep,
>> +			      int link_type, int dev_type, int dir, int bus_id)
>> +{
>> +	return ep &&
>> +	       (link_type < 0 || ep->link_type == link_type) &&
>> +	       (dev_type < 0 || ep->device_type == dev_type) &&
>> +	       (dir < 0 || ep->direction == dir) &&
>> +	       (bus_id < 0 || ep->virtual_bus_id == bus_id);
> 
> I think you can split these for better reading.
> 
> 	if (!ep)
> 		return false;
> 
> 	if (link_type >= 0 && ep->link_type != link_type)
> 		return false;
> 
> 	if (dev_type >= 0 && ep->device_type != dev_type)
> 		return false;
> 
> 	if (dir >= 0 && ep->direction != dir)
> 		return false;
> 
> 	if (bus_id >= 0 && ep->virtual_bus_id != bus_id)
> 		return false;
> 
> 	return true;
> 
> Yes, much more lines, but readability is better in my opinion.
> I also believe that code generation on x86_64 will be the same.

While I favor readability over less lines of code, I do not think 
splitting the conditions makes it easier in this case. Perhaps 
reverse-christmas-tree? Pivoted around '<'.

	return ep &&
	       (link_type < 0 || ep->link_type == link_type) &&
	       (dev_type < 0 || ep->device_type == dev_type) &&
	       (bus_id < 0 || ep->virtual_bus_id == bus_id) &&
	       (dir < 0 || ep->direction == dir);

In general I'd like to distinguish between conditions that one _has to_ 
read and understand and those which reader _may_ pass by. Here, we are 
checking description of an endpoint for equality. Nothing more, nothing 
less.

>> +}

...

>> +struct acpi_nhlt_endpoint *
>> +acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
>> +			int link_type, int dev_type, int dir, int bus_id)
>> +{
>> +	struct acpi_nhlt_endpoint *ep;
> 
>> +	if (!tb)
>> +		return ERR_PTR(-EINVAL);
> 
> Just wondering how often we will have a caller that supplies NULL for tb.

Depends on kernel's philosophy on public API. In general, public API 
should defend themselves from harmful and illegal callers. However, in 
low level areas 'illegal' is sometimes mistaken with illogical. In such 
cases double safety can be dropped.

Or, perhaps you were discussing return value here and ERR_PTR(-EINVAL) 
could be replaced by something else or even NULL.

>> +	for_each_nhlt_endpoint(tb, ep)
>> +		if (acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
>> +			return ep;
>> +	return NULL;
>> +}
> 
> ...
> 
>> +struct acpi_nhlt_format_config *
>> +acpi_nhlt_endpoint_find_fmtcfg(const struct acpi_nhlt_endpoint *ep,
>> +			       u16 ch, u32 rate, u16 vbps, u16 bps)
>> +{
>> +	struct acpi_nhlt_wave_extensible *wav;
>> +	struct acpi_nhlt_format_config *fmt;
> 
>> +	if (!ep)
>> +		return ERR_PTR(-EINVAL);
> 
> Similar Q as above.
> 
>> +	for_each_nhlt_endpoint_fmtcfg(ep, fmt) {
>> +		wav = &fmt->format;
>> +
>> +		if (wav->channel_count == ch &&
>> +		    wav->valid_bits_per_sample == vbps &&
>> +		    wav->bits_per_sample == bps &&
>> +		    wav->samples_per_sec == rate)
> 
> Also can be split, but this one readable in the original form.

As order does not really matter here, perhaps reverse-christmas-tree to 
improve readability?

		if (wav->valid_bits_per_sample == vpbs &&
		    wav->samples_per_sec == rate &&
		    wav->bits_per_sample == bps &&
		    wav->channel_count == ch)

>> +			return fmt;
>> +	}
>> +
>> +	return NULL;
>> +}

...

>> +struct acpi_nhlt_format_config *
>> +acpi_nhlt_find_fmtcfg(const struct acpi_table_nhlt *tb,
>> +		      int link_type, int dev_type, int dir, int bus_id,
>> +		      u16 ch, u32 rate, u16 vbps, u16 bps)
>> +{
>> +	struct acpi_nhlt_format_config *fmt;
>> +	struct acpi_nhlt_endpoint *ep;
> 
>> +	if (!tb)
>> +		return ERR_PTR(-EINVAL);
> 
> Same as above.
> Looking at them, wouldn't simply returning NULL suffice?
> 
>> +	for_each_nhlt_endpoint(tb, ep) {
>> +		if (!acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
>> +			continue;
>> +
>> +		fmt = acpi_nhlt_endpoint_find_fmtcfg(ep, ch, rate, vbps, bps);
>> +		if (fmt)
>> +			return fmt;
>> +	}
>> +
>> +	return NULL;
>> +}
> 
> ...
> 
>> +int acpi_nhlt_endpoint_dmic_count(const struct acpi_nhlt_endpoint *ep)
>> +{
>> +	struct acpi_nhlt_vendor_mic_devcfg *vendor_cfg;
>> +	struct acpi_nhlt_format_config *fmt;
>> +	struct acpi_nhlt_mic_devcfg *devcfg;
>> +	u16 max_ch = 0;
>> +
>> +	if (!ep || ep->link_type != ACPI_NHLT_PDM)
>> +		return -EINVAL;
>> +
>> +	/* Find max number of channels based on formats configuration. */
>> +	for_each_nhlt_endpoint_fmtcfg(ep, fmt)
>> +		max_ch = max(fmt->format.channel_count, max_ch);
>> +
>> +	/* If @ep not a mic array, fallback to channels count. */
>> +	devcfg = acpi_nhlt_endpoint_mic_devcfg(ep);
>> +	if (!devcfg || devcfg->config_type != ACPI_NHLT_CONFIG_TYPE_MIC_ARRAY)
>> +		return max_ch;
>> +
>> +	switch (devcfg->array_type) {
>> +	case ACPI_NHLT_SMALL_LINEAR_2ELEMENT:
>> +	case ACPI_NHLT_BIG_LINEAR_2ELEMENT:
>> +		return 2;
>> +
>> +	case ACPI_NHLT_FIRST_GEOMETRY_LINEAR_4ELEMENT:
>> +	case ACPI_NHLT_PLANAR_LSHAPED_4ELEMENT:
>> +	case ACPI_NHLT_SECOND_GEOMETRY_LINEAR_4ELEMENT:
>> +		return 4;
>> +
>> +	case ACPI_NHLT_VENDOR_DEFINED:
>> +		vendor_cfg = acpi_nhlt_endpoint_vendor_mic_devcfg(ep);
>> +		if (!vendor_cfg)
>> +			return -EINVAL;
>> +		return vendor_cfg->num_mics;
>> +
>> +	default:
> 
>> +		pr_warn("undefined mic array type: %#x\n", devcfg->array_type);
> 
> Is this function can ever be called without backing device? If not,
> I would supply (ACPI?) device pointer and use dev_warn() instead.
> 
> But I'm not sure about this. Up to you, folks.

Given what's our there in the market I wouldn't say it's impossible to 
encounter such scenario. Could you elaborate on what you meant by 
"supply device pointer"?

>> +		return max_ch;
>> +	}
>> +}
>
Andy Shevchenko July 17, 2023, 9:47 a.m. UTC | #2
On Mon, Jul 17, 2023 at 10:29:17AM +0200, Cezary Rojewski wrote:
> On 2023-07-12 5:48 PM, Andy Shevchenko wrote:
> > On Wed, Jul 12, 2023 at 11:10:48AM +0200, Cezary Rojewski wrote:

...

> > > +	return ep &&
> > > +	       (link_type < 0 || ep->link_type == link_type) &&
> > > +	       (dev_type < 0 || ep->device_type == dev_type) &&
> > > +	       (dir < 0 || ep->direction == dir) &&
> > > +	       (bus_id < 0 || ep->virtual_bus_id == bus_id);
> > 
> > I think you can split these for better reading.
> > 
> > 	if (!ep)
> > 		return false;
> > 
> > 	if (link_type >= 0 && ep->link_type != link_type)
> > 		return false;
> > 
> > 	if (dev_type >= 0 && ep->device_type != dev_type)
> > 		return false;
> > 
> > 	if (dir >= 0 && ep->direction != dir)
> > 		return false;
> > 
> > 	if (bus_id >= 0 && ep->virtual_bus_id != bus_id)
> > 		return false;
> > 
> > 	return true;
> > 
> > Yes, much more lines, but readability is better in my opinion.
> > I also believe that code generation on x86_64 will be the same.
> 
> While I favor readability over less lines of code, I do not think splitting
> the conditions makes it easier in this case. Perhaps reverse-christmas-tree?
> Pivoted around '<'.
> 
> 	return ep &&
> 	       (link_type < 0 || ep->link_type == link_type) &&
> 	       (dev_type < 0 || ep->device_type == dev_type) &&
> 	       (bus_id < 0 || ep->virtual_bus_id == bus_id) &&
> 	       (dir < 0 || ep->direction == dir);

This one definitely better.

> In general I'd like to distinguish between conditions that one _has to_ read
> and understand and those which reader _may_ pass by. Here, we are checking
> description of an endpoint for equality. Nothing more, nothing less.

...

> > > +struct acpi_nhlt_endpoint *
> > > +acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
> > > +			int link_type, int dev_type, int dir, int bus_id)
> > > +{
> > > +	struct acpi_nhlt_endpoint *ep;
> > 
> > > +	if (!tb)
> > > +		return ERR_PTR(-EINVAL);
> > 
> > Just wondering how often we will have a caller that supplies NULL for tb.
> 
> Depends on kernel's philosophy on public API. In general, public API should
> defend themselves from harmful and illegal callers. However, in low level
> areas 'illegal' is sometimes mistaken with illogical. In such cases double
> safety can be dropped.

What do you put under "public"? ABI? Or just internally available API?
If the latter, we don't do defensive programming there, we usually just
add NULL(invalid data)-awareness to the free()-like functions.

> Or, perhaps you were discussing return value here and ERR_PTR(-EINVAL) could
> be replaced by something else or even NULL.

I prefer to get rid of those.

> > > +	for_each_nhlt_endpoint(tb, ep)
> > > +		if (acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
> > > +			return ep;
> > > +	return NULL;
> > > +}

...

> > > +		if (wav->channel_count == ch &&
> > > +		    wav->valid_bits_per_sample == vbps &&
> > > +		    wav->bits_per_sample == bps &&
> > > +		    wav->samples_per_sec == rate)
> > 
> > Also can be split, but this one readable in the original form.
> 
> As order does not really matter here, perhaps reverse-christmas-tree to
> improve readability?
> 
> 		if (wav->valid_bits_per_sample == vpbs &&
> 		    wav->samples_per_sec == rate &&
> 		    wav->bits_per_sample == bps &&
> 		    wav->channel_count == ch)

OK!

> > > +			return fmt;

...

> > > +	default:
> > 
> > > +		pr_warn("undefined mic array type: %#x\n", devcfg->array_type);
> > 
> > Is this function can ever be called without backing device? If not,
> > I would supply (ACPI?) device pointer and use dev_warn() instead.
> > 
> > But I'm not sure about this. Up to you, folks.
> 
> Given what's our there in the market I wouldn't say it's impossible to
> encounter such scenario. Could you elaborate on what you meant by "supply
> device pointer"?

In the caller (assuming it has ACPI device):

	ret = acpi_nhlt_endpoint_dmic_count(adev, ep);
	if (ret < 0)
		...

> > > +		return max_ch;
> > > +	}
Cezary Rojewski July 17, 2023, 11:25 a.m. UTC | #3
On 2023-07-17 11:47 AM, Andy Shevchenko wrote:
> On Mon, Jul 17, 2023 at 10:29:17AM +0200, Cezary Rojewski wrote:
>> On 2023-07-12 5:48 PM, Andy Shevchenko wrote:
>>> On Wed, Jul 12, 2023 at 11:10:48AM +0200, Cezary Rojewski wrote:

...

>>>> +	return ep &&
>>>> +	       (link_type < 0 || ep->link_type == link_type) &&
>>>> +	       (dev_type < 0 || ep->device_type == dev_type) &&
>>>> +	       (dir < 0 || ep->direction == dir) &&
>>>> +	       (bus_id < 0 || ep->virtual_bus_id == bus_id);
>>>
>>> I think you can split these for better reading.
>>>
>>> 	if (!ep)
>>> 		return false;
>>>
>>> 	if (link_type >= 0 && ep->link_type != link_type)
>>> 		return false;
>>>
>>> 	if (dev_type >= 0 && ep->device_type != dev_type)
>>> 		return false;
>>>
>>> 	if (dir >= 0 && ep->direction != dir)
>>> 		return false;
>>>
>>> 	if (bus_id >= 0 && ep->virtual_bus_id != bus_id)
>>> 		return false;
>>>
>>> 	return true;
>>>
>>> Yes, much more lines, but readability is better in my opinion.
>>> I also believe that code generation on x86_64 will be the same.
>>
>> While I favor readability over less lines of code, I do not think splitting
>> the conditions makes it easier in this case. Perhaps reverse-christmas-tree?
>> Pivoted around '<'.
>>
>> 	return ep &&
>> 	       (link_type < 0 || ep->link_type == link_type) &&
>> 	       (dev_type < 0 || ep->device_type == dev_type) &&
>> 	       (bus_id < 0 || ep->virtual_bus_id == bus_id) &&
>> 	       (dir < 0 || ep->direction == dir);
> 
> This one definitely better.

Ack.

>> In general I'd like to distinguish between conditions that one _has to_ read
>> and understand and those which reader _may_ pass by. Here, we are checking
>> description of an endpoint for equality. Nothing more, nothing less.
> 
> ...
> 
>>>> +struct acpi_nhlt_endpoint *
>>>> +acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
>>>> +			int link_type, int dev_type, int dir, int bus_id)
>>>> +{
>>>> +	struct acpi_nhlt_endpoint *ep;
>>>
>>>> +	if (!tb)
>>>> +		return ERR_PTR(-EINVAL);
>>>
>>> Just wondering how often we will have a caller that supplies NULL for tb.
>>
>> Depends on kernel's philosophy on public API. In general, public API should
>> defend themselves from harmful and illegal callers. However, in low level
>> areas 'illegal' is sometimes mistaken with illogical. In such cases double
>> safety can be dropped.
> 
> What do you put under "public"? ABI? Or just internally available API?
> If the latter, we don't do defensive programming there, we usually just
> add NULL(invalid data)-awareness to the free()-like functions.

Thanks for explaining!

>> Or, perhaps you were discussing return value here and ERR_PTR(-EINVAL) could
>> be replaced by something else or even NULL.
> 
> I prefer to get rid of those.

Agreed.

>>>> +	for_each_nhlt_endpoint(tb, ep)
>>>> +		if (acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
>>>> +			return ep;
>>>> +	return NULL;
>>>> +}
> 
> ...
> 
>>>> +		if (wav->channel_count == ch &&
>>>> +		    wav->valid_bits_per_sample == vbps &&
>>>> +		    wav->bits_per_sample == bps &&
>>>> +		    wav->samples_per_sec == rate)
>>>
>>> Also can be split, but this one readable in the original form.
>>
>> As order does not really matter here, perhaps reverse-christmas-tree to
>> improve readability?
>>
>> 		if (wav->valid_bits_per_sample == vpbs &&
>> 		    wav->samples_per_sec == rate &&
>> 		    wav->bits_per_sample == bps &&
>> 		    wav->channel_count == ch)
> 
> OK!

Ack.

>>>> +			return fmt;
> 
> ...
> 
>>>> +	default:
>>>
>>>> +		pr_warn("undefined mic array type: %#x\n", devcfg->array_type);
>>>
>>> Is this function can ever be called without backing device? If not,
>>> I would supply (ACPI?) device pointer and use dev_warn() instead.
>>>
>>> But I'm not sure about this. Up to you, folks.
>>
>> Given what's our there in the market I wouldn't say it's impossible to
>> encounter such scenario. Could you elaborate on what you meant by "supply
>> device pointer"?
> 
> In the caller (assuming it has ACPI device):
> 
> 	ret = acpi_nhlt_endpoint_dmic_count(adev, ep);
> 	if (ret < 0)
> 		...
> 

Thanks for explaining. I'm going to kindly disagree here - dev pointer 
would be here only to print the warning. NHLT is also independent of any 
device - even if its present in the system, one may not have a single 
device that utilizes it. Worth mentioning is fact that all other 
functions do not accept such argument. Doing so here alone would break 
API consistency.

>>>> +		return max_ch;
>>>> +	}
>
Cezary Rojewski July 18, 2023, 11:11 a.m. UTC | #4
On 2023-07-17 11:47 AM, Andy Shevchenko wrote:
> On Mon, Jul 17, 2023 at 10:29:17AM +0200, Cezary Rojewski wrote:
>> On 2023-07-12 5:48 PM, Andy Shevchenko wrote:
>>> On Wed, Jul 12, 2023 at 11:10:48AM +0200, Cezary Rojewski wrote:

...

>>>> +struct acpi_nhlt_endpoint *
>>>> +acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
>>>> +			int link_type, int dev_type, int dir, int bus_id)
>>>> +{
>>>> +	struct acpi_nhlt_endpoint *ep;
>>>
>>>> +	if (!tb)
>>>> +		return ERR_PTR(-EINVAL);
>>>
>>> Just wondering how often we will have a caller that supplies NULL for tb.
>>
>> Depends on kernel's philosophy on public API. In general, public API should
>> defend themselves from harmful and illegal callers. However, in low level
>> areas 'illegal' is sometimes mistaken with illogical. In such cases double
>> safety can be dropped.
> 
> What do you put under "public"? ABI? Or just internally available API?
> If the latter, we don't do defensive programming there, we usually just
> add NULL(invalid data)-awareness to the free()-like functions.
> 
>> Or, perhaps you were discussing return value here and ERR_PTR(-EINVAL) could
>> be replaced by something else or even NULL.
> 
> I prefer to get rid of those.

Decided to do some manual tests on more exotic setups that are not part 
of our daily CI/CD routine and, completely getting rid of those ifs 
causes problems. Those setups are part of the market, expose DSP 
capabilities but have invalid BIOS configurations.

Rather than just bringing back the if-statement, different solution came 
to my mind:

static struct acpi_table_nhlt empty_nhlt = {
	.header = {
		.signature = ACPI_SIG_NHLT,
	},
};

struct acpi_table_nhlt *acpi_gbl_NHLT;
EXPORT_SYMBOL_GPL(acpi_gbl_NHLT);

acpi_status acpi_nhlt_get_gbl_table(void)
{
	acpi_status status;

	status = acpi_get_table(ACPI_SIG_NHLT, 0, (struct acpi_table_header 
**)(&acpi_gbl_NHLT));
	if (!acpi_gbl_NHLT)
		acpi_gbl_NHLT = &empty_nhlt;
	return status;
}
EXPORT_SYMBOL_GPL(acpi_nhlt_get_gbl_table);


What do you think?
Andy Shevchenko July 18, 2023, 2:17 p.m. UTC | #5
On Tue, Jul 18, 2023 at 01:11:03PM +0200, Cezary Rojewski wrote:
> On 2023-07-17 11:47 AM, Andy Shevchenko wrote:
> > On Mon, Jul 17, 2023 at 10:29:17AM +0200, Cezary Rojewski wrote:

...

> > I prefer to get rid of those.
> 
> Decided to do some manual tests on more exotic setups that are not part of
> our daily CI/CD routine and, completely getting rid of those ifs causes
> problems. Those setups are part of the market, expose DSP capabilities but
> have invalid BIOS configurations.
> 
> Rather than just bringing back the if-statement, different solution came to
> my mind:
> 
> static struct acpi_table_nhlt empty_nhlt = {
> 	.header = {
> 		.signature = ACPI_SIG_NHLT,
> 	},
> };
> 
> struct acpi_table_nhlt *acpi_gbl_NHLT;
> EXPORT_SYMBOL_GPL(acpi_gbl_NHLT);
> 
> acpi_status acpi_nhlt_get_gbl_table(void)
> {
> 	acpi_status status;
> 
> 	status = acpi_get_table(ACPI_SIG_NHLT, 0, (struct acpi_table_header
> **)(&acpi_gbl_NHLT));
> 	if (!acpi_gbl_NHLT)
> 		acpi_gbl_NHLT = &empty_nhlt;
> 	return status;
> }
> EXPORT_SYMBOL_GPL(acpi_nhlt_get_gbl_table);
> 
> What do you think?

I think it's wonderful what you found and I dunno how I missed this.
Go for this, definitely!
diff mbox series

Patch

diff --git a/drivers/acpi/nhlt.c b/drivers/acpi/nhlt.c
index 90d74d0d803e..c61cdfd78b74 100644
--- a/drivers/acpi/nhlt.c
+++ b/drivers/acpi/nhlt.c
@@ -6,8 +6,191 @@ 
 //          Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
 //
 
+#define pr_fmt(fmt) "ACPI: NHLT: " fmt
+
 #include <linux/export.h>
 #include <acpi/nhlt.h>
 
 struct acpi_table_nhlt *acpi_gbl_NHLT;
 EXPORT_SYMBOL_GPL(acpi_gbl_NHLT);
+
+/**
+ * acpi_nhlt_endpoint_match - Verify if an endpoint matches criteria.
+ * @ep:			the endpoint to check.
+ * @link_type:		the hardware link type, e.g.: PDM or SSP.
+ * @dev_type:		the device type.
+ * @dir:		stream direction.
+ * @bus_id:		the ID of virtual bus hosting the endpoint.
+ *
+ * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative
+ * value to ignore the parameter when matching.
+ *
+ * Return: %true if endpoint matches specified criteria or %false otherwise.
+ */
+bool acpi_nhlt_endpoint_match(const struct acpi_nhlt_endpoint *ep,
+			      int link_type, int dev_type, int dir, int bus_id)
+{
+	return ep &&
+	       (link_type < 0 || ep->link_type == link_type) &&
+	       (dev_type < 0 || ep->device_type == dev_type) &&
+	       (dir < 0 || ep->direction == dir) &&
+	       (bus_id < 0 || ep->virtual_bus_id == bus_id);
+}
+EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_match);
+
+/**
+ * acpi_nhlt_find_endpoint - Search a NHLT table for an endpoint.
+ * @tb:			the table to search.
+ * @link_type:		the hardware link type, e.g.: PDM or SSP.
+ * @dev_type:		the device type.
+ * @dir:		stream direction.
+ * @bus_id:		the ID of virtual bus hosting the endpoint.
+ *
+ * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative
+ * value to ignore the parameter during the search.
+ *
+ * Return: A pointer to endpoint matching the criteria, %NULL if not found or
+ * an ERR_PTR() otherwise.
+ */
+struct acpi_nhlt_endpoint *
+acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
+			int link_type, int dev_type, int dir, int bus_id)
+{
+	struct acpi_nhlt_endpoint *ep;
+
+	if (!tb)
+		return ERR_PTR(-EINVAL);
+
+	for_each_nhlt_endpoint(tb, ep)
+		if (acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
+			return ep;
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(acpi_nhlt_find_endpoint);
+
+/**
+ * acpi_nhlt_endpoint_find_fmtcfg - Search endpoint's formats configuration space
+ *                                  for a specific format.
+ * @ep:			the endpoint to search.
+ * @ch:			number of channels.
+ * @rate:		samples per second.
+ * @vbps:		valid bits per sample.
+ * @bps:		bits per sample.
+ *
+ * Return: A pointer to format matching the criteria, %NULL if not found or
+ * an ERR_PTR() otherwise.
+ */
+struct acpi_nhlt_format_config *
+acpi_nhlt_endpoint_find_fmtcfg(const struct acpi_nhlt_endpoint *ep,
+			       u16 ch, u32 rate, u16 vbps, u16 bps)
+{
+	struct acpi_nhlt_wave_extensible *wav;
+	struct acpi_nhlt_format_config *fmt;
+
+	if (!ep)
+		return ERR_PTR(-EINVAL);
+
+	for_each_nhlt_endpoint_fmtcfg(ep, fmt) {
+		wav = &fmt->format;
+
+		if (wav->channel_count == ch &&
+		    wav->valid_bits_per_sample == vbps &&
+		    wav->bits_per_sample == bps &&
+		    wav->samples_per_sec == rate)
+			return fmt;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_find_fmtcfg);
+
+/**
+ * acpi_nhlt_find_fmtcfg - Search a NHLT table for a specific format.
+ * @tb:			the table to search.
+ * @link_type:		the hardware link type, e.g.: PDM or SSP.
+ * @dev_type:		the device type.
+ * @dir:		stream direction.
+ * @bus_id:		the ID of virtual bus hosting the endpoint.
+ *
+ * @ch:			number of channels.
+ * @rate:		samples per second.
+ * @vbps:		valid bits per sample.
+ * @bps:		bits per sample.
+ *
+ * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative
+ * value to ignore the parameter during the search.
+ *
+ * Return: A pointer to format matching the criteria, %NULL if not found or
+ * an ERR_PTR() otherwise.
+ */
+struct acpi_nhlt_format_config *
+acpi_nhlt_find_fmtcfg(const struct acpi_table_nhlt *tb,
+		      int link_type, int dev_type, int dir, int bus_id,
+		      u16 ch, u32 rate, u16 vbps, u16 bps)
+{
+	struct acpi_nhlt_format_config *fmt;
+	struct acpi_nhlt_endpoint *ep;
+
+	if (!tb)
+		return ERR_PTR(-EINVAL);
+
+	for_each_nhlt_endpoint(tb, ep) {
+		if (!acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id))
+			continue;
+
+		fmt = acpi_nhlt_endpoint_find_fmtcfg(ep, ch, rate, vbps, bps);
+		if (fmt)
+			return fmt;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(acpi_nhlt_find_fmtcfg);
+
+/**
+ * acpi_nhlt_endpoint_dmic_count - Retrieve number of digital microphones for a PDM endpoint.
+ * @ep:			the endpoint to return microphones count for.
+ *
+ * Return: A number of microphones or an error code if an invalid endpoint is provided.
+ */
+int acpi_nhlt_endpoint_dmic_count(const struct acpi_nhlt_endpoint *ep)
+{
+	struct acpi_nhlt_vendor_mic_devcfg *vendor_cfg;
+	struct acpi_nhlt_format_config *fmt;
+	struct acpi_nhlt_mic_devcfg *devcfg;
+	u16 max_ch = 0;
+
+	if (!ep || ep->link_type != ACPI_NHLT_PDM)
+		return -EINVAL;
+
+	/* Find max number of channels based on formats configuration. */
+	for_each_nhlt_endpoint_fmtcfg(ep, fmt)
+		max_ch = max(fmt->format.channel_count, max_ch);
+
+	/* If @ep not a mic array, fallback to channels count. */
+	devcfg = acpi_nhlt_endpoint_mic_devcfg(ep);
+	if (!devcfg || devcfg->config_type != ACPI_NHLT_CONFIG_TYPE_MIC_ARRAY)
+		return max_ch;
+
+	switch (devcfg->array_type) {
+	case ACPI_NHLT_SMALL_LINEAR_2ELEMENT:
+	case ACPI_NHLT_BIG_LINEAR_2ELEMENT:
+		return 2;
+
+	case ACPI_NHLT_FIRST_GEOMETRY_LINEAR_4ELEMENT:
+	case ACPI_NHLT_PLANAR_LSHAPED_4ELEMENT:
+	case ACPI_NHLT_SECOND_GEOMETRY_LINEAR_4ELEMENT:
+		return 4;
+
+	case ACPI_NHLT_VENDOR_DEFINED:
+		vendor_cfg = acpi_nhlt_endpoint_vendor_mic_devcfg(ep);
+		if (!vendor_cfg)
+			return -EINVAL;
+		return vendor_cfg->num_mics;
+
+	default:
+		pr_warn("undefined mic array type: %#x\n", devcfg->array_type);
+		return max_ch;
+	}
+}
+EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_dmic_count);
diff --git a/include/acpi/nhlt.h b/include/acpi/nhlt.h
index 076aac41a74e..ba093fe871d5 100644
--- a/include/acpi/nhlt.h
+++ b/include/acpi/nhlt.h
@@ -149,4 +149,58 @@  acpi_nhlt_endpoint_fmtscfg(const struct acpi_nhlt_endpoint *ep)
 #define for_each_nhlt_endpoint_fmtcfg(ep, fmt) \
 	for_each_nhlt_fmtcfg(acpi_nhlt_endpoint_fmtscfg(ep), fmt)
 
+#if IS_ENABLED(CONFIG_ACPI_NHLT)
+
+bool acpi_nhlt_endpoint_match(const struct acpi_nhlt_endpoint *ep,
+			      int link_type, int dev_type, int dir, int bus_id);
+struct acpi_nhlt_endpoint *
+acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
+			int link_type, int dev_type, int dir, int bus_id);
+struct acpi_nhlt_format_config *
+acpi_nhlt_endpoint_find_fmtcfg(const struct acpi_nhlt_endpoint *ep,
+			       u16 ch, u32 rate, u16 vbps, u16 bps);
+struct acpi_nhlt_format_config *
+acpi_nhlt_find_fmtcfg(const struct acpi_table_nhlt *tb,
+		      int link_type, int dev_type, int dir, int bus_id,
+		      u16 ch, u32 rate, u16 vpbs, u16 bps);
+int acpi_nhlt_endpoint_dmic_count(const struct acpi_nhlt_endpoint *ep);
+
+#else /* !CONFIG_ACPI_NHLT */
+
+static bool
+acpi_nhlt_endpoint_match(const struct acpi_nhlt_endpoint *ep,
+			 int link_type, int dev_type, int dir, int bus_id)
+{
+	return false;
+}
+
+static inline struct acpi_nhlt_endpoint *
+acpi_nhlt_find_endpoint(const struct acpi_table_nhlt *tb,
+			int link_type, int dev_type, int dir, int bus_id)
+{
+	return NULL;
+}
+
+static inline struct acpi_nhlt_format_config *
+acpi_nhlt_endpoint_find_fmtcfg(const struct acpi_nhlt_endpoint *ep,
+			       u16 ch, u32 rate, u16 vbps, u16 bps)
+{
+	return NULL;
+}
+
+static inline struct acpi_nhlt_format_config *
+acpi_nhlt_find_fmtcfg(const struct acpi_table_nhlt *tb,
+		      int link_type, int dev_type, int dir, int bus_id,
+		      u16 ch, u32 rate, u16 vpbs, u16 bps);
+{
+	return NULL;
+}
+
+static inline int acpi_nhlt_endpoint_dmic_count(const struct acpi_nhlt_endpoint *ep)
+{
+	return 0;
+}
+
+#endif /* !CONFIG_ACPI_NHLT */
+
 #endif /* __ACPI_NHLT_H__ */