]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ASoC: update auto format selection method
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Tue, 9 Jun 2026 00:23:35 +0000 (00:23 +0000)
committerMark Brown <broonie@kernel.org>
Fri, 12 Jun 2026 17:57:58 +0000 (18:57 +0100)
Current DAI supports auto format selection. It allow to have array like
below.

(X) static u64 xxx_auto_formats[] = {
(A) /* First Priority */
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_LEFT_J,

/* Second Priority */
(B) SND_SOC_POSSIBLE_DAIFMT_DSP_A |
SND_SOC_POSSIBLE_DAIFMT_DSP_B,
};

It try to find available format from I2S/LEFT_J first (A).
Then, try to find from I2S/LEFT_J/DSP_A/DSP_B if couldn't find (A)+(B).
(OR:ed)

In this method, it can't handle if there is format combination.
For example, some driver has pattern.

Pattern1
I2S/RIFHT_J/LEFT_J (FORMAT) and NB_NF/IB_IF/IB_NF/NB_IF (INV)_
Pattern2
DSP_A/DSP_B        (FORMAT) and NB_NF/      IB_NF

Because it will try to OR Pattern1 and Pattern2, un-supported
pattern might be selected.

This patch update method not to use OR, and assumes full format array.
Above sample (X) need to be

static u64 xxx_auto_formats[] = {
/* First Priority */
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_LEFT_J,

/* Second Priority */
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_LEFT_J |
SND_SOC_POSSIBLE_DAIFMT_DSP_A |
SND_SOC_POSSIBLE_DAIFMT_DSP_B,
};

Note: It doesn't support Multi CPU/Codec for now

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://patch.msgid.link/87jys836k8.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
include/sound/soc-dai.h
sound/soc/soc-core.c
sound/soc/soc-dai.c

index c40823ba5456443dc9dc076b1892921e19fbb18c..ba3ae56c6b0620d5f4b12b5c08f770b64d5ccfd7 100644 (file)
@@ -81,10 +81,10 @@ struct clk;
 /*
  * define GATED -> CONT. GATED will be selected if both are selected.
  * see
- *     snd_soc_runtime_get_dai_fmt()
+ *     soc_dai_convert_possiblefmt_to_daifmt()
  */
 #define SND_SOC_POSSIBLE_DAIFMT_CLOCK_SHIFT    16
-#define SND_SOC_POSSIBLE_DAIFMT_CLOCK_MASK     (0xFFFF << SND_SOC_POSSIBLE_DAIFMT_CLOCK_SHIFT)
+#define SND_SOC_POSSIBLE_DAIFMT_CLOCK_MASK     (0xFFFFULL << SND_SOC_POSSIBLE_DAIFMT_CLOCK_SHIFT)
 #define SND_SOC_POSSIBLE_DAIFMT_GATED          (0x1ULL << SND_SOC_POSSIBLE_DAIFMT_CLOCK_SHIFT)
 #define SND_SOC_POSSIBLE_DAIFMT_CONT           (0x2ULL << SND_SOC_POSSIBLE_DAIFMT_CLOCK_SHIFT)
 
@@ -184,8 +184,7 @@ int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio);
 void snd_soc_dai_set_bclk_clk(struct snd_soc_dai *dai, struct clk *bclk);
 
 /* Digital Audio interface formatting */
-int snd_soc_dai_get_fmt_max_priority(const struct snd_soc_pcm_runtime *rtd);
-u64 snd_soc_dai_get_fmt(const struct snd_soc_dai *dai, int priority);
+unsigned int snd_soc_dai_auto_select_format(const struct snd_soc_pcm_runtime *rtd);
 int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt);
 
 int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
index b87219be5860e5e6dfc13c197c4af652cd984ec2..86b6c752a56b761b91f57ce1394d3e3f0b871a1e 100644 (file)
@@ -1284,148 +1284,6 @@ int snd_soc_add_pcm_runtimes(struct snd_soc_card *card,
 }
 EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtimes);
 
-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;
-       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++) {
-               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);
-                               possible_fmt &= fmt;
-                       }
-                       if (possible_fmt)
-                               goto found;
-               }
-       }
-       /* Not Found */
-       return;
-found:
-       /*
-        * 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 above 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;
-               }
-       }
-
-       /*
-        * 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
@@ -1504,8 +1362,7 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card,
        if (ret < 0)
                return ret;
 
-       snd_soc_runtime_get_dai_fmt(rtd);
-       ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);
+       ret = snd_soc_runtime_set_dai_fmt(rtd, snd_soc_dai_auto_select_format(rtd));
        if (ret)
                goto err;
 
index 1719ddcefa4b03926b7c13cda0e701a057e0bf54..b098d1100b4bb9524e79b497c812301f80c607c2 100644 (file)
@@ -138,68 +138,185 @@ void snd_soc_dai_set_bclk_clk(struct snd_soc_dai *dai, struct clk *bclk)
 }
 EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_clk);
 
-int snd_soc_dai_get_fmt_max_priority(const struct snd_soc_pcm_runtime *rtd)
+static int soc_dai_fmt_match_cnt(u64 fmt)
+{
+       int cnt = 0;
+
+       if (fmt & SND_SOC_POSSIBLE_DAIFMT_FORMAT_MASK)
+               cnt++;
+       if (fmt & SND_SOC_POSSIBLE_DAIFMT_CLOCK_MASK)
+               cnt++;
+       if (fmt & SND_SOC_POSSIBLE_DAIFMT_INV_MASK)
+               cnt++;
+
+       return cnt;
+}
+
+static void soc_dai_auto_select_format(u64 fmt, const struct snd_soc_pcm_runtime *rtd,
+                                     int idx, u64 *best_fmt)
 {
        struct snd_soc_dai *dai;
-       int i, max = 0;
+       const struct snd_soc_dai_ops *ops;
+       int max_idx = rtd->dai_link->num_cpus + rtd->dai_link->num_codecs;
+       u64 available_fmt;
 
        /*
-        * return max num if *ALL* DAIs have .auto_selectable_formats
+        * NOTE
+        * It doesn't support Multi CPU/Codec for now
         */
-       for_each_rtd_dais(rtd, i, dai) {
-               if (dai->driver->ops &&
-                   dai->driver->ops->num_auto_selectable_formats)
-                       max = max(max, dai->driver->ops->num_auto_selectable_formats);
-               else
-                       return 0;
-       }
+       if (rtd->dai_link->num_cpus     != 1 ||
+           rtd->dai_link->num_codecs   != 1)
+               return;
 
-       return max;
-}
+       if (idx >= max_idx)
+               return;
 
-/**
- * snd_soc_dai_get_fmt - get supported audio format.
- * @dai: DAI
- * @priority: priority level of supported audio format.
- *
- * This should return only formats implemented with high
- * quality by the DAI so that the core can configure a
- * format which will work well with other devices.
- * For example devices which don't support both edges of the
- * LRCLK signal in I2S style formats should only list DSP
- * modes.  This will mean that sometimes fewer formats
- * are reported here than are supported by set_fmt().
- */
-u64 snd_soc_dai_get_fmt(const struct snd_soc_dai *dai, int priority)
-{
-       const struct snd_soc_dai_ops *ops = dai->driver->ops;
-       u64 fmt = 0;
-       int i, max = 0, until = priority;
+       dai = rtd->dais[idx];
+       ops = dai->driver->ops;
+
+       /* zero chance of auto select format */
+       if (!ops || !ops->num_auto_selectable_formats)
+               return;
 
        /*
-        * Collect auto_selectable_formats until priority
+        ****************************
+        *            NOTE
+        ****************************
+        * Using .auto_selectable_formats is not mandatory,
+        * It try to find best formats as much as possible, but automatically selecting the
+        * perfect format is impossible. So you can select full or missing format manually
+        * from Sound Card.
         *
         * ex)
-        *      auto_selectable_formats[] = { A, B, C };
-        *      (A, B, C = SND_SOC_POSSIBLE_DAIFMT_xxx)
+        * CPU                                  Codec
+        * (A)[0] I2S/LEFT_J : IB_NF/IB_IF      (X)[0] I2S/DSP_A: NB_NF : GATED
+        * (B)[1] DSP_A/DSP_B: NB_NF/IB_NF      (Y)[1] LEFT_J:    NB_NF : GATED
+        * (C)[2] ...
         *
-        * priority = 1 :       A
-        * priority = 2 :       A | B
-        * priority = 3 :       A | B | C
-        * priority = 4 :       A | B | C
+        * 1. (A) -> (X) : I2S          :update best format
+        * 2. (A) -> (Y) : LEFT_J
+        * 3. (B) -> (X) : DSP_A/NB_NF  :update best format
+        * 4. (B) -> (Y) : NB_NF
+        * 5. (C) -> (X) ...
+        * 6. (C) -> (Y) ...
         * ...
+        *
+        * In above case GATED will not be selected
         */
-       if (ops)
-               max = ops->num_auto_selectable_formats;
 
-       if (max < until)
-               until = max;
+       /* find best formats */
+       for (int i = 0; i < ops->num_auto_selectable_formats; i++) {
+               available_fmt = fmt & ops->auto_selectable_formats[i];
+
+               /* In case of last DAI */
+               if (idx + 1 >= max_idx) {
+                       int cnt1 = soc_dai_fmt_match_cnt(*best_fmt);
+                       int cnt2 = soc_dai_fmt_match_cnt(available_fmt);
+
+                       if (cnt1 < cnt2)
+                               *best_fmt = available_fmt;
+               }
+               /* parse with next DAI */
+               else {
+                       soc_dai_auto_select_format(available_fmt, rtd, idx + 1, best_fmt);
+               }
+       }
+}
+
+static unsigned int soc_dai_convert_possiblefmt_to_daifmt(u64 possible_fmt, unsigned int configured_fmt)
+{
+       unsigned int fmt = 0;
+       unsigned int mask = 0;
+
+       /*
+        * 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 above are defined
+        * as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
+        */
+       for (int i = 63; i >= 0; i--) {
+               u64 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:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i;
+                       break;
+               /*
+                * for clock
+                */
+               case SND_SOC_POSSIBLE_DAIFMT_CONT:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT;
+                       break;
+               case SND_SOC_POSSIBLE_DAIFMT_GATED:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED;
+                       break;
+               /*
+                * for clock invert
+                */
+               case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF;
+                       break;
+               case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF;
+                       break;
+               case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF;
+                       break;
+               case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
+                       fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF;
+                       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.
+        *
+        * Ignore already configured format if exist
+        */
+       if (!(configured_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
+               mask |= SND_SOC_DAIFMT_FORMAT_MASK;
+       if (!(configured_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
+               mask |= SND_SOC_DAIFMT_CLOCK_MASK;
+       if (!(configured_fmt & SND_SOC_DAIFMT_INV_MASK))
+               mask |= SND_SOC_DAIFMT_INV_MASK;
+       if (!(configured_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
+               mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
+
+       return configured_fmt | (fmt & mask);
+}
+
+unsigned int snd_soc_dai_auto_select_format(const struct snd_soc_pcm_runtime *rtd)
+{
+       struct snd_soc_dai_link *dai_link = rtd->dai_link;
+       u64 possible_fmt = 0;
 
-       if (ops && ops->auto_selectable_formats)
-               for (i = 0; i < until; i++)
-                       fmt |= ops->auto_selectable_formats[i];
+       soc_dai_auto_select_format(~0, rtd, 0, &possible_fmt);
 
-       return fmt;
+       return soc_dai_convert_possiblefmt_to_daifmt(possible_fmt, dai_link->dai_fmt);
 }
 
 /**