[PATCH v2 2/4] ASoC: meson: aiu-encoder-i2s: prepare for multiple streams
Jerome Brunet
jbrunet at baylibre.com
Thu Jun 11 01:16:56 PDT 2026
On mer. 10 juin 2026 at 23:29, Valerio Setti <vsetti at baylibre.com> wrote:
> aiu-encoder-i2s is going to be the interface that handles both playback
> and capture, so this commit does all the required changes to prepare
> for that since so far it only handled playback:
> - probe/remove functions are added to allocate/free per stream data,
> respectively.
> - 'struc gx_iface' and 'struct gx_stream' are used to store interface or
> stream associated data, respecively.
> - interface wide rate symmetry is enforced.
> - quirks on bclk are also enforced if/when necessary.
>
> Clock-wise instead of bulk enabling all the clocks on startup and disabling
> them on shutdown, only the peripheral's internal ones are enabled/disabled
> in those functions, whereas MCLK and I2S clock divider are handled in
> prepare/hw_free.
>
> Finally a trigger() callback is also added to start/stop the associated
> I2S data formatter.
>
> Signed-off-by: Valerio Setti <vsetti at baylibre.com>
> ---
> sound/soc/meson/aiu-encoder-i2s.c | 207 ++++++++++++++++++++++++++++++++++----
> sound/soc/meson/aiu.h | 3 +
> 2 files changed, 193 insertions(+), 17 deletions(-)
>
> diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
> index 3b4061508c18047fe8d6f3f98061720f8ce238f2..f50b03824ad280afabb31eecc20ccb855defa11e 100644
> --- a/sound/soc/meson/aiu-encoder-i2s.c
> +++ b/sound/soc/meson/aiu-encoder-i2s.c
> @@ -10,6 +10,8 @@
> #include <sound/soc-dai.h>
>
> #include "aiu.h"
> +#include "gx-formatter.h"
> +#include "gx-interface.h"
>
> #define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
> #define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
> @@ -112,6 +114,9 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
> struct snd_pcm_hw_params *params,
> unsigned int bs)
> {
> + struct aiu *aiu = snd_soc_component_get_drvdata(component);
> + struct gx_iface *iface = &aiu->i2s.iface;
> +
> /*
> * NOTE: this HW is odd.
> * In most configuration, the i2s divider is 'mclk / blck'.
> @@ -126,6 +131,18 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
> return -EINVAL;
> }
> bs += bs / 2;
> + iface->bs_quirk = true;
> + } else {
> + /*
> + * If the bs quirk is currently applied for one stream and another
> + * ones tries to setup a configuration for which the quirk is
> + * not required, then fail.
> + */
> + if (iface->bs_quirk) {
> + dev_err(component->dev,
> + "bclk requirements are incompatible with active stream\n");
> + return -EINVAL;
This comment is not blocking thing IMO, more a request for future
improvments.
What I do not like here is the user may get an unexpected failure while
setting up the stream, because of something that did not show up the
contrainst we provided. IOW, it is kind of too late.
The problem is not new to this driver, the same applies to the error
returned if bs is not a multiple of 2 :(
It would be nice if this problem could be reflected in the contraints
to avoid returning an error this late in the stream setup.
> + }
> }
>
> /* Use CLK_MORE for mclk to bclk divider */
> @@ -145,14 +162,15 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
> struct snd_pcm_hw_params *params)
> {
> struct aiu *aiu = snd_soc_component_get_drvdata(component);
> + struct gx_iface *iface = &aiu->i2s.iface;
> unsigned int srate = params_rate(params);
> unsigned int fs, bs;
> int ret;
>
> /* Get the oversampling factor */
> - fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
> + fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate);
>
> - if (fs % 64)
> + if ((fs % 64) || (fs == 0))
> return -EINVAL;
>
> /* Send data MSB first */
> @@ -188,24 +206,59 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
> struct snd_pcm_hw_params *params,
> struct snd_soc_dai *dai)
> {
> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
> + struct gx_iface *iface = ts->iface;
> struct snd_soc_component *component = dai->component;
> int ret;
>
> - /* Disable the clock while changing the settings */
> - aiu_encoder_i2s_divider_enable(component, false);
> + /*
> + * Enforce interface wide rate symmetry only if there is more than
> + * 1 stream active.
> + */
> + if (snd_soc_dai_active(dai) > 1) {
> + if (iface->rate && iface->rate != params_rate(params)) {
> + dev_err(dai->dev, "can't set iface rate (%d != %d)\n",
> + iface->rate, params_rate(params));
> + return -EINVAL;
> + }
> + }
>
> ret = aiu_encoder_i2s_setup_desc(component, params);
> if (ret) {
> - dev_err(dai->dev, "setting i2s desc failed\n");
> + dev_err(dai->dev, "setting i2s desc failed: %d\n", ret);
> return ret;
> }
>
> ret = aiu_encoder_i2s_set_clocks(component, params);
> if (ret) {
> - dev_err(dai->dev, "setting i2s clocks failed\n");
> + dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret);
> return ret;
> }
>
> + iface->rate = params_rate(params);
> + ts->physical_width = params_physical_width(params);
> + ts->width = params_width(params);
> + ts->channels = params_channels(params);
> +
> + return 0;
> +}
> +
> +static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
> + struct snd_soc_component *component = dai->component;
> + int ret;
> +
> + if (ts->clk_enabled)
> + return 0;
> +
> + ret = clk_prepare_enable(ts->iface->mclk);
> + if (ret)
> + return ret;
> +
> + ts->clk_enabled = true;
> +
> aiu_encoder_i2s_divider_enable(component, true);
>
> return 0;
> @@ -214,9 +267,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
> static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
> struct snd_soc_dai *dai)
> {
> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
> + struct gx_iface *iface = ts->iface;
> struct snd_soc_component *component = dai->component;
>
> - aiu_encoder_i2s_divider_enable(component, false);
> + /*
> + * If this is the last substream being closed then disable the i2s
> + * clock divider and clear 'iface->rate'.
> + */
> + if (snd_soc_dai_active(dai) <= 1) {
> + aiu_encoder_i2s_divider_enable(component, 0);
> + iface->rate = 0;
> + iface->bs_quirk = false;
> + }
> +
> + if (ts->clk_enabled) {
> + clk_disable_unprepare(ts->iface->mclk);
> + ts->clk_enabled = false;
> + }
>
> return 0;
> }
> @@ -224,6 +292,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
> static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
> {
> struct snd_soc_component *component = dai->component;
> + struct aiu *aiu = snd_soc_component_get_drvdata(component);
> + struct gx_iface *iface = &aiu->i2s.iface;
> unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
> unsigned int val = 0;
> unsigned int skew;
> @@ -255,9 +325,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
> skew = 0;
> break;
> default:
> + dev_err(dai->dev, "unsupported dai format\n");
> return -EINVAL;
> }
>
> + iface->fmt = fmt;
> +
> val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
> snd_soc_component_update_bits(component, AIU_CLK_CTRL,
> AIU_CLK_CTRL_LRCLK_INVERT |
> @@ -272,6 +345,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
> unsigned int freq, int dir)
> {
> struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
> + struct gx_iface *iface = &aiu->i2s.iface;
> int ret;
>
> if (WARN_ON(clk_id != 0))
> @@ -280,11 +354,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
> if (dir == SND_SOC_CLOCK_IN)
> return 0;
>
> - ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);
> - if (ret)
> - dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
> + ret = clk_set_rate(iface->mclk, freq);
> + if (ret) {
> + dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret);
> + return ret;
> + }
>
> - return ret;
> + iface->mclk_rate = freq;
> +
> + return 0;
> }
>
> static const unsigned int hw_channels[] = {2, 8};
> @@ -305,15 +383,35 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
> SNDRV_PCM_HW_PARAM_CHANNELS,
> &hw_channel_constraints);
> if (ret) {
> - dev_err(dai->dev, "adding channels constraints failed\n");
> + dev_err(dai->dev, "adding channels constraints failed: %d\n", ret);
> return ret;
> }
>
> - ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);
> - if (ret)
> - dev_err(dai->dev, "failed to enable i2s clocks\n");
> + /*
> + * Enable only clocks which are required for the interface internal
> + * logic. MCLK is enabled/disabled from the formatter and the I2S
> + * divider is enabled/disabled in "hw_params"/"hw_free", respectively.
> + */
> + ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk);
> + if (ret) {
> + dev_err(dai->dev, "failed to enable PCLK: %d\n", ret);
> + return ret;
> + }
> + ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk);
> + if (ret) {
> + dev_err(dai->dev, "failed to enable MIXER: %d\n", ret);
> + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
> + return ret;
> + }
> + ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk);
> + if (ret) {
> + dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret);
> + clk_disable_unprepare(aiu->i2s.clks[MIXER].clk);
> + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
> + return ret;
> + }
>
> - return ret;
> + return 0;
> }
>
> static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
> @@ -321,14 +419,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
> {
> struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
>
> - clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
> + clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk);
> + clk_disable_unprepare(aiu->i2s.clks[MIXER].clk);
> + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
> +}
> +
> +static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream,
> + int cmd,
> + struct snd_soc_dai *dai)
> +{
> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
> + int ret;
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + case SNDRV_PCM_TRIGGER_RESUME:
> + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> + ret = gx_stream_start(ts);
> + break;
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> + case SNDRV_PCM_TRIGGER_STOP:
> + gx_stream_stop(ts);
> + ret = 0;
> + break;
> + default:
> + ret = -EINVAL;
> + }
> +
> + return ret;
> +}
> +
> +static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai)
> +{
> + int stream;
> +
> + for_each_pcm_streams(stream) {
> + struct gx_stream *ts;
> +
> + ts = snd_soc_dai_dma_data_get(dai, stream);
> + if (ts)
> + gx_stream_free(ts);
> +
> + snd_soc_dai_dma_data_set(dai, stream, NULL);
> + }
> +
> + return 0;
> +}
> +
> +static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai)
> +{
> + struct aiu *aiu = snd_soc_dai_get_drvdata(dai);
> + struct gx_iface *iface = &aiu->i2s.iface;
> + int stream;
> +
> + for_each_pcm_streams(stream) {
> + struct gx_stream *ts;
> +
> + if (!snd_soc_dai_get_widget(dai, stream))
> + continue;
> +
> + ts = gx_stream_alloc(iface);
> + if (!ts) {
> + aiu_encoder_i2s_remove_dai(dai);
> + return -ENOMEM;
> + }
> + snd_soc_dai_dma_data_set(dai, stream, ts);
> + }
> +
> + iface->mclk = aiu->i2s.clks[MCLK].clk;
> + iface->mclk_rate = clk_get_rate(iface->mclk);
> +
> + return 0;
> }
>
> const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
> + .probe = aiu_encoder_i2s_probe_dai,
> + .remove = aiu_encoder_i2s_remove_dai,
> .hw_params = aiu_encoder_i2s_hw_params,
> + .prepare = aiu_encoder_i2s_prepare,
> .hw_free = aiu_encoder_i2s_hw_free,
> .set_fmt = aiu_encoder_i2s_set_fmt,
> .set_sysclk = aiu_encoder_i2s_set_sysclk,
> .startup = aiu_encoder_i2s_startup,
> .shutdown = aiu_encoder_i2s_shutdown,
> + .trigger = aiu_encoder_i2s_trigger,
> };
> diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h
> index 0f94c8bf608181112d78402532b832eb50c2d409..68310de0bdf7a97d8de2ff306c159248ee9b0ede 100644
> --- a/sound/soc/meson/aiu.h
> +++ b/sound/soc/meson/aiu.h
> @@ -7,6 +7,8 @@
> #ifndef _MESON_AIU_H
> #define _MESON_AIU_H
>
> +#include "gx-formatter.h"
> +
> struct clk;
> struct clk_bulk_data;
> struct device;
> @@ -25,6 +27,7 @@ struct aiu_interface {
> struct clk_bulk_data *clks;
> unsigned int clk_num;
> int irq;
> + struct gx_iface iface;
> };
>
> struct aiu_platform_data {
--
Jerome
More information about the linux-arm-kernel
mailing list