OSDN Git Service

ASoC: meson: aiu: add i2s and spdif support
authorJerome Brunet <jbrunet@baylibre.com>
Thu, 13 Feb 2020 15:51:54 +0000 (16:51 +0100)
committerMark Brown <broonie@kernel.org>
Thu, 13 Feb 2020 20:57:22 +0000 (20:57 +0000)
Add support for the i2s and spdif audio outputs (AIU) found in the
amlogic Gx SoC family

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Link: https://lore.kernel.org/r/20200213155159.3235792-5-jbrunet@baylibre.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/meson/Kconfig
sound/soc/meson/Makefile
sound/soc/meson/aiu-encoder-i2s.c [new file with mode: 0644]
sound/soc/meson/aiu-encoder-spdif.c [new file with mode: 0644]
sound/soc/meson/aiu-fifo-i2s.c [new file with mode: 0644]
sound/soc/meson/aiu-fifo-spdif.c [new file with mode: 0644]
sound/soc/meson/aiu-fifo.c [new file with mode: 0644]
sound/soc/meson/aiu-fifo.h [new file with mode: 0644]
sound/soc/meson/aiu.c [new file with mode: 0644]
sound/soc/meson/aiu.h [new file with mode: 0644]

index ee6d539..ca269de 100644 (file)
@@ -2,6 +2,13 @@
 menu "ASoC support for Amlogic platforms"
        depends on ARCH_MESON || COMPILE_TEST
 
+config SND_MESON_AIU
+       tristate "Amlogic AIU"
+       select SND_PCM_IEC958
+       help
+         Select Y or M to add support for the Audio output subsystem found
+         in the Amlogic GX SoC family
+
 config SND_MESON_AXG_FIFO
        tristate
        select REGMAP_MMIO
index 529a807..a7b79d7 100644 (file)
@@ -1,5 +1,11 @@
 # SPDX-License-Identifier: (GPL-2.0 OR MIT)
 
+snd-soc-meson-aiu-objs := aiu.o
+snd-soc-meson-aiu-objs += aiu-encoder-i2s.o
+snd-soc-meson-aiu-objs += aiu-encoder-spdif.o
+snd-soc-meson-aiu-objs += aiu-fifo.o
+snd-soc-meson-aiu-objs += aiu-fifo-i2s.o
+snd-soc-meson-aiu-objs += aiu-fifo-spdif.o
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
@@ -14,6 +20,7 @@ snd-soc-meson-axg-pdm-objs := axg-pdm.o
 snd-soc-meson-codec-glue-objs := meson-codec-glue.o
 snd-soc-meson-g12a-tohdmitx-objs := g12a-tohdmitx.o
 
+obj-$(CONFIG_SND_MESON_AIU) += snd-soc-meson-aiu.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/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
new file mode 100644 (file)
index 0000000..13bf029
--- /dev/null
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@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 "aiu.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_I2S_MISC_HOLD_EN           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)
+#define AIU_CLK_CTRL_LRCLK_INVERT      BIT(7)
+#define AIU_CLK_CTRL_LRCLK_SKEW                GENMASK(9, 8)
+#define AIU_CLK_CTRL_MORE_HDMI_AMCLK   BIT(6)
+#define AIU_CLK_CTRL_MORE_I2S_DIV      GENMASK(5, 0)
+#define AIU_CODEC_DAC_LRCLK_CTRL_DIV   GENMASK(11, 0)
+
+struct aiu_encoder_i2s {
+       struct clk *aoclk;
+       struct clk *mclk;
+       struct clk *mixer;
+       struct clk *pclk;
+};
+
+static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
+                                          bool enable)
+{
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL,
+                                     AIU_CLK_CTRL_I2S_DIV_EN,
+                                     enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
+}
+
+static void aiu_encoder_i2s_hold(struct snd_soc_component *component,
+                                bool enable)
+{
+       snd_soc_component_update_bits(component, AIU_I2S_MISC,
+                                     AIU_I2S_MISC_HOLD_EN,
+                                     enable ? AIU_I2S_MISC_HOLD_EN : 0);
+}
+
+static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               aiu_encoder_i2s_hold(component, false);
+               return 0;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               aiu_encoder_i2s_hold(component, true);
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+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;
+       unsigned int val;
+
+       /* 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, &val);
+
+       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_clocks(struct snd_soc_component *component,
+                                     struct snd_pcm_hw_params *params)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(component);
+       unsigned int srate = params_rate(params);
+       unsigned int fs, bs;
+
+       /* Get the oversampling factor */
+       fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
+
+       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,
+                                     FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,
+                                                64 - 1));
+
+       /* Use CLK_MORE for mclk to bclk divider */
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL,
+                                     AIU_CLK_CTRL_I2S_DIV, 0);
+
+       /*
+        * NOTE: this HW is odd.
+        * In most configuration, the i2s divider is 'mclk / blck'.
+        * However, in 16 bits - 8ch mode, this factor needs to be
+        * increased by 50% to get the correct output rate.
+        * No idea why !
+        */
+       bs = fs / 64;
+       if (params_width(params) == 16 && params_channels(params) == 8) {
+               if (bs % 2) {
+                       dev_err(component->dev,
+                               "Cannot increase i2s divider by 50%%\n");
+                       return -EINVAL;
+               }
+               bs += bs / 2;
+       }
+
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
+                                     AIU_CLK_CTRL_MORE_I2S_DIV,
+                                     FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
+                                                bs - 1));
+
+       /* Make sure amclk is used for HDMI i2s as well */
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
+                                     AIU_CLK_CTRL_MORE_HDMI_AMCLK,
+                                     AIU_CLK_CTRL_MORE_HDMI_AMCLK);
+
+       return 0;
+}
+
+static int aiu_encoder_i2s_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;
+       int ret;
+
+       /* Disable the clock while changing the settings */
+       aiu_encoder_i2s_divider_enable(component, false);
+
+       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, params);
+       if (ret) {
+               dev_err(dai->dev, "setting i2s clocks failed\n");
+               return ret;
+       }
+
+       aiu_encoder_i2s_divider_enable(component, true);
+
+       return 0;
+}
+
+static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+
+       aiu_encoder_i2s_divider_enable(component, false);
+
+       return 0;
+}
+
+static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+       struct snd_soc_component *component = dai->component;
+       unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
+       unsigned int val = 0;
+       unsigned int skew;
+
+       /* Only CPU Master / Codec Slave supported ATM */
+       if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+               return -EINVAL;
+
+       if (inv == SND_SOC_DAIFMT_NB_IF ||
+           inv == SND_SOC_DAIFMT_IB_IF)
+               val |= AIU_CLK_CTRL_LRCLK_INVERT;
+
+       if (inv == SND_SOC_DAIFMT_IB_NF ||
+           inv == SND_SOC_DAIFMT_IB_IF)
+               val |= AIU_CLK_CTRL_AOCLK_INVERT;
+
+       /* Signal skew */
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               /* Invert sample clock for i2s */
+               val ^= AIU_CLK_CTRL_LRCLK_INVERT;
+               skew = 1;
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               skew = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL,
+                                     AIU_CLK_CTRL_LRCLK_INVERT |
+                                     AIU_CLK_CTRL_AOCLK_INVERT |
+                                     AIU_CLK_CTRL_LRCLK_SKEW,
+                                     val);
+
+       return 0;
+}
+
+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);
+       int ret;
+
+       if (WARN_ON(clk_id != 0))
+               return -EINVAL;
+
+       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);
+
+       return ret;
+}
+
+static const unsigned int hw_channels[] = {2, 8};
+static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {
+       .list = hw_channels,
+       .count = ARRAY_SIZE(hw_channels),
+       .mask = 0,
+};
+
+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);
+       int ret;
+
+       /* Make sure the encoder gets either 2 or 8 channels */
+       ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+                                        SNDRV_PCM_HW_PARAM_CHANNELS,
+                                        &hw_channel_constraints);
+       if (ret) {
+               dev_err(dai->dev, "adding channels constraints failed\n");
+               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");
+
+       return ret;
+}
+
+static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
+                                    struct snd_soc_dai *dai)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
+
+       clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
+}
+
+const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
+       .trigger        = aiu_encoder_i2s_trigger,
+       .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,
+};
+
diff --git a/sound/soc/meson/aiu-encoder-spdif.c b/sound/soc/meson/aiu-encoder-spdif.c
new file mode 100644 (file)
index 0000000..de85091
--- /dev/null
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm_iec958.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "aiu.h"
+
+#define AIU_958_MISC_NON_PCM           BIT(0)
+#define AIU_958_MISC_MODE_16BITS       BIT(1)
+#define AIU_958_MISC_16BITS_ALIGN      GENMASK(6, 5)
+#define AIU_958_MISC_MODE_32BITS       BIT(7)
+#define AIU_958_MISC_U_FROM_STREAM     BIT(12)
+#define AIU_958_MISC_FORCE_LR          BIT(13)
+#define AIU_958_CTRL_HOLD_EN           BIT(0)
+#define AIU_CLK_CTRL_958_DIV_EN                BIT(1)
+#define AIU_CLK_CTRL_958_DIV           GENMASK(5, 4)
+#define AIU_CLK_CTRL_958_DIV_MORE      BIT(12)
+
+#define AIU_CS_WORD_LEN                        4
+#define AIU_958_INTERNAL_DIV           2
+
+static void
+aiu_encoder_spdif_divider_enable(struct snd_soc_component *component,
+                                bool enable)
+{
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL,
+                                     AIU_CLK_CTRL_958_DIV_EN,
+                                     enable ? AIU_CLK_CTRL_958_DIV_EN : 0);
+}
+
+static void aiu_encoder_spdif_hold(struct snd_soc_component *component,
+                                  bool enable)
+{
+       snd_soc_component_update_bits(component, AIU_958_CTRL,
+                                     AIU_958_CTRL_HOLD_EN,
+                                     enable ? AIU_958_CTRL_HOLD_EN : 0);
+}
+
+static int
+aiu_encoder_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+                         struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               aiu_encoder_spdif_hold(component, false);
+               return 0;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               aiu_encoder_spdif_hold(component, true);
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int aiu_encoder_spdif_setup_cs_word(struct snd_soc_component *component,
+                                          struct snd_pcm_hw_params *params)
+{
+       u8 cs[AIU_CS_WORD_LEN];
+       unsigned int val;
+       int ret;
+
+       ret = snd_pcm_create_iec958_consumer_hw_params(params, cs,
+                                                      AIU_CS_WORD_LEN);
+       if (ret < 0)
+               return ret;
+
+       /* Write the 1st half word */
+       val = cs[1] | cs[0] << 8;
+       snd_soc_component_write(component, AIU_958_CHSTAT_L0, val);
+       snd_soc_component_write(component, AIU_958_CHSTAT_R0, val);
+
+       /* Write the 2nd half word */
+       val = cs[3] | cs[2] << 8;
+       snd_soc_component_write(component, AIU_958_CHSTAT_L1, val);
+       snd_soc_component_write(component, AIU_958_CHSTAT_R1, val);
+
+       return 0;
+}
+
+static int aiu_encoder_spdif_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 aiu *aiu = snd_soc_component_get_drvdata(component);
+       unsigned int val = 0, mrate;
+       int ret;
+
+       /* Disable the clock while changing the settings */
+       aiu_encoder_spdif_divider_enable(component, false);
+
+       switch (params_physical_width(params)) {
+       case 16:
+               val |= AIU_958_MISC_MODE_16BITS;
+               val |= FIELD_PREP(AIU_958_MISC_16BITS_ALIGN, 2);
+               break;
+       case 32:
+               val |= AIU_958_MISC_MODE_32BITS;
+               break;
+       default:
+               dev_err(dai->dev, "Unsupport physical width\n");
+               return -EINVAL;
+       }
+
+       snd_soc_component_update_bits(component, AIU_958_MISC,
+                                     AIU_958_MISC_NON_PCM |
+                                     AIU_958_MISC_MODE_16BITS |
+                                     AIU_958_MISC_16BITS_ALIGN |
+                                     AIU_958_MISC_MODE_32BITS |
+                                     AIU_958_MISC_FORCE_LR |
+                                     AIU_958_MISC_U_FROM_STREAM,
+                                     val);
+
+       /* Set the stream channel status word */
+       ret = aiu_encoder_spdif_setup_cs_word(component, params);
+       if (ret) {
+               dev_err(dai->dev, "failed to set channel status word\n");
+               return ret;
+       }
+
+       snd_soc_component_update_bits(component, AIU_CLK_CTRL,
+                                     AIU_CLK_CTRL_958_DIV |
+                                     AIU_CLK_CTRL_958_DIV_MORE,
+                                     FIELD_PREP(AIU_CLK_CTRL_958_DIV,
+                                                __ffs(AIU_958_INTERNAL_DIV)));
+
+       /* 2 * 32bits per subframe * 2 channels = 128 */
+       mrate = params_rate(params) * 128 * AIU_958_INTERNAL_DIV;
+       ret = clk_set_rate(aiu->spdif.clks[MCLK].clk, mrate);
+       if (ret) {
+               dev_err(dai->dev, "failed to set mclk rate\n");
+               return ret;
+       }
+
+       aiu_encoder_spdif_divider_enable(component, true);
+
+       return 0;
+}
+
+static int aiu_encoder_spdif_hw_free(struct snd_pcm_substream *substream,
+                                    struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+
+       aiu_encoder_spdif_divider_enable(component, false);
+
+       return 0;
+}
+
+static int aiu_encoder_spdif_startup(struct snd_pcm_substream *substream,
+                                    struct snd_soc_dai *dai)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
+       int ret;
+
+       /*
+        * NOTE: Make sure the spdif block is on its own divider.
+        *
+        * The spdif can be clocked by the i2s master clock or its own
+        * clock. We should (in theory) change the source depending on the
+        * origin of the data.
+        *
+        * However, considering the clocking scheme used on these platforms,
+        * the master clocks will pick the same PLL source when they are
+        * playing from the same FIFO. The clock should be in sync so, it
+        * should not be necessary to reparent the spdif master clock.
+        */
+       ret = clk_set_parent(aiu->spdif.clks[MCLK].clk,
+                            aiu->spdif_mclk);
+       if (ret)
+               return ret;
+
+       ret = clk_bulk_prepare_enable(aiu->spdif.clk_num, aiu->spdif.clks);
+       if (ret)
+               dev_err(dai->dev, "failed to enable spdif clocks\n");
+
+       return ret;
+}
+
+static void aiu_encoder_spdif_shutdown(struct snd_pcm_substream *substream,
+                                      struct snd_soc_dai *dai)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
+
+       clk_bulk_disable_unprepare(aiu->spdif.clk_num, aiu->spdif.clks);
+}
+
+const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops = {
+       .trigger        = aiu_encoder_spdif_trigger,
+       .hw_params      = aiu_encoder_spdif_hw_params,
+       .hw_free        = aiu_encoder_spdif_hw_free,
+       .startup        = aiu_encoder_spdif_startup,
+       .shutdown       = aiu_encoder_spdif_shutdown,
+};
diff --git a/sound/soc/meson/aiu-fifo-i2s.c b/sound/soc/meson/aiu-fifo-i2s.c
new file mode 100644 (file)
index 0000000..9a5271c
--- /dev/null
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@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 "aiu.h"
+#include "aiu-fifo.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_MEM_I2S_MASKS_IRQ_BLOCK    GENMASK(31, 16)
+#define AIU_MEM_I2S_CONTROL_MODE_16BIT BIT(6)
+#define AIU_MEM_I2S_BUF_CNTL_INIT      BIT(0)
+#define AIU_RST_SOFT_I2S_FAST          BIT(0)
+
+#define AIU_FIFO_I2S_BLOCK             256
+
+static struct snd_pcm_hardware fifo_i2s_pcm = {
+       .info = (SNDRV_PCM_INFO_INTERLEAVED |
+                SNDRV_PCM_INFO_MMAP |
+                SNDRV_PCM_INFO_MMAP_VALID |
+                SNDRV_PCM_INFO_PAUSE),
+       .formats = AIU_FORMATS,
+       .rate_min = 5512,
+       .rate_max = 192000,
+       .channels_min = 2,
+       .channels_max = 8,
+       .period_bytes_min = AIU_FIFO_I2S_BLOCK,
+       .period_bytes_max = AIU_FIFO_I2S_BLOCK * USHRT_MAX,
+       .periods_min = 2,
+       .periods_max = UINT_MAX,
+
+       /* No real justification for this */
+       .buffer_bytes_max = 1 * 1024 * 1024,
+};
+
+static int aiu_fifo_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       unsigned int val;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               snd_soc_component_write(component, AIU_RST_SOFT,
+                                       AIU_RST_SOFT_I2S_FAST);
+               snd_soc_component_read(component, AIU_I2S_SYNC, &val);
+               break;
+       }
+
+       return aiu_fifo_trigger(substream, cmd, dai);
+}
+
+static int aiu_fifo_i2s_prepare(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       int ret;
+
+       ret = aiu_fifo_prepare(substream, dai);
+       if (ret)
+               return ret;
+
+       snd_soc_component_update_bits(component,
+                                     AIU_MEM_I2S_BUF_CNTL,
+                                     AIU_MEM_I2S_BUF_CNTL_INIT,
+                                     AIU_MEM_I2S_BUF_CNTL_INIT);
+       snd_soc_component_update_bits(component,
+                                     AIU_MEM_I2S_BUF_CNTL,
+                                     AIU_MEM_I2S_BUF_CNTL_INIT, 0);
+
+       return 0;
+}
+
+static int aiu_fifo_i2s_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 aiu_fifo *fifo = dai->playback_dma_data;
+       unsigned int val;
+       int ret;
+
+       ret = aiu_fifo_hw_params(substream, params, dai);
+       if (ret)
+               return ret;
+
+       switch (params_physical_width(params)) {
+       case 16:
+               val = AIU_MEM_I2S_CONTROL_MODE_16BIT;
+               break;
+       case 32:
+               val = 0;
+               break;
+       default:
+               dev_err(dai->dev, "Unsupported physical width %u\n",
+                       params_physical_width(params));
+               return -EINVAL;
+       }
+
+       snd_soc_component_update_bits(component, AIU_MEM_I2S_CONTROL,
+                                     AIU_MEM_I2S_CONTROL_MODE_16BIT,
+                                     val);
+
+       /* Setup the irq periodicity */
+       val = params_period_bytes(params) / fifo->fifo_block;
+       val = FIELD_PREP(AIU_MEM_I2S_MASKS_IRQ_BLOCK, val);
+       snd_soc_component_update_bits(component, AIU_MEM_I2S_MASKS,
+                                     AIU_MEM_I2S_MASKS_IRQ_BLOCK, val);
+
+       return 0;
+}
+
+const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops = {
+       .trigger        = aiu_fifo_i2s_trigger,
+       .prepare        = aiu_fifo_i2s_prepare,
+       .hw_params      = aiu_fifo_i2s_hw_params,
+       .hw_free        = aiu_fifo_hw_free,
+       .startup        = aiu_fifo_startup,
+       .shutdown       = aiu_fifo_shutdown,
+};
+
+int aiu_fifo_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       struct aiu *aiu = snd_soc_component_get_drvdata(component);
+       struct aiu_fifo *fifo;
+       int ret;
+
+       ret = aiu_fifo_dai_probe(dai);
+       if (ret)
+               return ret;
+
+       fifo = dai->playback_dma_data;
+
+       fifo->pcm = &fifo_i2s_pcm;
+       fifo->mem_offset = AIU_MEM_I2S_START;
+       fifo->fifo_block = AIU_FIFO_I2S_BLOCK;
+       fifo->pclk = aiu->i2s.clks[PCLK].clk;
+       fifo->irq = aiu->i2s.irq;
+
+       return 0;
+}
diff --git a/sound/soc/meson/aiu-fifo-spdif.c b/sound/soc/meson/aiu-fifo-spdif.c
new file mode 100644 (file)
index 0000000..44eb6fa
--- /dev/null
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "aiu.h"
+#include "aiu-fifo.h"
+
+#define AIU_IEC958_DCU_FF_CTRL_EN              BIT(0)
+#define AIU_IEC958_DCU_FF_CTRL_AUTO_DISABLE    BIT(1)
+#define AIU_IEC958_DCU_FF_CTRL_IRQ_MODE                GENMASK(3, 2)
+#define AIU_IEC958_DCU_FF_CTRL_IRQ_OUT_THD     BIT(2)
+#define AIU_IEC958_DCU_FF_CTRL_IRQ_FRAME_READ  BIT(3)
+#define AIU_IEC958_DCU_FF_CTRL_SYNC_HEAD_EN    BIT(4)
+#define AIU_IEC958_DCU_FF_CTRL_BYTE_SEEK       BIT(5)
+#define AIU_IEC958_DCU_FF_CTRL_CONTINUE                BIT(6)
+#define AIU_MEM_IEC958_CONTROL_ENDIAN          GENMASK(5, 3)
+#define AIU_MEM_IEC958_CONTROL_RD_DDR          BIT(6)
+#define AIU_MEM_IEC958_CONTROL_MODE_16BIT      BIT(7)
+#define AIU_MEM_IEC958_CONTROL_MODE_LINEAR     BIT(8)
+#define AIU_MEM_IEC958_BUF_CNTL_INIT           BIT(0)
+
+#define AIU_FIFO_SPDIF_BLOCK                   8
+
+static struct snd_pcm_hardware fifo_spdif_pcm = {
+       .info = (SNDRV_PCM_INFO_INTERLEAVED |
+                SNDRV_PCM_INFO_MMAP |
+                SNDRV_PCM_INFO_MMAP_VALID |
+                SNDRV_PCM_INFO_PAUSE),
+       .formats = AIU_FORMATS,
+       .rate_min = 5512,
+       .rate_max = 192000,
+       .channels_min = 2,
+       .channels_max = 2,
+       .period_bytes_min = AIU_FIFO_SPDIF_BLOCK,
+       .period_bytes_max = AIU_FIFO_SPDIF_BLOCK * USHRT_MAX,
+       .periods_min = 2,
+       .periods_max = UINT_MAX,
+
+       /* No real justification for this */
+       .buffer_bytes_max = 1 * 1024 * 1024,
+};
+
+static void fifo_spdif_dcu_enable(struct snd_soc_component *component,
+                                 bool enable)
+{
+       snd_soc_component_update_bits(component, AIU_IEC958_DCU_FF_CTRL,
+                                     AIU_IEC958_DCU_FF_CTRL_EN,
+                                     enable ? AIU_IEC958_DCU_FF_CTRL_EN : 0);
+}
+
+static int fifo_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+                             struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       int ret;
+
+       ret = aiu_fifo_trigger(substream, cmd, dai);
+       if (ret)
+               return ret;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               fifo_spdif_dcu_enable(component, true);
+               break;
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_STOP:
+               fifo_spdif_dcu_enable(component, false);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int fifo_spdif_prepare(struct snd_pcm_substream *substream,
+                             struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       int ret;
+
+       ret = aiu_fifo_prepare(substream, dai);
+       if (ret)
+               return ret;
+
+       snd_soc_component_update_bits(component,
+                                     AIU_MEM_IEC958_BUF_CNTL,
+                                     AIU_MEM_IEC958_BUF_CNTL_INIT,
+                                     AIU_MEM_IEC958_BUF_CNTL_INIT);
+       snd_soc_component_update_bits(component,
+                                     AIU_MEM_IEC958_BUF_CNTL,
+                                     AIU_MEM_IEC958_BUF_CNTL_INIT, 0);
+
+       return 0;
+}
+
+static int fifo_spdif_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;
+       unsigned int val;
+       int ret;
+
+       ret = aiu_fifo_hw_params(substream, params, dai);
+       if (ret)
+               return ret;
+
+       val = AIU_MEM_IEC958_CONTROL_RD_DDR |
+             AIU_MEM_IEC958_CONTROL_MODE_LINEAR;
+
+       switch (params_physical_width(params)) {
+       case 16:
+               val |= AIU_MEM_IEC958_CONTROL_MODE_16BIT;
+               break;
+       case 32:
+               break;
+       default:
+               dev_err(dai->dev, "Unsupported physical width %u\n",
+                       params_physical_width(params));
+               return -EINVAL;
+       }
+
+       snd_soc_component_update_bits(component, AIU_MEM_IEC958_CONTROL,
+                                     AIU_MEM_IEC958_CONTROL_ENDIAN |
+                                     AIU_MEM_IEC958_CONTROL_RD_DDR |
+                                     AIU_MEM_IEC958_CONTROL_MODE_LINEAR |
+                                     AIU_MEM_IEC958_CONTROL_MODE_16BIT,
+                                     val);
+
+       /* Number bytes read by the FIFO between each IRQ */
+       snd_soc_component_write(component, AIU_IEC958_BPF,
+                               params_period_bytes(params));
+
+       /*
+        * AUTO_DISABLE and SYNC_HEAD are enabled by default but
+        * this should be disabled in PCM (uncompressed) mode
+        */
+       snd_soc_component_update_bits(component, AIU_IEC958_DCU_FF_CTRL,
+                                     AIU_IEC958_DCU_FF_CTRL_AUTO_DISABLE |
+                                     AIU_IEC958_DCU_FF_CTRL_IRQ_MODE |
+                                     AIU_IEC958_DCU_FF_CTRL_SYNC_HEAD_EN,
+                                     AIU_IEC958_DCU_FF_CTRL_IRQ_FRAME_READ);
+
+       return 0;
+}
+
+const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops = {
+       .trigger        = fifo_spdif_trigger,
+       .prepare        = fifo_spdif_prepare,
+       .hw_params      = fifo_spdif_hw_params,
+       .hw_free        = aiu_fifo_hw_free,
+       .startup        = aiu_fifo_startup,
+       .shutdown       = aiu_fifo_shutdown,
+};
+
+int aiu_fifo_spdif_dai_probe(struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       struct aiu *aiu = snd_soc_component_get_drvdata(component);
+       struct aiu_fifo *fifo;
+       int ret;
+
+       ret = aiu_fifo_dai_probe(dai);
+       if (ret)
+               return ret;
+
+       fifo = dai->playback_dma_data;
+
+       fifo->pcm = &fifo_spdif_pcm;
+       fifo->mem_offset = AIU_MEM_IEC958_START;
+       fifo->fifo_block = 1;
+       fifo->pclk = aiu->spdif.clks[PCLK].clk;
+       fifo->irq = aiu->spdif.irq;
+
+       return 0;
+}
diff --git a/sound/soc/meson/aiu-fifo.c b/sound/soc/meson/aiu-fifo.c
new file mode 100644 (file)
index 0000000..da8c098
--- /dev/null
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@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 "aiu-fifo.h"
+
+#define AIU_MEM_START  0x00
+#define AIU_MEM_RD     0x04
+#define AIU_MEM_END    0x08
+#define AIU_MEM_MASKS  0x0c
+#define  AIU_MEM_MASK_CH_RD GENMASK(7, 0)
+#define  AIU_MEM_MASK_CH_MEM GENMASK(15, 8)
+#define AIU_MEM_CONTROL        0x10
+#define  AIU_MEM_CONTROL_INIT BIT(0)
+#define  AIU_MEM_CONTROL_FILL_EN BIT(1)
+#define  AIU_MEM_CONTROL_EMPTY_EN BIT(2)
+
+static struct snd_soc_dai *aiu_fifo_dai(struct snd_pcm_substream *ss)
+{
+       struct snd_soc_pcm_runtime *rtd = ss->private_data;
+
+       return rtd->cpu_dai;
+}
+
+snd_pcm_uframes_t aiu_fifo_pointer(struct snd_soc_component *component,
+                                  struct snd_pcm_substream *substream)
+{
+       struct snd_soc_dai *dai = aiu_fifo_dai(substream);
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned int addr;
+
+       snd_soc_component_read(component, fifo->mem_offset + AIU_MEM_RD,
+                              &addr);
+
+       return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr);
+}
+
+static void aiu_fifo_enable(struct snd_soc_dai *dai, bool enable)
+{
+       struct snd_soc_component *component = dai->component;
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+       unsigned int en_mask = (AIU_MEM_CONTROL_FILL_EN |
+                               AIU_MEM_CONTROL_EMPTY_EN);
+
+       snd_soc_component_update_bits(component,
+                                     fifo->mem_offset + AIU_MEM_CONTROL,
+                                     en_mask, enable ? en_mask : 0);
+}
+
+int aiu_fifo_trigger(struct snd_pcm_substream *substream, int cmd,
+                    struct snd_soc_dai *dai)
+{
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               aiu_fifo_enable(dai, true);
+               break;
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_STOP:
+               aiu_fifo_enable(dai, false);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+int aiu_fifo_prepare(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai)
+{
+       struct snd_soc_component *component = dai->component;
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+
+       snd_soc_component_update_bits(component,
+                                     fifo->mem_offset + AIU_MEM_CONTROL,
+                                     AIU_MEM_CONTROL_INIT,
+                                     AIU_MEM_CONTROL_INIT);
+       snd_soc_component_update_bits(component,
+                                     fifo->mem_offset + AIU_MEM_CONTROL,
+                                     AIU_MEM_CONTROL_INIT, 0);
+       return 0;
+}
+
+int aiu_fifo_hw_params(struct snd_pcm_substream *substream,
+                      struct snd_pcm_hw_params *params,
+                      struct snd_soc_dai *dai)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_component *component = dai->component;
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+       dma_addr_t end;
+       int ret;
+
+       ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+       if (ret < 0)
+               return ret;
+
+       /* Setup the fifo boundaries */
+       end = runtime->dma_addr + runtime->dma_bytes - fifo->fifo_block;
+       snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_START,
+                               runtime->dma_addr);
+       snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_RD,
+                               runtime->dma_addr);
+       snd_soc_component_write(component, fifo->mem_offset + AIU_MEM_END,
+                               end);
+
+       /* Setup the fifo to read all the memory - no skip */
+       snd_soc_component_update_bits(component,
+                                     fifo->mem_offset + AIU_MEM_MASKS,
+                                     AIU_MEM_MASK_CH_RD | AIU_MEM_MASK_CH_MEM,
+                                     FIELD_PREP(AIU_MEM_MASK_CH_RD, 0xff) |
+                                     FIELD_PREP(AIU_MEM_MASK_CH_MEM, 0xff));
+
+       return 0;
+}
+
+int aiu_fifo_hw_free(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai)
+{
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static irqreturn_t aiu_fifo_isr(int irq, void *dev_id)
+{
+       struct snd_pcm_substream *playback = dev_id;
+
+       snd_pcm_period_elapsed(playback);
+
+       return IRQ_HANDLED;
+}
+
+int aiu_fifo_startup(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai)
+{
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+       int ret;
+
+       snd_soc_set_runtime_hwparams(substream, fifo->pcm);
+
+       /*
+        * Make sure the buffer and period size are multiple of the fifo burst
+        * size
+        */
+       ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+                                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+                                        fifo->fifo_block);
+       if (ret)
+               return ret;
+
+       ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+                                        SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+                                        fifo->fifo_block);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(fifo->pclk);
+       if (ret)
+               return ret;
+
+       ret = request_irq(fifo->irq, aiu_fifo_isr, 0, dev_name(dai->dev),
+                         substream);
+       if (ret)
+               clk_disable_unprepare(fifo->pclk);
+
+       return ret;
+}
+
+void aiu_fifo_shutdown(struct snd_pcm_substream *substream,
+                      struct snd_soc_dai *dai)
+{
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+
+       free_irq(fifo->irq, substream);
+       clk_disable_unprepare(fifo->pclk);
+}
+
+int aiu_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd,
+                    struct snd_soc_dai *dai)
+{
+       struct snd_pcm_substream *substream =
+               rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+       struct snd_card *card = rtd->card->snd_card;
+       struct aiu_fifo *fifo = dai->playback_dma_data;
+       size_t size = fifo->pcm->buffer_bytes_max;
+
+       snd_pcm_lib_preallocate_pages(substream,
+                                     SNDRV_DMA_TYPE_DEV,
+                                     card->dev, size, size);
+
+       return 0;
+}
+
+int aiu_fifo_dai_probe(struct snd_soc_dai *dai)
+{
+       struct aiu_fifo *fifo;
+
+       fifo = kzalloc(sizeof(*fifo), GFP_KERNEL);
+       if (!fifo)
+               return -ENOMEM;
+
+       dai->playback_dma_data = fifo;
+
+       return 0;
+}
+
+int aiu_fifo_dai_remove(struct snd_soc_dai *dai)
+{
+       kfree(dai->playback_dma_data);
+
+       return 0;
+}
+
diff --git a/sound/soc/meson/aiu-fifo.h b/sound/soc/meson/aiu-fifo.h
new file mode 100644 (file)
index 0000000..42ce266
--- /dev/null
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2020 BayLibre, SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AIU_FIFO_H
+#define _MESON_AIU_FIFO_H
+
+struct snd_pcm_hardware;
+struct snd_soc_component_driver;
+struct snd_soc_dai_driver;
+struct clk;
+struct snd_pcm_ops;
+struct snd_pcm_substream;
+struct snd_soc_dai;
+struct snd_pcm_hw_params;
+struct platform_device;
+
+struct aiu_fifo {
+       struct snd_pcm_hardware *pcm;
+       unsigned int mem_offset;
+       unsigned int fifo_block;
+       struct clk *pclk;
+       int irq;
+};
+
+int aiu_fifo_dai_probe(struct snd_soc_dai *dai);
+int aiu_fifo_dai_remove(struct snd_soc_dai *dai);
+
+snd_pcm_uframes_t aiu_fifo_pointer(struct snd_soc_component *component,
+                                  struct snd_pcm_substream *substream);
+
+int aiu_fifo_trigger(struct snd_pcm_substream *substream, int cmd,
+                    struct snd_soc_dai *dai);
+int aiu_fifo_prepare(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai);
+int aiu_fifo_hw_params(struct snd_pcm_substream *substream,
+                      struct snd_pcm_hw_params *params,
+                      struct snd_soc_dai *dai);
+int aiu_fifo_hw_free(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai);
+int aiu_fifo_startup(struct snd_pcm_substream *substream,
+                    struct snd_soc_dai *dai);
+void aiu_fifo_shutdown(struct snd_pcm_substream *substream,
+                      struct snd_soc_dai *dai);
+int aiu_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd,
+                    struct snd_soc_dai *dai);
+
+#endif /* _MESON_AIU_FIFO_H */
diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c
new file mode 100644 (file)
index 0000000..a62aced
--- /dev/null
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2020 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include <dt-bindings/sound/meson-aiu.h>
+#include "aiu.h"
+#include "aiu-fifo.h"
+
+#define AIU_I2S_MISC_958_SRC_SHIFT 3
+
+static const char * const aiu_spdif_encode_sel_texts[] = {
+       "SPDIF", "I2S",
+};
+
+static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC,
+                           AIU_I2S_MISC_958_SRC_SHIFT,
+                           aiu_spdif_encode_sel_texts);
+
+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),
+};
+
+static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = {
+       { "I2S Encoder Playback", NULL, "I2S FIFO Playback" },
+       { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" },
+       { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" },
+       { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" },
+};
+
+int aiu_of_xlate_dai_name(struct snd_soc_component *component,
+                         struct of_phandle_args *args,
+                         const char **dai_name,
+                         unsigned int component_id)
+{
+       struct snd_soc_dai *dai;
+       int id;
+
+       if (args->args_count != 2)
+               return -EINVAL;
+
+       if (args->args[0] != component_id)
+               return -EINVAL;
+
+       id = args->args[1];
+
+       if (id < 0 || id >= component->num_dai)
+               return -EINVAL;
+
+       for_each_component_dais(component, dai) {
+               if (id == 0)
+                       break;
+               id--;
+       }
+
+       *dai_name = dai->driver->name;
+
+       return 0;
+}
+
+static int aiu_cpu_of_xlate_dai_name(struct snd_soc_component *component,
+                                    struct of_phandle_args *args,
+                                    const char **dai_name)
+{
+       return aiu_of_xlate_dai_name(component, args, dai_name, AIU_CPU);
+}
+
+static int aiu_cpu_component_probe(struct snd_soc_component *component)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(component);
+
+       /* Required for the SPDIF Source control operation */
+       return clk_prepare_enable(aiu->i2s.clks[PCLK].clk);
+}
+
+static void aiu_cpu_component_remove(struct snd_soc_component *component)
+{
+       struct aiu *aiu = snd_soc_component_get_drvdata(component);
+
+       clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
+}
+
+static const struct snd_soc_component_driver aiu_cpu_component = {
+       .name                   = "AIU CPU",
+       .dapm_widgets           = aiu_cpu_dapm_widgets,
+       .num_dapm_widgets       = ARRAY_SIZE(aiu_cpu_dapm_widgets),
+       .dapm_routes            = aiu_cpu_dapm_routes,
+       .num_dapm_routes        = ARRAY_SIZE(aiu_cpu_dapm_routes),
+       .of_xlate_dai_name      = aiu_cpu_of_xlate_dai_name,
+       .pointer                = aiu_fifo_pointer,
+       .probe                  = aiu_cpu_component_probe,
+       .remove                 = aiu_cpu_component_remove,
+};
+
+static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = {
+       [CPU_I2S_FIFO] = {
+               .name = "I2S FIFO",
+               .playback = {
+                       .stream_name    = "I2S FIFO Playback",
+                       .channels_min   = 2,
+                       .channels_max   = 8,
+                       .rates          = SNDRV_PCM_RATE_CONTINUOUS,
+                       .rate_min       = 5512,
+                       .rate_max       = 192000,
+                       .formats        = AIU_FORMATS,
+               },
+               .ops            = &aiu_fifo_i2s_dai_ops,
+               .pcm_new        = aiu_fifo_pcm_new,
+               .probe          = aiu_fifo_i2s_dai_probe,
+               .remove         = aiu_fifo_dai_remove,
+       },
+       [CPU_SPDIF_FIFO] = {
+               .name = "SPDIF FIFO",
+               .playback = {
+                       .stream_name    = "SPDIF FIFO Playback",
+                       .channels_min   = 2,
+                       .channels_max   = 2,
+                       .rates          = SNDRV_PCM_RATE_CONTINUOUS,
+                       .rate_min       = 5512,
+                       .rate_max       = 192000,
+                       .formats        = AIU_FORMATS,
+               },
+               .ops            = &aiu_fifo_spdif_dai_ops,
+               .pcm_new        = aiu_fifo_pcm_new,
+               .probe          = aiu_fifo_spdif_dai_probe,
+               .remove         = aiu_fifo_dai_remove,
+       },
+       [CPU_I2S_ENCODER] = {
+               .name = "I2S Encoder",
+               .playback = {
+                       .stream_name = "I2S Encoder Playback",
+                       .channels_min = 2,
+                       .channels_max = 8,
+                       .rates = SNDRV_PCM_RATE_8000_192000,
+                       .formats = AIU_FORMATS,
+               },
+               .ops = &aiu_encoder_i2s_dai_ops,
+       },
+       [CPU_SPDIF_ENCODER] = {
+               .name = "SPDIF Encoder",
+               .playback = {
+                       .stream_name = "SPDIF Encoder Playback",
+                       .channels_min = 2,
+                       .channels_max = 2,
+                       .rates = (SNDRV_PCM_RATE_32000  |
+                                 SNDRV_PCM_RATE_44100  |
+                                 SNDRV_PCM_RATE_48000  |
+                                 SNDRV_PCM_RATE_88200  |
+                                 SNDRV_PCM_RATE_96000  |
+                                 SNDRV_PCM_RATE_176400 |
+                                 SNDRV_PCM_RATE_192000),
+                       .formats = AIU_FORMATS,
+               },
+               .ops = &aiu_encoder_spdif_dai_ops,
+       }
+};
+
+static const struct regmap_config aiu_regmap_cfg = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x2ac,
+};
+
+static int aiu_clk_bulk_get(struct device *dev,
+                           const char * const *ids,
+                           unsigned int num,
+                           struct aiu_interface *interface)
+{
+       struct clk_bulk_data *clks;
+       int i, ret;
+
+       clks = devm_kcalloc(dev, num, sizeof(clks), GFP_KERNEL);
+       if (!clks)
+               return -ENOMEM;
+
+       for (i = 0; i < num; i++)
+               clks[i].id = ids[i];
+
+       ret = devm_clk_bulk_get(dev, num, clks);
+       if (ret < 0)
+               return ret;
+
+       interface->clks = clks;
+       interface->clk_num = num;
+       return 0;
+}
+
+static const char * const aiu_i2s_ids[] = {
+       [PCLK]  = "i2s_pclk",
+       [AOCLK] = "i2s_aoclk",
+       [MCLK]  = "i2s_mclk",
+       [MIXER] = "i2s_mixer",
+};
+
+static const char * const aiu_spdif_ids[] = {
+       [PCLK]  = "spdif_pclk",
+       [AOCLK] = "spdif_aoclk",
+       [MCLK]  = "spdif_mclk_sel"
+};
+
+static int aiu_clk_get(struct device *dev)
+{
+       struct aiu *aiu = dev_get_drvdata(dev);
+       int ret;
+
+       aiu->pclk = devm_clk_get(dev, "pclk");
+       if (IS_ERR(aiu->pclk)) {
+               if (PTR_ERR(aiu->pclk) != -EPROBE_DEFER)
+                       dev_err(dev, "Can't get the aiu pclk\n");
+               return PTR_ERR(aiu->pclk);
+       }
+
+       aiu->spdif_mclk = devm_clk_get(dev, "spdif_mclk");
+       if (IS_ERR(aiu->spdif_mclk)) {
+               if (PTR_ERR(aiu->spdif_mclk) != -EPROBE_DEFER)
+                       dev_err(dev, "Can't get the aiu spdif master clock\n");
+               return PTR_ERR(aiu->spdif_mclk);
+       }
+
+       ret = aiu_clk_bulk_get(dev, aiu_i2s_ids, ARRAY_SIZE(aiu_i2s_ids),
+                              &aiu->i2s);
+       if (ret) {
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Can't get the i2s clocks\n");
+               return ret;
+       }
+
+       ret = aiu_clk_bulk_get(dev, aiu_spdif_ids, ARRAY_SIZE(aiu_spdif_ids),
+                              &aiu->spdif);
+       if (ret) {
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Can't get the spdif clocks\n");
+               return ret;
+       }
+
+       ret = clk_prepare_enable(aiu->pclk);
+       if (ret) {
+               dev_err(dev, "peripheral clock enable failed\n");
+               return ret;
+       }
+
+       ret = devm_add_action_or_reset(dev,
+                                      (void(*)(void *))clk_disable_unprepare,
+                                      aiu->pclk);
+       if (ret)
+               dev_err(dev, "failed to add reset action on pclk");
+
+       return ret;
+}
+
+static int aiu_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       void __iomem *regs;
+       struct regmap *map;
+       struct aiu *aiu;
+       int ret;
+
+       aiu = devm_kzalloc(dev, sizeof(*aiu), GFP_KERNEL);
+       if (!aiu)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, aiu);
+
+       ret = device_reset(dev);
+       if (ret) {
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Failed to reset device\n");
+               return ret;
+       }
+
+       regs = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(regs))
+               return PTR_ERR(regs);
+
+       map = devm_regmap_init_mmio(dev, regs, &aiu_regmap_cfg);
+       if (IS_ERR(map)) {
+               dev_err(dev, "failed to init regmap: %ld\n",
+                       PTR_ERR(map));
+               return PTR_ERR(map);
+       }
+
+       aiu->i2s.irq = platform_get_irq_byname(pdev, "i2s");
+       if (aiu->i2s.irq < 0) {
+               dev_err(dev, "Can't get i2s irq\n");
+               return aiu->i2s.irq;
+       }
+
+       aiu->spdif.irq = platform_get_irq_byname(pdev, "spdif");
+       if (aiu->spdif.irq < 0) {
+               dev_err(dev, "Can't get spdif irq\n");
+               return aiu->spdif.irq;
+       }
+
+       ret = aiu_clk_get(dev);
+       if (ret)
+               return ret;
+
+       /* Register the cpu component of the aiu */
+       ret = snd_soc_register_component(dev, &aiu_cpu_component,
+                                        aiu_cpu_dai_drv,
+                                        ARRAY_SIZE(aiu_cpu_dai_drv));
+       if (ret)
+               dev_err(dev, "Failed to register cpu component\n");
+
+       return ret;
+}
+
+static int aiu_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_component(&pdev->dev);
+
+       return 0;
+}
+
+static const struct of_device_id aiu_of_match[] = {
+       { .compatible = "amlogic,aiu-gxbb", },
+       { .compatible = "amlogic,aiu-gxl", },
+       {}
+};
+MODULE_DEVICE_TABLE(of, aiu_of_match);
+
+static struct platform_driver aiu_pdrv = {
+       .probe = aiu_probe,
+       .remove = aiu_remove,
+       .driver = {
+               .name = "meson-aiu",
+               .of_match_table = aiu_of_match,
+       },
+};
+module_platform_driver(aiu_pdrv);
+
+MODULE_DESCRIPTION("Meson AIU Driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h
new file mode 100644 (file)
index 0000000..a348802
--- /dev/null
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2018 BayLibre, SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AIU_H
+#define _MESON_AIU_H
+
+struct clk;
+struct clk_bulk_data;
+struct device;
+struct of_phandle_args;
+struct snd_soc_component_driver;
+struct snd_soc_dai;
+struct snd_soc_dai_driver;
+struct snd_soc_dai_ops;
+
+enum aiu_clk_ids {
+       PCLK = 0,
+       AOCLK,
+       MCLK,
+       MIXER
+};
+
+struct aiu_interface {
+       struct clk_bulk_data *clks;
+       unsigned int clk_num;
+       unsigned int irq;
+};
+
+struct aiu {
+       struct clk *pclk;
+       struct clk *spdif_mclk;
+       struct aiu_interface i2s;
+       struct aiu_interface spdif;
+};
+
+#define AIU_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+                    SNDRV_PCM_FMTBIT_S20_LE |  \
+                    SNDRV_PCM_FMTBIT_S24_LE)
+
+int aiu_of_xlate_dai_name(struct snd_soc_component *component,
+                         struct of_phandle_args *args,
+                         const char **dai_name,
+                         unsigned int component_id);
+
+int aiu_fifo_i2s_dai_probe(struct snd_soc_dai *dai);
+int aiu_fifo_spdif_dai_probe(struct snd_soc_dai *dai);
+
+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;
+
+#define AIU_IEC958_BPF                 0x000
+#define AIU_958_MISC                   0x010
+#define AIU_IEC958_DCU_FF_CTRL         0x01c
+#define AIU_958_CHSTAT_L0              0x020
+#define AIU_958_CHSTAT_L1              0x024
+#define AIU_958_CTRL                   0x028
+#define AIU_I2S_SOURCE_DESC            0x034
+#define AIU_I2S_DAC_CFG                        0x040
+#define AIU_I2S_SYNC                   0x044
+#define AIU_I2S_MISC                   0x048
+#define AIU_RST_SOFT                   0x054
+#define AIU_CLK_CTRL                   0x058
+#define AIU_CLK_CTRL_MORE              0x064
+#define AIU_CODEC_DAC_LRCLK_CTRL       0x0a0
+#define AIU_HDMI_CLK_DATA_CTRL         0x0a8
+#define AIU_ACODEC_CTRL                        0x0b0
+#define AIU_958_CHSTAT_R0              0x0c0
+#define AIU_958_CHSTAT_R1              0x0c4
+#define AIU_MEM_I2S_START              0x180
+#define AIU_MEM_I2S_MASKS              0x18c
+#define AIU_MEM_I2S_CONTROL            0x190
+#define AIU_MEM_IEC958_START           0x194
+#define AIU_MEM_IEC958_CONTROL         0x1a4
+#define AIU_MEM_I2S_BUF_CNTL           0x1d8
+#define AIU_MEM_IEC958_BUF_CNTL                0x1fc
+
+#endif /* _MESON_AIU_H */