The patch ASoC: meson: aiu: add i2s and spdif support has been applied to the asoc tree at https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted. You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed. If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced. Please add any relevant lists and maintainers to the CCs when replying to this mail. Thanks, Mark >From 6ae9ca9ce986bffe71fd0fbf9595de8500891b52 Mon Sep 17 00:00:00 2001 From: Jerome Brunet <jbrunet@xxxxxxxxxxxx> Date: Thu, 13 Feb 2020 16:51:54 +0100 Subject: [PATCH] ASoC: meson: aiu: add i2s and spdif support Add support for the i2s and spdif audio outputs (AIU) found in the amlogic Gx SoC family Signed-off-by: Jerome Brunet <jbrunet@xxxxxxxxxxxx> Link: https://lore.kernel.org/r/20200213155159.3235792-5-jbrunet@xxxxxxxxxxxx Signed-off-by: Mark Brown <broonie@xxxxxxxxxx> --- sound/soc/meson/Kconfig | 7 + sound/soc/meson/Makefile | 7 + sound/soc/meson/aiu-encoder-i2s.c | 324 ++++++++++++++++++++++++++ sound/soc/meson/aiu-encoder-spdif.c | 209 +++++++++++++++++ sound/soc/meson/aiu-fifo-i2s.c | 153 ++++++++++++ sound/soc/meson/aiu-fifo-spdif.c | 186 +++++++++++++++ sound/soc/meson/aiu-fifo.c | 223 ++++++++++++++++++ sound/soc/meson/aiu-fifo.h | 50 ++++ sound/soc/meson/aiu.c | 348 ++++++++++++++++++++++++++++ sound/soc/meson/aiu.h | 82 +++++++ 10 files changed, 1589 insertions(+) create mode 100644 sound/soc/meson/aiu-encoder-i2s.c create mode 100644 sound/soc/meson/aiu-encoder-spdif.c create mode 100644 sound/soc/meson/aiu-fifo-i2s.c create mode 100644 sound/soc/meson/aiu-fifo-spdif.c create mode 100644 sound/soc/meson/aiu-fifo.c create mode 100644 sound/soc/meson/aiu-fifo.h create mode 100644 sound/soc/meson/aiu.c create mode 100644 sound/soc/meson/aiu.h diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig index ee6d53949d45..ca269dedfc7f 100644 --- a/sound/soc/meson/Kconfig +++ b/sound/soc/meson/Kconfig @@ -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 diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index 529a807b3f37..a7b79d717288 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile @@ -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 index 000000000000..13bf029086a9 --- /dev/null +++ b/sound/soc/meson/aiu-encoder-i2s.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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 index 000000000000..de850913975f --- /dev/null +++ b/sound/soc/meson/aiu-encoder-spdif.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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 index 000000000000..9a5271ce80fe --- /dev/null +++ b/sound/soc/meson/aiu-fifo-i2s.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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 index 000000000000..44eb6faacf44 --- /dev/null +++ b/sound/soc/meson/aiu-fifo-spdif.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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 index 000000000000..da8c098e8750 --- /dev/null +++ b/sound/soc/meson/aiu-fifo.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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 index 000000000000..42ce266677cc --- /dev/null +++ b/sound/soc/meson/aiu-fifo.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2020 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + */ + +#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 index 000000000000..a62aced9b687 --- /dev/null +++ b/sound/soc/meson/aiu.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + +#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@xxxxxxxxxxxx>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h new file mode 100644 index 000000000000..a3488027b9d5 --- /dev/null +++ b/sound/soc/meson/aiu.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@xxxxxxxxxxxx> + */ + +#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 */ -- 2.20.1