diff options
Diffstat (limited to 'sound/hda/intel-nhlt.c')
-rw-r--r-- | sound/hda/intel-nhlt.c | 318 |
1 files changed, 267 insertions, 51 deletions
diff --git a/sound/hda/intel-nhlt.c b/sound/hda/intel-nhlt.c index 99a23fe7fab9..2c4dfc0b7e34 100644 --- a/sound/hda/intel-nhlt.c +++ b/sound/hda/intel-nhlt.c @@ -1,61 +1,28 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only // Copyright (c) 2015-2019 Intel Corporation #include <linux/acpi.h> #include <sound/intel-nhlt.h> -#define NHLT_ACPI_HEADER_SIG "NHLT" - -/* Unique identification for getting NHLT blobs */ -static const guid_t osc_guid = - GUID_INIT(0xA69F886E, 0x6CEB, 0x4594, - 0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53); - struct nhlt_acpi_table *intel_nhlt_init(struct device *dev) { - acpi_handle handle; - union acpi_object *obj; - struct nhlt_resource_desc *nhlt_ptr; - struct nhlt_acpi_table *nhlt_table = NULL; - - handle = ACPI_HANDLE(dev); - if (!handle) { - dev_err(dev, "Didn't find ACPI_HANDLE\n"); - return NULL; - } - - obj = acpi_evaluate_dsm(handle, &osc_guid, 1, 1, NULL); - - if (!obj) - return NULL; + struct nhlt_acpi_table *nhlt; + acpi_status status; - if (obj->type != ACPI_TYPE_BUFFER) { - dev_dbg(dev, "No NHLT table found\n"); - ACPI_FREE(obj); + status = acpi_get_table(ACPI_SIG_NHLT, 0, + (struct acpi_table_header **)&nhlt); + if (ACPI_FAILURE(status)) { + dev_warn(dev, "NHLT table not found\n"); return NULL; } - nhlt_ptr = (struct nhlt_resource_desc *)obj->buffer.pointer; - if (nhlt_ptr->length) - nhlt_table = (struct nhlt_acpi_table *) - memremap(nhlt_ptr->min_addr, nhlt_ptr->length, - MEMREMAP_WB); - ACPI_FREE(obj); - if (nhlt_table && - (strncmp(nhlt_table->header.signature, - NHLT_ACPI_HEADER_SIG, - strlen(NHLT_ACPI_HEADER_SIG)) != 0)) { - memunmap(nhlt_table); - dev_err(dev, "NHLT ACPI header signature incorrect\n"); - return NULL; - } - return nhlt_table; + return nhlt; } EXPORT_SYMBOL_GPL(intel_nhlt_init); void intel_nhlt_free(struct nhlt_acpi_table *nhlt) { - memunmap((void *)nhlt); + acpi_put_table((struct acpi_table_header *)nhlt); } EXPORT_SYMBOL_GPL(intel_nhlt_free); @@ -64,18 +31,55 @@ int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt) struct nhlt_endpoint *epnt; struct nhlt_dmic_array_config *cfg; struct nhlt_vendor_dmic_array_config *cfg_vendor; + struct nhlt_fmt *fmt_configs; unsigned int dmic_geo = 0; - u8 j; + u16 max_ch = 0; + u8 i, j; if (!nhlt) return 0; - epnt = (struct nhlt_endpoint *)nhlt->desc; + if (nhlt->header.length <= sizeof(struct acpi_table_header)) { + dev_warn(dev, "Invalid DMIC description table\n"); + return 0; + } + + for (j = 0, epnt = nhlt->desc; j < nhlt->endpoint_count; j++, + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length)) { + + if (epnt->linktype != NHLT_LINK_DMIC) + continue; - for (j = 0; j < nhlt->endpoint_count; j++) { - if (epnt->linktype == NHLT_LINK_DMIC) { - cfg = (struct nhlt_dmic_array_config *) - (epnt->config.caps); + cfg = (struct nhlt_dmic_array_config *)(epnt->config.caps); + fmt_configs = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size); + + /* find max number of channels based on format_configuration */ + if (fmt_configs->fmt_count) { + struct nhlt_fmt_cfg *fmt_cfg = fmt_configs->fmt_config; + + dev_dbg(dev, "found %d format definitions\n", + fmt_configs->fmt_count); + + for (i = 0; i < fmt_configs->fmt_count; i++) { + struct wav_fmt_ext *fmt_ext; + + fmt_ext = &fmt_cfg->fmt_ext; + + if (fmt_ext->fmt.channels > max_ch) + max_ch = fmt_ext->fmt.channels; + + /* Move to the next nhlt_fmt_cfg */ + fmt_cfg = (struct nhlt_fmt_cfg *)(fmt_cfg->config.caps + + fmt_cfg->config.size); + } + dev_dbg(dev, "max channels found %d\n", max_ch); + } else { + dev_dbg(dev, "No format information found\n"); + } + + if (cfg->device_config.config_type != NHLT_CONFIG_TYPE_MIC_ARRAY) { + dmic_geo = max_ch; + } else { switch (cfg->array_type) { case NHLT_MIC_ARRAY_2CH_SMALL: case NHLT_MIC_ARRAY_2CH_BIG: @@ -92,13 +96,225 @@ int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt) dmic_geo = cfg_vendor->nb_mics; break; default: - dev_warn(dev, "undefined DMIC array_type 0x%0x\n", - cfg->array_type); + dev_warn(dev, "%s: undefined DMIC array_type 0x%0x\n", + __func__, cfg->array_type); + } + + if (dmic_geo > 0) { + dev_dbg(dev, "Array with %d dmics\n", dmic_geo); + } + if (max_ch > dmic_geo) { + dev_dbg(dev, "max channels %d exceed dmic number %d\n", + max_ch, dmic_geo); } } - epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); } + dev_dbg(dev, "dmic number %d max_ch %d\n", dmic_geo, max_ch); + return dmic_geo; } EXPORT_SYMBOL_GPL(intel_nhlt_get_dmic_geo); + +bool intel_nhlt_has_endpoint_type(struct nhlt_acpi_table *nhlt, u8 link_type) +{ + struct nhlt_endpoint *epnt; + int i; + + if (!nhlt) + return false; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + for (i = 0; i < nhlt->endpoint_count; i++) { + if (epnt->linktype == link_type) + return true; + + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + return false; +} +EXPORT_SYMBOL(intel_nhlt_has_endpoint_type); + +int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 device_type) +{ + struct nhlt_endpoint *epnt; + int ssp_mask = 0; + int i; + + if (!nhlt || (device_type != NHLT_DEVICE_BT && device_type != NHLT_DEVICE_I2S)) + return 0; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + for (i = 0; i < nhlt->endpoint_count; i++) { + if (epnt->linktype == NHLT_LINK_SSP && epnt->device_type == device_type) { + /* for SSP the virtual bus id is the SSP port */ + ssp_mask |= BIT(epnt->virtual_bus_id); + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return ssp_mask; +} +EXPORT_SYMBOL(intel_nhlt_ssp_endpoint_mask); + +#define SSP_BLOB_V1_0_SIZE 84 +#define SSP_BLOB_V1_0_MDIVC_OFFSET 19 /* offset in u32 */ + +#define SSP_BLOB_V1_5_SIZE 96 +#define SSP_BLOB_V1_5_MDIVC_OFFSET 21 /* offset in u32 */ +#define SSP_BLOB_VER_1_5 0xEE000105 + +#define SSP_BLOB_V2_0_SIZE 88 +#define SSP_BLOB_V2_0_MDIVC_OFFSET 20 /* offset in u32 */ +#define SSP_BLOB_VER_2_0 0xEE000200 + +int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num) +{ + struct nhlt_endpoint *epnt; + struct nhlt_fmt *fmt; + struct nhlt_fmt_cfg *cfg; + int mclk_mask = 0; + int i, j; + + if (!nhlt) + return 0; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + for (i = 0; i < nhlt->endpoint_count; i++) { + + /* we only care about endpoints connected to an audio codec over SSP */ + if (epnt->linktype == NHLT_LINK_SSP && + epnt->device_type == NHLT_DEVICE_I2S && + epnt->virtual_bus_id == ssp_num) { + + fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size); + cfg = fmt->fmt_config; + + /* + * In theory all formats should use the same MCLK but it doesn't hurt to + * double-check that the configuration is consistent + */ + for (j = 0; j < fmt->fmt_count; j++) { + u32 *blob; + int mdivc_offset; + int size; + + /* first check we have enough data to read the blob type */ + if (cfg->config.size < 8) + return -EINVAL; + + blob = (u32 *)cfg->config.caps; + + if (blob[1] == SSP_BLOB_VER_2_0) { + mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET; + size = SSP_BLOB_V2_0_SIZE; + } else if (blob[1] == SSP_BLOB_VER_1_5) { + mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET; + size = SSP_BLOB_V1_5_SIZE; + } else { + mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET; + size = SSP_BLOB_V1_0_SIZE; + } + + /* make sure we have enough data for the fixed part of the blob */ + if (cfg->config.size < size) + return -EINVAL; + + mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0); + + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size); + } + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + /* make sure only one MCLK is used */ + if (hweight_long(mclk_mask) != 1) + return -EINVAL; + + return mclk_mask; +} +EXPORT_SYMBOL(intel_nhlt_ssp_mclk_mask); + +static struct nhlt_specific_cfg * +nhlt_get_specific_cfg(struct device *dev, struct nhlt_fmt *fmt, u8 num_ch, + u32 rate, u8 vbps, u8 bps) +{ + struct nhlt_fmt_cfg *cfg = fmt->fmt_config; + struct wav_fmt *wfmt; + u16 _bps, _vbps; + int i; + + dev_dbg(dev, "Endpoint format count=%d\n", fmt->fmt_count); + + for (i = 0; i < fmt->fmt_count; i++) { + wfmt = &cfg->fmt_ext.fmt; + _bps = wfmt->bits_per_sample; + _vbps = cfg->fmt_ext.sample.valid_bits_per_sample; + + dev_dbg(dev, "Endpoint format: ch=%d fmt=%d/%d rate=%d\n", + wfmt->channels, _vbps, _bps, wfmt->samples_per_sec); + + if (wfmt->channels == num_ch && wfmt->samples_per_sec == rate && + vbps == _vbps && bps == _bps) + return &cfg->config; + + cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size); + } + + return NULL; +} + +static bool nhlt_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt, + u32 bus_id, u8 link_type, u8 dir, u8 dev_type) +{ + dev_dbg(dev, "Endpoint: vbus_id=%d link_type=%d dir=%d dev_type = %d\n", + epnt->virtual_bus_id, epnt->linktype, + epnt->direction, epnt->device_type); + + if ((epnt->virtual_bus_id != bus_id) || + (epnt->linktype != link_type) || + (epnt->direction != dir)) + return false; + + /* link of type DMIC bypasses device_type check */ + return epnt->linktype == NHLT_LINK_DMIC || + epnt->device_type == dev_type; +} + +struct nhlt_specific_cfg * +intel_nhlt_get_endpoint_blob(struct device *dev, struct nhlt_acpi_table *nhlt, + u32 bus_id, u8 link_type, u8 vbps, u8 bps, + u8 num_ch, u32 rate, u8 dir, u8 dev_type) +{ + struct nhlt_specific_cfg *cfg; + struct nhlt_endpoint *epnt; + struct nhlt_fmt *fmt; + int i; + + if (!nhlt) + return NULL; + + dev_dbg(dev, "Looking for configuration:\n"); + dev_dbg(dev, " vbus_id=%d link_type=%d dir=%d, dev_type=%d\n", + bus_id, link_type, dir, dev_type); + dev_dbg(dev, " ch=%d fmt=%d/%d rate=%d\n", num_ch, vbps, bps, rate); + dev_dbg(dev, "Endpoint count=%d\n", nhlt->endpoint_count); + + epnt = (struct nhlt_endpoint *)nhlt->desc; + + for (i = 0; i < nhlt->endpoint_count; i++) { + if (nhlt_check_ep_match(dev, epnt, bus_id, link_type, dir, dev_type)) { + fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size); + + cfg = nhlt_get_specific_cfg(dev, fmt, num_ch, rate, vbps, bps); + if (cfg) + return cfg; + } + + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return NULL; +} +EXPORT_SYMBOL(intel_nhlt_get_endpoint_blob); |