// SPDX-License-Identifier: GPL-2.0-only // Copyright (c) 2020 Intel Corporation /* * sof_sdw - ASOC Machine driver for Intel SoundWire platforms */ #include #include #include #include #include #include #include #include "sof_sdw_common.h" #include "../../codecs/rt711.h" unsigned long sof_sdw_quirk = RT711_JD1; static int quirk_override = -1; module_param_named(quirk, quirk_override, int, 0444); MODULE_PARM_DESC(quirk, "Board-specific quirk override"); #define INC_ID(BE, CPU, LINK) do { (BE)++; (CPU)++; (LINK)++; } while (0) static void log_quirks(struct device *dev) { if (SOF_RT711_JDSRC(sof_sdw_quirk)) dev_dbg(dev, "quirk realtek,jack-detect-source %ld\n", SOF_RT711_JDSRC(sof_sdw_quirk)); if (sof_sdw_quirk & SOF_SDW_FOUR_SPK) dev_dbg(dev, "quirk SOF_SDW_FOUR_SPK enabled\n"); if (sof_sdw_quirk & SOF_SDW_TGL_HDMI) dev_dbg(dev, "quirk SOF_SDW_TGL_HDMI enabled\n"); if (sof_sdw_quirk & SOF_SDW_PCH_DMIC) dev_dbg(dev, "quirk SOF_SDW_PCH_DMIC enabled\n"); if (SOF_SSP_GET_PORT(sof_sdw_quirk)) dev_dbg(dev, "SSP port %ld\n", SOF_SSP_GET_PORT(sof_sdw_quirk)); if (sof_sdw_quirk & SOF_SDW_NO_AGGREGATION) dev_dbg(dev, "quirk SOF_SDW_NO_AGGREGATION enabled\n"); } static int sof_sdw_quirk_cb(const struct dmi_system_id *id) { sof_sdw_quirk = (unsigned long)id->driver_data; return 1; } static const struct dmi_system_id sof_sdw_quirk_table[] = { /* CometLake devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), DMI_MATCH(DMI_PRODUCT_NAME, "CometLake Client"), }, .driver_data = (void *)SOF_SDW_PCH_DMIC, }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "09C6") }, .driver_data = (void *)RT711_JD2, }, { /* early version of SKU 09C6 */ .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0983") }, .driver_data = (void *)RT711_JD2, }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "098F"), }, .driver_data = (void *)(RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0990"), }, .driver_data = (void *)(RT711_JD2 | SOF_SDW_FOUR_SPK), }, /* IceLake devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), }, .driver_data = (void *)SOF_SDW_PCH_DMIC, }, /* TigerLake devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), DMI_MATCH(DMI_PRODUCT_NAME, "Tiger Lake Client Platform"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD1 | SOF_SDW_PCH_DMIC | SOF_SSP_PORT(SOF_I2S_SSP2)), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3E") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2), }, { /* another SKU of Dell Latitude 9520 */ .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3F") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2), }, { /* Dell XPS 9710 */ .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A5D") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A5E") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), DMI_MATCH(DMI_PRODUCT_NAME, "Volteer"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_PCH_DMIC | SOF_SDW_FOUR_SPK | SOF_BT_OFFLOAD_SSP(2) | SOF_SSP_BT_OFFLOAD_PRESENT), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), DMI_MATCH(DMI_PRODUCT_NAME, "Ripto"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_PCH_DMIC | SOF_SDW_FOUR_SPK), }, { /* * this entry covers multiple HP SKUs. The family name * does not seem robust enough, so we use a partial * match that ignores the product name suffix * (e.g. 15-eb1xxx, 14t-ea000 or 13-aw2xxx) */ .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "HP"), DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Conv"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_PCH_DMIC | RT711_JD1), }, { /* NUC15 'Bishop County' LAPBC510 and LAPBC710 skews */ .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), DMI_MATCH(DMI_PRODUCT_NAME, "LAPBC"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_PCH_DMIC | RT711_JD1), }, /* TigerLake-SDCA devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A32") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A45") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2), }, /* AlderLake devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), DMI_MATCH(DMI_PRODUCT_NAME, "Alder Lake Client Platform"), }, .driver_data = (void *)(RT711_JD2_100K | SOF_SDW_TGL_HDMI | SOF_BT_OFFLOAD_SSP(2) | SOF_SSP_BT_OFFLOAD_PRESENT), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), DMI_MATCH(DMI_PRODUCT_NAME, "Brya"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_PCH_DMIC | SOF_SDW_FOUR_SPK | SOF_BT_OFFLOAD_SSP(2) | SOF_SSP_BT_OFFLOAD_PRESENT), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0AF3"), }, /* No Jack */ .driver_data = (void *)(SOF_SDW_TGL_HDMI | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B00") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B01") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B11") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B12") }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B13"), }, /* No Jack */ .driver_data = (void *)SOF_SDW_TGL_HDMI, }, { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B29"), }, .driver_data = (void *)(SOF_SDW_TGL_HDMI | RT711_JD2 | SOF_SDW_FOUR_SPK), }, /* MeteorLake devices */ { .callback = sof_sdw_quirk_cb, .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Intel_mtlrvp"), }, .driver_data = (void *)(RT711_JD1 | SOF_SDW_TGL_HDMI), }, {} }; static struct snd_soc_dai_link_component dmic_component[] = { { .name = "dmic-codec", .dai_name = "dmic-hifi", } }; static struct snd_soc_dai_link_component platform_component[] = { { /* name might be overridden during probe */ .name = "0000:00:1f.3" } }; /* these wrappers are only needed to avoid typecast compilation errors */ int sdw_startup(struct snd_pcm_substream *substream) { return sdw_startup_stream(substream); } int sdw_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sdw_stream_runtime *sdw_stream; struct snd_soc_dai *dai; /* Find stream from first CPU DAI */ dai = asoc_rtd_to_cpu(rtd, 0); sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); if (IS_ERR(sdw_stream)) { dev_err(rtd->dev, "no stream found for DAI %s", dai->name); return PTR_ERR(sdw_stream); } return sdw_prepare_stream(sdw_stream); } int sdw_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sdw_stream_runtime *sdw_stream; struct snd_soc_dai *dai; int ret; /* Find stream from first CPU DAI */ dai = asoc_rtd_to_cpu(rtd, 0); sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); if (IS_ERR(sdw_stream)) { dev_err(rtd->dev, "no stream found for DAI %s", dai->name); return PTR_ERR(sdw_stream); } switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: ret = sdw_enable_stream(sdw_stream); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: ret = sdw_disable_stream(sdw_stream); break; default: ret = -EINVAL; break; } if (ret) dev_err(rtd->dev, "%s trigger %d failed: %d", __func__, cmd, ret); return ret; } int sdw_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sdw_stream_runtime *sdw_stream; struct snd_soc_dai *dai; /* Find stream from first CPU DAI */ dai = asoc_rtd_to_cpu(rtd, 0); sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); if (IS_ERR(sdw_stream)) { dev_err(rtd->dev, "no stream found for DAI %s", dai->name); return PTR_ERR(sdw_stream); } return sdw_deprepare_stream(sdw_stream); } void sdw_shutdown(struct snd_pcm_substream *substream) { sdw_shutdown_stream(substream); } static const struct snd_soc_ops sdw_ops = { .startup = sdw_startup, .prepare = sdw_prepare, .trigger = sdw_trigger, .hw_free = sdw_hw_free, .shutdown = sdw_shutdown, }; static struct sof_sdw_codec_info codec_info_list[] = { { .part_id = 0x700, .direction = {true, true}, .dai_name = "rt700-aif1", .init = sof_sdw_rt700_init, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, .version_id = 3, .direction = {true, true}, .dai_name = "rt711-sdca-aif1", .init = sof_sdw_rt711_sdca_init, .exit = sof_sdw_rt711_sdca_exit, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, .version_id = 2, .direction = {true, true}, .dai_name = "rt711-aif1", .init = sof_sdw_rt711_init, .exit = sof_sdw_rt711_exit, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x1308, .acpi_id = "10EC1308", .direction = {true, false}, .dai_name = "rt1308-aif", .ops = &sof_sdw_rt1308_i2s_ops, .init = sof_sdw_rt1308_init, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x1316, .direction = {true, true}, .dai_name = "rt1316-aif", .init = sof_sdw_rt1316_init, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x714, .version_id = 3, .direction = {false, true}, .ignore_pch_dmic = true, .dai_name = "rt715-aif2", .init = sof_sdw_rt715_sdca_init, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, .version_id = 3, .direction = {false, true}, .ignore_pch_dmic = true, .dai_name = "rt715-aif2", .init = sof_sdw_rt715_sdca_init, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x714, .version_id = 2, .direction = {false, true}, .ignore_pch_dmic = true, .dai_name = "rt715-aif2", .init = sof_sdw_rt715_init, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, .version_id = 2, .direction = {false, true}, .ignore_pch_dmic = true, .dai_name = "rt715-aif2", .init = sof_sdw_rt715_init, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x8373, .direction = {true, true}, .dai_name = "max98373-aif1", .init = sof_sdw_mx8373_init, .codec_card_late_probe = sof_sdw_mx8373_late_probe, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5682, .direction = {true, true}, .dai_name = "rt5682-sdw", .init = sof_sdw_rt5682_init, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaaaa, /* generic codec mockup */ .version_id = 0, .direction = {true, true}, .dai_name = "sdw-mockup-aif1", .init = NULL, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaa55, /* headset codec mockup */ .version_id = 0, .direction = {true, true}, .dai_name = "sdw-mockup-aif1", .init = NULL, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x55aa, /* amplifier mockup */ .version_id = 0, .direction = {true, false}, .dai_name = "sdw-mockup-aif1", .init = NULL, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5555, .version_id = 0, .direction = {false, true}, .dai_name = "sdw-mockup-aif1", .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, }; static inline int find_codec_info_part(u64 adr) { unsigned int part_id, sdw_version; int i; part_id = SDW_PART_ID(adr); sdw_version = SDW_VERSION(adr); for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) /* * A codec info is for all sdw version with the part id if * version_id is not specified in the codec info. */ if (part_id == codec_info_list[i].part_id && (!codec_info_list[i].version_id || sdw_version == codec_info_list[i].version_id)) return i; return -EINVAL; } static inline int find_codec_info_acpi(const u8 *acpi_id) { int i; if (!acpi_id[0]) return -EINVAL; for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) if (!memcmp(codec_info_list[i].acpi_id, acpi_id, ACPI_ID_LEN)) break; if (i == ARRAY_SIZE(codec_info_list)) return -EINVAL; return i; } /* * get BE dailink number and CPU DAI number based on sdw link adr. * Since some sdw slaves may be aggregated, the CPU DAI number * may be larger than the number of BE dailinks. */ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_link_adr *links, int *sdw_be_num, int *sdw_cpu_dai_num) { const struct snd_soc_acpi_link_adr *link; int _codec_type = SOF_SDW_CODEC_TYPE_JACK; bool group_visited[SDW_MAX_GROUPS]; bool no_aggregation; int i; no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION; *sdw_cpu_dai_num = 0; *sdw_be_num = 0; if (!links) return -EINVAL; for (i = 0; i < SDW_MAX_GROUPS; i++) group_visited[i] = false; for (link = links; link->num_adr; link++) { const struct snd_soc_acpi_endpoint *endpoint; int codec_index; int stream; u64 adr; adr = link->adr_d->adr; codec_index = find_codec_info_part(adr); if (codec_index < 0) return codec_index; if (codec_info_list[codec_index].codec_type < _codec_type) dev_warn(dev, "Unexpected address table ordering. Expected order: jack -> amp -> mic\n"); _codec_type = codec_info_list[codec_index].codec_type; endpoint = link->adr_d->endpoints; /* count DAI number for playback and capture */ for_each_pcm_streams(stream) { if (!codec_info_list[codec_index].direction[stream]) continue; (*sdw_cpu_dai_num)++; /* count BE for each non-aggregated slave or group */ if (!endpoint->aggregated || no_aggregation || !group_visited[endpoint->group_id]) (*sdw_be_num)++; } if (endpoint->aggregated) group_visited[endpoint->group_id] = true; } return 0; } static void init_dai_link(struct device *dev, struct snd_soc_dai_link *dai_links, int be_id, char *name, int playback, int capture, struct snd_soc_dai_link_component *cpus, int cpus_num, struct snd_soc_dai_link_component *codecs, int codecs_num, int (*init)(struct snd_soc_pcm_runtime *rtd), const struct snd_soc_ops *ops) { dev_dbg(dev, "create dai link %s, id %d\n", name, be_id); dai_links->id = be_id; dai_links->name = name; dai_links->platforms = platform_component; dai_links->num_platforms = ARRAY_SIZE(platform_component); dai_links->no_pcm = 1; dai_links->cpus = cpus; dai_links->num_cpus = cpus_num; dai_links->codecs = codecs; dai_links->num_codecs = codecs_num; dai_links->dpcm_playback = playback; dai_links->dpcm_capture = capture; dai_links->init = init; dai_links->ops = ops; } static bool is_unique_device(const struct snd_soc_acpi_link_adr *link, unsigned int sdw_version, unsigned int mfg_id, unsigned int part_id, unsigned int class_id, int index_in_link ) { int i; for (i = 0; i < link->num_adr; i++) { unsigned int sdw1_version, mfg1_id, part1_id, class1_id; u64 adr; /* skip itself */ if (i == index_in_link) continue; adr = link->adr_d[i].adr; sdw1_version = SDW_VERSION(adr); mfg1_id = SDW_MFG_ID(adr); part1_id = SDW_PART_ID(adr); class1_id = SDW_CLASS_ID(adr); if (sdw_version == sdw1_version && mfg_id == mfg1_id && part_id == part1_id && class_id == class1_id) return false; } return true; } static int create_codec_dai_name(struct device *dev, const struct snd_soc_acpi_link_adr *link, struct snd_soc_dai_link_component *codec, int offset, struct snd_soc_codec_conf *codec_conf, int codec_count, int *codec_conf_index) { int i; /* sanity check */ if (*codec_conf_index + link->num_adr > codec_count) { dev_err(dev, "codec_conf: out-of-bounds access requested\n"); return -EINVAL; } for (i = 0; i < link->num_adr; i++) { unsigned int sdw_version, unique_id, mfg_id; unsigned int link_id, part_id, class_id; int codec_index, comp_index; char *codec_str; u64 adr; adr = link->adr_d[i].adr; sdw_version = SDW_VERSION(adr); link_id = SDW_DISCO_LINK_ID(adr); unique_id = SDW_UNIQUE_ID(adr); mfg_id = SDW_MFG_ID(adr); part_id = SDW_PART_ID(adr); class_id = SDW_CLASS_ID(adr); comp_index = i + offset; if (is_unique_device(link, sdw_version, mfg_id, part_id, class_id, i)) { codec_str = "sdw:%01x:%04x:%04x:%02x"; codec[comp_index].name = devm_kasprintf(dev, GFP_KERNEL, codec_str, link_id, mfg_id, part_id, class_id); } else { codec_str = "sdw:%01x:%04x:%04x:%02x:%01x"; codec[comp_index].name = devm_kasprintf(dev, GFP_KERNEL, codec_str, link_id, mfg_id, part_id, class_id, unique_id); } if (!codec[comp_index].name) return -ENOMEM; codec_index = find_codec_info_part(adr); if (codec_index < 0) return codec_index; codec[comp_index].dai_name = codec_info_list[codec_index].dai_name; codec_conf[*codec_conf_index].dlc = codec[comp_index]; codec_conf[*codec_conf_index].name_prefix = link->adr_d[i].name_prefix; ++*codec_conf_index; } return 0; } static int set_codec_init_func(struct snd_soc_card *card, const struct snd_soc_acpi_link_adr *link, struct snd_soc_dai_link *dai_links, bool playback, int group_id) { int i; do { /* * Initialize the codec. If codec is part of an aggregated * group (group_id>0), initialize all codecs belonging to * same group. */ for (i = 0; i < link->num_adr; i++) { int codec_index; codec_index = find_codec_info_part(link->adr_d[i].adr); if (codec_index < 0) return codec_index; /* The group_id is > 0 iff the codec is aggregated */ if (link->adr_d[i].endpoints->group_id != group_id) continue; if (codec_info_list[codec_index].init) codec_info_list[codec_index].init(card, link, dai_links, &codec_info_list[codec_index], playback); } link++; } while (link->mask && group_id); return 0; } /* * check endpoint status in slaves and gather link ID for all slaves in * the same group to generate different CPU DAI. Now only support * one sdw link with all slaves set with only single group id. * * one slave on one sdw link with aggregated = 0 * one sdw BE DAI <---> one-cpu DAI <---> one-codec DAI * * two or more slaves on one sdw link with aggregated = 0 * one sdw BE DAI <---> one-cpu DAI <---> multi-codec DAIs * * multiple links with multiple slaves with aggregated = 1 * one sdw BE DAI <---> 1 .. N CPU DAIs <----> 1 .. N codec DAIs */ static int get_slave_info(const struct snd_soc_acpi_link_adr *adr_link, struct device *dev, int *cpu_dai_id, int *cpu_dai_num, int *codec_num, unsigned int *group_id, bool *group_generated) { const struct snd_soc_acpi_adr_device *adr_d; const struct snd_soc_acpi_link_adr *adr_next; bool no_aggregation; int index = 0; no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION; *codec_num = adr_link->num_adr; adr_d = adr_link->adr_d; /* make sure the link mask has a single bit set */ if (!is_power_of_2(adr_link->mask)) return -EINVAL; cpu_dai_id[index++] = ffs(adr_link->mask) - 1; if (!adr_d->endpoints->aggregated || no_aggregation) { *cpu_dai_num = 1; *group_id = 0; return 0; } *group_id = adr_d->endpoints->group_id; /* gather other link ID of slaves in the same group */ for (adr_next = adr_link + 1; adr_next && adr_next->num_adr; adr_next++) { const struct snd_soc_acpi_endpoint *endpoint; endpoint = adr_next->adr_d->endpoints; if (!endpoint->aggregated || endpoint->group_id != *group_id) continue; /* make sure the link mask has a single bit set */ if (!is_power_of_2(adr_next->mask)) return -EINVAL; if (index >= SDW_MAX_CPU_DAIS) { dev_err(dev, " cpu_dai_id array overflows"); return -EINVAL; } cpu_dai_id[index++] = ffs(adr_next->mask) - 1; *codec_num += adr_next->num_adr; } /* * indicate CPU DAIs for this group have been generated * to avoid generating CPU DAIs for this group again. */ group_generated[*group_id] = true; *cpu_dai_num = index; return 0; } static int create_sdw_dailink(struct snd_soc_card *card, struct device *dev, int *link_index, struct snd_soc_dai_link *dai_links, int sdw_be_num, int sdw_cpu_dai_num, struct snd_soc_dai_link_component *cpus, const struct snd_soc_acpi_link_adr *link, int *cpu_id, bool *group_generated, struct snd_soc_codec_conf *codec_conf, int codec_count, int *link_id, int *codec_conf_index, bool *ignore_pch_dmic) { const struct snd_soc_acpi_link_adr *link_next; struct snd_soc_dai_link_component *codecs; int cpu_dai_id[SDW_MAX_CPU_DAIS]; int cpu_dai_num, cpu_dai_index; unsigned int group_id; int codec_idx = 0; int i = 0, j = 0; int codec_index; int codec_num; int stream; int ret; int k; ret = get_slave_info(link, dev, cpu_dai_id, &cpu_dai_num, &codec_num, &group_id, group_generated); if (ret) return ret; codecs = devm_kcalloc(dev, codec_num, sizeof(*codecs), GFP_KERNEL); if (!codecs) return -ENOMEM; /* generate codec name on different links in the same group */ for (link_next = link; link_next && link_next->num_adr && i < cpu_dai_num; link_next++) { const struct snd_soc_acpi_endpoint *endpoints; endpoints = link_next->adr_d->endpoints; if (group_id && (!endpoints->aggregated || endpoints->group_id != group_id)) continue; /* skip the link excluded by this processed group */ if (cpu_dai_id[i] != ffs(link_next->mask) - 1) continue; ret = create_codec_dai_name(dev, link_next, codecs, codec_idx, codec_conf, codec_count, codec_conf_index); if (ret < 0) return ret; /* check next link to create codec dai in the processed group */ i++; codec_idx += link_next->num_adr; } /* find codec info to create BE DAI */ codec_index = find_codec_info_part(link->adr_d[0].adr); if (codec_index < 0) return codec_index; if (codec_info_list[codec_index].ignore_pch_dmic) *ignore_pch_dmic = true; /* Shift the first amplifier's *link_id to SDW_AMP_DAI_ID */ if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_AMP && *link_id < SDW_AMP_DAI_ID) *link_id = SDW_AMP_DAI_ID; /* * DAI ID is fixed at SDW_DMIC_DAI_ID for MICs to * keep sdw DMIC and HDMI setting static in UCM */ if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_MIC && *link_id < SDW_DMIC_DAI_ID) *link_id = SDW_DMIC_DAI_ID; cpu_dai_index = *cpu_id; for_each_pcm_streams(stream) { char *name, *cpu_name; int playback, capture; static const char * const sdw_stream_name[] = { "SDW%d-Playback", "SDW%d-Capture", }; if (!codec_info_list[codec_index].direction[stream]) continue; /* create stream name according to first link id */ name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream], cpu_dai_id[0]); if (!name) return -ENOMEM; /* * generate CPU DAI name base on the sdw link ID and * PIN ID with offset of 2 according to sdw dai driver. */ for (k = 0; k < cpu_dai_num; k++) { cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", cpu_dai_id[k], j + SDW_INTEL_BIDIR_PDI_BASE); if (!cpu_name) return -ENOMEM; if (cpu_dai_index >= sdw_cpu_dai_num) { dev_err(dev, "invalid cpu dai index %d", cpu_dai_index); return -EINVAL; } cpus[cpu_dai_index++].dai_name = cpu_name; } /* * We create sdw dai links at first stage, so link index should * not be larger than sdw_be_num */ if (*link_index >= sdw_be_num) { dev_err(dev, "invalid dai link index %d", *link_index); return -EINVAL; } if (*cpu_id >= sdw_cpu_dai_num) { dev_err(dev, " invalid cpu dai index %d", *cpu_id); return -EINVAL; } playback = (stream == SNDRV_PCM_STREAM_PLAYBACK); capture = (stream == SNDRV_PCM_STREAM_CAPTURE); init_dai_link(dev, dai_links + *link_index, (*link_id)++, name, playback, capture, cpus + *cpu_id, cpu_dai_num, codecs, codec_num, NULL, &sdw_ops); /* * SoundWire DAILINKs use 'stream' functions and Bank Switch operations * based on wait_for_completion(), tag them as 'nonatomic'. */ dai_links[*link_index].nonatomic = true; ret = set_codec_init_func(card, link, dai_links + (*link_index)++, playback, group_id); if (ret < 0) { dev_err(dev, "failed to init codec %d", codec_index); return ret; } *cpu_id += cpu_dai_num; j++; } return 0; } #define IDISP_CODEC_MASK 0x4 static int sof_card_codec_conf_alloc(struct device *dev, struct snd_soc_acpi_mach_params *mach_params, struct snd_soc_codec_conf **codec_conf, int *codec_conf_count) { const struct snd_soc_acpi_link_adr *adr_link; struct snd_soc_codec_conf *c_conf; int num_codecs = 0; int i; adr_link = mach_params->links; if (!adr_link) return -EINVAL; /* generate DAI links by each sdw link */ for (; adr_link->num_adr; adr_link++) { for (i = 0; i < adr_link->num_adr; i++) { if (!adr_link->adr_d[i].name_prefix) { dev_err(dev, "codec 0x%llx does not have a name prefix\n", adr_link->adr_d[i].adr); return -EINVAL; } } num_codecs += adr_link->num_adr; } c_conf = devm_kzalloc(dev, num_codecs * sizeof(*c_conf), GFP_KERNEL); if (!c_conf) return -ENOMEM; *codec_conf = c_conf; *codec_conf_count = num_codecs; return 0; } static int sof_card_dai_links_create(struct device *dev, struct snd_soc_acpi_mach *mach, struct snd_soc_card *card) { int ssp_num, sdw_be_num = 0, hdmi_num = 0, dmic_num; struct mc_private *ctx = snd_soc_card_get_drvdata(card); struct snd_soc_dai_link_component *idisp_components; struct snd_soc_dai_link_component *ssp_components; struct snd_soc_acpi_mach_params *mach_params; const struct snd_soc_acpi_link_adr *adr_link; struct snd_soc_dai_link_component *cpus; struct snd_soc_codec_conf *codec_conf; bool ignore_pch_dmic = false; int codec_conf_count; int codec_conf_index = 0; bool group_generated[SDW_MAX_GROUPS]; int ssp_codec_index, ssp_mask; struct snd_soc_dai_link *links; int num_links, link_index = 0; char *name, *cpu_name; int total_cpu_dai_num; int sdw_cpu_dai_num; int i, j, be_id = 0; int cpu_id = 0; int comp_num; int ret; mach_params = &mach->mach_params; /* allocate codec conf, will be populated when dailinks are created */ ret = sof_card_codec_conf_alloc(dev, mach_params, &codec_conf, &codec_conf_count); if (ret < 0) return ret; /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) codec_info_list[i].amp_num = 0; if (mach_params->codec_mask & IDISP_CODEC_MASK) { ctx->idisp_codec = true; if (sof_sdw_quirk & SOF_SDW_TGL_HDMI) hdmi_num = SOF_TGL_HDMI_COUNT; else hdmi_num = SOF_PRE_TGL_HDMI_COUNT; } ssp_mask = SOF_SSP_GET_PORT(sof_sdw_quirk); /* * on generic tgl platform, I2S or sdw mode is supported * based on board rework. A ACPI device is registered in * system only when I2S mode is supported, not sdw mode. * Here check ACPI ID to confirm I2S is supported. */ ssp_codec_index = find_codec_info_acpi(mach->id); ssp_num = ssp_codec_index >= 0 ? hweight_long(ssp_mask) : 0; comp_num = hdmi_num + ssp_num; ret = get_sdw_dailink_info(dev, mach_params->links, &sdw_be_num, &sdw_cpu_dai_num); if (ret < 0) { dev_err(dev, "failed to get sdw link info %d", ret); return ret; } /* enable dmic01 & dmic16k */ dmic_num = (sof_sdw_quirk & SOF_SDW_PCH_DMIC || mach_params->dmic_num) ? 2 : 0; comp_num += dmic_num; if (sof_sdw_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) comp_num++; dev_dbg(dev, "sdw %d, ssp %d, dmic %d, hdmi %d", sdw_be_num, ssp_num, dmic_num, ctx->idisp_codec ? hdmi_num : 0); /* allocate BE dailinks */ num_links = comp_num + sdw_be_num; links = devm_kcalloc(dev, num_links, sizeof(*links), GFP_KERNEL); /* allocated CPU DAIs */ total_cpu_dai_num = comp_num + sdw_cpu_dai_num; cpus = devm_kcalloc(dev, total_cpu_dai_num, sizeof(*cpus), GFP_KERNEL); if (!links || !cpus) return -ENOMEM; /* SDW */ if (!sdw_be_num) goto SSP; adr_link = mach_params->links; if (!adr_link) return -EINVAL; /* * SoundWire Slaves aggregated in the same group may be * located on different hardware links. Clear array to indicate * CPU DAIs for this group have not been generated. */ for (i = 0; i < SDW_MAX_GROUPS; i++) group_generated[i] = false; /* generate DAI links by each sdw link */ for (; adr_link->num_adr; adr_link++) { const struct snd_soc_acpi_endpoint *endpoint; endpoint = adr_link->adr_d->endpoints; if (endpoint->aggregated && !endpoint->group_id) { dev_err(dev, "invalid group id on link %x", adr_link->mask); continue; } /* this group has been generated */ if (endpoint->aggregated && group_generated[endpoint->group_id]) continue; ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num, sdw_cpu_dai_num, cpus, adr_link, &cpu_id, group_generated, codec_conf, codec_conf_count, &be_id, &codec_conf_index, &ignore_pch_dmic); if (ret < 0) { dev_err(dev, "failed to create dai link %d", link_index); return ret; } } SSP: /* SSP */ if (!ssp_num) goto DMIC; for (i = 0, j = 0; ssp_mask; i++, ssp_mask >>= 1) { struct sof_sdw_codec_info *info; int playback, capture; char *codec_name; if (!(ssp_mask & 0x1)) continue; name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", i); if (!name) return -ENOMEM; cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", i); if (!cpu_name) return -ENOMEM; ssp_components = devm_kzalloc(dev, sizeof(*ssp_components), GFP_KERNEL); if (!ssp_components) return -ENOMEM; info = &codec_info_list[ssp_codec_index]; codec_name = devm_kasprintf(dev, GFP_KERNEL, "i2c-%s:0%d", info->acpi_id, j++); if (!codec_name) return -ENOMEM; ssp_components->name = codec_name; ssp_components->dai_name = info->dai_name; cpus[cpu_id].dai_name = cpu_name; playback = info->direction[SNDRV_PCM_STREAM_PLAYBACK]; capture = info->direction[SNDRV_PCM_STREAM_CAPTURE]; init_dai_link(dev, links + link_index, be_id, name, playback, capture, cpus + cpu_id, 1, ssp_components, 1, NULL, info->ops); ret = info->init(card, NULL, links + link_index, info, 0); if (ret < 0) return ret; INC_ID(be_id, cpu_id, link_index); } DMIC: /* dmic */ if (dmic_num > 0) { if (ignore_pch_dmic) { dev_warn(dev, "Ignoring PCH DMIC\n"); goto HDMI; } cpus[cpu_id].dai_name = "DMIC01 Pin"; init_dai_link(dev, links + link_index, be_id, "dmic01", 0, 1, // DMIC only supports capture cpus + cpu_id, 1, dmic_component, 1, sof_sdw_dmic_init, NULL); INC_ID(be_id, cpu_id, link_index); cpus[cpu_id].dai_name = "DMIC16k Pin"; init_dai_link(dev, links + link_index, be_id, "dmic16k", 0, 1, // DMIC only supports capture cpus + cpu_id, 1, dmic_component, 1, /* don't call sof_sdw_dmic_init() twice */ NULL, NULL); INC_ID(be_id, cpu_id, link_index); } HDMI: /* HDMI */ if (hdmi_num > 0) { idisp_components = devm_kcalloc(dev, hdmi_num, sizeof(*idisp_components), GFP_KERNEL); if (!idisp_components) return -ENOMEM; } for (i = 0; i < hdmi_num; i++) { name = devm_kasprintf(dev, GFP_KERNEL, "iDisp%d", i + 1); if (!name) return -ENOMEM; if (ctx->idisp_codec) { idisp_components[i].name = "ehdaudio0D2"; idisp_components[i].dai_name = devm_kasprintf(dev, GFP_KERNEL, "intel-hdmi-hifi%d", i + 1); if (!idisp_components[i].dai_name) return -ENOMEM; } else { idisp_components[i].name = "snd-soc-dummy"; idisp_components[i].dai_name = "snd-soc-dummy-dai"; } cpu_name = devm_kasprintf(dev, GFP_KERNEL, "iDisp%d Pin", i + 1); if (!cpu_name) return -ENOMEM; cpus[cpu_id].dai_name = cpu_name; init_dai_link(dev, links + link_index, be_id, name, 1, 0, // HDMI only supports playback cpus + cpu_id, 1, idisp_components + i, 1, sof_sdw_hdmi_init, NULL); INC_ID(be_id, cpu_id, link_index); } if (sof_sdw_quirk & SOF_SSP_BT_OFFLOAD_PRESENT) { int port = (sof_sdw_quirk & SOF_BT_OFFLOAD_SSP_MASK) >> SOF_BT_OFFLOAD_SSP_SHIFT; name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); if (!name) return -ENOMEM; ssp_components = devm_kzalloc(dev, sizeof(*ssp_components), GFP_KERNEL); if (!ssp_components) return -ENOMEM; ssp_components->name = "snd-soc-dummy"; ssp_components->dai_name = "snd-soc-dummy-dai"; cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", port); if (!cpu_name) return -ENOMEM; cpus[cpu_id].dai_name = cpu_name; init_dai_link(dev, links + link_index, be_id, name, 1, 1, cpus + cpu_id, 1, ssp_components, 1, NULL, NULL); } card->dai_link = links; card->num_links = num_links; card->codec_conf = codec_conf; card->num_configs = codec_conf_count; return 0; } static int sof_sdw_card_late_probe(struct snd_soc_card *card) { struct mc_private *ctx = snd_soc_card_get_drvdata(card); int ret = 0; int i; for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { if (!codec_info_list[i].late_probe) continue; ret = codec_info_list[i].codec_card_late_probe(card); if (ret < 0) return ret; } if (ctx->idisp_codec) ret = sof_sdw_hdmi_card_late_probe(card); return ret; } /* SoC card */ static const char sdw_card_long_name[] = "Intel Soundwire SOF"; static struct snd_soc_card card_sof_sdw = { .name = "soundwire", .owner = THIS_MODULE, .late_probe = sof_sdw_card_late_probe, }; static void mc_dailink_exit_loop(struct snd_soc_card *card) { struct snd_soc_dai_link *link; int ret; int i, j; for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { if (!codec_info_list[i].exit) continue; /* * We don't need to call .exit function if there is no matched * dai link found. */ for_each_card_prelinks(card, j, link) { if (!strcmp(link->codecs[0].dai_name, codec_info_list[i].dai_name)) { ret = codec_info_list[i].exit(card, link); if (ret) dev_warn(card->dev, "codec exit failed %d\n", ret); break; } } } } static int mc_probe(struct platform_device *pdev) { struct snd_soc_card *card = &card_sof_sdw; struct snd_soc_acpi_mach *mach; struct mc_private *ctx; int amp_num = 0, i; int ret; dev_dbg(&pdev->dev, "Entry\n"); ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; dmi_check_system(sof_sdw_quirk_table); if (quirk_override != -1) { dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", sof_sdw_quirk, quirk_override); sof_sdw_quirk = quirk_override; } log_quirks(&pdev->dev); INIT_LIST_HEAD(&ctx->hdmi_pcm_list); card->dev = &pdev->dev; snd_soc_card_set_drvdata(card, ctx); mach = pdev->dev.platform_data; ret = sof_card_dai_links_create(&pdev->dev, mach, card); if (ret < 0) return ret; /* * the default amp_num is zero for each codec and * amp_num will only be increased for active amp * codecs on used platform */ for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) amp_num += codec_info_list[i].amp_num; card->components = devm_kasprintf(card->dev, GFP_KERNEL, "cfg-spk:%d cfg-amp:%d", (sof_sdw_quirk & SOF_SDW_FOUR_SPK) ? 4 : 2, amp_num); if (!card->components) return -ENOMEM; if (mach->mach_params.dmic_num) { card->components = devm_kasprintf(card->dev, GFP_KERNEL, "%s mic:dmic cfg-mics:%d", card->components, mach->mach_params.dmic_num); if (!card->components) return -ENOMEM; } card->long_name = sdw_card_long_name; /* Register the card */ ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) { dev_err(card->dev, "snd_soc_register_card failed %d\n", ret); mc_dailink_exit_loop(card); return ret; } platform_set_drvdata(pdev, card); return ret; } static int mc_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); mc_dailink_exit_loop(card); return 0; } static struct platform_driver sof_sdw_driver = { .driver = { .name = "sof_sdw", .pm = &snd_soc_pm_ops, }, .probe = mc_probe, .remove = mc_remove, }; module_platform_driver(sof_sdw_driver); MODULE_DESCRIPTION("ASoC SoundWire Generic Machine driver"); MODULE_AUTHOR("Bard Liao "); MODULE_AUTHOR("Rander Wang "); MODULE_AUTHOR("Pierre-Louis Bossart "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:sof_sdw"); MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); MODULE_IMPORT_NS(SND_SOC_INTEL_SOF_MAXIM_COMMON);