| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright(c) 2023-2024 Intel Corporation | |
| * | |
| * Authors: Cezary Rojewski <cezary.rojewski@intel.com> | |
| * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> | |
| */ | |
| #define pr_fmt(fmt) "ACPI: NHLT: " fmt | |
| #include <linux/acpi.h> | |
| #include <linux/errno.h> | |
| #include <linux/export.h> | |
| #include <linux/minmax.h> | |
| #include <linux/printk.h> | |
| #include <linux/types.h> | |
| #include <acpi/nhlt.h> | |
| static struct acpi_table_nhlt *acpi_gbl_nhlt; | |
| static struct acpi_table_nhlt empty_nhlt = { | |
| .header = { | |
| .signature = ACPI_SIG_NHLT, | |
| }, | |
| }; | |
| /** | |
| * acpi_nhlt_get_gbl_table - Retrieve a pointer to the first NHLT table. | |
| * | |
| * If there is no NHLT in the system, acpi_gbl_nhlt will instead point to an | |
| * empty table. | |
| * | |
| * Return: ACPI status code of the operation. | |
| */ | |
| 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); | |
| /** | |
| * acpi_nhlt_put_gbl_table - Release the global NHLT table. | |
| */ | |
| void acpi_nhlt_put_gbl_table(void) | |
| { | |
| acpi_put_table((struct acpi_table_header *)acpi_gbl_nhlt); | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_nhlt_put_gbl_table); | |
| /** | |
| * 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) && | |
| (bus_id < 0 || ep->virtual_bus_id == bus_id) && | |
| (dir < 0 || ep->direction == dir); | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_match); | |
| /** | |
| * acpi_nhlt_tb_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_tb_find_endpoint(const struct acpi_table_nhlt *tb, | |
| int link_type, int dev_type, int dir, int bus_id) | |
| { | |
| struct acpi_nhlt_endpoint *ep; | |
| 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_tb_find_endpoint); | |
| /** | |
| * acpi_nhlt_find_endpoint - Search all NHLT tables for an endpoint. | |
| * @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(int link_type, int dev_type, int dir, int bus_id) | |
| { | |
| /* TODO: Currently limited to table of index 0. */ | |
| return acpi_nhlt_tb_find_endpoint(acpi_gbl_nhlt, link_type, dev_type, dir, bus_id); | |
| } | |
| 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_formatext *wav; | |
| struct acpi_nhlt_format_config *fmt; | |
| for_each_nhlt_endpoint_fmtcfg(ep, fmt) { | |
| wav = &fmt->format; | |
| if (wav->valid_bits_per_sample == vbps && | |
| wav->samples_per_sec == rate && | |
| wav->bits_per_sample == bps && | |
| wav->channel_count == ch) | |
| return fmt; | |
| } | |
| return NULL; | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_find_fmtcfg); | |
| /** | |
| * acpi_nhlt_tb_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_tb_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; | |
| 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_tb_find_fmtcfg); | |
| /** | |
| * acpi_nhlt_find_fmtcfg - Search all NHLT tables for a specific format. | |
| * @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(int link_type, int dev_type, int dir, int bus_id, | |
| u16 ch, u32 rate, u16 vbps, u16 bps) | |
| { | |
| /* TODO: Currently limited to table of index 0. */ | |
| return acpi_nhlt_tb_find_fmtcfg(acpi_gbl_nhlt, link_type, dev_type, dir, bus_id, | |
| ch, rate, vbps, bps); | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_nhlt_find_fmtcfg); | |
| static bool acpi_nhlt_config_is_micdevice(struct acpi_nhlt_config *cfg) | |
| { | |
| return cfg->capabilities_size >= sizeof(struct acpi_nhlt_micdevice_config); | |
| } | |
| static bool acpi_nhlt_config_is_vendor_micdevice(struct acpi_nhlt_config *cfg) | |
| { | |
| struct acpi_nhlt_vendor_micdevice_config *devcfg = __acpi_nhlt_config_caps(cfg); | |
| return cfg->capabilities_size >= sizeof(*devcfg) && | |
| cfg->capabilities_size == struct_size(devcfg, mics, devcfg->mics_count); | |
| } | |
| /** | |
| * acpi_nhlt_endpoint_mic_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_mic_count(const struct acpi_nhlt_endpoint *ep) | |
| { | |
| union acpi_nhlt_device_config *devcfg; | |
| struct acpi_nhlt_format_config *fmt; | |
| struct acpi_nhlt_config *cfg; | |
| u16 max_ch = 0; | |
| if (!ep || ep->link_type != ACPI_NHLT_LINKTYPE_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); | |
| cfg = __acpi_nhlt_endpoint_config(ep); | |
| devcfg = __acpi_nhlt_config_caps(cfg); | |
| /* If @ep is not a mic array, fallback to channels count. */ | |
| if (!acpi_nhlt_config_is_micdevice(cfg) || | |
| devcfg->gen.config_type != ACPI_NHLT_CONFIGTYPE_MICARRAY) | |
| return max_ch; | |
| switch (devcfg->mic.array_type) { | |
| case ACPI_NHLT_ARRAYTYPE_LINEAR2_SMALL: | |
| case ACPI_NHLT_ARRAYTYPE_LINEAR2_BIG: | |
| return 2; | |
| case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO1: | |
| case ACPI_NHLT_ARRAYTYPE_PLANAR4_LSHAPED: | |
| case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO2: | |
| return 4; | |
| case ACPI_NHLT_ARRAYTYPE_VENDOR: | |
| if (!acpi_nhlt_config_is_vendor_micdevice(cfg)) | |
| return -EINVAL; | |
| return devcfg->vendor_mic.mics_count; | |
| default: | |
| pr_warn("undefined mic array type: %#x\n", devcfg->mic.array_type); | |
| return max_ch; | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_mic_count); |