diff options
Diffstat (limited to 'sound/soc/soc-dai.c')
| -rw-r--r-- | sound/soc/soc-dai.c | 207 |
1 files changed, 162 insertions, 45 deletions
diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 1719ddcefa4b..b098d1100b4b 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -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); } /** |
