ASoC: update auto format selection method

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>
This commit is contained in:
Kuninori Morimoto
2026-06-09 00:23:35 +00:00
committed by Mark Brown
parent 97c7d3d203
commit c6b09cda39
3 changed files with 167 additions and 194 deletions
+3 -4
View 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,
+1 -144
View 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;
+163 -46
View 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)
{
struct snd_soc_dai *dai;
int i, max = 0;
int cnt = 0;
/*
* return max num if *ALL* DAIs have .auto_selectable_formats
*/
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 (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 max;
return cnt;
}
/**
* 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)
static void soc_dai_auto_select_format(u64 fmt, const struct snd_soc_pcm_runtime *rtd,
int idx, u64 *best_fmt)
{
const struct snd_soc_dai_ops *ops = dai->driver->ops;
u64 fmt = 0;
int i, max = 0, until = priority;
struct snd_soc_dai *dai;
const struct snd_soc_dai_ops *ops;
int max_idx = rtd->dai_link->num_cpus + rtd->dai_link->num_codecs;
u64 available_fmt;
/*
* Collect auto_selectable_formats until priority
* NOTE
* It doesn't support Multi CPU/Codec for now
*/
if (rtd->dai_link->num_cpus != 1 ||
rtd->dai_link->num_codecs != 1)
return;
if (idx >= max_idx)
return;
dai = rtd->dais[idx];
ops = dai->driver->ops;
/* zero chance of auto select format */
if (!ops || !ops->num_auto_selectable_formats)
return;
/*
****************************
* 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];
if (ops && ops->auto_selectable_formats)
for (i = 0; i < until; i++)
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);
return 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;
soc_dai_auto_select_format(~0, rtd, 0, &possible_fmt);
return soc_dai_convert_possiblefmt_to_daifmt(possible_fmt, dai_link->dai_fmt);
}
/**