* [PATCH RFC v2 01/11] ASoC: meson: gx: add gx-formatter and gx-interface
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 02/11] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures Valerio Setti
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
These files are the basic block which allow to shape I2S in GX devices
the same as the AXG ones: the DAI backend only controls the interface
(i.e. clocks and pins) whereas a formatter takes care of properly
formatting the data.
gx-formatter and gx-interface are strongly inspired to axg-tdm-formatter
and axg-tdm, respectively. The long term plan is to join the two platforms
to use the same formatter solution.
There is only a minor addition here compared to what has been done for
AXG and it's "gx_formatter_create()" which is required in order to let
already existing AIU code to make use of this formatter without making any
devicetree change.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Makefile | 1 +
sound/soc/meson/gx-formatter.c | 304 +++++++++++++++++++++++++++++++++++++++++
sound/soc/meson/gx-formatter.h | 47 +++++++
sound/soc/meson/gx-interface.h | 50 +++++++
4 files changed, 402 insertions(+)
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 24078e4396b02d545d8ba4bcb1632979001354e3..146ec81526ba091a174a113ce3d8412ddbbfd9dd 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -4,6 +4,7 @@ snd-soc-meson-aiu-y := aiu.o
snd-soc-meson-aiu-y += aiu-acodec-ctrl.o
snd-soc-meson-aiu-y += aiu-codec-ctrl.o
snd-soc-meson-aiu-y += aiu-encoder-i2s.o
+snd-soc-meson-aiu-y += gx-formatter.o
snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c
new file mode 100644
index 0000000000000000000000000000000000000000..3f6d01f8d755ddedaaa0b0ab0a2683155e43d1b6
--- /dev/null
+++ b/sound/soc/meson/gx-formatter.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+
+#include "gx-formatter.h"
+
+struct gx_formatter {
+ struct list_head list;
+ struct gx_stream *stream;
+ const struct gx_formatter_driver *drv;
+ bool enabled;
+ struct regmap *map;
+};
+
+static int gx_formatter_enable(struct gx_formatter *formatter)
+{
+ int ret;
+
+ /* Do nothing if the formatter is already enabled */
+ if (formatter->enabled)
+ return 0;
+
+ /* Setup the stream parameter in the formatter */
+ if (formatter->drv->ops->prepare) {
+ ret = formatter->drv->ops->prepare(formatter->map,
+ formatter->drv->quirks,
+ formatter->stream);
+ if (ret)
+ return ret;
+ }
+
+ /* Finally, actually enable the formatter */
+ if (formatter->drv->ops->enable)
+ formatter->drv->ops->enable(formatter->map);
+
+ formatter->enabled = true;
+
+ return 0;
+}
+
+static void gx_formatter_disable(struct gx_formatter *formatter)
+{
+ /* Do nothing if the formatter is already disabled */
+ if (!formatter->enabled)
+ return;
+
+ if (formatter->drv->ops->disable)
+ formatter->drv->ops->disable(formatter->map);
+
+ formatter->enabled = false;
+}
+
+static int gx_formatter_attach(struct gx_formatter *formatter)
+{
+ struct gx_stream *ts = formatter->stream;
+ int ret = 0;
+
+ mutex_lock(&ts->lock);
+
+ /* Catch up if the stream is already running when we attach */
+ if (ts->ready) {
+ ret = gx_formatter_enable(formatter);
+ if (ret) {
+ pr_err("failed to enable formatter\n");
+ goto out;
+ }
+ }
+
+ list_add_tail(&formatter->list, &ts->formatter_list);
+out:
+ mutex_unlock(&ts->lock);
+ return ret;
+}
+
+static void gx_formatter_detach(struct gx_formatter *formatter)
+{
+ struct gx_stream *ts = formatter->stream;
+
+ mutex_lock(&ts->lock);
+ list_del(&formatter->list);
+ mutex_unlock(&ts->lock);
+
+ gx_formatter_disable(formatter);
+}
+
+static int gx_formatter_power_up(struct gx_formatter *formatter,
+ struct snd_soc_dapm_widget *w)
+{
+ struct gx_stream *ts = formatter->drv->ops->get_stream(w);
+ int ret;
+
+ /*
+ * If we don't get a stream at this stage, it would mean that the
+ * widget is powering up but is not attached to any backend DAI.
+ * It should not happen, ever !
+ */
+ if (WARN_ON(!ts))
+ return -ENODEV;
+
+ formatter->stream = ts;
+ ret = gx_formatter_attach(formatter);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void gx_formatter_power_down(struct gx_formatter *formatter)
+{
+ gx_formatter_detach(formatter);
+ formatter->stream = NULL;
+}
+
+int gx_formatter_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control,
+ int event)
+{
+ struct snd_soc_component *c;
+ struct gx_formatter *formatter;
+ int ret = 0;
+
+ c = snd_soc_dapm_to_component(w->dapm);
+
+ if (w->priv != NULL)
+ formatter = w->priv;
+ else
+ formatter = snd_soc_component_get_drvdata(c);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = gx_formatter_power_up(formatter, w);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ gx_formatter_power_down(formatter);
+ break;
+
+ default:
+ dev_err(c->dev, "Unexpected event %d\n", event);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gx_formatter_event);
+
+int gx_formatter_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct gx_formatter_driver *drv;
+ struct gx_formatter *formatter;
+ void __iomem *regs;
+
+ drv = of_device_get_match_data(dev);
+ if (!drv) {
+ dev_err(dev, "failed to match device\n");
+ return -ENODEV;
+ }
+
+ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+ if (!formatter)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, formatter);
+ formatter->drv = drv;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg);
+ if (IS_ERR(formatter->map)) {
+ dev_err(dev, "failed to init regmap: %ld\n",
+ PTR_ERR(formatter->map));
+ return PTR_ERR(formatter->map);
+ }
+
+ return snd_soc_register_component(dev, drv->component_drv, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gx_formatter_probe);
+
+int gx_formatter_create(struct device *dev,
+ struct snd_soc_dapm_widget *w,
+ const struct gx_formatter_driver *drv,
+ struct regmap *regmap)
+{
+ struct gx_formatter *formatter;
+
+ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+ if (!formatter)
+ return -ENOMEM;
+
+ formatter->drv = drv;
+ formatter->map = regmap;
+
+ w->priv = formatter;
+
+ return 0;
+}
+
+int gx_stream_start(struct gx_stream *ts)
+{
+ struct gx_formatter *formatter;
+ int ret = 0;
+
+ mutex_lock(&ts->lock);
+ ts->ready = true;
+
+ /* Start all the formatters attached to the stream */
+ list_for_each_entry(formatter, &ts->formatter_list, list) {
+ ret = gx_formatter_enable(formatter);
+ if (ret) {
+ pr_err("failed to start tdm stream\n");
+ goto out;
+ }
+ }
+
+out:
+ mutex_unlock(&ts->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gx_stream_start);
+
+void gx_stream_stop(struct gx_stream *ts)
+{
+ struct gx_formatter *formatter;
+
+ mutex_lock(&ts->lock);
+ ts->ready = false;
+
+ /* Stop all the formatters attached to the stream */
+ list_for_each_entry(formatter, &ts->formatter_list, list) {
+ gx_formatter_disable(formatter);
+ }
+
+ mutex_unlock(&ts->lock);
+}
+EXPORT_SYMBOL_GPL(gx_stream_stop);
+
+struct gx_stream *gx_stream_alloc(struct gx_iface *iface)
+{
+ struct gx_stream *ts;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (ts) {
+ INIT_LIST_HEAD(&ts->formatter_list);
+ mutex_init(&ts->lock);
+ ts->iface = iface;
+ }
+
+ return ts;
+}
+EXPORT_SYMBOL_GPL(gx_stream_alloc);
+
+void gx_stream_free(struct gx_stream *ts)
+{
+ /*
+ * If the list is not empty, it would mean that one of the formatter
+ * widget is still powered and attached to the interface while we
+ * are removing the TDM DAI. It should not be possible
+ */
+ WARN_ON(!list_empty(&ts->formatter_list));
+ mutex_destroy(&ts->lock);
+ kfree(ts);
+}
+EXPORT_SYMBOL_GPL(gx_stream_free);
+
+int gx_stream_set_cont_clocks(struct gx_stream *ts,
+ unsigned int fmt)
+{
+ int ret = 0;
+
+ if (fmt & SND_SOC_DAIFMT_CONT) {
+ /* Clock are already enabled - skipping */
+ if (ts->clk_enabled)
+ return 0;
+
+ ret = clk_prepare_enable(ts->iface->mclk);
+ if (ret)
+ return ret;
+
+ ts->clk_enabled = true;
+ return 0;
+ }
+
+ /* Clocks are already disabled - skipping */
+ if (!ts->clk_enabled)
+ return 0;
+
+ clk_disable_unprepare(ts->iface->mclk);
+
+ ts->clk_enabled = false;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gx_stream_set_cont_clocks);
+
+MODULE_DESCRIPTION("Amlogic GX formatter driver");
+MODULE_AUTHOR("Valerio Setti <vsetti@baylibre.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h
new file mode 100644
index 0000000000000000000000000000000000000000..05670c3dfb9f43ac3ee959f1d3d11bacee020c43
--- /dev/null
+++ b/sound/soc/meson/gx-formatter.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2026 Baylibre SAS.
+ * Author: Valerio Setti <vsetti@baylibre.com>
+ */
+
+#ifndef _MESON_GX_FORMATTER_H
+#define _MESON_GX_FORMATTER_H
+
+#include "gx-interface.h"
+
+struct platform_device;
+struct regmap;
+struct snd_soc_dapm_widget;
+struct snd_kcontrol;
+
+struct gx_formatter_hw {
+ unsigned int skew_offset;
+};
+
+struct gx_formatter_ops {
+ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w);
+ void (*enable)(struct regmap *map);
+ void (*disable)(struct regmap *map);
+ int (*prepare)(struct regmap *map,
+ const struct gx_formatter_hw *quirks,
+ struct gx_stream *ts);
+};
+
+struct gx_formatter_driver {
+ const struct snd_soc_component_driver *component_drv;
+ const struct regmap_config *regmap_cfg;
+ const struct gx_formatter_ops *ops;
+ const struct gx_formatter_hw *quirks;
+};
+
+int gx_formatter_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control,
+ int event);
+int gx_formatter_probe(struct platform_device *pdev);
+
+int gx_formatter_create(struct device *dev,
+ struct snd_soc_dapm_widget *w,
+ const struct gx_formatter_driver *drv,
+ struct regmap *regmap);
+
+#endif /* _MESON_GX_FORMATTER_H */
diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..c6b78635d7807333e4cb2f09191e29390da8e077
--- /dev/null
+++ b/sound/soc/meson/gx-interface.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2026 Baylibre SAS.
+ * Author: Valerio Setti <vsetti@baylibre.com>
+ */
+
+#ifndef _MESON_GX_INTERFACE_H
+#define _MESON_GX_INTERFACE_H
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+struct gx_iface {
+ struct clk *sclk;
+ struct clk *lrclk;
+ struct clk *mclk;
+ unsigned long mclk_rate;
+
+ /* format is common to all the DAIs of the iface */
+ unsigned int fmt;
+ unsigned int slots;
+ unsigned int slot_width;
+
+ /* For component wide symmetry */
+ int rate;
+};
+
+struct gx_stream {
+ struct gx_iface *iface;
+ struct list_head formatter_list;
+ struct mutex lock;
+ unsigned int channels;
+ unsigned int width;
+ unsigned int physical_width;
+ bool ready;
+
+ /* For continuous clock tracking */
+ bool clk_enabled;
+};
+
+struct gx_stream *gx_stream_alloc(struct gx_iface *iface);
+void gx_stream_free(struct gx_stream *ts);
+int gx_stream_start(struct gx_stream *ts);
+void gx_stream_stop(struct gx_stream *ts);
+int gx_stream_set_cont_clocks(struct gx_stream *ts, unsigned int fmt);
+
+#endif /* _MESON_GX_INTERFACE_H */
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 02/11] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 01/11] ASoC: meson: gx: add gx-formatter and gx-interface Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 03/11] ASoC: meson: aiu: introduce I2S output formatter Valerio Setti
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Start using gx_iface and gx_stream to store interface and stream info,
respectively. probe()/remove() functions are added to allocate/free the
gx_stream structures for each PCM stream.
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
hw_params/hw_free.
Interface wide rate symmetry is also enforced here. This is useful when the
interface is used for playback and capture at the same time.
Finally a trigger() callback is also added to start/stop the associated
I2S data formatter.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/aiu-encoder-i2s.c | 163 ++++++++++++++++++++++++++++++++------
sound/soc/meson/aiu.h | 3 +
2 files changed, 141 insertions(+), 25 deletions(-)
diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
index 3b4061508c18047fe8d6f3f98061720f8ce238f2..76a33878b9df101ad62b18abd8cc14b7908c2c42 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)
@@ -79,7 +81,7 @@ static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params,
+ struct gx_stream *ts,
unsigned int bs)
{
switch (bs) {
@@ -109,7 +111,7 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params,
+ struct gx_stream *ts,
unsigned int bs)
{
/*
@@ -119,7 +121,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
* increased by 50% to get the correct output rate.
* No idea why !
*/
- if (params_width(params) == 16 && params_channels(params) == 8) {
+ if (ts->width == 16 && ts->channels == 8) {
if (bs % 2) {
dev_err(component->dev,
"Cannot increase i2s divider by 50%%\n");
@@ -142,24 +144,18 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params)
+ struct gx_stream *ts)
{
struct aiu *aiu = snd_soc_component_get_drvdata(component);
- 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(ts->iface->mclk_rate, ts->iface->rate);
if (fs % 64)
return -EINVAL;
- /* Send data MSB first */
- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,
- AIU_I2S_DAC_CFG_MSB_FIRST,
- AIU_I2S_DAC_CFG_MSB_FIRST);
-
/* Set bclk to lrlck ratio */
snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,
AIU_CODEC_DAC_LRCLK_CTRL_DIV,
@@ -169,9 +165,9 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
bs = fs / 64;
if (aiu->platform->has_clk_ctrl_more_i2s_div)
- ret = aiu_encoder_i2s_set_more_div(component, params, bs);
+ ret = aiu_encoder_i2s_set_more_div(component, ts, bs);
else
- ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);
+ ret = aiu_encoder_i2s_set_legacy_div(component, ts, bs);
if (ret)
return ret;
@@ -188,11 +184,15 @@ 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);
+ iface->rate = params_rate(params);
+ ts->physical_width = params_physical_width(params);
+ ts->width = params_width(params);
+ ts->channels = params_channels(params);
ret = aiu_encoder_i2s_setup_desc(component, params);
if (ret) {
@@ -200,13 +200,17 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
return ret;
}
- ret = aiu_encoder_i2s_set_clocks(component, params);
+ ret = aiu_encoder_i2s_set_clocks(component, ts);
if (ret) {
dev_err(dai->dev, "setting i2s clocks failed\n");
return ret;
}
- aiu_encoder_i2s_divider_enable(component, true);
+ ret = gx_stream_set_cont_clocks(ts, iface->fmt);
+ if (ret)
+ dev_err(dai->dev, "failed to apply continuous clock setting\n");
+
+ aiu_encoder_i2s_divider_enable(component, 1);
return 0;
}
@@ -214,16 +218,20 @@ 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 snd_soc_component *component = dai->component;
- aiu_encoder_i2s_divider_enable(component, false);
-
- return 0;
+ /* This is the last substream open and that is going to be closed. */
+ if (snd_soc_dai_active(dai) <= 1)
+ aiu_encoder_i2s_divider_enable(component, 0);
+ return gx_stream_set_cont_clocks(ts, 0);
}
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 +263,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 |
@@ -284,6 +295,8 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
if (ret)
dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
+ aiu->i2s.iface.mclk_rate = freq;
+
return ret;
}
@@ -298,6 +311,7 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
+ struct gx_iface *iface = &aiu->i2s.iface;
int ret;
/* Make sure the encoder gets either 2 or 8 channels */
@@ -309,11 +323,40 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
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");
+ if (snd_soc_dai_active(dai)) {
+ /* Apply interface wide rate symmetry */
+ ret = snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ iface->rate);
+ if (ret < 0)
+ dev_err(dai->dev, "can't set iface rate constraint\n");
+ }
- return ret;
+ /*
+ * 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\n");
+ return ret;
+ }
+ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable MIXER\n");
+ 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\n");
+ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk);
+ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
+ return ret;
+ }
+
+ return 0;
}
static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
@@ -321,14 +364,84 @@ 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 = snd_soc_dai_dma_data_get(dai, stream);
+
+ if (ts)
+ gx_stream_free(ts);
+ }
+
+ 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;
+
+ 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,
.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 {
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 03/11] ASoC: meson: aiu: introduce I2S output formatter
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 01/11] ASoC: meson: gx: add gx-formatter and gx-interface Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 02/11] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 04/11] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data Valerio Setti
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Introduce aiu-formatter-i2s, a gx_formatter implementation for the AIU I2S
playback path. This is going to replace data formatting tasks that are
currently being implemented in aiu-encoder-i2s.
This should ideally follow the same design pattern used on the AXG
platform (see axg-tdmout), but the problem here is that all playback
features (including data formatting) so far are implemented in the AIU
component. Getting the full AXG design would mean introducing incompatible
device-tree changes. Therefore aiu-formatter-i2s is kept very simple and
it only implements the bare minimum functionalities to provide I2S
playback formatting. It's not a standalone component though because this
is still implemented by AIU.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Makefile | 1 +
sound/soc/meson/aiu-formatter-i2s.c | 106 ++++++++++++++++++++++++++++++++++++
2 files changed, 107 insertions(+)
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 146ec81526ba091a174a113ce3d8412ddbbfd9dd..f9ec0ebb01f048728b8f85fd8e58fb90df990470 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-aiu-y += aiu-acodec-ctrl.o
snd-soc-meson-aiu-y += aiu-codec-ctrl.o
snd-soc-meson-aiu-y += aiu-encoder-i2s.o
snd-soc-meson-aiu-y += gx-formatter.o
+snd-soc-meson-aiu-y += aiu-formatter-i2s.o
snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c
new file mode 100644
index 0000000000000000000000000000000000000000..c7eff04521de3c282f7f79864143e073ff1b2f27
--- /dev/null
+++ b/sound/soc/meson/aiu-formatter-i2s.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "aiu.h"
+#include "gx-formatter.h"
+
+#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
+#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
+#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
+#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
+#define AIU_RST_SOFT_I2S_FAST BIT(0)
+
+#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
+
+static struct snd_soc_dai *
+aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dai *be;
+
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (!p->connect)
+ continue;
+
+ if (p->sink->id == snd_soc_dapm_dai_in)
+ return (struct snd_soc_dai *)p->sink->priv;
+
+ be = aiu_formatter_i2s_get_be(p->sink);
+ if (be)
+ return be;
+ }
+
+ return NULL;
+}
+
+static struct gx_stream *
+aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w);
+
+ if (!be)
+ return NULL;
+
+ return snd_soc_dai_dma_data_get_playback(be);
+}
+
+static int aiu_formatter_i2s_prepare(struct regmap *map,
+ const struct gx_formatter_hw *quirks,
+ struct gx_stream *ts)
+{
+ /* Always operate in split (classic interleaved) mode */
+ unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
+ unsigned int tmp;
+
+ /* Reset required to update the pipeline */
+ regmap_write(map, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
+ regmap_read(map, AIU_I2S_SYNC, &tmp);
+
+ switch (ts->physical_width) {
+ case 16: /* Nothing to do */
+ break;
+
+ case 32:
+ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
+ AIU_I2S_SOURCE_DESC_MODE_32BIT);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (ts->channels) {
+ case 2: /* Nothing to do */
+ break;
+ case 8:
+ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(map, AIU_I2S_SOURCE_DESC,
+ AIU_I2S_SOURCE_DESC_MODE_8CH |
+ AIU_I2S_SOURCE_DESC_MODE_24BIT |
+ AIU_I2S_SOURCE_DESC_MODE_32BIT |
+ AIU_I2S_SOURCE_DESC_MODE_SPLIT,
+ desc);
+
+ /* Send data MSB first */
+ regmap_update_bits(map, AIU_I2S_DAC_CFG,
+ AIU_I2S_DAC_CFG_MSB_FIRST,
+ AIU_I2S_DAC_CFG_MSB_FIRST);
+
+ return 0;
+}
+
+const struct gx_formatter_ops aiu_formatter_i2s_ops = {
+ .get_stream = aiu_formatter_i2s_get_stream,
+ .prepare = aiu_formatter_i2s_prepare,
+};
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 04/11] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (2 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 03/11] ASoC: meson: aiu: introduce I2S output formatter Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 05/11] ASoC: dt-bindings: amlogic: add schema for audin-formatter and audin-toddr Valerio Setti
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Create a new DAPM widget for "I2S formatter" and place it on the path
between FIFO and output DAI interface. Remove I2S output formatting code
from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/aiu-encoder-i2s.c | 56 ---------------------------------------
sound/soc/meson/aiu.c | 30 ++++++++++++++++++---
sound/soc/meson/aiu.h | 1 +
3 files changed, 27 insertions(+), 60 deletions(-)
diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
index 76a33878b9df101ad62b18abd8cc14b7908c2c42..ce28850fde23f4fd1872c4364e13588138ba26ba 100644
--- a/sound/soc/meson/aiu-encoder-i2s.c
+++ b/sound/soc/meson/aiu-encoder-i2s.c
@@ -13,13 +13,6 @@
#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)
-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
-#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
-#define AIU_RST_SOFT_I2S_FAST BIT(0)
-
-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0)
#define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2)
#define AIU_CLK_CTRL_AOCLK_INVERT BIT(6)
@@ -37,49 +30,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
}
-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params)
-{
- /* Always operate in split (classic interleaved) mode */
- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
-
- /* Reset required to update the pipeline */
- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
- snd_soc_component_read(component, AIU_I2S_SYNC);
-
- switch (params_physical_width(params)) {
- case 16: /* Nothing to do */
- break;
-
- case 32:
- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
- AIU_I2S_SOURCE_DESC_MODE_32BIT);
- break;
-
- default:
- return -EINVAL;
- }
-
- switch (params_channels(params)) {
- case 2: /* Nothing to do */
- break;
- case 8:
- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
- break;
- default:
- return -EINVAL;
- }
-
- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,
- AIU_I2S_SOURCE_DESC_MODE_8CH |
- AIU_I2S_SOURCE_DESC_MODE_24BIT |
- AIU_I2S_SOURCE_DESC_MODE_32BIT |
- AIU_I2S_SOURCE_DESC_MODE_SPLIT,
- desc);
-
- return 0;
-}
-
static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
struct gx_stream *ts,
unsigned int bs)
@@ -194,12 +144,6 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
ts->width = params_width(params);
ts->channels = params_channels(params);
- ret = aiu_encoder_i2s_setup_desc(component, params);
- if (ret) {
- dev_err(dai->dev, "setting i2s desc failed\n");
- return ret;
- }
-
ret = aiu_encoder_i2s_set_clocks(component, ts);
if (ret) {
dev_err(dai->dev, "setting i2s clocks failed\n");
diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c
index f2890111c1d2cfa2213bf01849957a796744b9ae..bb3e0364190766ab4ce9ea3ebd313eecf220a244 100644
--- a/sound/soc/meson/aiu.c
+++ b/sound/soc/meson/aiu.c
@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC,
static const struct snd_kcontrol_new aiu_spdif_encode_mux =
SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum);
-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
- &aiu_spdif_encode_mux),
+#define AIU_WIDGET_SPDIF_SRC_SEL 0
+#define AIU_WIDGET_I2S_FORMATTER 1
+
+static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
+ [AIU_WIDGET_SPDIF_SRC_SEL] =
+ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
+ &aiu_spdif_encode_mux),
+ [AIU_WIDGET_I2S_FORMATTER] =
+ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0,
+ gx_formatter_event,
+ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
};
static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = {
- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" },
+ { "I2S Formatter", NULL, "I2S FIFO Playback" },
+ { "I2S Encoder Playback", NULL, "I2S Formatter" },
{ "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" },
{ "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" },
{ "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" },
@@ -172,6 +181,11 @@ static const struct regmap_config aiu_regmap_cfg = {
.max_register = 0x2ac,
};
+const struct gx_formatter_driver aiu_formatter_i2s_drv = {
+ .regmap_cfg = &aiu_regmap_cfg,
+ .ops = &aiu_formatter_i2s_ops,
+};
+
static int aiu_clk_bulk_get(struct device *dev,
const char * const *ids,
unsigned int num,
@@ -291,6 +305,14 @@ static int aiu_probe(struct platform_device *pdev)
return ret;
}
+ /* Allocate the aiu-formatter into its widget */
+ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER],
+ &aiu_formatter_i2s_drv, map);
+ if (ret) {
+ dev_err(dev, "Failed to allocate aiu formatter\n");
+ return ret;
+ }
+
/* Register the hdmi codec control component */
ret = aiu_hdmi_ctrl_register_component(dev);
if (ret) {
diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h
index 68310de0bdf7a97d8de2ff306c159248ee9b0ede..7d0b98c1f351b3c526ca06c43a4c04ee5f4b6dfa 100644
--- a/sound/soc/meson/aiu.h
+++ b/sound/soc/meson/aiu.h
@@ -61,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops;
+extern const struct gx_formatter_ops aiu_formatter_i2s_ops;
#define AIU_IEC958_BPF 0x000
#define AIU_958_MISC 0x010
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 05/11] ASoC: dt-bindings: amlogic: add schema for audin-formatter and audin-toddr
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (3 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 04/11] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 06/11] ASoC: meson: gx: add AUDIN I2S Decoder driver Valerio Setti
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Add schema for "amlogic,meson-gx-audin-decoder-i2s" and
"amlogic,meson-gx-audin-fifo" modules which are used to
provide I2S audio input support to the Meson GX platform.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
.../sound/amlogic,meson-gx-audin-decoder-i2s.yaml | 49 +++++++++++++++++
.../sound/amlogic,meson-gx-audin-fifo.yaml | 63 ++++++++++++++++++++++
2 files changed, 112 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-decoder-i2s.yaml b/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-decoder-i2s.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..eaf57603e1ecf5944ffdcf7a1b6146d1a0295696
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-decoder-i2s.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/amlogic,meson-gx-audin-decoder-i2s.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic GX AUDIN I2S Input Decoder
+
+maintainers:
+ - Valerio Setti <vsetti@baylibre.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ $nodename:
+ pattern: "^audio-controller@.*"
+
+ "#sound-dai-cells":
+ const: 0
+
+ compatible:
+ items:
+ - enum:
+ - amlogic,meson-gxbb-audin-decoder-i2s
+ - const: amlogic,meson-gx-audin-decoder-i2s
+
+ reg:
+ maxItems: 1
+
+ sound-name-prefix: true
+
+required:
+ - "#sound-dai-cells"
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ audio-controller@a040 {
+ compatible = "amlogic,meson-gxbb-audin-decoder-i2s",
+ "amlogic,meson-gx-audin-decoder-i2s";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "AUDIN I2S Decoder";
+ reg = <0xa040 0x4>;
+ status = "disabled";
+ };
diff --git a/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-fifo.yaml b/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-fifo.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4957c8711623ac45d2826ca96e6344d0aeac2735
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,meson-gx-audin-fifo.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/amlogic,meson-gx-audin-fifo.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic GX AUDIN FIFO controller
+
+maintainers:
+ - Valerio Setti <vsetti@baylibre.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ $nodename:
+ pattern: "^audio-controller@.*"
+
+ "#sound-dai-cells":
+ const: 0
+
+ compatible:
+ items:
+ - enum:
+ - amlogic,meson-gxbb-audin-fifo
+ - const: amlogic,meson-gx-audin-fifo
+
+ clocks:
+ items:
+ - description: AUDIN peripheral clock
+
+ clock-names:
+ items:
+ - const: i2s_input_clk
+
+ reg:
+ maxItems: 1
+
+ sound-name-prefix: true
+
+required:
+ - "#sound-dai-cells"
+ - compatible
+ - clocks
+ - clock-names
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/gxbb-clkc.h>
+
+ audio-controller@a080 {
+ compatible = "amlogic,meson-gxbb-audin-fifo",
+ "amlogic,meson-gx-audin-fifo";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "FIFO0";
+ reg = <0xa080 0x1c>;
+ status = "disabled";
+ clocks = <&clkc CLKID_I2S_SPDIF>;
+ clock-names = "i2s_input_clk";
+ };
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 06/11] ASoC: meson: gx: add AUDIN I2S Decoder driver
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (4 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 05/11] ASoC: dt-bindings: amlogic: add schema for audin-formatter and audin-toddr Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 07/11] ASoC: meson: gx: add AUDIN FIFO driver Valerio Setti
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
This driver takes care of formatting I2S data being captured from the I2S
interface. Differently from aiu-formatter-i2s this driver implements
all the functionalities of a gx_formatter and it fully follows the design
proposed in the AXG platform (see axg-tdmin).
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Kconfig | 8 ++
sound/soc/meson/Makefile | 2 +
sound/soc/meson/audin-decoder-i2s.c | 218 ++++++++++++++++++++++++++++++++++++
3 files changed, 228 insertions(+)
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index d9a730994a2a2ad315a576981555203b379a212e..0a1d166bed3477efdaffa8538150f7aca33a29e6 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -12,6 +12,13 @@ config SND_MESON_AIU
Select Y or M to add support for the Audio output subsystem found
in the Amlogic Meson8, Meson8b and GX SoC families
+config SND_MESON_GX_AUDIN_DECODER_I2S
+ tristate "Amlogic GX AUDIN I2S Decoder"
+ imply SND_MESON_AIU
+ help
+ Select Y or M to add support for the I2S audio input decoder found
+ in the Amlogic GX SoC family
+
config SND_MESON_AXG_FIFO
tristate
select REGMAP_MMIO
@@ -108,6 +115,7 @@ config SND_MESON_GX_SOUND_CARD
tristate "Amlogic GX Sound Card Support"
select SND_MESON_CARD_UTILS
imply SND_MESON_AIU
+ imply SND_MESON_GX_AUDIN_DECODER_I2S
help
Select Y or M to add support for the GXBB/GXL SoC sound card
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index f9ec0ebb01f048728b8f85fd8e58fb90df990470..a5a8e5b5a3bb8ca8ca0f27e1a29865e0dab64b73 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -10,6 +10,7 @@ snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
snd-soc-meson-aiu-y += aiu-fifo-spdif.o
+snd-soc-meson-gx-audin-decoder-i2s-y := audin-decoder-i2s.o
snd-soc-meson-axg-fifo-y := axg-fifo.o
snd-soc-meson-axg-frddr-y := axg-frddr.o
snd-soc-meson-axg-toddr-y := axg-toddr.o
@@ -29,6 +30,7 @@ snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
snd-soc-meson-t9015-y := t9015.o
obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.o
+obj-$(CONFIG_SND_MESON_GX_AUDIN_DECODER_I2S) += snd-soc-meson-gx-audin-decoder-i2s.o
obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
diff --git a/sound/soc/meson/audin-decoder-i2s.c b/sound/soc/meson/audin-decoder-i2s.c
new file mode 100644
index 0000000000000000000000000000000000000000..4d8ba81ce321bd9bbb7fdee57ccf320e61b81fa2
--- /dev/null
+++ b/sound/soc/meson/audin-decoder-i2s.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "gx-formatter.h"
+
+/* I2SIN_CTRL register and bits */
+#define AUDIN_I2SIN_CTRL 0x0
+#define AUDIN_I2SIN_CTRL_I2SIN_DIR BIT(0)
+#define AUDIN_I2SIN_CTRL_I2SIN_CLK_SEL BIT(1)
+#define AUDIN_I2SIN_CTRL_I2SIN_LRCLK_SEL BIT(2)
+#define AUDIN_I2SIN_CTRL_I2SIN_POS_SYNC BIT(3)
+#define AUDIN_I2SIN_CTRL_I2SIN_LRCLK_SKEW_MASK GENMASK(6, 4)
+#define AUDIN_I2SIN_CTRL_I2SIN_LRCLK_INV BIT(7)
+#define AUDIN_I2SIN_CTRL_I2SIN_SIZE_MASK GENMASK(9, 8)
+#define AUDIN_I2SIN_CTRL_I2SIN_CHAN_EN_MASK GENMASK(13, 10)
+#define AUDIN_I2SIN_CTRL_I2SIN_EN BIT(15)
+
+static struct snd_soc_dai *
+audin_decoder_i2s_get_be(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dai *be;
+
+ snd_soc_dapm_widget_for_each_source_path(w, p) {
+ if (!p->connect)
+ continue;
+
+ if (p->source->id == snd_soc_dapm_dai_out)
+ return (struct snd_soc_dai *)p->source->priv;
+
+ be = audin_decoder_i2s_get_be(p->source);
+ if (be)
+ return be;
+ }
+
+ return NULL;
+}
+
+static struct gx_stream *
+audin_decoder_i2s_get_stream(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dai *be = audin_decoder_i2s_get_be(w);
+
+ if (!be)
+ return NULL;
+
+ return snd_soc_dai_dma_data_get_capture(be);
+}
+
+static void audin_decoder_i2s_enable(struct regmap *map)
+{
+ regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_EN,
+ AUDIN_I2SIN_CTRL_I2SIN_EN);
+}
+
+static void audin_decoder_i2s_disable(struct regmap *map)
+{
+ regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_EN, 0);
+}
+
+static int audin_decoder_i2s_prepare(struct regmap *map,
+ const struct gx_formatter_hw *quirks,
+ struct gx_stream *ts)
+{
+ unsigned int val;
+ int ret;
+
+ if (ts->width != 16)
+ return -EINVAL;
+
+ if (ts->channels != 2)
+ return -EINVAL;
+
+ /*
+ * I2S decoder always outputs 24bits to the FIFO according to the
+ * manual. The only thing we can do is mask some bits as follows:
+ * - 0: 16 bit
+ * - 1: 18 bits (not exposed as supported format)
+ * - 2: 20 bits (not exposed as supported format)
+ * - 3: 24 bits
+ *
+ * At the moment only 16 bit format is supported, but we force 24 bit
+ * anyway here to ease the future support of 24 bit format. Extra bits
+ * will be filtered out at FIFO stage.
+ * Note: data is left-justified, so in case of 16 bits samples, this
+ * means that the LSB is to be discarded at FIFO level and the
+ * relevant part is in bits [23:8].
+ */
+ val = FIELD_PREP(AUDIN_I2SIN_CTRL_I2SIN_SIZE_MASK, 3);
+ ret = regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_SIZE_MASK, val);
+ if (ret)
+ return ret;
+
+ /*
+ * The manual claims that this platform supports up to 4 streams
+ * (8 channels), but the SOC only has 1 input pin (i.e. it only allows
+ * for 1 stream and 2 channels) so this is what we support here.
+ */
+ val = FIELD_PREP(AUDIN_I2SIN_CTRL_I2SIN_CHAN_EN_MASK, 1);
+ ret = regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_CHAN_EN_MASK, val);
+ if (ret)
+ return ret;
+
+ /*
+ * Use clocks from AIU and not from the pads since we only want to
+ * support master mode.
+ */
+ val = AUDIN_I2SIN_CTRL_I2SIN_CLK_SEL |
+ AUDIN_I2SIN_CTRL_I2SIN_LRCLK_SEL |
+ AUDIN_I2SIN_CTRL_I2SIN_DIR;
+ ret = regmap_update_bits(map, AUDIN_I2SIN_CTRL, val, val);
+ if (ret)
+ return ret;
+
+ switch (ts->iface->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ val = AUDIN_I2SIN_CTRL_I2SIN_POS_SYNC;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ val = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_POS_SYNC, val);
+ if (ret)
+ return ret;
+
+ /*
+ * MSB data starts 1 clock cycle after LRCLK transition, as per I2S
+ * specs.
+ */
+ val = FIELD_PREP(AUDIN_I2SIN_CTRL_I2SIN_LRCLK_SKEW_MASK, 1);
+ ret = regmap_update_bits(map, AUDIN_I2SIN_CTRL,
+ AUDIN_I2SIN_CTRL_I2SIN_LRCLK_INV |
+ AUDIN_I2SIN_CTRL_I2SIN_LRCLK_SKEW_MASK,
+ val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget audin_decoder_i2s_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
+ gx_formatter_event,
+ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+ SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route audin_decoder_i2s_dapm_routes[] = {
+ { "DEC", NULL, "IN" },
+ { "OUT", NULL, "DEC" },
+};
+
+static const struct snd_soc_component_driver audin_decoder_i2s_component = {
+ .dapm_widgets = audin_decoder_i2s_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(audin_decoder_i2s_dapm_widgets),
+ .dapm_routes = audin_decoder_i2s_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(audin_decoder_i2s_dapm_routes),
+};
+
+static const struct regmap_config audin_decoder_i2s_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x3,
+};
+
+static const struct gx_formatter_ops audin_decoder_i2s_ops = {
+ .get_stream = audin_decoder_i2s_get_stream,
+ .prepare = audin_decoder_i2s_prepare,
+ .enable = audin_decoder_i2s_enable,
+ .disable = audin_decoder_i2s_disable,
+};
+
+static const struct gx_formatter_driver audin_decoder_i2s_drv = {
+ .component_drv = &audin_decoder_i2s_component,
+ .regmap_cfg = &audin_decoder_i2s_regmap_cfg,
+ .ops = &audin_decoder_i2s_ops,
+};
+
+static const struct of_device_id audin_decoder_i2s_of_match[] = {
+ {
+ .compatible = "amlogic,meson-gxbb-audin-decoder-i2s",
+ .data = &audin_decoder_i2s_drv
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, audin_decoder_i2s_of_match);
+
+static struct platform_driver audin_decoder_i2s_pdrv = {
+ .probe = gx_formatter_probe,
+ .driver = {
+ .name = "meson-gx-audin-decoder-i2s",
+ .of_match_table = audin_decoder_i2s_of_match,
+ },
+};
+module_platform_driver(audin_decoder_i2s_pdrv);
+
+MODULE_DESCRIPTION("Meson AUDIN Formatter Driver");
+MODULE_AUTHOR("Valerio Setti <vsetti@baylibre.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 07/11] ASoC: meson: gx: add AUDIN FIFO driver
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (5 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 06/11] ASoC: meson: gx: add AUDIN I2S Decoder driver Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 08/11] ASoC: meson: aiu: add I2S Capture DAI Valerio Setti
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Add support for the frontend DAI of the capture interface which is in
charge of receiving decoded data from "audin-decoder-i2s" and move them to
an internal FIFO before they are block-transferring to RAM.
This component could ideally handle multiple input sources (SPDIF, I2S,
PCM, HDMI), but for the time being only I2S is added and has been tested.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Kconfig | 9 +
sound/soc/meson/Makefile | 2 +
sound/soc/meson/audin-fifo.c | 432 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 443 insertions(+)
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 0a1d166bed3477efdaffa8538150f7aca33a29e6..50307e54f96fda0f9c114c3bff6aae4094018934 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -19,6 +19,14 @@ config SND_MESON_GX_AUDIN_DECODER_I2S
Select Y or M to add support for the I2S audio input decoder found
in the Amlogic GX SoC family
+config SND_MESON_GX_AUDIN_FIFO
+ tristate "Amlogic GX AUDIN FIFO"
+ select REGMAP_MMIO
+ imply SND_MESON_AIU
+ help
+ Select Y or M to add support for the frontend capture interfaces
+ embedded in the Amlogic GX SoC family
+
config SND_MESON_AXG_FIFO
tristate
select REGMAP_MMIO
@@ -116,6 +124,7 @@ config SND_MESON_GX_SOUND_CARD
select SND_MESON_CARD_UTILS
imply SND_MESON_AIU
imply SND_MESON_GX_AUDIN_DECODER_I2S
+ imply SND_MESON_GX_AUDIN_FIFO
help
Select Y or M to add support for the GXBB/GXL SoC sound card
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index a5a8e5b5a3bb8ca8ca0f27e1a29865e0dab64b73..085ddcdc87639d5d79aa39152eafc030f2643e2e 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -11,6 +11,7 @@ snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
snd-soc-meson-aiu-y += aiu-fifo-spdif.o
snd-soc-meson-gx-audin-decoder-i2s-y := audin-decoder-i2s.o
+snd-soc-meson-gx-audin-fifo-y := audin-fifo.o
snd-soc-meson-axg-fifo-y := axg-fifo.o
snd-soc-meson-axg-frddr-y := axg-frddr.o
snd-soc-meson-axg-toddr-y := axg-toddr.o
@@ -31,6 +32,7 @@ snd-soc-meson-t9015-y := t9015.o
obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.o
obj-$(CONFIG_SND_MESON_GX_AUDIN_DECODER_I2S) += snd-soc-meson-gx-audin-decoder-i2s.o
+obj-$(CONFIG_SND_MESON_GX_AUDIN_FIFO) += snd-soc-meson-gx-audin-fifo.o
obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
diff --git a/sound/soc/meson/audin-fifo.c b/sound/soc/meson/audin-fifo.c
new file mode 100644
index 0000000000000000000000000000000000000000..62f0b03cdfc33056e3be7aaf7333ab71086a9c25
--- /dev/null
+++ b/sound/soc/meson/audin-fifo.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <sound/pcm_params.h>
+#include <linux/dma-mapping.h>
+#include <linux/hrtimer.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+/* FIFO registers */
+#define AUDIN_FIFO_START 0x00
+#define AUDIN_FIFO_END 0x04
+#define AUDIN_FIFO_PTR 0x08
+
+/* FIFOx CTRL registers and bits */
+#define AUDIN_FIFO_CTRL 0x14
+#define AUDIN_FIFO_CTRL_EN BIT(0)
+#define AUDIN_FIFO_CTRL_RST BIT(1)
+#define AUDIN_FIFO_CTRL_LOAD BIT(2)
+#define AUDIN_FIFO_CTRL_DIN_SEL_OFF 3
+#define AUDIN_FIFO_CTRL_DIN_SEL_MASK GENMASK(5, 3)
+#define AUDIN_FIFO_CTRL_ENDIAN_MASK GENMASK(10, 8)
+#define AUDIN_FIFO_CTRL_CHAN_MASK GENMASK(14, 11)
+#define AUDIN_FIFO_CTRL_UG BIT(15)
+
+/* FIFOx_CTRL1 registers and bits */
+#define AUDIN_FIFO_CTRL1 0x18
+#define AUDIN_FIFO_CTRL1_DIN_POS_2 BIT(7)
+#define AUDIN_FIFO_CTRL1_DIN_BYTE_NUM_MASK GENMASK(3, 2)
+#define AUDIN_FIFO_CTRL1_DIN_POS_01_MASK GENMASK(1, 0)
+
+/* This is the size of the FIFO (i.e. 64*64 bytes). */
+#define AUDIN_FIFO_I2S_BLOCK 4096
+
+static const struct snd_pcm_hardware audin_fifo_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 2 * AUDIN_FIFO_I2S_BLOCK,
+ .period_bytes_max = AUDIN_FIFO_I2S_BLOCK * USHRT_MAX,
+ .periods_min = 2,
+ .periods_max = UINT_MAX,
+
+ /* No real justification for this */
+ .buffer_bytes_max = 1 * 1024 * 1024,
+};
+
+struct audin_fifo_drvdata {
+ struct clk *input_clk;
+};
+
+struct audin_fifo_dai_data {
+ /*
+ * The AUDIN peripheral has an IRQ to signal when data is received, but
+ * it cannot grant a periodic behavior. The reason is that the register
+ * which holds the address which triggers the IRQ must be updated
+ * continuously. This create a risk of overflow if for any reason the
+ * ISR execution is delayed. Using a periodic time is therefore simpler
+ * and more reliable.
+ */
+ struct hrtimer polling_timer;
+ ktime_t poll_time_ns;
+ struct snd_pcm_substream *substream;
+};
+
+static int audin_fifo_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ (void) dai;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_EN,
+ AUDIN_FIFO_CTRL_EN);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_EN, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int audin_fifo_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ dma_addr_t dma_end = runtime->dma_addr + runtime->dma_bytes - 8;
+ unsigned int val;
+
+ /* Setup memory boundaries */
+ snd_soc_component_write(component, AUDIN_FIFO_START, runtime->dma_addr);
+ snd_soc_component_write(component, AUDIN_FIFO_PTR, runtime->dma_addr);
+ snd_soc_component_write(component, AUDIN_FIFO_END, dma_end);
+
+ /* Load new addresses */
+ val = AUDIN_FIFO_CTRL_LOAD | AUDIN_FIFO_CTRL_UG;
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL, val, val);
+
+ /* Reset */
+ snd_soc_component_update_bits(dai->component, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_RST,
+ AUDIN_FIFO_CTRL_RST);
+
+ return 0;
+}
+
+static int audin_fifo_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct audin_fifo_dai_data *data = snd_soc_dai_dma_data_get_capture(dai);
+ unsigned int val;
+
+ if (params_width(params) != 16) {
+ dev_err(dai->dev, "Unsupported width %u\n",
+ params_physical_width(params));
+ return -EINVAL;
+ }
+
+ /*
+ * FIFO is filled line by line and each of them is 8 bytes. The
+ * problem is that each line is filled starting from the end,
+ * so we need to properly reorder them before moving to the
+ * RAM. This is the value required to properly re-order samples stored
+ * in 16 bit format.
+ */
+ val = FIELD_PREP(AUDIN_FIFO_CTRL_ENDIAN_MASK, 6);
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_ENDIAN_MASK, val);
+
+ /*
+ * The I2S input decoder passed 24 bits of left-justified data
+ * but for the time being we only 16 bit formatted samples which means
+ * that we drop the LSB.
+ */
+ val = FIELD_PREP(AUDIN_FIFO_CTRL1_DIN_POS_01_MASK, 1);
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL1,
+ AUDIN_FIFO_CTRL1_DIN_POS_01_MASK,
+ val);
+
+ /* Set sample size to 2 bytes (16 bit) */
+ val = FIELD_PREP(AUDIN_FIFO_CTRL1_DIN_BYTE_NUM_MASK, 1);
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL1,
+ AUDIN_FIFO_CTRL1_DIN_BYTE_NUM_MASK,
+ val);
+
+ /*
+ * This is a bit counterintuitive. Even though the platform has a single
+ * pin for I2S input which would mean that we can only support 2
+ * channels, doing so would cause samples to be stored in a weird way
+ * into the FIFO: all the samples from the 1st channel on the 1st half
+ * of the FIFO, then samples from the 2nd channel in the other half. Of
+ * course extra work would be required to properly interleave them
+ * before returning to the userspace.
+ * Setting a single channel mode instead solves the problem: samples
+ * from 1st and 2nd channel are stored interleaved and sequentially in
+ * the FIFO.
+ */
+ val = FIELD_PREP(AUDIN_FIFO_CTRL_CHAN_MASK, 1);
+ snd_soc_component_update_bits(component, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_CHAN_MASK, val);
+
+ /* Setup the period for the polling timer and start it. */
+ data->poll_time_ns = NSEC_PER_SEC * params_period_size(params) /
+ params_rate(params);
+
+ hrtimer_start(&data->polling_timer, data->poll_time_ns,
+ HRTIMER_MODE_REL);
+
+ return 0;
+}
+
+static int audin_fifo_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct audin_fifo_dai_data *data = snd_soc_dai_dma_data_get_capture(dai);
+ (void) substream;
+
+ hrtimer_cancel(&data->polling_timer);
+
+ return 0;
+}
+
+static int audin_fifo_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct audin_fifo_dai_data *data = snd_soc_dai_dma_data_get_capture(dai);
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &audin_fifo_pcm_hw);
+
+ ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ AUDIN_FIFO_I2S_BLOCK);
+ if (ret) {
+ dev_err(dai->dev, "Failed to set runtime constraint %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ AUDIN_FIFO_I2S_BLOCK);
+ if (ret) {
+ dev_err(dai->dev, "Failed to set runtime constraint %d\n", ret);
+ return ret;
+ }
+
+ data->substream = substream;
+
+ return ret;
+}
+
+static int audin_fifo_dai_pcm_new(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(dai->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(dai->dev, "Failed to set DMA mask %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+ dai->dev,
+ audin_fifo_pcm_hw.buffer_bytes_max,
+ audin_fifo_pcm_hw.buffer_bytes_max);
+ if (ret) {
+ dev_err(dai->dev, "Failed to set PCM managed buffer %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static enum hrtimer_restart dai_timer_cb(struct hrtimer *timer)
+{
+ struct audin_fifo_dai_data *data =
+ container_of(timer, struct audin_fifo_dai_data, polling_timer);
+ snd_pcm_period_elapsed(data->substream);
+ hrtimer_forward_now(timer, data->poll_time_ns);
+ return HRTIMER_RESTART;
+}
+
+static int audin_fifo_dai_probe(struct snd_soc_dai *dai)
+{
+ struct audin_fifo_dai_data *data;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ hrtimer_setup(&data->polling_timer, dai_timer_cb, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+
+ snd_soc_dai_dma_data_set_capture(dai, data);
+
+ return 0;
+}
+
+static int audin_fifo_dai_remove(struct snd_soc_dai *dai)
+{
+ kfree(snd_soc_dai_dma_data_get_capture(dai));
+
+ return 0;
+}
+
+const struct snd_soc_dai_ops audin_fifo_dai_ops = {
+ .trigger = audin_fifo_dai_trigger,
+ .prepare = audin_fifo_dai_prepare,
+ .hw_params = audin_fifo_dai_hw_params,
+ .hw_free = audin_fifo_dai_hw_free,
+ .startup = audin_fifo_dai_startup,
+ .pcm_new = audin_fifo_dai_pcm_new,
+ .probe = audin_fifo_dai_probe,
+ .remove = audin_fifo_dai_remove,
+};
+
+static struct snd_soc_dai_driver audin_fifo_dai_drv[] = {
+ {
+ .name = "FIFO",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &audin_fifo_dai_ops,
+ },
+};
+
+static snd_pcm_uframes_t
+audin_fifo_component_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ unsigned int start, ptr;
+
+ start = snd_soc_component_read(component, AUDIN_FIFO_START);
+ ptr = snd_soc_component_read(component, AUDIN_FIFO_PTR);
+
+ return bytes_to_frames(substream->runtime, ptr - start);
+}
+
+static const char * const audin_fifo_fifo_input_sel_texts[] = {
+ "SPDIF", "I2S", "PCM", "HDMI", "Demodulator"
+};
+
+static SOC_ENUM_SINGLE_DECL(audin_fifo_input_sel_enum, AUDIN_FIFO_CTRL,
+ AUDIN_FIFO_CTRL_DIN_SEL_OFF,
+ audin_fifo_fifo_input_sel_texts);
+
+static const struct snd_kcontrol_new audin_fifo_input_sel_mux =
+ SOC_DAPM_ENUM("SRC SEL", audin_fifo_input_sel_enum);
+
+static const struct snd_soc_dapm_widget audin_fifo_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("I2S IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0,
+ &audin_fifo_input_sel_mux),
+};
+
+static const struct snd_soc_dapm_route audin_fifo_dapm_routes[] = {
+ { "SRC SEL", "I2S", "I2S IN" },
+ { "Capture", NULL, "SRC SEL" },
+};
+
+static const struct snd_soc_component_driver audin_fifo_component = {
+ .dapm_widgets = audin_fifo_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(audin_fifo_dapm_widgets),
+ .dapm_routes = audin_fifo_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(audin_fifo_dapm_routes),
+ .pointer = audin_fifo_component_pointer,
+};
+
+static const struct regmap_config audin_fifo_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x1b,
+};
+
+static int audin_fifo_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ void __iomem *regs;
+ struct regmap *map;
+ struct audin_fifo_drvdata *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, data);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ map = devm_regmap_init_mmio(dev, regs, &audin_fifo_regmap_cfg);
+ if (IS_ERR(map)) {
+ dev_err(dev, "Failed to init regmap: %ld\n", PTR_ERR(map));
+ return PTR_ERR(map);
+ }
+
+ data->input_clk = devm_clk_get_enabled(dev, "i2s_input_clk");
+ if (IS_ERR(data->input_clk)) {
+ dev_err(dev, "can't get the i2s input clock\n");
+ return PTR_ERR(data->input_clk);
+ }
+
+ ret = snd_soc_register_component(dev, &audin_fifo_component,
+ audin_fifo_dai_drv,
+ ARRAY_SIZE(audin_fifo_dai_drv));
+ if (ret) {
+ dev_err(dev, "failed to register component\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void audin_fifo_remove(struct platform_device *pdev)
+{
+ struct audin_fifo_drvdata *data = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(data->input_clk);
+ snd_soc_unregister_component(&pdev->dev);
+}
+
+static const struct of_device_id audin_fifo_of_match[] = {
+ { .compatible = "amlogic,meson-gxbb-audin-fifo", .data = NULL },
+ {}
+};
+MODULE_DEVICE_TABLE(of, audin_fifo_of_match);
+
+static struct platform_driver audin_fifo_pdrv = {
+ .probe = audin_fifo_probe,
+ .remove = audin_fifo_remove,
+ .driver = {
+ .name = "meson-gx-audin-fifo",
+ .of_match_table = audin_fifo_of_match,
+ },
+};
+module_platform_driver(audin_fifo_pdrv);
+
+MODULE_DESCRIPTION("Meson AUDIN FIFO Driver");
+MODULE_AUTHOR("Valerio Setti <vsetti@baylibre.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 08/11] ASoC: meson: aiu: add I2S Capture DAI
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (6 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 07/11] ASoC: meson: gx: add AUDIN FIFO driver Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 09/11] ASoC: meson: gx-card: add support for AUDIN FIFO Valerio Setti
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Add capture stream to CPU_I2S_ENCODER. This is the final step to add
support for I2S capture following the recent addition of
"audin-decoder-i2s" and "audin-fifo".
As for the naming of the stream "I2S Encoder" is kept as base following
the same pattern used for the playback stream and with the goal to minimize
changes.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/aiu.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c
index bb3e0364190766ab4ce9ea3ebd313eecf220a244..2b6b94957b051976191d9b1e0dbdb4ca01ba2a94 100644
--- a/sound/soc/meson/aiu.c
+++ b/sound/soc/meson/aiu.c
@@ -153,6 +153,13 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = {
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = AIU_FORMATS,
},
+ .capture = {
+ .stream_name = "I2S Encoder Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
.ops = &aiu_encoder_i2s_dai_ops,
},
[CPU_SPDIF_ENCODER] = {
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 09/11] ASoC: meson: gx-card: add support for AUDIN FIFO
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (7 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 08/11] ASoC: meson: aiu: add I2S Capture DAI Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 10/11] arm64: dts: amlogic: gx: add nodes for AUDIN decoder and FIFO Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 11/11] arm64: dts: amlogic: odroid-c2: add support for I2S audio input Valerio Setti
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Slightly modify "gx_card_cpu_identify()" by making the compatible matching
string an input parameter. This allows to easily support also
"meson-gx-audin-fifo" with minimal changes.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/gx-card.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c
index b408cc2bbc9193ae56d02b4fcd05af8df3f93d80..1e48dbbf7324dfa6ca66026d944422dadf4204c0 100644
--- a/sound/soc/meson/gx-card.c
+++ b/sound/soc/meson/gx-card.c
@@ -65,10 +65,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card,
}
static int gx_card_cpu_identify(struct snd_soc_dai_link_component *c,
- char *match)
+ char *compatible_match, char *dai_match)
{
- if (of_device_is_compatible(c->of_node, DT_PREFIX "aiu")) {
- if (strstr(c->dai_name, match))
+ if (of_device_is_compatible(c->of_node, compatible_match)) {
+ if (strstr(c->dai_name, dai_match))
return 1;
}
@@ -94,21 +94,23 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np,
if (ret)
return ret;
- if (gx_card_cpu_identify(dai_link->cpus, "FIFO"))
+ if (gx_card_cpu_identify(dai_link->cpus, DT_PREFIX "aiu", "FIFO"))
return meson_card_set_fe_link(card, dai_link, np, true);
+ else if (gx_card_cpu_identify(dai_link->cpus, DT_PREFIX "meson-gx-audin-fifo", "FIFO"))
+ return meson_card_set_fe_link(card, dai_link, np, false);
ret = meson_card_set_be_link(card, dai_link, np);
if (ret)
return ret;
/* Or apply codec to codec params if necessary */
- if (gx_card_cpu_identify(dai_link->cpus, "CODEC CTRL")) {
+ if (gx_card_cpu_identify(dai_link->cpus, DT_PREFIX "aiu", "CODEC CTRL")) {
dai_link->c2c_params = &codec_params;
dai_link->num_c2c_params = 1;
} else {
dai_link->no_pcm = 1;
/* Check if the cpu is the i2s encoder and parse i2s data */
- if (gx_card_cpu_identify(dai_link->cpus, "I2S Encoder"))
+ if (gx_card_cpu_identify(dai_link->cpus, DT_PREFIX "aiu", "I2S Encoder"))
ret = gx_card_parse_i2s(card, np, index);
}
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 10/11] arm64: dts: amlogic: gx: add nodes for AUDIN decoder and FIFO
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (8 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 09/11] ASoC: meson: gx-card: add support for AUDIN FIFO Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
2026-04-11 14:57 ` [PATCH RFC v2 11/11] arm64: dts: amlogic: odroid-c2: add support for I2S audio input Valerio Setti
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
Adding nodes for "amlogic,meson-gx-audin-decoder-i2s" and
"amlogic,meson-gx-audin-fifo". These provide support for I2S capture on
the GX platform.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 32 +++++++++++++++++++++++++++++
arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 26 +++++++++++++++++++++++
2 files changed, 58 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
index c1d8e81d95cb9b7758d8d12c230be13d4311e5e4..411b3e82f3ce1e535a23d9966ba82099d5be2282 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
@@ -331,6 +331,38 @@ aiu: audio-controller@5400 {
status = "disabled";
};
+ audin_decoder_i2s: audio-controller@a040 {
+ compatible = "amlogic,meson-gx-audin-decoder-i2s";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "AUDIN I2S Decoder";
+ reg = <0x0 0xa040 0x0 0x4>;
+ status = "disabled";
+ };
+
+ audin_fifo0: audio-controller@a080 {
+ compatible = "amlogic,meson-gx-audin-fifo";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "AUDIN FIFO0";
+ reg = <0x0 0xa080 0x0 0x1c>;
+ status = "disabled";
+ };
+
+ audin_fifo1: audio-controller@a0cc {
+ compatible = "amlogic,meson-gx-audin-fifo";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "AUDIN FIFO1";
+ reg = <0x0 0xa0cc 0x0 0x1c>;
+ status = "disabled";
+ };
+
+ audin_fifo2: audio-controller@a114 {
+ compatible = "amlogic,meson-gx-audin-fifo";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "AUDIN FIFO2";
+ reg = <0x0 0xa114 0x0 0x1c>;
+ status = "disabled";
+ };
+
uart_A: serial@84c0 {
compatible = "amlogic,meson-gx-uart";
reg = <0x0 0x84c0 0x0 0x18>;
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
index a9c830a570cc6cd2875553fa9b0e3ef72a2f6478..71a47aa4c2fc72195386a11c905723b3c6f6943c 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
@@ -84,6 +84,32 @@ &aiu {
resets = <&reset RESET_AIU>;
};
+&audin_decoder_i2s {
+ compatible = "amlogic,meson-gxbb-audin-decoder-i2s",
+ "amlogic,meson-gx-audin-decoder-i2s";
+};
+
+&audin_fifo0 {
+ compatible = "amlogic,meson-gxbb-audin-fifo",
+ "amlogic,meson-gx-audin-fifo";
+ clocks = <&clkc CLKID_I2S_SPDIF>;
+ clock-names = "i2s_input_clk";
+};
+
+&audin_fifo1 {
+ compatible = "amlogic,meson-gxbb-audin-fifo",
+ "amlogic,meson-gx-audin-fifo";
+ clocks = <&clkc CLKID_I2S_SPDIF>;
+ clock-names = "i2s_input_clk";
+};
+
+&audin_fifo2 {
+ compatible = "amlogic,meson-gxbb-audin-fifo",
+ "amlogic,meson-gx-audin-fifo";
+ clocks = <&clkc CLKID_I2S_SPDIF>;
+ clock-names = "i2s_input_clk";
+};
+
&aobus {
pinctrl_aobus: pinctrl@14 {
compatible = "amlogic,meson-gxbb-aobus-pinctrl";
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH RFC v2 11/11] arm64: dts: amlogic: odroid-c2: add support for I2S audio input
2026-04-11 14:57 [PATCH RFC v2 00/11] Add support for AUDIN driver in Amlogic GXBB Valerio Setti
` (9 preceding siblings ...)
2026-04-11 14:57 ` [PATCH RFC v2 10/11] arm64: dts: amlogic: gx: add nodes for AUDIN decoder and FIFO Valerio Setti
@ 2026-04-11 14:57 ` Valerio Setti
10 siblings, 0 replies; 12+ messages in thread
From: Valerio Setti @ 2026-04-11 14:57 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Valerio Setti
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
devicetree
- Enable AUDIN I2S decoder and all FIFO components;
- Add AUDIN I2S Decoder as AUX device to the GX sound card;
- Add AUDIN Capture frontend DAIs in the GX sound card;
- Add I2S input data routing to the GX sound card.
Note: in the routing part, usage of "AIU I2S Encoder Capture" as source
for "AUDIN I2S Decoder IN" is fine (despite the Encoder/Decoder mismatch).
This belong to the fact that the interface is implemented by "AIU I2S
Encoder", which was already existing (and named) before the AUDIN addition.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
.../arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts | 34 ++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
index 5943bc810678edc81fe1a8e3eeae69786e27010c..54798e5b631fdc594cabc5876ab73a3a85944ab0 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
@@ -188,6 +188,12 @@ sound {
<270950400>,
<393216000>;
+ audio-aux-devs = <&audin_decoder_i2s>;
+ audio-routing = "AUDIN I2S Decoder IN", "AIU I2S Encoder Capture",
+ "AUDIN FIFO0 I2S IN", "AUDIN I2S Decoder OUT",
+ "AUDIN FIFO1 I2S IN", "AUDIN I2S Decoder OUT",
+ "AUDIN FIFO2 I2S IN", "AUDIN I2S Decoder OUT";
+
dai-link-0 {
sound-dai = <&aiu AIU_CPU CPU_I2S_FIFO>;
};
@@ -209,6 +215,18 @@ codec-0 {
sound-dai = <&hdmi_tx>;
};
};
+
+ dai-link-3 {
+ sound-dai = <&audin_fifo0>;
+ };
+
+ dai-link-4 {
+ sound-dai = <&audin_fifo1>;
+ };
+
+ dai-link-5 {
+ sound-dai = <&audin_fifo2>;
+ };
};
};
@@ -216,6 +234,22 @@ &aiu {
status = "okay";
};
+&audin_decoder_i2s {
+ status = "okay";
+};
+
+&audin_fifo0 {
+ status = "okay";
+};
+
+&audin_fifo1 {
+ status = "okay";
+};
+
+&audin_fifo2 {
+ status = "okay";
+};
+
&cec_AO {
status = "okay";
pinctrl-0 = <&ao_cec_pins>;
--
2.39.5
^ permalink raw reply related [flat|nested] 12+ messages in thread