summaryrefslogtreecommitdiffstats
path: root/sound/soc/soc-core.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2021-07-01 08:36:12 +0200
committerTakashi Iwai <tiwai@suse.de>2021-07-01 08:36:12 +0200
commit50de417b7a5bfe8ab5c571427703f67c934736dc (patch)
treec791889f8e1c325e286eea593743cc4a24732e07 /sound/soc/soc-core.c
parent459d2320dc21bd69533589d2f0a37ca942edefb7 (diff)
parent192664528154a84fab4e6d820f9cb2e2e0835544 (diff)
downloadlinux-50de417b7a5bfe8ab5c571427703f67c934736dc.tar.bz2
Merge tag 'asoc-v5.14' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linus
ASoC: Updates for v5.14 This release sees a nice new feature in the core from Morimoto-san, support for automatic negotiation of DAI formats between the components on the link. Otherwise the big highlight was the merging of the Tegra machine drivers into a single driver avoiding a bunch of duplication. - Support for automatic negotiation of DAI formats. - Accessory detection support for several Qualcomm parts. - Support for IEC958 control with hdmi-codec. - Merging of Tegra machine drivers into a single driver. - Support for AmLogic SM1 TOACODEC, Intel AlderLake-M, several NXP i.MX8 variants, NXP TFA1 and TDF9897, Rockchip RK817, Qualcomm Quinary MI2S, Texas Instruments TAS2505
Diffstat (limited to 'sound/soc/soc-core.c')
-rw-r--r--sound/soc/soc-core.c377
1 files changed, 288 insertions, 89 deletions
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 724c05f31464..583f2381cfc8 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -75,9 +75,9 @@ static ssize_t pmdown_time_show(struct device *dev,
return sprintf(buf, "%ld\n", rtd->pmdown_time);
}
-static ssize_t pmdown_time_set(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static ssize_t pmdown_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
int ret;
@@ -89,7 +89,7 @@ static ssize_t pmdown_time_set(struct device *dev,
return count;
}
-static DEVICE_ATTR(pmdown_time, 0644, pmdown_time_show, pmdown_time_set);
+static DEVICE_ATTR_RW(pmdown_time);
static struct attribute *soc_dev_attrs[] = {
&dev_attr_pmdown_time.attr,
@@ -1054,6 +1054,218 @@ _err_defer:
}
EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtime);
+static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+ struct snd_soc_dai *dai, *not_used;
+ struct device *dev = rtd->dev;
+ u64 pos, possible_fmt;
+ unsigned int mask = 0, dai_fmt = 0;
+ int i, j, priority, pri, until;
+
+ /*
+ * Get selectable format from each DAIs.
+ *
+ ****************************
+ * NOTE
+ * Using .auto_selectable_formats is not mandatory,
+ * we can select format manually from Sound Card.
+ * When use it, driver should list well tested format only.
+ ****************************
+ *
+ * ex)
+ * auto_selectable_formats (= SND_SOC_POSSIBLE_xxx)
+ * (A) (B) (C)
+ * DAI0_: { 0x000F, 0x00F0, 0x0F00 };
+ * DAI1 : { 0xF000, 0x0F00 };
+ * (X) (Y)
+ *
+ * "until" will be 3 in this case (MAX array size from DAI0 and DAI1)
+ * Here is dev_dbg() message and comments
+ *
+ * priority = 1
+ * DAI0: (pri, fmt) = (1, 000000000000000F) // 1st check (A) DAI1 is not selected
+ * DAI1: (pri, fmt) = (0, 0000000000000000) // Necessary Waste
+ * DAI0: (pri, fmt) = (1, 000000000000000F) // 2nd check (A)
+ * DAI1: (pri, fmt) = (1, 000000000000F000) // (X)
+ * priority = 2
+ * DAI0: (pri, fmt) = (2, 00000000000000FF) // 3rd check (A) + (B)
+ * DAI1: (pri, fmt) = (1, 000000000000F000) // (X)
+ * DAI0: (pri, fmt) = (2, 00000000000000FF) // 4th check (A) + (B)
+ * DAI1: (pri, fmt) = (2, 000000000000FF00) // (X) + (Y)
+ * priority = 3
+ * DAI0: (pri, fmt) = (3, 0000000000000FFF) // 5th check (A) + (B) + (C)
+ * DAI1: (pri, fmt) = (2, 000000000000FF00) // (X) + (Y)
+ * found auto selected format: 0000000000000F00
+ */
+ until = snd_soc_dai_get_fmt_max_priority(rtd);
+ for (priority = 1; priority <= until; priority++) {
+
+ dev_dbg(dev, "priority = %d\n", priority);
+ for_each_rtd_dais(rtd, j, not_used) {
+
+ possible_fmt = ULLONG_MAX;
+ for_each_rtd_dais(rtd, i, dai) {
+ u64 fmt = 0;
+
+ pri = (j >= i) ? priority : priority - 1;
+ fmt = snd_soc_dai_get_fmt(dai, pri);
+ dev_dbg(dev, "%s: (pri, fmt) = (%d, %016llX)\n", dai->name, pri, fmt);
+ possible_fmt &= fmt;
+ }
+ if (possible_fmt)
+ goto found;
+ }
+ }
+ /* Not Found */
+ return;
+found:
+ dev_dbg(dev, "found auto selected format: %016llX\n", possible_fmt);
+
+ /*
+ * convert POSSIBLE_DAIFMT to DAIFMT
+ *
+ * Some basic/default settings on each is defined as 0.
+ * see
+ * SND_SOC_DAIFMT_NB_NF
+ * SND_SOC_DAIFMT_GATED
+ *
+ * SND_SOC_DAIFMT_xxx_MASK can't notice it if Sound Card specify
+ * these value, and will be overwrite to auto selected value.
+ *
+ * To avoid such issue, loop from 63 to 0 here.
+ * Small number of SND_SOC_POSSIBLE_xxx will be Hi priority.
+ * Basic/Default settings of each part and aboves are defined
+ * as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
+ */
+ for (i = 63; i >= 0; i--) {
+ pos = 1ULL << i;
+ switch (possible_fmt & pos) {
+ /*
+ * for format
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_I2S:
+ case SND_SOC_POSSIBLE_DAIFMT_RIGHT_J:
+ case SND_SOC_POSSIBLE_DAIFMT_LEFT_J:
+ case SND_SOC_POSSIBLE_DAIFMT_DSP_A:
+ case SND_SOC_POSSIBLE_DAIFMT_DSP_B:
+ case SND_SOC_POSSIBLE_DAIFMT_AC97:
+ case SND_SOC_POSSIBLE_DAIFMT_PDM:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i;
+ break;
+ /*
+ * for clock
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_CONT:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_GATED:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED;
+ break;
+ /*
+ * for clock invert
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF;
+ break;
+ /*
+ * for clock provider / consumer
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_CBP_CFP:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFP;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_CBC_CFP:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFP;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_CBP_CFC:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFC;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_CBC_CFC:
+ dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFC;
+ break;
+ }
+ }
+
+ /*
+ * Some driver might have very complex limitation.
+ * In such case, user want to auto-select non-limitation part,
+ * and want to manually specify complex part.
+ *
+ * Or for example, if both CPU and Codec can be clock provider,
+ * but because of its quality, user want to specify it manually.
+ *
+ * Use manually specified settings if sound card did.
+ */
+ if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
+ mask |= SND_SOC_DAIFMT_FORMAT_MASK;
+ if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
+ mask |= SND_SOC_DAIFMT_CLOCK_MASK;
+ if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_INV_MASK))
+ mask |= SND_SOC_DAIFMT_INV_MASK;
+ if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
+ mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
+
+ dai_link->dai_fmt |= (dai_fmt & mask);
+}
+
+/**
+ * snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime
+ * @rtd: The runtime for which the DAI link format should be changed
+ * @dai_fmt: The new DAI link format
+ *
+ * This function updates the DAI link format for all DAIs connected to the DAI
+ * link for the specified runtime.
+ *
+ * Note: For setups with a static format set the dai_fmt field in the
+ * corresponding snd_dai_link struct instead of using this function.
+ *
+ * Returns 0 on success, otherwise a negative error code.
+ */
+int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
+ unsigned int dai_fmt)
+{
+ struct snd_soc_dai *cpu_dai;
+ struct snd_soc_dai *codec_dai;
+ unsigned int inv_dai_fmt;
+ unsigned int i;
+ int ret;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
+ if (ret != 0 && ret != -ENOTSUPP)
+ return ret;
+ }
+
+ /*
+ * Flip the polarity for the "CPU" end of a CODEC<->CODEC link
+ * the component which has non_legacy_dai_naming is Codec
+ */
+ inv_dai_fmt = snd_soc_daifmt_clock_provider_fliped(dai_fmt);
+
+ for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
+ unsigned int fmt = dai_fmt;
+
+ if (cpu_dai->component->driver->non_legacy_dai_naming)
+ fmt = inv_dai_fmt;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret != 0 && ret != -ENOTSUPP)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt);
+
static int soc_init_pcm_runtime(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
{
@@ -1070,6 +1282,7 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card,
if (ret < 0)
return ret;
+ snd_soc_runtime_get_dai_fmt(rtd);
if (dai_link->dai_fmt) {
ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);
if (ret)
@@ -1402,68 +1615,6 @@ static void soc_remove_aux_devices(struct snd_soc_card *card)
}
}
-/**
- * snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime
- * @rtd: The runtime for which the DAI link format should be changed
- * @dai_fmt: The new DAI link format
- *
- * This function updates the DAI link format for all DAIs connected to the DAI
- * link for the specified runtime.
- *
- * Note: For setups with a static format set the dai_fmt field in the
- * corresponding snd_dai_link struct instead of using this function.
- *
- * Returns 0 on success, otherwise a negative error code.
- */
-int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
- unsigned int dai_fmt)
-{
- struct snd_soc_dai *cpu_dai;
- struct snd_soc_dai *codec_dai;
- unsigned int inv_dai_fmt;
- unsigned int i;
- int ret;
-
- for_each_rtd_codec_dais(rtd, i, codec_dai) {
- ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
- if (ret != 0 && ret != -ENOTSUPP)
- return ret;
- }
-
- /*
- * Flip the polarity for the "CPU" end of a CODEC<->CODEC link
- * the component which has non_legacy_dai_naming is Codec
- */
- inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_MASTER_MASK;
- switch (dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFM:
- inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
- break;
- case SND_SOC_DAIFMT_CBM_CFS:
- inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
- break;
- case SND_SOC_DAIFMT_CBS_CFM:
- inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
- break;
- case SND_SOC_DAIFMT_CBS_CFS:
- inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
- break;
- }
- for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
- unsigned int fmt = dai_fmt;
-
- if (cpu_dai->component->driver->non_legacy_dai_naming)
- fmt = inv_dai_fmt;
-
- ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
- if (ret != 0 && ret != -ENOTSUPP)
- return ret;
- }
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt);
-
#ifdef CONFIG_DMI
/*
* If a DMI filed contain strings in this blacklist (e.g.
@@ -2793,7 +2944,7 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
if (!routes) {
dev_err(card->dev,
"ASoC: Could not allocate DAPM route table\n");
- return -EINVAL;
+ return -ENOMEM;
}
for (i = 0; i < num_routes; i++) {
@@ -2853,10 +3004,54 @@ int snd_soc_of_parse_aux_devs(struct snd_soc_card *card, const char *propname)
}
EXPORT_SYMBOL_GPL(snd_soc_of_parse_aux_devs);
-unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
- const char *prefix,
- struct device_node **bitclkmaster,
- struct device_node **framemaster)
+unsigned int snd_soc_daifmt_clock_provider_fliped(unsigned int dai_fmt)
+{
+ unsigned int inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
+
+ switch (dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_CBP_CFP:
+ inv_dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
+ break;
+ case SND_SOC_DAIFMT_CBP_CFC:
+ inv_dai_fmt |= SND_SOC_DAIFMT_CBC_CFP;
+ break;
+ case SND_SOC_DAIFMT_CBC_CFP:
+ inv_dai_fmt |= SND_SOC_DAIFMT_CBP_CFC;
+ break;
+ case SND_SOC_DAIFMT_CBC_CFC:
+ inv_dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
+ break;
+ }
+
+ return inv_dai_fmt;
+}
+EXPORT_SYMBOL_GPL(snd_soc_daifmt_clock_provider_fliped);
+
+unsigned int snd_soc_daifmt_clock_provider_from_bitmap(unsigned int bit_frame)
+{
+ /*
+ * bit_frame is return value from
+ * snd_soc_daifmt_parse_clock_provider_raw()
+ */
+
+ /* Codec base */
+ switch (bit_frame) {
+ case 0x11:
+ return SND_SOC_DAIFMT_CBP_CFP;
+ case 0x10:
+ return SND_SOC_DAIFMT_CBP_CFC;
+ case 0x01:
+ return SND_SOC_DAIFMT_CBC_CFP;
+ default:
+ return SND_SOC_DAIFMT_CBC_CFC;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_daifmt_clock_provider_from_bitmap);
+
+unsigned int snd_soc_daifmt_parse_format(struct device_node *np,
+ const char *prefix)
{
int ret, i;
char prop[128];
@@ -2936,10 +3131,24 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
break;
}
+ return format;
+}
+EXPORT_SYMBOL_GPL(snd_soc_daifmt_parse_format);
+
+unsigned int snd_soc_daifmt_parse_clock_provider_raw(struct device_node *np,
+ const char *prefix,
+ struct device_node **bitclkmaster,
+ struct device_node **framemaster)
+{
+ char prop[128];
+ unsigned int bit, frame;
+
+ if (!prefix)
+ prefix = "";
+
/*
* check "[prefix]bitclock-master"
* check "[prefix]frame-master"
- * SND_SOC_DAIFMT_MASTER_MASK area
*/
snprintf(prop, sizeof(prop), "%sbitclock-master", prefix);
bit = !!of_get_property(np, prop, NULL);
@@ -2951,24 +3160,14 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
if (frame && framemaster)
*framemaster = of_parse_phandle(np, prop, 0);
- switch ((bit << 4) + frame) {
- case 0x11:
- format |= SND_SOC_DAIFMT_CBM_CFM;
- break;
- case 0x10:
- format |= SND_SOC_DAIFMT_CBM_CFS;
- break;
- case 0x01:
- format |= SND_SOC_DAIFMT_CBS_CFM;
- break;
- default:
- format |= SND_SOC_DAIFMT_CBS_CFS;
- break;
- }
-
- return format;
+ /*
+ * return bitmap.
+ * It will be parameter of
+ * snd_soc_daifmt_clock_provider_from_bitmap()
+ */
+ return (bit << 4) + frame;
}
-EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt);
+EXPORT_SYMBOL_GPL(snd_soc_daifmt_parse_clock_provider_raw);
int snd_soc_get_dai_id(struct device_node *ep)
{