diff mbox series

[2/3] ASoC: meson: s4:support for the on-chip audio

Message ID 20250113-audio_drvier-v1-2-8c14770f38a0@amlogic.com (mailing list archive)
State New
Headers show
Series Add support for S4 audio | expand

Commit Message

jiebing chen via B4 Relay Jan. 13, 2025, 6:35 a.m. UTC
From: jiebing chen <jiebing.chen@amlogic.com>

Add audio support for Amlogic S4.The audio output pad
can be freelycombined with the output lane,and the tocodec
control logic has been optimized.

Signed-off-by: jiebing chen <jiebing.chen@amlogic.com>
---
 sound/soc/meson/Kconfig              |  16 ++
 sound/soc/meson/Makefile             |   6 +
 sound/soc/meson/s4-pad-out-control.c | 372 ++++++++++++++++++++++++++++++++++
 sound/soc/meson/s4-tocodec-control.c | 376 +++++++++++++++++++++++++++++++++++
 sound/soc/meson/t9015.c              |   5 +-
 5 files changed, 771 insertions(+), 4 deletions(-)

Comments

Jerome Brunet Jan. 13, 2025, 2:31 p.m. UTC | #1
On Mon 13 Jan 2025 at 14:35, jiebing chen via B4 Relay <devnull+jiebing.chen.amlogic.com@kernel.org> wrote:

> From: jiebing chen <jiebing.chen@amlogic.com>
>
> Add audio support for Amlogic S4.The audio output pad
> can be freelycombined with the output lane,and the tocodec
> control logic has been optimized.

The patch is a mixture of different HW modules.

Each patch should have one clear purpose and, as such, deal with a
single HW module

>
> Signed-off-by: jiebing chen <jiebing.chen@amlogic.com>
> ---
>  sound/soc/meson/Kconfig              |  16 ++
>  sound/soc/meson/Makefile             |   6 +
>  sound/soc/meson/s4-pad-out-control.c | 372 ++++++++++++++++++++++++++++++++++
>  sound/soc/meson/s4-tocodec-control.c | 376 +++++++++++++++++++++++++++++++++++
>  sound/soc/meson/t9015.c              |   5 +-
>  5 files changed, 771 insertions(+), 4 deletions(-)
>
> diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
> index 6458d5dc4902f665211bb9e4ae7d274e4bff2fdc..d01e284642fd987cf4bdf88e5bf5f7c9a241af59 100644
> --- a/sound/soc/meson/Kconfig
> +++ b/sound/soc/meson/Kconfig
> @@ -69,6 +69,8 @@ config SND_MESON_AXG_SOUND_CARD
>  	imply SND_MESON_AXG_SPDIFIN
>  	imply SND_MESON_AXG_PDM
>  	imply SND_MESON_G12A_TOACODEC
> +	imply SND_SOC_MESON_PAD_OUT
> +	imply SND_SOC_MESON_TOCODEC_CONTROL
>  	imply SND_MESON_G12A_TOHDMITX if DRM_MESON_DW_HDMI
>  	help
>  	  Select Y or M to add support for the AXG SoC sound card
> @@ -135,4 +137,18 @@ config SND_SOC_MESON_T9015
>  	help
>  	  Say Y or M if you want to add support for the internal DAC found
>  	  on GXL, G12 and SM1 SoC family.
> +
> +config SND_SOC_MESON_PAD_OUT
> +	tristate "Amlogic PAD OUT"
> +	select REGMAP_MMIO
> +	help
> +	  Say Y or M if you want to add support for the S4 Audio Output from
> +	  the data Pad.
> +
> +config SND_SOC_MESON_TOCODEC_CONTROL
> +	tristate "Amlogic TOCODEC CONTROL"
> +	select REGMAP_MMIO
> +	help
> +	 Say Y or M if you want to add support for the internal DAC control
> +	 on SM1 SoC family.
>  endmenu
> diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
> index 24078e4396b02d545d8ba4bcb1632979001354e3..afbefcb1313670f9b1365e88b8eb1a0badd7dc1e 100644
> --- a/sound/soc/meson/Makefile
> +++ b/sound/soc/meson/Makefile
> @@ -24,8 +24,11 @@ snd-soc-meson-codec-glue-y := meson-codec-glue.o
>  snd-soc-meson-gx-sound-card-y := gx-card.o
>  snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
>  snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
> +snd-soc-meson-s4-padout-objs := s4-pad-out-control.o
> +snd-soc-meson-s4-tocodec-control-objs := s4-tocodec-control.o
>  snd-soc-meson-t9015-y := t9015.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
> @@ -43,4 +46,7 @@ obj-$(CONFIG_SND_MESON_CODEC_GLUE) += snd-soc-meson-codec-glue.o
>  obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += snd-soc-meson-gx-sound-card.o
>  obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += snd-soc-meson-g12a-toacodec.o
>  obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += snd-soc-meson-g12a-tohdmitx.o
> +obj-$(CONFIG_SND_SOC_MESON_PAD_OUT) += snd-soc-meson-s4-padout.o
> +obj-$(CONFIG_SND_SOC_MESON_TOCODEC_CONTROL) += snd-soc-meson-s4-tocodec-control.o
>  obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
> +
> diff --git a/sound/soc/meson/s4-pad-out-control.c b/sound/soc/meson/s4-pad-out-control.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..a86dcf8a5995926f0ddf8d2911f42006daed0feb
> --- /dev/null
> +++ b/sound/soc/meson/s4-pad-out-control.c
> @@ -0,0 +1,372 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <sound/soc.h>
> +#include <sound/soc-dai.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include<linux/kstrtox.h>
> +
> +#include "axg-tdm.h"
> +
> +static const struct regmap_config tdmout_pad_regmap_cfg = {
> +	.reg_bits	= 32,
> +	.val_bits	= 32,
> +	.reg_stride	= 4,
> +	.max_register	= 0x28,
> +};
> +
> +#define TDM_IFACE 0
> +#define TDM_A_PAD 0
> +#define TDM_B_PAD 1
> +#define TDM_C_PAD 2
> +
> +#define EE_AUDIO_DAT_PAD_CTRL6 0x0
> +#define EE_AUDIO_DAT_PAD_CTRL7 0x4
> +#define EE_AUDIO_DAT_PAD_CTRL8 0x8
> +#define EE_AUDIO_DAT_PAD_CTRL9 0xc
> +#define EE_AUDIO_DAT_PAD_CTRLA 0x10
> +#define EE_AUDIO_DAT_PAD_CTRLB 0x14
> +#define EE_AUDIO_DAT_PAD_CTRLC 0x1c
> +#define EE_AUDIO_DAT_PAD_CTRLD 0x20
> +#define EE_AUDIO_DAT_PAD_CTRLE 0x24
> +#define EE_AUDIO_DAT_PAD_CTRLF 0x28
> +
> +#define REG_OFFSET 4
> +
> +static const char * const s4_tdmout_sel_texts[] = {
> +	"TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", "TDM_D6", "TDM_D7",
> +	"TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", "TDM_D13", "TDM_D14", "TDM_D15",
> +	"TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", "TDM_D21", "TDM_D22", "TDM_D23",
> +	"TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", "TDM_D29", "TDM_D30", "TDM_D31"
> +};

This thing does not belong in ASoC. This is clearly yet another layer of
pinctrl. Please deal with it there.


> +
> +static const struct soc_enum tdmout_sel_enum =
> +	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
> +			s4_tdmout_sel_texts);
> +
> +static struct snd_soc_dai *tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_dapm_path *p;
> +	struct snd_soc_dai *be;
> +
> +	snd_soc_dapm_widget_for_each_source_path(w, p) {
> +		if (p->source->id == snd_soc_dapm_dai_in)
> +			return (struct snd_soc_dai *)p->source->priv;
> +		be = tdm_get_ahead_be(p->source);
> +		if (be && be->id == TDM_IFACE)
> +			return be;
> +	}
> +	return NULL;
> +}
> +
> +#define SND_SOC_DAPM_DEMUX_E(wname, wreg, wshift, winvert, wcontrols, \
> +	wevent, wflags) \
> +((struct snd_soc_dapm_widget) { \
> +	.id = snd_soc_dapm_demux, .name = wname, \
> +	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
> +	.kcontrol_news = wcontrols, .num_kcontrols = 1, \
> +	.event = wevent, .event_flags = wflags})
> +
> +static const struct snd_kcontrol_new tdmout_sel_demux[] = {
> +	SOC_DAPM_ENUM("TDMOUTA SEL", tdmout_sel_enum),
> +	SOC_DAPM_ENUM("TDMOUTB SEL", tdmout_sel_enum),
> +	SOC_DAPM_ENUM("TDMOUTC SEL", tdmout_sel_enum),
> +};
> +
> +static unsigned int aml_simple_strtoull(const char *cp)
> +{
> +	unsigned int result = 0;
> +	unsigned int value = 0;
> +	unsigned int len =  strlen(cp);
> +
> +	while (len != 0) {
> +		len--;
> +		value = isdigit(*cp);
> +		if (value) {
> +			value = *cp - '0';
> +		} else {
> +			cp++;
> +			continue;
> +		}
> +		cp++;
> +		result = result * 10 + value;
> +	}
> +	return result;
> +}
> +
> +static int tdm_out_pad_set(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_dai *be;
> +	struct axg_tdm_stream *stream;
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +	unsigned int tdm_id = TDM_A_PAD;
> +	const char *dai_widget_name;
> +	struct snd_soc_dapm_path *p;
> +	unsigned int lane_num = 0;
> +	unsigned long pin = 0;
> +	unsigned int reg, mask, val = 0;
> +	int lane_cnt;
> +
> +	be = tdm_get_ahead_be(w);
> +	if (!be) {
> +		dev_err(component->dev, "%s not find the be\n", __func__);
> +		return -EINVAL;
> +	}
> +	stream = snd_soc_dai_dma_data_get_playback(be);
> +	if (!stream) {
> +		dev_err(component->dev, "%s not find the stream\n", __func__);
> +		return -EINVAL;
> +	}
> +	lane_cnt = (stream->channels - 1) / stream->iface->slots + 1;
> +	/*we like to use dai id, but it is fixed val*/
> +	dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
> +	if (strstr(dai_widget_name, "TDM_A"))
> +		tdm_id = TDM_A_PAD;
> +	else if (strstr(dai_widget_name, "TDM_B"))
> +		tdm_id = TDM_B_PAD;
> +	else if (strstr(dai_widget_name, "TDM_C"))
> +		tdm_id = TDM_C_PAD;
> +	else
> +		dev_err(component->dev, "%s not find the be dai widget\n", __func__);
> +	dev_dbg(component->dev, "tdm_id:%d, channel:%d, slot:%d\n",
> +		tdm_id, stream->channels, stream->iface->slots);
> +	snd_soc_dapm_widget_for_each_sink_path(w, p) {
> +		if (p->sink->id == snd_soc_dapm_output) {
> +			if (p->connect) {
> +				pin = aml_simple_strtoull(p->name);
> +				reg = (pin / 4) * REG_OFFSET;
> +				/*calculate mask pos */
> +				mask = 0x1f << ((pin % 4) * 8);
> +				val = tdm_id * 8 + lane_num;
> +				snd_soc_component_update_bits(component, reg, mask, val);
> +				snd_soc_component_update_bits(component, EE_AUDIO_DAT_PAD_CTRLF,
> +							      0x1 << pin, 0 << pin);
> +				lane_num++;
> +				if (lane_num > lane_cnt - 1)
> +					break;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int tdmout_sel_pad_event(struct snd_soc_dapm_widget *w,
> +				struct snd_kcontrol *control,
> +				int event)
> +{
> +	int ret = 0;
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_PRE_PMU:
> +		tdm_out_pad_set(w);
> +		break;
> +
> +	case SND_SOC_DAPM_PRE_PMD:
> +		break;
> +
> +	default:
> +		dev_err(component->dev, "Unexpected event %d\n", event);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct snd_soc_dapm_widget s4_tdmout_pad_dapm_widgets[] = {
> +	SND_SOC_DAPM_DEMUX_E("TDMA_OUT SEL", SND_SOC_NOPM, 0, 0,
> +			     &tdmout_sel_demux[TDM_A_PAD], tdmout_sel_pad_event,
> +			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
> +	SND_SOC_DAPM_DEMUX_E("TDMB_OUT SEL", SND_SOC_NOPM, 0, 0,
> +			     &tdmout_sel_demux[TDM_B_PAD], tdmout_sel_pad_event,
> +			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
> +	SND_SOC_DAPM_DEMUX_E("TDMC_OUT SEL", SND_SOC_NOPM, 0, 0,
> +			     &tdmout_sel_demux[TDM_C_PAD], tdmout_sel_pad_event,
> +			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
> +	SND_SOC_DAPM_OUTPUT("TDM_D0"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D1"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D2"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D3"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D4"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D5"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D6"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D7"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D8"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D9"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D10"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D11"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D12"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D13"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D14"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D15"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D16"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D17"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D18"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D19"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D20"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D21"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D22"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D23"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D24"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D25"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D26"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D27"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D28"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D29"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D30"),
> +	SND_SOC_DAPM_OUTPUT("TDM_D31"),
> +};
> +
> +static const struct snd_soc_dapm_route s4_tdmout_pad_dapm_routes[] = {
> +	{ "TDM_D0", "TDM_D0", "TDMA_OUT SEL" },
> +	{ "TDM_D1", "TDM_D1", "TDMA_OUT SEL" },
> +	{ "TDM_D2", "TDM_D2", "TDMA_OUT SEL" },
> +	{ "TDM_D3", "TDM_D3", "TDMA_OUT SEL" },
> +	{ "TDM_D4", "TDM_D4", "TDMA_OUT SEL" },
> +	{ "TDM_D5", "TDM_D5", "TDMA_OUT SEL" },
> +	{ "TDM_D6", "TDM_D6", "TDMA_OUT SEL" },
> +	{ "TDM_D7", "TDM_D7", "TDMA_OUT SEL" },
> +	{ "TDM_D8", "TDM_D8", "TDMA_OUT SEL" },
> +	{ "TDM_D9", "TDM_D9", "TDMA_OUT SEL" },
> +	{ "TDM_D10", "TDM_D10", "TDMA_OUT SEL" },
> +	{ "TDM_D11", "TDM_D11", "TDMA_OUT SEL" },
> +	{ "TDM_D12", "TDM_D12", "TDMA_OUT SEL" },
> +	{ "TDM_D13", "TDM_D13", "TDMA_OUT SEL" },
> +	{ "TDM_D14", "TDM_D14", "TDMA_OUT SEL" },
> +	{ "TDM_D15", "TDM_D15", "TDMA_OUT SEL" },
> +	{ "TDM_D16", "TDM_D16", "TDMA_OUT SEL" },
> +	{ "TDM_D17", "TDM_D17", "TDMA_OUT SEL" },
> +	{ "TDM_D18", "TDM_D18", "TDMA_OUT SEL" },
> +	{ "TDM_D19", "TDM_D19", "TDMA_OUT SEL" },
> +	{ "TDM_D20", "TDM_D20", "TDMA_OUT SEL" },
> +	{ "TDM_D21", "TDM_D21", "TDMA_OUT SEL" },
> +	{ "TDM_D22", "TDM_D22", "TDMA_OUT SEL" },
> +	{ "TDM_D23", "TDM_D23", "TDMA_OUT SEL" },
> +	{ "TDM_D24", "TDM_D24", "TDMA_OUT SEL" },
> +	{ "TDM_D25", "TDM_D25", "TDMA_OUT SEL" },
> +	{ "TDM_D26", "TDM_D26", "TDMA_OUT SEL" },
> +	{ "TDM_D27", "TDM_D27", "TDMA_OUT SEL" },
> +	{ "TDM_D28", "TDM_D28", "TDMA_OUT SEL" },
> +	{ "TDM_D29", "TDM_D29", "TDMA_OUT SEL" },
> +	{ "TDM_D30", "TDM_D30", "TDMA_OUT SEL" },
> +	{ "TDM_D31", "TDM_D31", "TDMA_OUT SEL" },
> +	{ "TDM_D0", "TDM_D0", "TDMB_OUT SEL" },
> +	{ "TDM_D1", "TDM_D1", "TDMB_OUT SEL" },
> +	{ "TDM_D2", "TDM_D2", "TDMB_OUT SEL" },
> +	{ "TDM_D3", "TDM_D3", "TDMB_OUT SEL" },
> +	{ "TDM_D4", "TDM_D4", "TDMB_OUT SEL" },
> +	{ "TDM_D5", "TDM_D5", "TDMB_OUT SEL" },
> +	{ "TDM_D6", "TDM_D6", "TDMB_OUT SEL" },
> +	{ "TDM_D7", "TDM_D7", "TDMB_OUT SEL" },
> +	{ "TDM_D8", "TDM_D8", "TDMB_OUT SEL" },
> +	{ "TDM_D9", "TDM_D9", "TDMB_OUT SEL" },
> +	{ "TDM_D10", "TDM_D10", "TDMB_OUT SEL" },
> +	{ "TDM_D11", "TDM_D11", "TDMB_OUT SEL" },
> +	{ "TDM_D12", "TDM_D12", "TDMB_OUT SEL" },
> +	{ "TDM_D13", "TDM_D13", "TDMB_OUT SEL" },
> +	{ "TDM_D14", "TDM_D14", "TDMB_OUT SEL" },
> +	{ "TDM_D15", "TDM_D15", "TDMB_OUT SEL" },
> +
> +	{ "TDM_D16", "TDM_D16", "TDMB_OUT SEL" },
> +	{ "TDM_D17", "TDM_D17", "TDMB_OUT SEL" },
> +	{ "TDM_D18", "TDM_D18", "TDMB_OUT SEL" },
> +	{ "TDM_D19", "TDM_D19", "TDMB_OUT SEL" },
> +	{ "TDM_D20", "TDM_D20", "TDMB_OUT SEL" },
> +	{ "TDM_D21", "TDM_D21", "TDMB_OUT SEL" },
> +	{ "TDM_D22", "TDM_D22", "TDMB_OUT SEL" },
> +	{ "TDM_D23", "TDM_D23", "TDMB_OUT SEL" },
> +	{ "TDM_D24", "TDM_D24", "TDMB_OUT SEL" },
> +	{ "TDM_D25", "TDM_D25", "TDMB_OUT SEL" },
> +	{ "TDM_D26", "TDM_D26", "TDMB_OUT SEL" },
> +	{ "TDM_D27", "TDM_D27", "TDMB_OUT SEL" },
> +	{ "TDM_D28", "TDM_D28", "TDMB_OUT SEL" },
> +	{ "TDM_D29", "TDM_D29", "TDMB_OUT SEL" },
> +	{ "TDM_D30", "TDM_D30", "TDMB_OUT SEL" },
> +	{ "TDM_D31", "TDM_D31", "TDMB_OUT SEL" },
> +	{ "TDM_D0", "TDM_D0", "TDMC_OUT SEL" },
> +	{ "TDM_D1", "TDM_D1", "TDMC_OUT SEL" },
> +	{ "TDM_D2", "TDM_D2", "TDMC_OUT SEL" },
> +	{ "TDM_D3", "TDM_D3", "TDMC_OUT SEL" },
> +	{ "TDM_D4", "TDM_D4", "TDMC_OUT SEL" },
> +	{ "TDM_D5", "TDM_D5", "TDMC_OUT SEL" },
> +	{ "TDM_D6", "TDM_D6", "TDMC_OUT SEL" },
> +	{ "TDM_D7", "TDM_D7", "TDMC_OUT SEL" },
> +	{ "TDM_D8", "TDM_D8", "TDMC_OUT SEL" },
> +	{ "TDM_D9", "TDM_D9", "TDMC_OUT SEL" },
> +	{ "TDM_D10", "TDM_D10", "TDMC_OUT SEL" },
> +	{ "TDM_D11", "TDM_D11", "TDMC_OUT SEL" },
> +	{ "TDM_D12", "TDM_D12", "TDMC_OUT SEL" },
> +	{ "TDM_D13", "TDM_D13", "TDMC_OUT SEL" },
> +	{ "TDM_D14", "TDM_D14", "TDMC_OUT SEL" },
> +	{ "TDM_D15", "TDM_D15", "TDMC_OUT SEL" },
> +	{ "TDM_D16", "TDM_D16", "TDMC_OUT SEL" },
> +	{ "TDM_D17", "TDM_D17", "TDMC_OUT SEL" },
> +	{ "TDM_D18", "TDM_D18", "TDMC_OUT SEL" },
> +	{ "TDM_D19", "TDM_D19", "TDMC_OUT SEL" },
> +	{ "TDM_D20", "TDM_D20", "TDMC_OUT SEL" },
> +	{ "TDM_D21", "TDM_D21", "TDMC_OUT SEL" },
> +	{ "TDM_D22", "TDM_D22", "TDMC_OUT SEL" },
> +	{ "TDM_D23", "TDM_D23", "TDMC_OUT SEL" },
> +	{ "TDM_D24", "TDM_D24", "TDMC_OUT SEL" },
> +	{ "TDM_D25", "TDM_D25", "TDMC_OUT SEL" },
> +	{ "TDM_D26", "TDM_D26", "TDMC_OUT SEL" },
> +	{ "TDM_D27", "TDM_D27", "TDMC_OUT SEL" },
> +	{ "TDM_D28", "TDM_D28", "TDMC_OUT SEL" },
> +	{ "TDM_D29", "TDM_D29", "TDMC_OUT SEL" },
> +	{ "TDM_D30", "TDM_D30", "TDMC_OUT SEL" },
> +	{ "TDM_D31", "TDM_D31", "TDMC_OUT SEL" },
> +};
> +
> +static const struct snd_soc_component_driver s4_tdmout_pad_component_drv = {
> +	.dapm_widgets		= s4_tdmout_pad_dapm_widgets,
> +	.num_dapm_widgets	= ARRAY_SIZE(s4_tdmout_pad_dapm_widgets),
> +	.dapm_routes		= s4_tdmout_pad_dapm_routes,
> +	.num_dapm_routes	= ARRAY_SIZE(s4_tdmout_pad_dapm_routes),
> +
> +};
> +
> +static const struct of_device_id s4_tdmout_pad_of_match[] = {
> +	{
> +		.compatible = "amlogic,s4-tdmout-pad",
> +	}, {}
> +};
> +
> +MODULE_DEVICE_TABLE(of, s4_tdmout_pad_of_match);
> +
> +static int tdm_pad_out_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct regmap *map;
> +	void __iomem *regs;
> +
> +	regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +
> +	map = devm_regmap_init_mmio(dev, regs, &tdmout_pad_regmap_cfg);
> +	if (IS_ERR(map))
> +		return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
> +
> +	return devm_snd_soc_register_component(dev, &s4_tdmout_pad_component_drv,
> +					       NULL, 0);
> +}
> +
> +static struct platform_driver tdmout_pad_pdrv = {
> +	.probe = tdm_pad_out_probe,
> +	.driver = {
> +		.name = "s4-pad-out",
> +		.of_match_table = s4_tdmout_pad_of_match,
> +	},
> +};
> +
> +module_platform_driver(tdmout_pad_pdrv);
> +
> +MODULE_DESCRIPTION("Amlogic TDM PAD DRIVER");
> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
> +MODULE_LICENSE("GPL");
> diff --git a/sound/soc/meson/s4-tocodec-control.c b/sound/soc/meson/s4-tocodec-control.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
> --- /dev/null
> +++ b/sound/soc/meson/s4-tocodec-control.c

There is already a to-acodec driver a not reason has been provided as to why a
completly new driver is required.

Please have look at the existing driver and do try to use it.
If you need to do things so differently, clear justification are necessary.

> @@ -0,0 +1,376 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +/*
> + * Copyright (C) 2023 Amlogic, Inc. All rights reserved
> + */
> +
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <sound/soc.h>
> +#include <sound/soc-dai.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include<linux/kstrtox.h>
> +#include <linux/clk-provider.h>
> +#include <linux/reset.h>
> +#include "axg-tdm.h"
> +
> +#define TOACODEC_CTRL0			0x0
> +
> +#define CTRL0_ENABLE_SHIFT		31
> +#define CTRL0_BCLK_ENABLE_SHIFT		30
> +#define CTRL0_MCLK_ENABLE_SHIFT		29
> +#define CTRL0_BLK_CAP_INV_SHIFT		9
> +
> +#define TDM_IFACE 0
> +#define TDM_A_PAD 0
> +#define TDM_B_PAD 1
> +#define TDM_C_PAD 2
> +
> +struct toacodec {
> +	struct regmap_field *field_dat_sel;
> +	struct regmap_field *field_lrclk_sel;
> +	struct regmap_field *field_bclk_sel;
> +	struct regmap_field *field_mclk_sel;
> +};
> +
> +struct toacodec_match_data {
> +	const struct snd_soc_component_driver *component_drv;
> +	const struct reg_field field_dat_sel;
> +	const struct reg_field field_lrclk_sel;
> +	const struct reg_field field_bclk_sel;
> +	const struct reg_field field_mclk_sel;
> +};
> +
> +static const struct regmap_config tocodec_regmap_cfg = {
> +	.reg_bits	= 32,
> +	.val_bits	= 32,
> +	.reg_stride	= 4,
> +	.max_register	= 0x1,
> +};
> +
> +#define S4_LANE_OFFSET 8
> +
> +static const char * const s4_tocodec_lane_sel_texts[] = {
> +	"Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", "Lane7"
> +};
> +
> +static const struct soc_enum s4_tocodec_lane_sel_enum =
> +	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_lane_sel_texts),
> +			s4_tocodec_lane_sel_texts);
> +
> +static const struct snd_kcontrol_new s4_tocodec_lane_sel =
> +	SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
> +
> +static const char * const s4_tocodec_src_sel_texts[] = {
> +	"TDMA", "TDMB", "TDMC"
> +};
> +
> +static const struct soc_enum s4_tocodec_src_sel_enum =
> +	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_src_sel_texts),
> +			s4_tocodec_src_sel_texts);
> +
> +static const struct snd_kcontrol_new s4_tocodec_src_sel =
> +	SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
> +
> +static const struct snd_kcontrol_new s4_toacodec_out_enable =
> +	SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
> +				    CTRL0_ENABLE_SHIFT, 1, 0);
> +
> +static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_dapm_path *p;
> +	struct snd_soc_dai *be;
> +
> +	snd_soc_dapm_widget_for_each_source_path(w, p) {
> +		if (!p->connect)
> +			continue;
> +		if (p->source->id == snd_soc_dapm_dai_in)
> +			return (struct snd_soc_dai *)p->source->priv;
> +		be = tocodec_tdm_get_ahead_be(p->source);
> +		if (be && be->id == TDM_IFACE)
> +			return be;
> +	}
> +	return NULL;
> +}
> +
> +static unsigned int aml_simple_strtoull(const char *cp)
> +{
> +	unsigned int result = 0;
> +	unsigned int value = 0;
> +	unsigned int len = strlen(cp);
> +
> +	while (len != 0) {
> +		len--;
> +		value = isdigit(*cp);
> +		if (value) {
> +			value = *cp - '0';
> +		} else {
> +			cp++;
> +			continue;
> +		}
> +		cp++;
> +		result = result * 10 + value;
> +	}
> +	return result;
> +}
> +
> +static int aml_get_clk_id(const char *name)
> +{
> +	int clk_id = 0;
> +
> +	if (strstr(name, "mst_a"))
> +		clk_id = 0;
> +	else if (strstr(name, "mst_b"))
> +		clk_id = 1;
> +	else if (strstr(name, "mst_c"))
> +		clk_id = 2;
> +	else if (strstr(name, "mst_d"))
> +		clk_id = 3;
> +	else if (strstr(name, "mst_e"))
> +		clk_id = 4;
> +	else if (strstr(name, "mst_f"))
> +		clk_id = 5;
> +
> +	return clk_id;
> +}
> +
> +static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_dai *be;
> +	struct axg_tdm_stream *stream;
> +	struct axg_tdm_iface *iface;
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +	struct toacodec *priv = snd_soc_component_get_drvdata(component);
> +	unsigned int tdm_id = TDM_A_PAD;
> +	const char *dai_widget_name;
> +	struct snd_soc_dapm_path *p;
> +	unsigned int lane = 0;
> +	unsigned int val = 0;
> +	struct clk *sclk, *mclk;
> +	char *clk_name;
> +	int mclk_id, sclk_id;
> +
> +	be = tocodec_tdm_get_ahead_be(w);
> +	if (!be) {
> +		dev_err(component->dev, "%s not find the be\n", __func__);
> +		return -EINVAL;
> +	}
> +	stream = snd_soc_dai_dma_data_get_playback(be);
> +	if (!stream) {
> +		dev_err(component->dev, "%s not find the stream\n", __func__);
> +		return -EINVAL;
> +	}
> +	/*we like to use dai id, but it is fixed val*/
> +	dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
> +	if (strstr(dai_widget_name, "TDM_A"))
> +		tdm_id = TDM_A_PAD;
> +	else if (strstr(dai_widget_name, "TDM_B"))
> +		tdm_id = TDM_B_PAD;
> +	else if (strstr(dai_widget_name, "TDM_C"))
> +		tdm_id = TDM_C_PAD;
> +	snd_soc_dapm_widget_for_each_source_path(w, p) {
> +		if (p->connect && p->name) {
> +			lane = aml_simple_strtoull(p->name);
> +			val = lane + tdm_id * S4_LANE_OFFSET;
> +			regmap_field_write(priv->field_dat_sel, val);
> +		}
> +	}
> +	iface = stream->iface;
> +	mclk = iface->mclk;
> +	sclk = iface->sclk;
> +	mclk_id = aml_get_clk_id(__clk_get_name(mclk));
> +	sclk_id = aml_get_clk_id(__clk_get_name(sclk));
> +	regmap_field_write(priv->field_mclk_sel, mclk_id);
> +	regmap_field_write(priv->field_bclk_sel, sclk_id);
> +	regmap_field_write(priv->field_lrclk_sel, sclk_id);
> +
> +	return 0;
> +}
> +
> +static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
> +			     struct snd_kcontrol *control,
> +			     int event)
> +{
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +	int ret = 0;
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_PRE_PMU:
> +		ret = aml_tocodec_sel_set(w);
> +		break;
> +
> +	case SND_SOC_DAPM_PRE_PMD:
> +		break;
> +
> +	default:
> +		dev_err(component->dev, "Unexpected event %d\n", event);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
> +			      struct snd_kcontrol *control,
> +			      int event)
> +{
> +	int ret = 0;
> +	unsigned int mask = 0, val = 0;
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +
> +	snd_soc_component_update_bits(component, TOACODEC_CTRL0,
> +				      1 << CTRL0_BLK_CAP_INV_SHIFT, 1 << CTRL0_BLK_CAP_INV_SHIFT);
> +	switch (event) {
> +	case SND_SOC_DAPM_PRE_PMU:
> +		mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
> +		val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
> +		snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
> +		break;
> +	case SND_SOC_DAPM_PRE_PMD:
> +		mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
> +		val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << CTRL0_BCLK_ENABLE_SHIFT;
> +		snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
> +		break;
> +	default:
> +		dev_err(component->dev, "Unexpected event %d\n", event);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
> +	SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
> +			   &s4_tocodec_lane_sel, tocodec_sel_event,
> +			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
> +	SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, &s4_tocodec_src_sel),
> +	SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
> +			      &s4_toacodec_out_enable, tocodec_clk_enable,
> +				(SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
> +	SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
> +};
> +
> +static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
> +	{ "INPUT SRC", "TDMA", "TDMA"},
> +	{ "INPUT SRC", "TDMB", "TDMB"},
> +	{ "INPUT SRC", "TDMC", "TDMC"},
> +	{ "Lane0", NULL, "INPUT SRC" },
> +	{ "Lane1", NULL, "INPUT SRC"},
> +	{ "Lane2", NULL, "INPUT SRC"},
> +	{ "Lane3", NULL, "INPUT SRC"},
> +	{ "Lane4", NULL, "INPUT SRC"},
> +	{ "Lane5", NULL, "INPUT SRC"},
> +	{ "Lane6", NULL, "INPUT SRC"},
> +	{ "Lane7", NULL, "INPUT SRC"},
> +	{ "Lane SRC", "Lane0", "Lane0"},
> +	{ "Lane SRC", "Lane1", "Lane1"},
> +	{ "Lane SRC", "Lane2", "Lane2"},
> +	{ "Lane SRC", "Lane3", "Lane3"},
> +	{ "Lane SRC", "Lane4", "Lane4"},
> +	{ "Lane SRC", "Lane5", "Lane5"},
> +	{ "Lane SRC", "Lane6", "Lane6"},
> +	{ "Lane SRC", "Lane7", "Lane7"},
> +	{ "OUT EN", "Switch", "Lane SRC"},
> +	{ "TDM_TO_ACODEC", NULL, "OUT EN"},
> +
> +};
> +
> +static const struct snd_soc_component_driver s4_tocodec_component_drv = {
> +	.dapm_widgets		= s4_toacodec_widgets,
> +	.num_dapm_widgets	= ARRAY_SIZE(s4_toacodec_widgets),
> +	.dapm_routes		= s4_tocodec_dapm_routes,
> +	.num_dapm_routes	= ARRAY_SIZE(s4_tocodec_dapm_routes),
> +};
> +
> +static const struct toacodec_match_data s4_toacodec_match_data = {
> +	.component_drv	= &s4_tocodec_component_drv,
> +	.field_dat_sel	= REG_FIELD(TOACODEC_CTRL0, 16, 20),
> +	.field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
> +	.field_bclk_sel	= REG_FIELD(TOACODEC_CTRL0, 4, 6),
> +	.field_mclk_sel	= REG_FIELD(TOACODEC_CTRL0, 0, 2),
> +};
> +
> +static const struct of_device_id s4_tocodec_of_match[] = {
> +	{
> +		.compatible = "amlogic,s4-tocodec",
> +		.data = &s4_toacodec_match_data,
> +	}, {}
> +};
> +
> +MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
> +
> +static int tocodec_probe(struct platform_device *pdev)
> +{
> +	const struct toacodec_match_data *data;
> +	struct device *dev = &pdev->dev;
> +	struct toacodec *priv;
> +	void __iomem *regs;
> +	struct regmap *map;
> +	int ret;
> +
> +	data = device_get_match_data(dev);
> +	if (!data)
> +		return dev_err_probe(dev, -ENODEV, "failed to match device\n");
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	ret = device_reset(dev);
> +	if (ret)
> +		return ret;
> +
> +	regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +
> +	map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
> +	if (IS_ERR(map))
> +		return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
> +
> +	priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
> +	if (IS_ERR(priv->field_dat_sel))
> +		return PTR_ERR(priv->field_dat_sel);
> +
> +	priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
> +	if (IS_ERR(priv->field_lrclk_sel))
> +		return PTR_ERR(priv->field_lrclk_sel);
> +
> +	priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
> +	if (IS_ERR(priv->field_bclk_sel))
> +		return PTR_ERR(priv->field_bclk_sel);
> +
> +	priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, data->field_mclk_sel);
> +	if (IS_ERR(priv->field_mclk_sel))
> +		return PTR_ERR(priv->field_mclk_sel);
> +
> +	return devm_snd_soc_register_component(dev,
> +			data->component_drv, NULL, 0);
> +}
> +
> +static struct platform_driver tocodec_pdrv = {
> +	.probe = tocodec_probe,
> +	.driver = {
> +		.name = "s4-tocodec",
> +		.of_match_table = s4_tocodec_of_match,
> +	},
> +};
> +
> +module_platform_driver(tocodec_pdrv);
> +
> +MODULE_DESCRIPTION("Amlogic to codec driver");
> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
> +MODULE_LICENSE("GPL");
> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
> index 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 100644
> --- a/sound/soc/meson/t9015.c
> +++ b/sound/soc/meson/t9015.c
> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>  		.channels_min = 1,
>  		.channels_max = 2,
>  		.rates = SNDRV_PCM_RATE_8000_96000,
> -		.formats = (SNDRV_PCM_FMTBIT_S8 |
> -			    SNDRV_PCM_FMTBIT_S16_LE |
> -			    SNDRV_PCM_FMTBIT_S20_LE |
> -			    SNDRV_PCM_FMTBIT_S24_LE),
> +		.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),

Again, mixed up changes with zero justification.

This drops S8 and S16 format support for the existing SoCs (such as GXL)
which is known to work and add S32 support on an HW documented as 24bits
only. Can you explain ?

>  	},
>  	.ops = &t9015_dai_ops,
>  };
Jiebing Chen Jan. 14, 2025, 8:16 a.m. UTC | #2
在 2025/1/13 22:31, Jerome Brunet 写道:
> [ EXTERNAL EMAIL ]
>
> On Mon 13 Jan 2025 at 14:35, jiebing chen via B4 Relay <devnull+jiebing.chen.amlogic.com@kernel.org> wrote:
>
>> From: jiebing chen <jiebing.chen@amlogic.com>
>>
>> Add audio support for Amlogic S4.The audio output pad
>> can be freelycombined with the output lane,and the tocodec
>> control logic has been optimized.
> The patch is a mixture of different HW modules.
>
> Each patch should have one clear purpose and, as such, deal with a
> single HW module
>
>> Signed-off-by: jiebing chen <jiebing.chen@amlogic.com>
>> ---
>>   sound/soc/meson/Kconfig              |  16 ++
>>   sound/soc/meson/Makefile             |   6 +
>>   sound/soc/meson/s4-pad-out-control.c | 372 ++++++++++++++++++++++++++++++++++
>>   sound/soc/meson/s4-tocodec-control.c | 376 +++++++++++++++++++++++++++++++++++
>>   sound/soc/meson/t9015.c              |   5 +-
>>   5 files changed, 771 insertions(+), 4 deletions(-)
>>
>> diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
>> index 6458d5dc4902f665211bb9e4ae7d274e4bff2fdc..d01e284642fd987cf4bdf88e5bf5f7c9a241af59 100644
>> --- a/sound/soc/meson/Kconfig
>> +++ b/sound/soc/meson/Kconfig
>> @@ -69,6 +69,8 @@ config SND_MESON_AXG_SOUND_CARD
>>        imply SND_MESON_AXG_SPDIFIN
>>        imply SND_MESON_AXG_PDM
>>        imply SND_MESON_G12A_TOACODEC
>> +     imply SND_SOC_MESON_PAD_OUT
>> +     imply SND_SOC_MESON_TOCODEC_CONTROL
>>        imply SND_MESON_G12A_TOHDMITX if DRM_MESON_DW_HDMI
>>        help
>>          Select Y or M to add support for the AXG SoC sound card
>> @@ -135,4 +137,18 @@ config SND_SOC_MESON_T9015
>>        help
>>          Say Y or M if you want to add support for the internal DAC found
>>          on GXL, G12 and SM1 SoC family.
>> +
>> +config SND_SOC_MESON_PAD_OUT
>> +     tristate "Amlogic PAD OUT"
>> +     select REGMAP_MMIO
>> +     help
>> +       Say Y or M if you want to add support for the S4 Audio Output from
>> +       the data Pad.
>> +
>> +config SND_SOC_MESON_TOCODEC_CONTROL
>> +     tristate "Amlogic TOCODEC CONTROL"
>> +     select REGMAP_MMIO
>> +     help
>> +      Say Y or M if you want to add support for the internal DAC control
>> +      on SM1 SoC family.
>>   endmenu
>> diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
>> index 24078e4396b02d545d8ba4bcb1632979001354e3..afbefcb1313670f9b1365e88b8eb1a0badd7dc1e 100644
>> --- a/sound/soc/meson/Makefile
>> +++ b/sound/soc/meson/Makefile
>> @@ -24,8 +24,11 @@ snd-soc-meson-codec-glue-y := meson-codec-glue.o
>>   snd-soc-meson-gx-sound-card-y := gx-card.o
>>   snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
>>   snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
>> +snd-soc-meson-s4-padout-objs := s4-pad-out-control.o
>> +snd-soc-meson-s4-tocodec-control-objs := s4-tocodec-control.o
>>   snd-soc-meson-t9015-y := t9015.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
>> @@ -43,4 +46,7 @@ obj-$(CONFIG_SND_MESON_CODEC_GLUE) += snd-soc-meson-codec-glue.o
>>   obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += snd-soc-meson-gx-sound-card.o
>>   obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += snd-soc-meson-g12a-toacodec.o
>>   obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += snd-soc-meson-g12a-tohdmitx.o
>> +obj-$(CONFIG_SND_SOC_MESON_PAD_OUT) += snd-soc-meson-s4-padout.o
>> +obj-$(CONFIG_SND_SOC_MESON_TOCODEC_CONTROL) += snd-soc-meson-s4-tocodec-control.o
>>   obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
>> +
>> diff --git a/sound/soc/meson/s4-pad-out-control.c b/sound/soc/meson/s4-pad-out-control.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..a86dcf8a5995926f0ddf8d2911f42006daed0feb
>> --- /dev/null
>> +++ b/sound/soc/meson/s4-pad-out-control.c
>> @@ -0,0 +1,372 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/regmap.h>
>> +#include <sound/soc.h>
>> +#include <sound/soc-dai.h>
>> +#include <linux/init.h>
>> +#include <linux/kernel.h>
>> +#include<linux/kstrtox.h>
>> +
>> +#include "axg-tdm.h"
>> +
>> +static const struct regmap_config tdmout_pad_regmap_cfg = {
>> +     .reg_bits       = 32,
>> +     .val_bits       = 32,
>> +     .reg_stride     = 4,
>> +     .max_register   = 0x28,
>> +};
>> +
>> +#define TDM_IFACE 0
>> +#define TDM_A_PAD 0
>> +#define TDM_B_PAD 1
>> +#define TDM_C_PAD 2
>> +
>> +#define EE_AUDIO_DAT_PAD_CTRL6 0x0
>> +#define EE_AUDIO_DAT_PAD_CTRL7 0x4
>> +#define EE_AUDIO_DAT_PAD_CTRL8 0x8
>> +#define EE_AUDIO_DAT_PAD_CTRL9 0xc
>> +#define EE_AUDIO_DAT_PAD_CTRLA 0x10
>> +#define EE_AUDIO_DAT_PAD_CTRLB 0x14
>> +#define EE_AUDIO_DAT_PAD_CTRLC 0x1c
>> +#define EE_AUDIO_DAT_PAD_CTRLD 0x20
>> +#define EE_AUDIO_DAT_PAD_CTRLE 0x24
>> +#define EE_AUDIO_DAT_PAD_CTRLF 0x28
>> +
>> +#define REG_OFFSET 4
>> +
>> +static const char * const s4_tdmout_sel_texts[] = {
>> +     "TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", "TDM_D6", "TDM_D7",
>> +     "TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", "TDM_D13", "TDM_D14", "TDM_D15",
>> +     "TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", "TDM_D21", "TDM_D22", "TDM_D23",
>> +     "TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", "TDM_D29", "TDM_D30", "TDM_D31"
>> +};
> This thing does not belong in ASoC. This is clearly yet another layer of
> pinctrl. Please deal with it there.

Thanks for your suggestion, add audio pinctrl driver to control the which tdm_dx pin can map the which tdm lane_x
for example
	tdm_d6_pin {
		mux {
			groups = "tdm_d6";
			function = "tdmoutb_lane0";
		};
	}
tdm_d6 pin map the tdmoutb lane 0, right ?

>> +
>> +static const struct soc_enum tdmout_sel_enum =
>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
>> +                     s4_tdmout_sel_texts);
>> +
>> +static struct snd_soc_dai *tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
>> +{
>> +     struct snd_soc_dapm_path *p;
>> +     struct snd_soc_dai *be;
>> +
>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>> +             if (p->source->id == snd_soc_dapm_dai_in)
>> +                     return (struct snd_soc_dai *)p->source->priv;
>> +             be = tdm_get_ahead_be(p->source);
>> +             if (be && be->id == TDM_IFACE)
>> +                     return be;
>> +     }
>> +     return NULL;
>> +}
>> +
>> +#define SND_SOC_DAPM_DEMUX_E(wname, wreg, wshift, winvert, wcontrols, \
>> +     wevent, wflags) \
>> +((struct snd_soc_dapm_widget) { \
>> +     .id = snd_soc_dapm_demux, .name = wname, \
>> +     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
>> +     .kcontrol_news = wcontrols, .num_kcontrols = 1, \
>> +     .event = wevent, .event_flags = wflags})
>> +
>> +static const struct snd_kcontrol_new tdmout_sel_demux[] = {
>> +     SOC_DAPM_ENUM("TDMOUTA SEL", tdmout_sel_enum),
>> +     SOC_DAPM_ENUM("TDMOUTB SEL", tdmout_sel_enum),
>> +     SOC_DAPM_ENUM("TDMOUTC SEL", tdmout_sel_enum),
>> +};
>> +
>> +static unsigned int aml_simple_strtoull(const char *cp)
>> +{
>> +     unsigned int result = 0;
>> +     unsigned int value = 0;
>> +     unsigned int len =  strlen(cp);
>> +
>> +     while (len != 0) {
>> +             len--;
>> +             value = isdigit(*cp);
>> +             if (value) {
>> +                     value = *cp - '0';
>> +             } else {
>> +                     cp++;
>> +                     continue;
>> +             }
>> +             cp++;
>> +             result = result * 10 + value;
>> +     }
>> +     return result;
>> +}
>> +
>> +static int tdm_out_pad_set(struct snd_soc_dapm_widget *w)
>> +{
>> +     struct snd_soc_dai *be;
>> +     struct axg_tdm_stream *stream;
>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>> +     unsigned int tdm_id = TDM_A_PAD;
>> +     const char *dai_widget_name;
>> +     struct snd_soc_dapm_path *p;
>> +     unsigned int lane_num = 0;
>> +     unsigned long pin = 0;
>> +     unsigned int reg, mask, val = 0;
>> +     int lane_cnt;
>> +
>> +     be = tdm_get_ahead_be(w);
>> +     if (!be) {
>> +             dev_err(component->dev, "%s not find the be\n", __func__);
>> +             return -EINVAL;
>> +     }
>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>> +     if (!stream) {
>> +             dev_err(component->dev, "%s not find the stream\n", __func__);
>> +             return -EINVAL;
>> +     }
>> +     lane_cnt = (stream->channels - 1) / stream->iface->slots + 1;
>> +     /*we like to use dai id, but it is fixed val*/
>> +     dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>> +     if (strstr(dai_widget_name, "TDM_A"))
>> +             tdm_id = TDM_A_PAD;
>> +     else if (strstr(dai_widget_name, "TDM_B"))
>> +             tdm_id = TDM_B_PAD;
>> +     else if (strstr(dai_widget_name, "TDM_C"))
>> +             tdm_id = TDM_C_PAD;
>> +     else
>> +             dev_err(component->dev, "%s not find the be dai widget\n", __func__);
>> +     dev_dbg(component->dev, "tdm_id:%d, channel:%d, slot:%d\n",
>> +             tdm_id, stream->channels, stream->iface->slots);
>> +     snd_soc_dapm_widget_for_each_sink_path(w, p) {
>> +             if (p->sink->id == snd_soc_dapm_output) {
>> +                     if (p->connect) {
>> +                             pin = aml_simple_strtoull(p->name);
>> +                             reg = (pin / 4) * REG_OFFSET;
>> +                             /*calculate mask pos */
>> +                             mask = 0x1f << ((pin % 4) * 8);
>> +                             val = tdm_id * 8 + lane_num;
>> +                             snd_soc_component_update_bits(component, reg, mask, val);
>> +                             snd_soc_component_update_bits(component, EE_AUDIO_DAT_PAD_CTRLF,
>> +                                                           0x1 << pin, 0 << pin);
>> +                             lane_num++;
>> +                             if (lane_num > lane_cnt - 1)
>> +                                     break;
>> +                     }
>> +             }
>> +     }
>> +     return 0;
>> +}
>> +
>> +static int tdmout_sel_pad_event(struct snd_soc_dapm_widget *w,
>> +                             struct snd_kcontrol *control,
>> +                             int event)
>> +{
>> +     int ret = 0;
>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>> +
>> +     switch (event) {
>> +     case SND_SOC_DAPM_PRE_PMU:
>> +             tdm_out_pad_set(w);
>> +             break;
>> +
>> +     case SND_SOC_DAPM_PRE_PMD:
>> +             break;
>> +
>> +     default:
>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>> +             return -EINVAL;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct snd_soc_dapm_widget s4_tdmout_pad_dapm_widgets[] = {
>> +     SND_SOC_DAPM_DEMUX_E("TDMA_OUT SEL", SND_SOC_NOPM, 0, 0,
>> +                          &tdmout_sel_demux[TDM_A_PAD], tdmout_sel_pad_event,
>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>> +     SND_SOC_DAPM_DEMUX_E("TDMB_OUT SEL", SND_SOC_NOPM, 0, 0,
>> +                          &tdmout_sel_demux[TDM_B_PAD], tdmout_sel_pad_event,
>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>> +     SND_SOC_DAPM_DEMUX_E("TDMC_OUT SEL", SND_SOC_NOPM, 0, 0,
>> +                          &tdmout_sel_demux[TDM_C_PAD], tdmout_sel_pad_event,
>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D0"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D1"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D2"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D3"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D4"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D5"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D6"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D7"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D8"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D9"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D10"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D11"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D12"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D13"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D14"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D15"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D16"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D17"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D18"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D19"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D20"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D21"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D22"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D23"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D24"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D25"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D26"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D27"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D28"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D29"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D30"),
>> +     SND_SOC_DAPM_OUTPUT("TDM_D31"),
>> +};
>> +
>> +static const struct snd_soc_dapm_route s4_tdmout_pad_dapm_routes[] = {
>> +     { "TDM_D0", "TDM_D0", "TDMA_OUT SEL" },
>> +     { "TDM_D1", "TDM_D1", "TDMA_OUT SEL" },
>> +     { "TDM_D2", "TDM_D2", "TDMA_OUT SEL" },
>> +     { "TDM_D3", "TDM_D3", "TDMA_OUT SEL" },
>> +     { "TDM_D4", "TDM_D4", "TDMA_OUT SEL" },
>> +     { "TDM_D5", "TDM_D5", "TDMA_OUT SEL" },
>> +     { "TDM_D6", "TDM_D6", "TDMA_OUT SEL" },
>> +     { "TDM_D7", "TDM_D7", "TDMA_OUT SEL" },
>> +     { "TDM_D8", "TDM_D8", "TDMA_OUT SEL" },
>> +     { "TDM_D9", "TDM_D9", "TDMA_OUT SEL" },
>> +     { "TDM_D10", "TDM_D10", "TDMA_OUT SEL" },
>> +     { "TDM_D11", "TDM_D11", "TDMA_OUT SEL" },
>> +     { "TDM_D12", "TDM_D12", "TDMA_OUT SEL" },
>> +     { "TDM_D13", "TDM_D13", "TDMA_OUT SEL" },
>> +     { "TDM_D14", "TDM_D14", "TDMA_OUT SEL" },
>> +     { "TDM_D15", "TDM_D15", "TDMA_OUT SEL" },
>> +     { "TDM_D16", "TDM_D16", "TDMA_OUT SEL" },
>> +     { "TDM_D17", "TDM_D17", "TDMA_OUT SEL" },
>> +     { "TDM_D18", "TDM_D18", "TDMA_OUT SEL" },
>> +     { "TDM_D19", "TDM_D19", "TDMA_OUT SEL" },
>> +     { "TDM_D20", "TDM_D20", "TDMA_OUT SEL" },
>> +     { "TDM_D21", "TDM_D21", "TDMA_OUT SEL" },
>> +     { "TDM_D22", "TDM_D22", "TDMA_OUT SEL" },
>> +     { "TDM_D23", "TDM_D23", "TDMA_OUT SEL" },
>> +     { "TDM_D24", "TDM_D24", "TDMA_OUT SEL" },
>> +     { "TDM_D25", "TDM_D25", "TDMA_OUT SEL" },
>> +     { "TDM_D26", "TDM_D26", "TDMA_OUT SEL" },
>> +     { "TDM_D27", "TDM_D27", "TDMA_OUT SEL" },
>> +     { "TDM_D28", "TDM_D28", "TDMA_OUT SEL" },
>> +     { "TDM_D29", "TDM_D29", "TDMA_OUT SEL" },
>> +     { "TDM_D30", "TDM_D30", "TDMA_OUT SEL" },
>> +     { "TDM_D31", "TDM_D31", "TDMA_OUT SEL" },
>> +     { "TDM_D0", "TDM_D0", "TDMB_OUT SEL" },
>> +     { "TDM_D1", "TDM_D1", "TDMB_OUT SEL" },
>> +     { "TDM_D2", "TDM_D2", "TDMB_OUT SEL" },
>> +     { "TDM_D3", "TDM_D3", "TDMB_OUT SEL" },
>> +     { "TDM_D4", "TDM_D4", "TDMB_OUT SEL" },
>> +     { "TDM_D5", "TDM_D5", "TDMB_OUT SEL" },
>> +     { "TDM_D6", "TDM_D6", "TDMB_OUT SEL" },
>> +     { "TDM_D7", "TDM_D7", "TDMB_OUT SEL" },
>> +     { "TDM_D8", "TDM_D8", "TDMB_OUT SEL" },
>> +     { "TDM_D9", "TDM_D9", "TDMB_OUT SEL" },
>> +     { "TDM_D10", "TDM_D10", "TDMB_OUT SEL" },
>> +     { "TDM_D11", "TDM_D11", "TDMB_OUT SEL" },
>> +     { "TDM_D12", "TDM_D12", "TDMB_OUT SEL" },
>> +     { "TDM_D13", "TDM_D13", "TDMB_OUT SEL" },
>> +     { "TDM_D14", "TDM_D14", "TDMB_OUT SEL" },
>> +     { "TDM_D15", "TDM_D15", "TDMB_OUT SEL" },
>> +
>> +     { "TDM_D16", "TDM_D16", "TDMB_OUT SEL" },
>> +     { "TDM_D17", "TDM_D17", "TDMB_OUT SEL" },
>> +     { "TDM_D18", "TDM_D18", "TDMB_OUT SEL" },
>> +     { "TDM_D19", "TDM_D19", "TDMB_OUT SEL" },
>> +     { "TDM_D20", "TDM_D20", "TDMB_OUT SEL" },
>> +     { "TDM_D21", "TDM_D21", "TDMB_OUT SEL" },
>> +     { "TDM_D22", "TDM_D22", "TDMB_OUT SEL" },
>> +     { "TDM_D23", "TDM_D23", "TDMB_OUT SEL" },
>> +     { "TDM_D24", "TDM_D24", "TDMB_OUT SEL" },
>> +     { "TDM_D25", "TDM_D25", "TDMB_OUT SEL" },
>> +     { "TDM_D26", "TDM_D26", "TDMB_OUT SEL" },
>> +     { "TDM_D27", "TDM_D27", "TDMB_OUT SEL" },
>> +     { "TDM_D28", "TDM_D28", "TDMB_OUT SEL" },
>> +     { "TDM_D29", "TDM_D29", "TDMB_OUT SEL" },
>> +     { "TDM_D30", "TDM_D30", "TDMB_OUT SEL" },
>> +     { "TDM_D31", "TDM_D31", "TDMB_OUT SEL" },
>> +     { "TDM_D0", "TDM_D0", "TDMC_OUT SEL" },
>> +     { "TDM_D1", "TDM_D1", "TDMC_OUT SEL" },
>> +     { "TDM_D2", "TDM_D2", "TDMC_OUT SEL" },
>> +     { "TDM_D3", "TDM_D3", "TDMC_OUT SEL" },
>> +     { "TDM_D4", "TDM_D4", "TDMC_OUT SEL" },
>> +     { "TDM_D5", "TDM_D5", "TDMC_OUT SEL" },
>> +     { "TDM_D6", "TDM_D6", "TDMC_OUT SEL" },
>> +     { "TDM_D7", "TDM_D7", "TDMC_OUT SEL" },
>> +     { "TDM_D8", "TDM_D8", "TDMC_OUT SEL" },
>> +     { "TDM_D9", "TDM_D9", "TDMC_OUT SEL" },
>> +     { "TDM_D10", "TDM_D10", "TDMC_OUT SEL" },
>> +     { "TDM_D11", "TDM_D11", "TDMC_OUT SEL" },
>> +     { "TDM_D12", "TDM_D12", "TDMC_OUT SEL" },
>> +     { "TDM_D13", "TDM_D13", "TDMC_OUT SEL" },
>> +     { "TDM_D14", "TDM_D14", "TDMC_OUT SEL" },
>> +     { "TDM_D15", "TDM_D15", "TDMC_OUT SEL" },
>> +     { "TDM_D16", "TDM_D16", "TDMC_OUT SEL" },
>> +     { "TDM_D17", "TDM_D17", "TDMC_OUT SEL" },
>> +     { "TDM_D18", "TDM_D18", "TDMC_OUT SEL" },
>> +     { "TDM_D19", "TDM_D19", "TDMC_OUT SEL" },
>> +     { "TDM_D20", "TDM_D20", "TDMC_OUT SEL" },
>> +     { "TDM_D21", "TDM_D21", "TDMC_OUT SEL" },
>> +     { "TDM_D22", "TDM_D22", "TDMC_OUT SEL" },
>> +     { "TDM_D23", "TDM_D23", "TDMC_OUT SEL" },
>> +     { "TDM_D24", "TDM_D24", "TDMC_OUT SEL" },
>> +     { "TDM_D25", "TDM_D25", "TDMC_OUT SEL" },
>> +     { "TDM_D26", "TDM_D26", "TDMC_OUT SEL" },
>> +     { "TDM_D27", "TDM_D27", "TDMC_OUT SEL" },
>> +     { "TDM_D28", "TDM_D28", "TDMC_OUT SEL" },
>> +     { "TDM_D29", "TDM_D29", "TDMC_OUT SEL" },
>> +     { "TDM_D30", "TDM_D30", "TDMC_OUT SEL" },
>> +     { "TDM_D31", "TDM_D31", "TDMC_OUT SEL" },
>> +};
>> +
>> +static const struct snd_soc_component_driver s4_tdmout_pad_component_drv = {
>> +     .dapm_widgets           = s4_tdmout_pad_dapm_widgets,
>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_tdmout_pad_dapm_widgets),
>> +     .dapm_routes            = s4_tdmout_pad_dapm_routes,
>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tdmout_pad_dapm_routes),
>> +
>> +};
>> +
>> +static const struct of_device_id s4_tdmout_pad_of_match[] = {
>> +     {
>> +             .compatible = "amlogic,s4-tdmout-pad",
>> +     }, {}
>> +};
>> +
>> +MODULE_DEVICE_TABLE(of, s4_tdmout_pad_of_match);
>> +
>> +static int tdm_pad_out_probe(struct platform_device *pdev)
>> +{
>> +     struct device *dev = &pdev->dev;
>> +     struct regmap *map;
>> +     void __iomem *regs;
>> +
>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>> +     if (IS_ERR(regs))
>> +             return PTR_ERR(regs);
>> +
>> +     map = devm_regmap_init_mmio(dev, regs, &tdmout_pad_regmap_cfg);
>> +     if (IS_ERR(map))
>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
>> +
>> +     return devm_snd_soc_register_component(dev, &s4_tdmout_pad_component_drv,
>> +                                            NULL, 0);
>> +}
>> +
>> +static struct platform_driver tdmout_pad_pdrv = {
>> +     .probe = tdm_pad_out_probe,
>> +     .driver = {
>> +             .name = "s4-pad-out",
>> +             .of_match_table = s4_tdmout_pad_of_match,
>> +     },
>> +};
>> +
>> +module_platform_driver(tdmout_pad_pdrv);
>> +
>> +MODULE_DESCRIPTION("Amlogic TDM PAD DRIVER");
>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>> +MODULE_LICENSE("GPL");
>> diff --git a/sound/soc/meson/s4-tocodec-control.c b/sound/soc/meson/s4-tocodec-control.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
>> --- /dev/null
>> +++ b/sound/soc/meson/s4-tocodec-control.c
> There is already a to-acodec driver a not reason has been provided as to why a
> completly new driver is required.
>
> Please have look at the existing driver and do try to use it.
> If you need to do things so differently, clear justification are necessary.

for g12a-toacodec.c, we find the tocodec clock source can't get 
the clock id from the tdm Be device,

and set it by the kcontrol from user,  For different soc chips, The 
kcontrol value maybe different, The kcontrol configuration doesn't look 
very friendly for user

so we use dapm route path to manage it, 
fe(fddr)->be(tdm)->(tocodec)->(codec),  and use the aux-devs to 
register,  and sound card only include the

sound-dai = <&tdmif_a>

codec-0 {
                 sound-dai = <&acodec>;
  };

and not include

codec-1 {
                 sound-dai = <&toacodec>;
  };

when tdm work, only connect the tocodec path

  "TDM_A Playback" ->"TOACODEC TDMA"->"TOACODEC INPUT SRC"

iterate it find the be device ,and get the struct axg_tdm_stream, so we 
can get the tdm clock id

Take into account behavioral differences, we add new tocodec driver for s4

>> @@ -0,0 +1,376 @@
>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>> +/*
>> + * Copyright (C) 2023 Amlogic, Inc. All rights reserved
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/regmap.h>
>> +#include <sound/soc.h>
>> +#include <sound/soc-dai.h>
>> +#include <linux/init.h>
>> +#include <linux/kernel.h>
>> +#include<linux/kstrtox.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/reset.h>
>> +#include "axg-tdm.h"
>> +
>> +#define TOACODEC_CTRL0                       0x0
>> +
>> +#define CTRL0_ENABLE_SHIFT           31
>> +#define CTRL0_BCLK_ENABLE_SHIFT              30
>> +#define CTRL0_MCLK_ENABLE_SHIFT              29
>> +#define CTRL0_BLK_CAP_INV_SHIFT              9
>> +
>> +#define TDM_IFACE 0
>> +#define TDM_A_PAD 0
>> +#define TDM_B_PAD 1
>> +#define TDM_C_PAD 2
>> +
>> +struct toacodec {
>> +     struct regmap_field *field_dat_sel;
>> +     struct regmap_field *field_lrclk_sel;
>> +     struct regmap_field *field_bclk_sel;
>> +     struct regmap_field *field_mclk_sel;
>> +};
>> +
>> +struct toacodec_match_data {
>> +     const struct snd_soc_component_driver *component_drv;
>> +     const struct reg_field field_dat_sel;
>> +     const struct reg_field field_lrclk_sel;
>> +     const struct reg_field field_bclk_sel;
>> +     const struct reg_field field_mclk_sel;
>> +};
>> +
>> +static const struct regmap_config tocodec_regmap_cfg = {
>> +     .reg_bits       = 32,
>> +     .val_bits       = 32,
>> +     .reg_stride     = 4,
>> +     .max_register   = 0x1,
>> +};
>> +
>> +#define S4_LANE_OFFSET 8
>> +
>> +static const char * const s4_tocodec_lane_sel_texts[] = {
>> +     "Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", "Lane7"
>> +};
>> +
>> +static const struct soc_enum s4_tocodec_lane_sel_enum =
>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_lane_sel_texts),
>> +                     s4_tocodec_lane_sel_texts);
>> +
>> +static const struct snd_kcontrol_new s4_tocodec_lane_sel =
>> +     SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
>> +
>> +static const char * const s4_tocodec_src_sel_texts[] = {
>> +     "TDMA", "TDMB", "TDMC"
>> +};
>> +
>> +static const struct soc_enum s4_tocodec_src_sel_enum =
>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_src_sel_texts),
>> +                     s4_tocodec_src_sel_texts);
>> +
>> +static const struct snd_kcontrol_new s4_tocodec_src_sel =
>> +     SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
>> +
>> +static const struct snd_kcontrol_new s4_toacodec_out_enable =
>> +     SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
>> +                                 CTRL0_ENABLE_SHIFT, 1, 0);
>> +
>> +static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
>> +{
>> +     struct snd_soc_dapm_path *p;
>> +     struct snd_soc_dai *be;
>> +
>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>> +             if (!p->connect)
>> +                     continue;
>> +             if (p->source->id == snd_soc_dapm_dai_in)
>> +                     return (struct snd_soc_dai *)p->source->priv;
>> +             be = tocodec_tdm_get_ahead_be(p->source);
>> +             if (be && be->id == TDM_IFACE)
>> +                     return be;
>> +     }
>> +     return NULL;
>> +}
>> +
>> +static unsigned int aml_simple_strtoull(const char *cp)
>> +{
>> +     unsigned int result = 0;
>> +     unsigned int value = 0;
>> +     unsigned int len = strlen(cp);
>> +
>> +     while (len != 0) {
>> +             len--;
>> +             value = isdigit(*cp);
>> +             if (value) {
>> +                     value = *cp - '0';
>> +             } else {
>> +                     cp++;
>> +                     continue;
>> +             }
>> +             cp++;
>> +             result = result * 10 + value;
>> +     }
>> +     return result;
>> +}
>> +
>> +static int aml_get_clk_id(const char *name)
>> +{
>> +     int clk_id = 0;
>> +
>> +     if (strstr(name, "mst_a"))
>> +             clk_id = 0;
>> +     else if (strstr(name, "mst_b"))
>> +             clk_id = 1;
>> +     else if (strstr(name, "mst_c"))
>> +             clk_id = 2;
>> +     else if (strstr(name, "mst_d"))
>> +             clk_id = 3;
>> +     else if (strstr(name, "mst_e"))
>> +             clk_id = 4;
>> +     else if (strstr(name, "mst_f"))
>> +             clk_id = 5;
>> +
>> +     return clk_id;
>> +}
>> +
>> +static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
>> +{
>> +     struct snd_soc_dai *be;
>> +     struct axg_tdm_stream *stream;
>> +     struct axg_tdm_iface *iface;
>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>> +     struct toacodec *priv = snd_soc_component_get_drvdata(component);
>> +     unsigned int tdm_id = TDM_A_PAD;
>> +     const char *dai_widget_name;
>> +     struct snd_soc_dapm_path *p;
>> +     unsigned int lane = 0;
>> +     unsigned int val = 0;
>> +     struct clk *sclk, *mclk;
>> +     char *clk_name;
>> +     int mclk_id, sclk_id;
>> +
>> +     be = tocodec_tdm_get_ahead_be(w);
>> +     if (!be) {
>> +             dev_err(component->dev, "%s not find the be\n", __func__);
>> +             return -EINVAL;
>> +     }
>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>> +     if (!stream) {
>> +             dev_err(component->dev, "%s not find the stream\n", __func__);
>> +             return -EINVAL;
>> +     }
>> +     /*we like to use dai id, but it is fixed val*/
>> +     dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>> +     if (strstr(dai_widget_name, "TDM_A"))
>> +             tdm_id = TDM_A_PAD;
>> +     else if (strstr(dai_widget_name, "TDM_B"))
>> +             tdm_id = TDM_B_PAD;
>> +     else if (strstr(dai_widget_name, "TDM_C"))
>> +             tdm_id = TDM_C_PAD;
>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>> +             if (p->connect && p->name) {
>> +                     lane = aml_simple_strtoull(p->name);
>> +                     val = lane + tdm_id * S4_LANE_OFFSET;
>> +                     regmap_field_write(priv->field_dat_sel, val);
>> +             }
>> +     }
>> +     iface = stream->iface;
>> +     mclk = iface->mclk;
>> +     sclk = iface->sclk;
>> +     mclk_id = aml_get_clk_id(__clk_get_name(mclk));
>> +     sclk_id = aml_get_clk_id(__clk_get_name(sclk));
>> +     regmap_field_write(priv->field_mclk_sel, mclk_id);
>> +     regmap_field_write(priv->field_bclk_sel, sclk_id);
>> +     regmap_field_write(priv->field_lrclk_sel, sclk_id);
>> +
>> +     return 0;
>> +}
>> +
>> +static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
>> +                          struct snd_kcontrol *control,
>> +                          int event)
>> +{
>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>> +     int ret = 0;
>> +
>> +     switch (event) {
>> +     case SND_SOC_DAPM_PRE_PMU:
>> +             ret = aml_tocodec_sel_set(w);
>> +             break;
>> +
>> +     case SND_SOC_DAPM_PRE_PMD:
>> +             break;
>> +
>> +     default:
>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>> +             return -EINVAL;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
>> +                           struct snd_kcontrol *control,
>> +                           int event)
>> +{
>> +     int ret = 0;
>> +     unsigned int mask = 0, val = 0;
>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>> +
>> +     snd_soc_component_update_bits(component, TOACODEC_CTRL0,
>> +                                   1 << CTRL0_BLK_CAP_INV_SHIFT, 1 << CTRL0_BLK_CAP_INV_SHIFT);
>> +     switch (event) {
>> +     case SND_SOC_DAPM_PRE_PMU:
>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>> +             val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>> +             break;
>> +     case SND_SOC_DAPM_PRE_PMD:
>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>> +             val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << CTRL0_BCLK_ENABLE_SHIFT;
>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>> +             break;
>> +     default:
>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>> +             return -EINVAL;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
>> +     SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
>> +                        &s4_tocodec_lane_sel, tocodec_sel_event,
>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>> +     SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, &s4_tocodec_src_sel),
>> +     SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
>> +                           &s4_toacodec_out_enable, tocodec_clk_enable,
>> +                             (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>> +     SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +     SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +     SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
>> +     SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
>> +};
>> +
>> +static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
>> +     { "INPUT SRC", "TDMA", "TDMA"},
>> +     { "INPUT SRC", "TDMB", "TDMB"},
>> +     { "INPUT SRC", "TDMC", "TDMC"},
>> +     { "Lane0", NULL, "INPUT SRC" },
>> +     { "Lane1", NULL, "INPUT SRC"},
>> +     { "Lane2", NULL, "INPUT SRC"},
>> +     { "Lane3", NULL, "INPUT SRC"},
>> +     { "Lane4", NULL, "INPUT SRC"},
>> +     { "Lane5", NULL, "INPUT SRC"},
>> +     { "Lane6", NULL, "INPUT SRC"},
>> +     { "Lane7", NULL, "INPUT SRC"},
>> +     { "Lane SRC", "Lane0", "Lane0"},
>> +     { "Lane SRC", "Lane1", "Lane1"},
>> +     { "Lane SRC", "Lane2", "Lane2"},
>> +     { "Lane SRC", "Lane3", "Lane3"},
>> +     { "Lane SRC", "Lane4", "Lane4"},
>> +     { "Lane SRC", "Lane5", "Lane5"},
>> +     { "Lane SRC", "Lane6", "Lane6"},
>> +     { "Lane SRC", "Lane7", "Lane7"},
>> +     { "OUT EN", "Switch", "Lane SRC"},
>> +     { "TDM_TO_ACODEC", NULL, "OUT EN"},
>> +
>> +};
>> +
>> +static const struct snd_soc_component_driver s4_tocodec_component_drv = {
>> +     .dapm_widgets           = s4_toacodec_widgets,
>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_toacodec_widgets),
>> +     .dapm_routes            = s4_tocodec_dapm_routes,
>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tocodec_dapm_routes),
>> +};
>> +
>> +static const struct toacodec_match_data s4_toacodec_match_data = {
>> +     .component_drv  = &s4_tocodec_component_drv,
>> +     .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 16, 20),
>> +     .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
>> +     .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
>> +     .field_mclk_sel = REG_FIELD(TOACODEC_CTRL0, 0, 2),
>> +};
>> +
>> +static const struct of_device_id s4_tocodec_of_match[] = {
>> +     {
>> +             .compatible = "amlogic,s4-tocodec",
>> +             .data = &s4_toacodec_match_data,
>> +     }, {}
>> +};
>> +
>> +MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
>> +
>> +static int tocodec_probe(struct platform_device *pdev)
>> +{
>> +     const struct toacodec_match_data *data;
>> +     struct device *dev = &pdev->dev;
>> +     struct toacodec *priv;
>> +     void __iomem *regs;
>> +     struct regmap *map;
>> +     int ret;
>> +
>> +     data = device_get_match_data(dev);
>> +     if (!data)
>> +             return dev_err_probe(dev, -ENODEV, "failed to match device\n");
>> +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +     if (!priv)
>> +             return -ENOMEM;
>> +
>> +     platform_set_drvdata(pdev, priv);
>> +
>> +     ret = device_reset(dev);
>> +     if (ret)
>> +             return ret;
>> +
>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>> +     if (IS_ERR(regs))
>> +             return PTR_ERR(regs);
>> +
>> +     map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
>> +     if (IS_ERR(map))
>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
>> +
>> +     priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
>> +     if (IS_ERR(priv->field_dat_sel))
>> +             return PTR_ERR(priv->field_dat_sel);
>> +
>> +     priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
>> +     if (IS_ERR(priv->field_lrclk_sel))
>> +             return PTR_ERR(priv->field_lrclk_sel);
>> +
>> +     priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
>> +     if (IS_ERR(priv->field_bclk_sel))
>> +             return PTR_ERR(priv->field_bclk_sel);
>> +
>> +     priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, data->field_mclk_sel);
>> +     if (IS_ERR(priv->field_mclk_sel))
>> +             return PTR_ERR(priv->field_mclk_sel);
>> +
>> +     return devm_snd_soc_register_component(dev,
>> +                     data->component_drv, NULL, 0);
>> +}
>> +
>> +static struct platform_driver tocodec_pdrv = {
>> +     .probe = tocodec_probe,
>> +     .driver = {
>> +             .name = "s4-tocodec",
>> +             .of_match_table = s4_tocodec_of_match,
>> +     },
>> +};
>> +
>> +module_platform_driver(tocodec_pdrv);
>> +
>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>> +MODULE_LICENSE("GPL");
>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>> index 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 100644
>> --- a/sound/soc/meson/t9015.c
>> +++ b/sound/soc/meson/t9015.c
>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>                .channels_min = 1,
>>                .channels_max = 2,
>>                .rates = SNDRV_PCM_RATE_8000_96000,
>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
> Again, mixed up changes with zero justification.
>
> This drops S8 and S16 format support for the existing SoCs (such as GXL)
> which is known to work and add S32 support on an HW documented as 24bits
> only. Can you explain ?
>
>>        },
>>        .ops = &t9015_dai_ops,
>>   };
> --
> Jerome
kernel test robot Jan. 14, 2025, 9:09 a.m. UTC | #3
Hi jiebing,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 6ecd20965bdc21b265a0671ccf36d9ad8043f5ab]

url:    https://github.com/intel-lab-lkp/linux/commits/jiebing-chen-via-B4-Relay/ASoC-dt-bindings-Add-Amlogic-S4-audio/20250113-143911
base:   6ecd20965bdc21b265a0671ccf36d9ad8043f5ab
patch link:    https://lore.kernel.org/r/20250113-audio_drvier-v1-2-8c14770f38a0%40amlogic.com
patch subject: [PATCH 2/3] ASoC: meson: s4:support for the on-chip audio
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20250114/202501141658.tM15P1iG-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250114/202501141658.tM15P1iG-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202501141658.tM15P1iG-lkp@intel.com/

All warnings (new ones prefixed by >>):

   sound/soc/meson/s4-tocodec-control.c: In function 'aml_tocodec_sel_set':
>> sound/soc/meson/s4-tocodec-control.c:151:15: warning: unused variable 'clk_name' [-Wunused-variable]
     151 |         char *clk_name;
         |               ^~~~~~~~


vim +/clk_name +151 sound/soc/meson/s4-tocodec-control.c

   137	
   138	static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
   139	{
   140		struct snd_soc_dai *be;
   141		struct axg_tdm_stream *stream;
   142		struct axg_tdm_iface *iface;
   143		struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
   144		struct toacodec *priv = snd_soc_component_get_drvdata(component);
   145		unsigned int tdm_id = TDM_A_PAD;
   146		const char *dai_widget_name;
   147		struct snd_soc_dapm_path *p;
   148		unsigned int lane = 0;
   149		unsigned int val = 0;
   150		struct clk *sclk, *mclk;
 > 151		char *clk_name;
   152		int mclk_id, sclk_id;
   153	
   154		be = tocodec_tdm_get_ahead_be(w);
   155		if (!be) {
   156			dev_err(component->dev, "%s not find the be\n", __func__);
   157			return -EINVAL;
   158		}
   159		stream = snd_soc_dai_dma_data_get_playback(be);
   160		if (!stream) {
   161			dev_err(component->dev, "%s not find the stream\n", __func__);
   162			return -EINVAL;
   163		}
   164		/*we like to use dai id, but it is fixed val*/
   165		dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
   166		if (strstr(dai_widget_name, "TDM_A"))
   167			tdm_id = TDM_A_PAD;
   168		else if (strstr(dai_widget_name, "TDM_B"))
   169			tdm_id = TDM_B_PAD;
   170		else if (strstr(dai_widget_name, "TDM_C"))
   171			tdm_id = TDM_C_PAD;
   172		snd_soc_dapm_widget_for_each_source_path(w, p) {
   173			if (p->connect && p->name) {
   174				lane = aml_simple_strtoull(p->name);
   175				val = lane + tdm_id * S4_LANE_OFFSET;
   176				regmap_field_write(priv->field_dat_sel, val);
   177			}
   178		}
   179		iface = stream->iface;
   180		mclk = iface->mclk;
   181		sclk = iface->sclk;
   182		mclk_id = aml_get_clk_id(__clk_get_name(mclk));
   183		sclk_id = aml_get_clk_id(__clk_get_name(sclk));
   184		regmap_field_write(priv->field_mclk_sel, mclk_id);
   185		regmap_field_write(priv->field_bclk_sel, sclk_id);
   186		regmap_field_write(priv->field_lrclk_sel, sclk_id);
   187	
   188		return 0;
   189	}
   190
Jiebing Chen Jan. 14, 2025, 11:20 a.m. UTC | #4
在 2025/1/14 16:16, Jiebing Chen 写道:
>
> 在 2025/1/13 22:31, Jerome Brunet 写道:
>> [ EXTERNAL EMAIL ]
>>
>> On Mon 13 Jan 2025 at 14:35, jiebing chen via B4 Relay 
>> <devnull+jiebing.chen.amlogic.com@kernel.org> wrote:
>>
>>> From: jiebing chen <jiebing.chen@amlogic.com>
>>>
>>> Add audio support for Amlogic S4.The audio output pad
>>> can be freelycombined with the output lane,and the tocodec
>>> control logic has been optimized.
>> The patch is a mixture of different HW modules.
>>
>> Each patch should have one clear purpose and, as such, deal with a
>> single HW module
>>
>>> Signed-off-by: jiebing chen <jiebing.chen@amlogic.com>
>>> ---
>>>   sound/soc/meson/Kconfig              |  16 ++
>>>   sound/soc/meson/Makefile             |   6 +
>>>   sound/soc/meson/s4-pad-out-control.c | 372 
>>> ++++++++++++++++++++++++++++++++++
>>>   sound/soc/meson/s4-tocodec-control.c | 376 
>>> +++++++++++++++++++++++++++++++++++
>>>   sound/soc/meson/t9015.c              |   5 +-
>>>   5 files changed, 771 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
>>> index 
>>> 6458d5dc4902f665211bb9e4ae7d274e4bff2fdc..d01e284642fd987cf4bdf88e5bf5f7c9a241af59 
>>> 100644
>>> --- a/sound/soc/meson/Kconfig
>>> +++ b/sound/soc/meson/Kconfig
>>> @@ -69,6 +69,8 @@ config SND_MESON_AXG_SOUND_CARD
>>>        imply SND_MESON_AXG_SPDIFIN
>>>        imply SND_MESON_AXG_PDM
>>>        imply SND_MESON_G12A_TOACODEC
>>> +     imply SND_SOC_MESON_PAD_OUT
>>> +     imply SND_SOC_MESON_TOCODEC_CONTROL
>>>        imply SND_MESON_G12A_TOHDMITX if DRM_MESON_DW_HDMI
>>>        help
>>>          Select Y or M to add support for the AXG SoC sound card
>>> @@ -135,4 +137,18 @@ config SND_SOC_MESON_T9015
>>>        help
>>>          Say Y or M if you want to add support for the internal DAC 
>>> found
>>>          on GXL, G12 and SM1 SoC family.
>>> +
>>> +config SND_SOC_MESON_PAD_OUT
>>> +     tristate "Amlogic PAD OUT"
>>> +     select REGMAP_MMIO
>>> +     help
>>> +       Say Y or M if you want to add support for the S4 Audio 
>>> Output from
>>> +       the data Pad.
>>> +
>>> +config SND_SOC_MESON_TOCODEC_CONTROL
>>> +     tristate "Amlogic TOCODEC CONTROL"
>>> +     select REGMAP_MMIO
>>> +     help
>>> +      Say Y or M if you want to add support for the internal DAC 
>>> control
>>> +      on SM1 SoC family.
>>>   endmenu
>>> diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
>>> index 
>>> 24078e4396b02d545d8ba4bcb1632979001354e3..afbefcb1313670f9b1365e88b8eb1a0badd7dc1e 
>>> 100644
>>> --- a/sound/soc/meson/Makefile
>>> +++ b/sound/soc/meson/Makefile
>>> @@ -24,8 +24,11 @@ snd-soc-meson-codec-glue-y := meson-codec-glue.o
>>>   snd-soc-meson-gx-sound-card-y := gx-card.o
>>>   snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
>>>   snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
>>> +snd-soc-meson-s4-padout-objs := s4-pad-out-control.o
>>> +snd-soc-meson-s4-tocodec-control-objs := s4-tocodec-control.o
>>>   snd-soc-meson-t9015-y := t9015.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
>>> @@ -43,4 +46,7 @@ obj-$(CONFIG_SND_MESON_CODEC_GLUE) += 
>>> snd-soc-meson-codec-glue.o
>>>   obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += 
>>> snd-soc-meson-gx-sound-card.o
>>>   obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += 
>>> snd-soc-meson-g12a-toacodec.o
>>>   obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += 
>>> snd-soc-meson-g12a-tohdmitx.o
>>> +obj-$(CONFIG_SND_SOC_MESON_PAD_OUT) += snd-soc-meson-s4-padout.o
>>> +obj-$(CONFIG_SND_SOC_MESON_TOCODEC_CONTROL) += 
>>> snd-soc-meson-s4-tocodec-control.o
>>>   obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
>>> +
>>> diff --git a/sound/soc/meson/s4-pad-out-control.c 
>>> b/sound/soc/meson/s4-pad-out-control.c
>>> new file mode 100644
>>> index 
>>> 0000000000000000000000000000000000000000..a86dcf8a5995926f0ddf8d2911f42006daed0feb
>>> --- /dev/null
>>> +++ b/sound/soc/meson/s4-pad-out-control.c
>>> @@ -0,0 +1,372 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2024 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/regmap.h>
>>> +#include <sound/soc.h>
>>> +#include <sound/soc-dai.h>
>>> +#include <linux/init.h>
>>> +#include <linux/kernel.h>
>>> +#include<linux/kstrtox.h>
>>> +
>>> +#include "axg-tdm.h"
>>> +
>>> +static const struct regmap_config tdmout_pad_regmap_cfg = {
>>> +     .reg_bits       = 32,
>>> +     .val_bits       = 32,
>>> +     .reg_stride     = 4,
>>> +     .max_register   = 0x28,
>>> +};
>>> +
>>> +#define TDM_IFACE 0
>>> +#define TDM_A_PAD 0
>>> +#define TDM_B_PAD 1
>>> +#define TDM_C_PAD 2
>>> +
>>> +#define EE_AUDIO_DAT_PAD_CTRL6 0x0
>>> +#define EE_AUDIO_DAT_PAD_CTRL7 0x4
>>> +#define EE_AUDIO_DAT_PAD_CTRL8 0x8
>>> +#define EE_AUDIO_DAT_PAD_CTRL9 0xc
>>> +#define EE_AUDIO_DAT_PAD_CTRLA 0x10
>>> +#define EE_AUDIO_DAT_PAD_CTRLB 0x14
>>> +#define EE_AUDIO_DAT_PAD_CTRLC 0x1c
>>> +#define EE_AUDIO_DAT_PAD_CTRLD 0x20
>>> +#define EE_AUDIO_DAT_PAD_CTRLE 0x24
>>> +#define EE_AUDIO_DAT_PAD_CTRLF 0x28
>>> +
>>> +#define REG_OFFSET 4
>>> +
>>> +static const char * const s4_tdmout_sel_texts[] = {
>>> +     "TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", 
>>> "TDM_D6", "TDM_D7",
>>> +     "TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", 
>>> "TDM_D13", "TDM_D14", "TDM_D15",
>>> +     "TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", 
>>> "TDM_D21", "TDM_D22", "TDM_D23",
>>> +     "TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", 
>>> "TDM_D29", "TDM_D30", "TDM_D31"
>>> +};
>> This thing does not belong in ASoC. This is clearly yet another layer of
>> pinctrl. Please deal with it there.
>
> Thanks for your suggestion, add audio pinctrl driver to control the 
> which tdm_dx pin can map the which tdm lane_x
> for example
>     tdm_d6_pin {
>         mux {
>             groups = "tdm_d6";
>             function = "tdmoutb_lane0";
>         };
>     }
> tdm_d6 pin map the tdmoutb lane 0, right ?
>
>>> +
>>> +static const struct soc_enum tdmout_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
>>> +                     s4_tdmout_sel_texts);
>>> +
>>> +static struct snd_soc_dai *tdm_get_ahead_be(struct 
>>> snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dapm_path *p;
>>> +     struct snd_soc_dai *be;
>>> +
>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>> +             if (p->source->id == snd_soc_dapm_dai_in)
>>> +                     return (struct snd_soc_dai *)p->source->priv;
>>> +             be = tdm_get_ahead_be(p->source);
>>> +             if (be && be->id == TDM_IFACE)
>>> +                     return be;
>>> +     }
>>> +     return NULL;
>>> +}
>>> +
>>> +#define SND_SOC_DAPM_DEMUX_E(wname, wreg, wshift, winvert, 
>>> wcontrols, \
>>> +     wevent, wflags) \
>>> +((struct snd_soc_dapm_widget) { \
>>> +     .id = snd_soc_dapm_demux, .name = wname, \
>>> +     SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
>>> +     .kcontrol_news = wcontrols, .num_kcontrols = 1, \
>>> +     .event = wevent, .event_flags = wflags})
>>> +
>>> +static const struct snd_kcontrol_new tdmout_sel_demux[] = {
>>> +     SOC_DAPM_ENUM("TDMOUTA SEL", tdmout_sel_enum),
>>> +     SOC_DAPM_ENUM("TDMOUTB SEL", tdmout_sel_enum),
>>> +     SOC_DAPM_ENUM("TDMOUTC SEL", tdmout_sel_enum),
>>> +};
>>> +
>>> +static unsigned int aml_simple_strtoull(const char *cp)
>>> +{
>>> +     unsigned int result = 0;
>>> +     unsigned int value = 0;
>>> +     unsigned int len =  strlen(cp);
>>> +
>>> +     while (len != 0) {
>>> +             len--;
>>> +             value = isdigit(*cp);
>>> +             if (value) {
>>> +                     value = *cp - '0';
>>> +             } else {
>>> +                     cp++;
>>> +                     continue;
>>> +             }
>>> +             cp++;
>>> +             result = result * 10 + value;
>>> +     }
>>> +     return result;
>>> +}
>>> +
>>> +static int tdm_out_pad_set(struct snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dai *be;
>>> +     struct axg_tdm_stream *stream;
>>> +     struct snd_soc_component *component = 
>>> snd_soc_dapm_to_component(w->dapm);
>>> +     unsigned int tdm_id = TDM_A_PAD;
>>> +     const char *dai_widget_name;
>>> +     struct snd_soc_dapm_path *p;
>>> +     unsigned int lane_num = 0;
>>> +     unsigned long pin = 0;
>>> +     unsigned int reg, mask, val = 0;
>>> +     int lane_cnt;
>>> +
>>> +     be = tdm_get_ahead_be(w);
>>> +     if (!be) {
>>> +             dev_err(component->dev, "%s not find the be\n", 
>>> __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>>> +     if (!stream) {
>>> +             dev_err(component->dev, "%s not find the stream\n", 
>>> __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     lane_cnt = (stream->channels - 1) / stream->iface->slots + 1;
>>> +     /*we like to use dai id, but it is fixed val*/
>>> +     dai_widget_name = 
>>> be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>>> +     if (strstr(dai_widget_name, "TDM_A"))
>>> +             tdm_id = TDM_A_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_B"))
>>> +             tdm_id = TDM_B_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_C"))
>>> +             tdm_id = TDM_C_PAD;
>>> +     else
>>> +             dev_err(component->dev, "%s not find the be dai 
>>> widget\n", __func__);
>>> +     dev_dbg(component->dev, "tdm_id:%d, channel:%d, slot:%d\n",
>>> +             tdm_id, stream->channels, stream->iface->slots);
>>> +     snd_soc_dapm_widget_for_each_sink_path(w, p) {
>>> +             if (p->sink->id == snd_soc_dapm_output) {
>>> +                     if (p->connect) {
>>> +                             pin = aml_simple_strtoull(p->name);
>>> +                             reg = (pin / 4) * REG_OFFSET;
>>> +                             /*calculate mask pos */
>>> +                             mask = 0x1f << ((pin % 4) * 8);
>>> +                             val = tdm_id * 8 + lane_num;
>>> + snd_soc_component_update_bits(component, reg, mask, val);
>>> + snd_soc_component_update_bits(component, EE_AUDIO_DAT_PAD_CTRLF,
>>> + 0x1 << pin, 0 << pin);
>>> +                             lane_num++;
>>> +                             if (lane_num > lane_cnt - 1)
>>> +                                     break;
>>> +                     }
>>> +             }
>>> +     }
>>> +     return 0;
>>> +}
>>> +
>>> +static int tdmout_sel_pad_event(struct snd_soc_dapm_widget *w,
>>> +                             struct snd_kcontrol *control,
>>> +                             int event)
>>> +{
>>> +     int ret = 0;
>>> +     struct snd_soc_component *component = 
>>> snd_soc_dapm_to_component(w->dapm);
>>> +
>>> +     switch (event) {
>>> +     case SND_SOC_DAPM_PRE_PMU:
>>> +             tdm_out_pad_set(w);
>>> +             break;
>>> +
>>> +     case SND_SOC_DAPM_PRE_PMD:
>>> +             break;
>>> +
>>> +     default:
>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct snd_soc_dapm_widget 
>>> s4_tdmout_pad_dapm_widgets[] = {
>>> +     SND_SOC_DAPM_DEMUX_E("TDMA_OUT SEL", SND_SOC_NOPM, 0, 0,
>>> +                          &tdmout_sel_demux[TDM_A_PAD], 
>>> tdmout_sel_pad_event,
>>> +                        (SND_SOC_DAPM_PRE_PMU | 
>>> SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_DEMUX_E("TDMB_OUT SEL", SND_SOC_NOPM, 0, 0,
>>> +                          &tdmout_sel_demux[TDM_B_PAD], 
>>> tdmout_sel_pad_event,
>>> +                        (SND_SOC_DAPM_PRE_PMU | 
>>> SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_DEMUX_E("TDMC_OUT SEL", SND_SOC_NOPM, 0, 0,
>>> +                          &tdmout_sel_demux[TDM_C_PAD], 
>>> tdmout_sel_pad_event,
>>> +                        (SND_SOC_DAPM_PRE_PMU | 
>>> SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D0"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D1"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D2"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D3"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D4"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D5"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D6"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D7"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D8"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D9"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D10"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D11"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D12"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D13"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D14"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D15"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D16"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D17"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D18"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D19"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D20"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D21"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D22"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D23"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D24"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D25"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D26"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D27"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D28"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D29"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D30"),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_D31"),
>>> +};
>>> +
>>> +static const struct snd_soc_dapm_route s4_tdmout_pad_dapm_routes[] = {
>>> +     { "TDM_D0", "TDM_D0", "TDMA_OUT SEL" },
>>> +     { "TDM_D1", "TDM_D1", "TDMA_OUT SEL" },
>>> +     { "TDM_D2", "TDM_D2", "TDMA_OUT SEL" },
>>> +     { "TDM_D3", "TDM_D3", "TDMA_OUT SEL" },
>>> +     { "TDM_D4", "TDM_D4", "TDMA_OUT SEL" },
>>> +     { "TDM_D5", "TDM_D5", "TDMA_OUT SEL" },
>>> +     { "TDM_D6", "TDM_D6", "TDMA_OUT SEL" },
>>> +     { "TDM_D7", "TDM_D7", "TDMA_OUT SEL" },
>>> +     { "TDM_D8", "TDM_D8", "TDMA_OUT SEL" },
>>> +     { "TDM_D9", "TDM_D9", "TDMA_OUT SEL" },
>>> +     { "TDM_D10", "TDM_D10", "TDMA_OUT SEL" },
>>> +     { "TDM_D11", "TDM_D11", "TDMA_OUT SEL" },
>>> +     { "TDM_D12", "TDM_D12", "TDMA_OUT SEL" },
>>> +     { "TDM_D13", "TDM_D13", "TDMA_OUT SEL" },
>>> +     { "TDM_D14", "TDM_D14", "TDMA_OUT SEL" },
>>> +     { "TDM_D15", "TDM_D15", "TDMA_OUT SEL" },
>>> +     { "TDM_D16", "TDM_D16", "TDMA_OUT SEL" },
>>> +     { "TDM_D17", "TDM_D17", "TDMA_OUT SEL" },
>>> +     { "TDM_D18", "TDM_D18", "TDMA_OUT SEL" },
>>> +     { "TDM_D19", "TDM_D19", "TDMA_OUT SEL" },
>>> +     { "TDM_D20", "TDM_D20", "TDMA_OUT SEL" },
>>> +     { "TDM_D21", "TDM_D21", "TDMA_OUT SEL" },
>>> +     { "TDM_D22", "TDM_D22", "TDMA_OUT SEL" },
>>> +     { "TDM_D23", "TDM_D23", "TDMA_OUT SEL" },
>>> +     { "TDM_D24", "TDM_D24", "TDMA_OUT SEL" },
>>> +     { "TDM_D25", "TDM_D25", "TDMA_OUT SEL" },
>>> +     { "TDM_D26", "TDM_D26", "TDMA_OUT SEL" },
>>> +     { "TDM_D27", "TDM_D27", "TDMA_OUT SEL" },
>>> +     { "TDM_D28", "TDM_D28", "TDMA_OUT SEL" },
>>> +     { "TDM_D29", "TDM_D29", "TDMA_OUT SEL" },
>>> +     { "TDM_D30", "TDM_D30", "TDMA_OUT SEL" },
>>> +     { "TDM_D31", "TDM_D31", "TDMA_OUT SEL" },
>>> +     { "TDM_D0", "TDM_D0", "TDMB_OUT SEL" },
>>> +     { "TDM_D1", "TDM_D1", "TDMB_OUT SEL" },
>>> +     { "TDM_D2", "TDM_D2", "TDMB_OUT SEL" },
>>> +     { "TDM_D3", "TDM_D3", "TDMB_OUT SEL" },
>>> +     { "TDM_D4", "TDM_D4", "TDMB_OUT SEL" },
>>> +     { "TDM_D5", "TDM_D5", "TDMB_OUT SEL" },
>>> +     { "TDM_D6", "TDM_D6", "TDMB_OUT SEL" },
>>> +     { "TDM_D7", "TDM_D7", "TDMB_OUT SEL" },
>>> +     { "TDM_D8", "TDM_D8", "TDMB_OUT SEL" },
>>> +     { "TDM_D9", "TDM_D9", "TDMB_OUT SEL" },
>>> +     { "TDM_D10", "TDM_D10", "TDMB_OUT SEL" },
>>> +     { "TDM_D11", "TDM_D11", "TDMB_OUT SEL" },
>>> +     { "TDM_D12", "TDM_D12", "TDMB_OUT SEL" },
>>> +     { "TDM_D13", "TDM_D13", "TDMB_OUT SEL" },
>>> +     { "TDM_D14", "TDM_D14", "TDMB_OUT SEL" },
>>> +     { "TDM_D15", "TDM_D15", "TDMB_OUT SEL" },
>>> +
>>> +     { "TDM_D16", "TDM_D16", "TDMB_OUT SEL" },
>>> +     { "TDM_D17", "TDM_D17", "TDMB_OUT SEL" },
>>> +     { "TDM_D18", "TDM_D18", "TDMB_OUT SEL" },
>>> +     { "TDM_D19", "TDM_D19", "TDMB_OUT SEL" },
>>> +     { "TDM_D20", "TDM_D20", "TDMB_OUT SEL" },
>>> +     { "TDM_D21", "TDM_D21", "TDMB_OUT SEL" },
>>> +     { "TDM_D22", "TDM_D22", "TDMB_OUT SEL" },
>>> +     { "TDM_D23", "TDM_D23", "TDMB_OUT SEL" },
>>> +     { "TDM_D24", "TDM_D24", "TDMB_OUT SEL" },
>>> +     { "TDM_D25", "TDM_D25", "TDMB_OUT SEL" },
>>> +     { "TDM_D26", "TDM_D26", "TDMB_OUT SEL" },
>>> +     { "TDM_D27", "TDM_D27", "TDMB_OUT SEL" },
>>> +     { "TDM_D28", "TDM_D28", "TDMB_OUT SEL" },
>>> +     { "TDM_D29", "TDM_D29", "TDMB_OUT SEL" },
>>> +     { "TDM_D30", "TDM_D30", "TDMB_OUT SEL" },
>>> +     { "TDM_D31", "TDM_D31", "TDMB_OUT SEL" },
>>> +     { "TDM_D0", "TDM_D0", "TDMC_OUT SEL" },
>>> +     { "TDM_D1", "TDM_D1", "TDMC_OUT SEL" },
>>> +     { "TDM_D2", "TDM_D2", "TDMC_OUT SEL" },
>>> +     { "TDM_D3", "TDM_D3", "TDMC_OUT SEL" },
>>> +     { "TDM_D4", "TDM_D4", "TDMC_OUT SEL" },
>>> +     { "TDM_D5", "TDM_D5", "TDMC_OUT SEL" },
>>> +     { "TDM_D6", "TDM_D6", "TDMC_OUT SEL" },
>>> +     { "TDM_D7", "TDM_D7", "TDMC_OUT SEL" },
>>> +     { "TDM_D8", "TDM_D8", "TDMC_OUT SEL" },
>>> +     { "TDM_D9", "TDM_D9", "TDMC_OUT SEL" },
>>> +     { "TDM_D10", "TDM_D10", "TDMC_OUT SEL" },
>>> +     { "TDM_D11", "TDM_D11", "TDMC_OUT SEL" },
>>> +     { "TDM_D12", "TDM_D12", "TDMC_OUT SEL" },
>>> +     { "TDM_D13", "TDM_D13", "TDMC_OUT SEL" },
>>> +     { "TDM_D14", "TDM_D14", "TDMC_OUT SEL" },
>>> +     { "TDM_D15", "TDM_D15", "TDMC_OUT SEL" },
>>> +     { "TDM_D16", "TDM_D16", "TDMC_OUT SEL" },
>>> +     { "TDM_D17", "TDM_D17", "TDMC_OUT SEL" },
>>> +     { "TDM_D18", "TDM_D18", "TDMC_OUT SEL" },
>>> +     { "TDM_D19", "TDM_D19", "TDMC_OUT SEL" },
>>> +     { "TDM_D20", "TDM_D20", "TDMC_OUT SEL" },
>>> +     { "TDM_D21", "TDM_D21", "TDMC_OUT SEL" },
>>> +     { "TDM_D22", "TDM_D22", "TDMC_OUT SEL" },
>>> +     { "TDM_D23", "TDM_D23", "TDMC_OUT SEL" },
>>> +     { "TDM_D24", "TDM_D24", "TDMC_OUT SEL" },
>>> +     { "TDM_D25", "TDM_D25", "TDMC_OUT SEL" },
>>> +     { "TDM_D26", "TDM_D26", "TDMC_OUT SEL" },
>>> +     { "TDM_D27", "TDM_D27", "TDMC_OUT SEL" },
>>> +     { "TDM_D28", "TDM_D28", "TDMC_OUT SEL" },
>>> +     { "TDM_D29", "TDM_D29", "TDMC_OUT SEL" },
>>> +     { "TDM_D30", "TDM_D30", "TDMC_OUT SEL" },
>>> +     { "TDM_D31", "TDM_D31", "TDMC_OUT SEL" },
>>> +};
>>> +
>>> +static const struct snd_soc_component_driver 
>>> s4_tdmout_pad_component_drv = {
>>> +     .dapm_widgets           = s4_tdmout_pad_dapm_widgets,
>>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_tdmout_pad_dapm_widgets),
>>> +     .dapm_routes            = s4_tdmout_pad_dapm_routes,
>>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tdmout_pad_dapm_routes),
>>> +
>>> +};
>>> +
>>> +static const struct of_device_id s4_tdmout_pad_of_match[] = {
>>> +     {
>>> +             .compatible = "amlogic,s4-tdmout-pad",
>>> +     }, {}
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(of, s4_tdmout_pad_of_match);
>>> +
>>> +static int tdm_pad_out_probe(struct platform_device *pdev)
>>> +{
>>> +     struct device *dev = &pdev->dev;
>>> +     struct regmap *map;
>>> +     void __iomem *regs;
>>> +
>>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +
>>> +     map = devm_regmap_init_mmio(dev, regs, &tdmout_pad_regmap_cfg);
>>> +     if (IS_ERR(map))
>>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to 
>>> init regmap\n");
>>> +
>>> +     return devm_snd_soc_register_component(dev, 
>>> &s4_tdmout_pad_component_drv,
>>> +                                            NULL, 0);
>>> +}
>>> +
>>> +static struct platform_driver tdmout_pad_pdrv = {
>>> +     .probe = tdm_pad_out_probe,
>>> +     .driver = {
>>> +             .name = "s4-pad-out",
>>> +             .of_match_table = s4_tdmout_pad_of_match,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(tdmout_pad_pdrv);
>>> +
>>> +MODULE_DESCRIPTION("Amlogic TDM PAD DRIVER");
>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/sound/soc/meson/s4-tocodec-control.c 
>>> b/sound/soc/meson/s4-tocodec-control.c
>>> new file mode 100644
>>> index 
>>> 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
>>> --- /dev/null
>>> +++ b/sound/soc/meson/s4-tocodec-control.c
>> There is already a to-acodec driver a not reason has been provided as 
>> to why a
>> completly new driver is required.
>>
>> Please have look at the existing driver and do try to use it.
>> If you need to do things so differently, clear justification are 
>> necessary.
>
> for g12a-toacodec.c, we find the tocodec clock source can't get 
> the clock id from the tdm Be device,
>
> and set it by the kcontrol from user,  For different soc chips, The 
> kcontrol value maybe different, The kcontrol configuration doesn't 
> look very friendly for user
>
> so we use dapm route path to manage it, 
> fe(fddr)->be(tdm)->(tocodec)->(codec),  and use the aux-devs to 
> register,  and sound card only include the
>
> sound-dai = <&tdmif_a>
>
> codec-0 {
>                 sound-dai = <&acodec>;
>  };
>
> and not include
>
> codec-1 {
>                 sound-dai = <&toacodec>;
>  };
>
> when tdm work, only connect the tocodec path
>
>  "TDM_A Playback" ->"TOACODEC TDMA"->"TOACODEC INPUT SRC"
>
> iterate it find the be device ,and get the struct axg_tdm_stream, so 
> we can get the tdm clock id
>
> Take into account behavioral differences, we add new tocodec driver 
> for s4
>
>>> @@ -0,0 +1,376 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2023 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/regmap.h>
>>> +#include <sound/soc.h>
>>> +#include <sound/soc-dai.h>
>>> +#include <linux/init.h>
>>> +#include <linux/kernel.h>
>>> +#include<linux/kstrtox.h>
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/reset.h>
>>> +#include "axg-tdm.h"
>>> +
>>> +#define TOACODEC_CTRL0                       0x0
>>> +
>>> +#define CTRL0_ENABLE_SHIFT           31
>>> +#define CTRL0_BCLK_ENABLE_SHIFT              30
>>> +#define CTRL0_MCLK_ENABLE_SHIFT              29
>>> +#define CTRL0_BLK_CAP_INV_SHIFT              9
>>> +
>>> +#define TDM_IFACE 0
>>> +#define TDM_A_PAD 0
>>> +#define TDM_B_PAD 1
>>> +#define TDM_C_PAD 2
>>> +
>>> +struct toacodec {
>>> +     struct regmap_field *field_dat_sel;
>>> +     struct regmap_field *field_lrclk_sel;
>>> +     struct regmap_field *field_bclk_sel;
>>> +     struct regmap_field *field_mclk_sel;
>>> +};
>>> +
>>> +struct toacodec_match_data {
>>> +     const struct snd_soc_component_driver *component_drv;
>>> +     const struct reg_field field_dat_sel;
>>> +     const struct reg_field field_lrclk_sel;
>>> +     const struct reg_field field_bclk_sel;
>>> +     const struct reg_field field_mclk_sel;
>>> +};
>>> +
>>> +static const struct regmap_config tocodec_regmap_cfg = {
>>> +     .reg_bits       = 32,
>>> +     .val_bits       = 32,
>>> +     .reg_stride     = 4,
>>> +     .max_register   = 0x1,
>>> +};
>>> +
>>> +#define S4_LANE_OFFSET 8
>>> +
>>> +static const char * const s4_tocodec_lane_sel_texts[] = {
>>> +     "Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", 
>>> "Lane7"
>>> +};
>>> +
>>> +static const struct soc_enum s4_tocodec_lane_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 
>>> ARRAY_SIZE(s4_tocodec_lane_sel_texts),
>>> +                     s4_tocodec_lane_sel_texts);
>>> +
>>> +static const struct snd_kcontrol_new s4_tocodec_lane_sel =
>>> +     SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
>>> +
>>> +static const char * const s4_tocodec_src_sel_texts[] = {
>>> +     "TDMA", "TDMB", "TDMC"
>>> +};
>>> +
>>> +static const struct soc_enum s4_tocodec_src_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 
>>> ARRAY_SIZE(s4_tocodec_src_sel_texts),
>>> +                     s4_tocodec_src_sel_texts);
>>> +
>>> +static const struct snd_kcontrol_new s4_tocodec_src_sel =
>>> +     SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
>>> +
>>> +static const struct snd_kcontrol_new s4_toacodec_out_enable =
>>> +     SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
>>> +                                 CTRL0_ENABLE_SHIFT, 1, 0);
>>> +
>>> +static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct 
>>> snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dapm_path *p;
>>> +     struct snd_soc_dai *be;
>>> +
>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>> +             if (!p->connect)
>>> +                     continue;
>>> +             if (p->source->id == snd_soc_dapm_dai_in)
>>> +                     return (struct snd_soc_dai *)p->source->priv;
>>> +             be = tocodec_tdm_get_ahead_be(p->source);
>>> +             if (be && be->id == TDM_IFACE)
>>> +                     return be;
>>> +     }
>>> +     return NULL;
>>> +}
>>> +
>>> +static unsigned int aml_simple_strtoull(const char *cp)
>>> +{
>>> +     unsigned int result = 0;
>>> +     unsigned int value = 0;
>>> +     unsigned int len = strlen(cp);
>>> +
>>> +     while (len != 0) {
>>> +             len--;
>>> +             value = isdigit(*cp);
>>> +             if (value) {
>>> +                     value = *cp - '0';
>>> +             } else {
>>> +                     cp++;
>>> +                     continue;
>>> +             }
>>> +             cp++;
>>> +             result = result * 10 + value;
>>> +     }
>>> +     return result;
>>> +}
>>> +
>>> +static int aml_get_clk_id(const char *name)
>>> +{
>>> +     int clk_id = 0;
>>> +
>>> +     if (strstr(name, "mst_a"))
>>> +             clk_id = 0;
>>> +     else if (strstr(name, "mst_b"))
>>> +             clk_id = 1;
>>> +     else if (strstr(name, "mst_c"))
>>> +             clk_id = 2;
>>> +     else if (strstr(name, "mst_d"))
>>> +             clk_id = 3;
>>> +     else if (strstr(name, "mst_e"))
>>> +             clk_id = 4;
>>> +     else if (strstr(name, "mst_f"))
>>> +             clk_id = 5;
>>> +
>>> +     return clk_id;
>>> +}
>>> +
>>> +static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dai *be;
>>> +     struct axg_tdm_stream *stream;
>>> +     struct axg_tdm_iface *iface;
>>> +     struct snd_soc_component *component = 
>>> snd_soc_dapm_to_component(w->dapm);
>>> +     struct toacodec *priv = snd_soc_component_get_drvdata(component);
>>> +     unsigned int tdm_id = TDM_A_PAD;
>>> +     const char *dai_widget_name;
>>> +     struct snd_soc_dapm_path *p;
>>> +     unsigned int lane = 0;
>>> +     unsigned int val = 0;
>>> +     struct clk *sclk, *mclk;
>>> +     char *clk_name;
>>> +     int mclk_id, sclk_id;
>>> +
>>> +     be = tocodec_tdm_get_ahead_be(w);
>>> +     if (!be) {
>>> +             dev_err(component->dev, "%s not find the be\n", 
>>> __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>>> +     if (!stream) {
>>> +             dev_err(component->dev, "%s not find the stream\n", 
>>> __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     /*we like to use dai id, but it is fixed val*/
>>> +     dai_widget_name = 
>>> be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>>> +     if (strstr(dai_widget_name, "TDM_A"))
>>> +             tdm_id = TDM_A_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_B"))
>>> +             tdm_id = TDM_B_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_C"))
>>> +             tdm_id = TDM_C_PAD;
>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>> +             if (p->connect && p->name) {
>>> +                     lane = aml_simple_strtoull(p->name);
>>> +                     val = lane + tdm_id * S4_LANE_OFFSET;
>>> + regmap_field_write(priv->field_dat_sel, val);
>>> +             }
>>> +     }
>>> +     iface = stream->iface;
>>> +     mclk = iface->mclk;
>>> +     sclk = iface->sclk;
>>> +     mclk_id = aml_get_clk_id(__clk_get_name(mclk));
>>> +     sclk_id = aml_get_clk_id(__clk_get_name(sclk));
>>> +     regmap_field_write(priv->field_mclk_sel, mclk_id);
>>> +     regmap_field_write(priv->field_bclk_sel, sclk_id);
>>> +     regmap_field_write(priv->field_lrclk_sel, sclk_id);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
>>> +                          struct snd_kcontrol *control,
>>> +                          int event)
>>> +{
>>> +     struct snd_soc_component *component = 
>>> snd_soc_dapm_to_component(w->dapm);
>>> +     int ret = 0;
>>> +
>>> +     switch (event) {
>>> +     case SND_SOC_DAPM_PRE_PMU:
>>> +             ret = aml_tocodec_sel_set(w);
>>> +             break;
>>> +
>>> +     case SND_SOC_DAPM_PRE_PMD:
>>> +             break;
>>> +
>>> +     default:
>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
>>> +                           struct snd_kcontrol *control,
>>> +                           int event)
>>> +{
>>> +     int ret = 0;
>>> +     unsigned int mask = 0, val = 0;
>>> +     struct snd_soc_component *component = 
>>> snd_soc_dapm_to_component(w->dapm);
>>> +
>>> +     snd_soc_component_update_bits(component, TOACODEC_CTRL0,
>>> +                                   1 << CTRL0_BLK_CAP_INV_SHIFT, 1 
>>> << CTRL0_BLK_CAP_INV_SHIFT);
>>> +     switch (event) {
>>> +     case SND_SOC_DAPM_PRE_PMU:
>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << 
>>> CTRL0_BCLK_ENABLE_SHIFT;
>>> +             val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << 
>>> CTRL0_BCLK_ENABLE_SHIFT;
>>> +             snd_soc_component_update_bits(component, 
>>> TOACODEC_CTRL0, mask, val);
>>> +             break;
>>> +     case SND_SOC_DAPM_PRE_PMD:
>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << 
>>> CTRL0_BCLK_ENABLE_SHIFT;
>>> +             val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << 
>>> CTRL0_BCLK_ENABLE_SHIFT;
>>> +             snd_soc_component_update_bits(component, 
>>> TOACODEC_CTRL0, mask, val);
>>> +             break;
>>> +     default:
>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
>>> +     SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
>>> +                        &s4_tocodec_lane_sel, tocodec_sel_event,
>>> +                        (SND_SOC_DAPM_PRE_PMU | 
>>> SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, 
>>> &s4_tocodec_src_sel),
>>> +     SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
>>> +                           &s4_toacodec_out_enable, 
>>> tocodec_clk_enable,
>>> +                             (SND_SOC_DAPM_PRE_PMU | 
>>> SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
>>> +};
>>> +
>>> +static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
>>> +     { "INPUT SRC", "TDMA", "TDMA"},
>>> +     { "INPUT SRC", "TDMB", "TDMB"},
>>> +     { "INPUT SRC", "TDMC", "TDMC"},
>>> +     { "Lane0", NULL, "INPUT SRC" },
>>> +     { "Lane1", NULL, "INPUT SRC"},
>>> +     { "Lane2", NULL, "INPUT SRC"},
>>> +     { "Lane3", NULL, "INPUT SRC"},
>>> +     { "Lane4", NULL, "INPUT SRC"},
>>> +     { "Lane5", NULL, "INPUT SRC"},
>>> +     { "Lane6", NULL, "INPUT SRC"},
>>> +     { "Lane7", NULL, "INPUT SRC"},
>>> +     { "Lane SRC", "Lane0", "Lane0"},
>>> +     { "Lane SRC", "Lane1", "Lane1"},
>>> +     { "Lane SRC", "Lane2", "Lane2"},
>>> +     { "Lane SRC", "Lane3", "Lane3"},
>>> +     { "Lane SRC", "Lane4", "Lane4"},
>>> +     { "Lane SRC", "Lane5", "Lane5"},
>>> +     { "Lane SRC", "Lane6", "Lane6"},
>>> +     { "Lane SRC", "Lane7", "Lane7"},
>>> +     { "OUT EN", "Switch", "Lane SRC"},
>>> +     { "TDM_TO_ACODEC", NULL, "OUT EN"},
>>> +
>>> +};
>>> +
>>> +static const struct snd_soc_component_driver 
>>> s4_tocodec_component_drv = {
>>> +     .dapm_widgets           = s4_toacodec_widgets,
>>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_toacodec_widgets),
>>> +     .dapm_routes            = s4_tocodec_dapm_routes,
>>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tocodec_dapm_routes),
>>> +};
>>> +
>>> +static const struct toacodec_match_data s4_toacodec_match_data = {
>>> +     .component_drv  = &s4_tocodec_component_drv,
>>> +     .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 16, 20),
>>> +     .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
>>> +     .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
>>> +     .field_mclk_sel = REG_FIELD(TOACODEC_CTRL0, 0, 2),
>>> +};
>>> +
>>> +static const struct of_device_id s4_tocodec_of_match[] = {
>>> +     {
>>> +             .compatible = "amlogic,s4-tocodec",
>>> +             .data = &s4_toacodec_match_data,
>>> +     }, {}
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
>>> +
>>> +static int tocodec_probe(struct platform_device *pdev)
>>> +{
>>> +     const struct toacodec_match_data *data;
>>> +     struct device *dev = &pdev->dev;
>>> +     struct toacodec *priv;
>>> +     void __iomem *regs;
>>> +     struct regmap *map;
>>> +     int ret;
>>> +
>>> +     data = device_get_match_data(dev);
>>> +     if (!data)
>>> +             return dev_err_probe(dev, -ENODEV, "failed to match 
>>> device\n");
>>> +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +     if (!priv)
>>> +             return -ENOMEM;
>>> +
>>> +     platform_set_drvdata(pdev, priv);
>>> +
>>> +     ret = device_reset(dev);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +
>>> +     map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
>>> +     if (IS_ERR(map))
>>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to 
>>> init regmap\n");
>>> +
>>> +     priv->field_dat_sel = devm_regmap_field_alloc(dev, map, 
>>> data->field_dat_sel);
>>> +     if (IS_ERR(priv->field_dat_sel))
>>> +             return PTR_ERR(priv->field_dat_sel);
>>> +
>>> +     priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, 
>>> data->field_lrclk_sel);
>>> +     if (IS_ERR(priv->field_lrclk_sel))
>>> +             return PTR_ERR(priv->field_lrclk_sel);
>>> +
>>> +     priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, 
>>> data->field_bclk_sel);
>>> +     if (IS_ERR(priv->field_bclk_sel))
>>> +             return PTR_ERR(priv->field_bclk_sel);
>>> +
>>> +     priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, 
>>> data->field_mclk_sel);
>>> +     if (IS_ERR(priv->field_mclk_sel))
>>> +             return PTR_ERR(priv->field_mclk_sel);
>>> +
>>> +     return devm_snd_soc_register_component(dev,
>>> +                     data->component_drv, NULL, 0);
>>> +}
>>> +
>>> +static struct platform_driver tocodec_pdrv = {
>>> +     .probe = tocodec_probe,
>>> +     .driver = {
>>> +             .name = "s4-tocodec",
>>> +             .of_match_table = s4_tocodec_of_match,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(tocodec_pdrv);
>>> +
>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>> index 
>>> 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 
>>> 100644
>>> --- a/sound/soc/meson/t9015.c
>>> +++ b/sound/soc/meson/t9015.c
>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>                .channels_min = 1,
>>>                .channels_max = 2,
>>>                .rates = SNDRV_PCM_RATE_8000_96000,
>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE | 
>>> SNDRV_PCM_FMTBIT_S32_LE),
>> Again, mixed up changes with zero justification.
>>
>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>> which is known to work and add S32 support on an HW documented as 24bits
>> only. Can you explain ?

for g12a, sm1 etc, it is use new audio ip, GXL is old ip, the new ip not 
support 24 bit,

usually support 16/32 bit for new audio ip , for 
SNDRV_PCM_FMTBIT_S24_LE, it width =24, phy =32

it was  treated as 32 bit to send for tdm, so we can only add the S32LE 
base on it , right ? but if the gxl not support the 32bit

we need add new snd_soc_dai_driver t9015_dai_s4 ?

>>
>>>        },
>>>        .ops = &t9015_dai_ops,
>>>   };
>> -- 
>> Jerome
Jerome Brunet Jan. 14, 2025, 11:29 a.m. UTC | #5
On Tue 14 Jan 2025 at 16:16, Jiebing Chen <jiebing.chen@amlogic.com> wrote:

>>> +static const char * const s4_tdmout_sel_texts[] = {
>>> +     "TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", "TDM_D6", "TDM_D7",
>>> +     "TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", "TDM_D13", "TDM_D14", "TDM_D15",
>>> +     "TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", "TDM_D21", "TDM_D22", "TDM_D23",
>>> +     "TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", "TDM_D29", "TDM_D30", "TDM_D31"
>>> +};
>> This thing does not belong in ASoC. This is clearly yet another layer of
>> pinctrl. Please deal with it there.
>
> Thanks for your suggestion, add audio pinctrl driver to control the which tdm_dx pin can map the which tdm lane_x
> for example
> 	tdm_d6_pin {
> 		mux {
> 			groups = "tdm_d6";
> 			function = "tdmoutb_lane0";
> 		};
> 	}
> tdm_d6 pin map the tdmoutb lane 0, right ?

possibly

>
>>> +
>>> +static const struct soc_enum tdmout_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
>>> +                     s4_tdmout_sel_texts);
>>> +

[...]

>>> diff --git a/sound/soc/meson/s4-tocodec-control.c b/sound/soc/meson/s4-tocodec-control.c
>>> new file mode 100644
>>> index 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
>>> --- /dev/null
>>> +++ b/sound/soc/meson/s4-tocodec-control.c
>> There is already a to-acodec driver a not reason has been provided as to why a
>> completly new driver is required.
>>
>> Please have look at the existing driver and do try to use it.
>> If you need to do things so differently, clear justification are necessary.
>
> for g12a-toacodec.c, we find the tocodec clock source can't get the clock
> id from the tdm Be device,

This is clearly documented limitation of the current to-acodec driver:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/meson/g12a-toacodec.c?h=v6.13-rc7#n91

While it is a limitation, it is a manageable one considering the amount
of master clocks available and the fact the master should be manually
assinged to the output pad, which you did not do.

See the u200:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/boot/dts/amlogic/meson-g12a-u200.dts?h=v6.13-rc7#n569

You are more than welcome to help fix this limitation in the current
driver but just adding a fork is not OK

I would suggest to start with what is currently available and move on to
fixing this as a 2nd step, if you want to.

>
> and set it by the kcontrol from user,  For different soc chips, The
> kcontrol value maybe different, The kcontrol configuration doesn't look
> very friendly for user
>
> so we use dapm route path to manage it,
> fe(fddr)->be(tdm)->(tocodec)->(codec),  and use the aux-devs to register, 
> and sound card only include the
>
> sound-dai = <&tdmif_a>
>
> codec-0 {
>                 sound-dai = <&acodec>;
>  };
>
> and not include
>
> codec-1 {
>                 sound-dai = <&toacodec>;
>  };
>
> when tdm work, only connect the tocodec path
>
>  "TDM_A Playback" ->"TOACODEC TDMA"->"TOACODEC INPUT SRC"
>
> iterate it find the be device ,and get the struct axg_tdm_stream, so we can
> get the tdm clock id
>
> Take into account behavioral differences, we add new tocodec driver
> for s4

Still not seeing sufficient reason to make another driver.

>
>>> @@ -0,0 +1,376 @@
>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>> +/*
>>> + * Copyright (C) 2023 Amlogic, Inc. All rights reserved
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/regmap.h>
>>> +#include <sound/soc.h>
>>> +#include <sound/soc-dai.h>
>>> +#include <linux/init.h>
>>> +#include <linux/kernel.h>
>>> +#include<linux/kstrtox.h>
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/reset.h>
>>> +#include "axg-tdm.h"
>>> +
>>> +#define TOACODEC_CTRL0                       0x0
>>> +
>>> +#define CTRL0_ENABLE_SHIFT           31
>>> +#define CTRL0_BCLK_ENABLE_SHIFT              30
>>> +#define CTRL0_MCLK_ENABLE_SHIFT              29
>>> +#define CTRL0_BLK_CAP_INV_SHIFT              9
>>> +
>>> +#define TDM_IFACE 0
>>> +#define TDM_A_PAD 0
>>> +#define TDM_B_PAD 1
>>> +#define TDM_C_PAD 2
>>> +
>>> +struct toacodec {
>>> +     struct regmap_field *field_dat_sel;
>>> +     struct regmap_field *field_lrclk_sel;
>>> +     struct regmap_field *field_bclk_sel;
>>> +     struct regmap_field *field_mclk_sel;
>>> +};
>>> +
>>> +struct toacodec_match_data {
>>> +     const struct snd_soc_component_driver *component_drv;
>>> +     const struct reg_field field_dat_sel;
>>> +     const struct reg_field field_lrclk_sel;
>>> +     const struct reg_field field_bclk_sel;
>>> +     const struct reg_field field_mclk_sel;
>>> +};
>>> +
>>> +static const struct regmap_config tocodec_regmap_cfg = {
>>> +     .reg_bits       = 32,
>>> +     .val_bits       = 32,
>>> +     .reg_stride     = 4,
>>> +     .max_register   = 0x1,
>>> +};
>>> +
>>> +#define S4_LANE_OFFSET 8
>>> +
>>> +static const char * const s4_tocodec_lane_sel_texts[] = {
>>> +     "Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", "Lane7"
>>> +};
>>> +
>>> +static const struct soc_enum s4_tocodec_lane_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_lane_sel_texts),
>>> +                     s4_tocodec_lane_sel_texts);
>>> +
>>> +static const struct snd_kcontrol_new s4_tocodec_lane_sel =
>>> +     SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
>>> +
>>> +static const char * const s4_tocodec_src_sel_texts[] = {
>>> +     "TDMA", "TDMB", "TDMC"
>>> +};
>>> +
>>> +static const struct soc_enum s4_tocodec_src_sel_enum =
>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_src_sel_texts),
>>> +                     s4_tocodec_src_sel_texts);
>>> +
>>> +static const struct snd_kcontrol_new s4_tocodec_src_sel =
>>> +     SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
>>> +
>>> +static const struct snd_kcontrol_new s4_toacodec_out_enable =
>>> +     SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
>>> +                                 CTRL0_ENABLE_SHIFT, 1, 0);
>>> +
>>> +static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dapm_path *p;
>>> +     struct snd_soc_dai *be;
>>> +
>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>> +             if (!p->connect)
>>> +                     continue;
>>> +             if (p->source->id == snd_soc_dapm_dai_in)
>>> +                     return (struct snd_soc_dai *)p->source->priv;
>>> +             be = tocodec_tdm_get_ahead_be(p->source);
>>> +             if (be && be->id == TDM_IFACE)
>>> +                     return be;
>>> +     }
>>> +     return NULL;
>>> +}
>>> +
>>> +static unsigned int aml_simple_strtoull(const char *cp)
>>> +{
>>> +     unsigned int result = 0;
>>> +     unsigned int value = 0;
>>> +     unsigned int len = strlen(cp);
>>> +
>>> +     while (len != 0) {
>>> +             len--;
>>> +             value = isdigit(*cp);
>>> +             if (value) {
>>> +                     value = *cp - '0';
>>> +             } else {
>>> +                     cp++;
>>> +                     continue;
>>> +             }
>>> +             cp++;
>>> +             result = result * 10 + value;
>>> +     }
>>> +     return result;
>>> +}
>>> +
>>> +static int aml_get_clk_id(const char *name)
>>> +{
>>> +     int clk_id = 0;
>>> +
>>> +     if (strstr(name, "mst_a"))
>>> +             clk_id = 0;
>>> +     else if (strstr(name, "mst_b"))
>>> +             clk_id = 1;
>>> +     else if (strstr(name, "mst_c"))
>>> +             clk_id = 2;
>>> +     else if (strstr(name, "mst_d"))
>>> +             clk_id = 3;
>>> +     else if (strstr(name, "mst_e"))
>>> +             clk_id = 4;
>>> +     else if (strstr(name, "mst_f"))
>>> +             clk_id = 5;
>>> +
>>> +     return clk_id;
>>> +}
>>> +
>>> +static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
>>> +{
>>> +     struct snd_soc_dai *be;
>>> +     struct axg_tdm_stream *stream;
>>> +     struct axg_tdm_iface *iface;
>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>> +     struct toacodec *priv = snd_soc_component_get_drvdata(component);
>>> +     unsigned int tdm_id = TDM_A_PAD;
>>> +     const char *dai_widget_name;
>>> +     struct snd_soc_dapm_path *p;
>>> +     unsigned int lane = 0;
>>> +     unsigned int val = 0;
>>> +     struct clk *sclk, *mclk;
>>> +     char *clk_name;
>>> +     int mclk_id, sclk_id;
>>> +
>>> +     be = tocodec_tdm_get_ahead_be(w);
>>> +     if (!be) {
>>> +             dev_err(component->dev, "%s not find the be\n", __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>>> +     if (!stream) {
>>> +             dev_err(component->dev, "%s not find the stream\n", __func__);
>>> +             return -EINVAL;
>>> +     }
>>> +     /*we like to use dai id, but it is fixed val*/
>>> +     dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>>> +     if (strstr(dai_widget_name, "TDM_A"))
>>> +             tdm_id = TDM_A_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_B"))
>>> +             tdm_id = TDM_B_PAD;
>>> +     else if (strstr(dai_widget_name, "TDM_C"))
>>> +             tdm_id = TDM_C_PAD;
>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>> +             if (p->connect && p->name) {
>>> +                     lane = aml_simple_strtoull(p->name);
>>> +                     val = lane + tdm_id * S4_LANE_OFFSET;
>>> +                     regmap_field_write(priv->field_dat_sel, val);
>>> +             }
>>> +     }
>>> +     iface = stream->iface;
>>> +     mclk = iface->mclk;
>>> +     sclk = iface->sclk;
>>> +     mclk_id = aml_get_clk_id(__clk_get_name(mclk));
>>> +     sclk_id = aml_get_clk_id(__clk_get_name(sclk));
>>> +     regmap_field_write(priv->field_mclk_sel, mclk_id);
>>> +     regmap_field_write(priv->field_bclk_sel, sclk_id);
>>> +     regmap_field_write(priv->field_lrclk_sel, sclk_id);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
>>> +                          struct snd_kcontrol *control,
>>> +                          int event)
>>> +{
>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>> +     int ret = 0;
>>> +
>>> +     switch (event) {
>>> +     case SND_SOC_DAPM_PRE_PMU:
>>> +             ret = aml_tocodec_sel_set(w);
>>> +             break;
>>> +
>>> +     case SND_SOC_DAPM_PRE_PMD:
>>> +             break;
>>> +
>>> +     default:
>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
>>> +                           struct snd_kcontrol *control,
>>> +                           int event)
>>> +{
>>> +     int ret = 0;
>>> +     unsigned int mask = 0, val = 0;
>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>> +
>>> +     snd_soc_component_update_bits(component, TOACODEC_CTRL0,
>>> +                                   1 << CTRL0_BLK_CAP_INV_SHIFT, 1 << CTRL0_BLK_CAP_INV_SHIFT);
>>> +     switch (event) {
>>> +     case SND_SOC_DAPM_PRE_PMU:
>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>> +             val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>>> +             break;
>>> +     case SND_SOC_DAPM_PRE_PMD:
>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>> +             val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << CTRL0_BCLK_ENABLE_SHIFT;
>>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>>> +             break;
>>> +     default:
>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>> +             return -EINVAL;
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
>>> +     SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
>>> +                        &s4_tocodec_lane_sel, tocodec_sel_event,
>>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, &s4_tocodec_src_sel),
>>> +     SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
>>> +                           &s4_toacodec_out_enable, tocodec_clk_enable,
>>> +                             (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>>> +     SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
>>> +     SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
>>> +};
>>> +
>>> +static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
>>> +     { "INPUT SRC", "TDMA", "TDMA"},
>>> +     { "INPUT SRC", "TDMB", "TDMB"},
>>> +     { "INPUT SRC", "TDMC", "TDMC"},
>>> +     { "Lane0", NULL, "INPUT SRC" },
>>> +     { "Lane1", NULL, "INPUT SRC"},
>>> +     { "Lane2", NULL, "INPUT SRC"},
>>> +     { "Lane3", NULL, "INPUT SRC"},
>>> +     { "Lane4", NULL, "INPUT SRC"},
>>> +     { "Lane5", NULL, "INPUT SRC"},
>>> +     { "Lane6", NULL, "INPUT SRC"},
>>> +     { "Lane7", NULL, "INPUT SRC"},
>>> +     { "Lane SRC", "Lane0", "Lane0"},
>>> +     { "Lane SRC", "Lane1", "Lane1"},
>>> +     { "Lane SRC", "Lane2", "Lane2"},
>>> +     { "Lane SRC", "Lane3", "Lane3"},
>>> +     { "Lane SRC", "Lane4", "Lane4"},
>>> +     { "Lane SRC", "Lane5", "Lane5"},
>>> +     { "Lane SRC", "Lane6", "Lane6"},
>>> +     { "Lane SRC", "Lane7", "Lane7"},
>>> +     { "OUT EN", "Switch", "Lane SRC"},
>>> +     { "TDM_TO_ACODEC", NULL, "OUT EN"},
>>> +
>>> +};
>>> +
>>> +static const struct snd_soc_component_driver s4_tocodec_component_drv = {
>>> +     .dapm_widgets           = s4_toacodec_widgets,
>>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_toacodec_widgets),
>>> +     .dapm_routes            = s4_tocodec_dapm_routes,
>>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tocodec_dapm_routes),
>>> +};
>>> +
>>> +static const struct toacodec_match_data s4_toacodec_match_data = {
>>> +     .component_drv  = &s4_tocodec_component_drv,
>>> +     .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 16, 20),
>>> +     .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
>>> +     .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
>>> +     .field_mclk_sel = REG_FIELD(TOACODEC_CTRL0, 0, 2),
>>> +};
>>> +
>>> +static const struct of_device_id s4_tocodec_of_match[] = {
>>> +     {
>>> +             .compatible = "amlogic,s4-tocodec",
>>> +             .data = &s4_toacodec_match_data,
>>> +     }, {}
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
>>> +
>>> +static int tocodec_probe(struct platform_device *pdev)
>>> +{
>>> +     const struct toacodec_match_data *data;
>>> +     struct device *dev = &pdev->dev;
>>> +     struct toacodec *priv;
>>> +     void __iomem *regs;
>>> +     struct regmap *map;
>>> +     int ret;
>>> +
>>> +     data = device_get_match_data(dev);
>>> +     if (!data)
>>> +             return dev_err_probe(dev, -ENODEV, "failed to match device\n");
>>> +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +     if (!priv)
>>> +             return -ENOMEM;
>>> +
>>> +     platform_set_drvdata(pdev, priv);
>>> +
>>> +     ret = device_reset(dev);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +
>>> +     map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
>>> +     if (IS_ERR(map))
>>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
>>> +
>>> +     priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
>>> +     if (IS_ERR(priv->field_dat_sel))
>>> +             return PTR_ERR(priv->field_dat_sel);
>>> +
>>> +     priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
>>> +     if (IS_ERR(priv->field_lrclk_sel))
>>> +             return PTR_ERR(priv->field_lrclk_sel);
>>> +
>>> +     priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
>>> +     if (IS_ERR(priv->field_bclk_sel))
>>> +             return PTR_ERR(priv->field_bclk_sel);
>>> +
>>> +     priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, data->field_mclk_sel);
>>> +     if (IS_ERR(priv->field_mclk_sel))
>>> +             return PTR_ERR(priv->field_mclk_sel);
>>> +
>>> +     return devm_snd_soc_register_component(dev,
>>> +                     data->component_drv, NULL, 0);
>>> +}
>>> +
>>> +static struct platform_driver tocodec_pdrv = {
>>> +     .probe = tocodec_probe,
>>> +     .driver = {
>>> +             .name = "s4-tocodec",
>>> +             .of_match_table = s4_tocodec_of_match,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(tocodec_pdrv);
>>> +
>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>> index 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 100644
>>> --- a/sound/soc/meson/t9015.c
>>> +++ b/sound/soc/meson/t9015.c
>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>                .channels_min = 1,
>>>                .channels_max = 2,
>>>                .rates = SNDRV_PCM_RATE_8000_96000,
>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
>> Again, mixed up changes with zero justification.
>>
>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>> which is known to work and add S32 support on an HW documented as 24bits
>> only. Can you explain ?
>>
>>>        },
>>>        .ops = &t9015_dai_ops,
>>>   };
>> --
>> Jerome
Jiebing Chen Jan. 14, 2025, 12:41 p.m. UTC | #6
在 2025/1/14 19:29, Jerome Brunet 写道:
> [ EXTERNAL EMAIL ]
>
> On Tue 14 Jan 2025 at 16:16, Jiebing Chen <jiebing.chen@amlogic.com> wrote:
>
>>>> +static const char * const s4_tdmout_sel_texts[] = {
>>>> +     "TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", "TDM_D6", "TDM_D7",
>>>> +     "TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", "TDM_D13", "TDM_D14", "TDM_D15",
>>>> +     "TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", "TDM_D21", "TDM_D22", "TDM_D23",
>>>> +     "TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", "TDM_D29", "TDM_D30", "TDM_D31"
>>>> +};
>>> This thing does not belong in ASoC. This is clearly yet another layer of
>>> pinctrl. Please deal with it there.
>> Thanks for your suggestion, add audio pinctrl driver to control the which tdm_dx pin can map the which tdm lane_x
>> for example
>>        tdm_d6_pin {
>>                mux {
>>                        groups = "tdm_d6";
>>                        function = "tdmoutb_lane0";
>>                };
>>        }
>> tdm_d6 pin map the tdmoutb lane 0, right ?
> possibly
>
>>>> +
>>>> +static const struct soc_enum tdmout_sel_enum =
>>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
>>>> +                     s4_tdmout_sel_texts);
>>>> +
> [...]
>
>>>> diff --git a/sound/soc/meson/s4-tocodec-control.c b/sound/soc/meson/s4-tocodec-control.c
>>>> new file mode 100644
>>>> index 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
>>>> --- /dev/null
>>>> +++ b/sound/soc/meson/s4-tocodec-control.c
>>> There is already a to-acodec driver a not reason has been provided as to why a
>>> completly new driver is required.
>>>
>>> Please have look at the existing driver and do try to use it.
>>> If you need to do things so differently, clear justification are necessary.
>> for g12a-toacodec.c, we find the tocodec clock source can't get the clock
>> id from the tdm Be device,
> This is clearly documented limitation of the current to-acodec driver:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/meson/g12a-toacodec.c?h=v6.13-rc7#n91
>
> While it is a limitation, it is a manageable one considering the amount
> of master clocks available and the fact the master should be manually
> assinged to the output pad, which you did not do.
>
> See the u200:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/boot/dts/amlogic/meson-g12a-u200.dts?h=v6.13-rc7#n569
>
> You are more than welcome to help fix this limitation in the current
> driver but just adding a fork is not OK
>
> I would suggest to start with what is currently available and move on to
> fixing this as a 2nd step, if you want to.
>
>> and set it by the kcontrol from user,  For different soc chips, The
>> kcontrol value maybe different, The kcontrol configuration doesn't look
>> very friendly for user
>>
>> so we use dapm route path to manage it,
>> fe(fddr)->be(tdm)->(tocodec)->(codec),  and use the aux-devs to register,
>> and sound card only include the
>>
>> sound-dai = <&tdmif_a>
>>
>> codec-0 {
>>                  sound-dai = <&acodec>;
>>   };
>>
>> and not include
>>
>> codec-1 {
>>                  sound-dai = <&toacodec>;
>>   };
>>
>> when tdm work, only connect the tocodec path
>>
>>   "TDM_A Playback" ->"TOACODEC TDMA"->"TOACODEC INPUT SRC"
>>
>> iterate it find the be device ,and get the struct axg_tdm_stream, so we can
>> get the tdm clock id
>>
>> Take into account behavioral differences, we add new tocodec driver
>> for s4
> Still not seeing sufficient reason to make another driver.
it is a good idea, i will add the change for s4 base on g12a-toacodec.c
>
>>>> @@ -0,0 +1,376 @@
>>>> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
>>>> +/*
>>>> + * Copyright (C) 2023 Amlogic, Inc. All rights reserved
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/of_platform.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <sound/soc.h>
>>>> +#include <sound/soc-dai.h>
>>>> +#include <linux/init.h>
>>>> +#include <linux/kernel.h>
>>>> +#include<linux/kstrtox.h>
>>>> +#include <linux/clk-provider.h>
>>>> +#include <linux/reset.h>
>>>> +#include "axg-tdm.h"
>>>> +
>>>> +#define TOACODEC_CTRL0                       0x0
>>>> +
>>>> +#define CTRL0_ENABLE_SHIFT           31
>>>> +#define CTRL0_BCLK_ENABLE_SHIFT              30
>>>> +#define CTRL0_MCLK_ENABLE_SHIFT              29
>>>> +#define CTRL0_BLK_CAP_INV_SHIFT              9
>>>> +
>>>> +#define TDM_IFACE 0
>>>> +#define TDM_A_PAD 0
>>>> +#define TDM_B_PAD 1
>>>> +#define TDM_C_PAD 2
>>>> +
>>>> +struct toacodec {
>>>> +     struct regmap_field *field_dat_sel;
>>>> +     struct regmap_field *field_lrclk_sel;
>>>> +     struct regmap_field *field_bclk_sel;
>>>> +     struct regmap_field *field_mclk_sel;
>>>> +};
>>>> +
>>>> +struct toacodec_match_data {
>>>> +     const struct snd_soc_component_driver *component_drv;
>>>> +     const struct reg_field field_dat_sel;
>>>> +     const struct reg_field field_lrclk_sel;
>>>> +     const struct reg_field field_bclk_sel;
>>>> +     const struct reg_field field_mclk_sel;
>>>> +};
>>>> +
>>>> +static const struct regmap_config tocodec_regmap_cfg = {
>>>> +     .reg_bits       = 32,
>>>> +     .val_bits       = 32,
>>>> +     .reg_stride     = 4,
>>>> +     .max_register   = 0x1,
>>>> +};
>>>> +
>>>> +#define S4_LANE_OFFSET 8
>>>> +
>>>> +static const char * const s4_tocodec_lane_sel_texts[] = {
>>>> +     "Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", "Lane7"
>>>> +};
>>>> +
>>>> +static const struct soc_enum s4_tocodec_lane_sel_enum =
>>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_lane_sel_texts),
>>>> +                     s4_tocodec_lane_sel_texts);
>>>> +
>>>> +static const struct snd_kcontrol_new s4_tocodec_lane_sel =
>>>> +     SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
>>>> +
>>>> +static const char * const s4_tocodec_src_sel_texts[] = {
>>>> +     "TDMA", "TDMB", "TDMC"
>>>> +};
>>>> +
>>>> +static const struct soc_enum s4_tocodec_src_sel_enum =
>>>> +     SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_src_sel_texts),
>>>> +                     s4_tocodec_src_sel_texts);
>>>> +
>>>> +static const struct snd_kcontrol_new s4_tocodec_src_sel =
>>>> +     SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
>>>> +
>>>> +static const struct snd_kcontrol_new s4_toacodec_out_enable =
>>>> +     SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
>>>> +                                 CTRL0_ENABLE_SHIFT, 1, 0);
>>>> +
>>>> +static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
>>>> +{
>>>> +     struct snd_soc_dapm_path *p;
>>>> +     struct snd_soc_dai *be;
>>>> +
>>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>>> +             if (!p->connect)
>>>> +                     continue;
>>>> +             if (p->source->id == snd_soc_dapm_dai_in)
>>>> +                     return (struct snd_soc_dai *)p->source->priv;
>>>> +             be = tocodec_tdm_get_ahead_be(p->source);
>>>> +             if (be && be->id == TDM_IFACE)
>>>> +                     return be;
>>>> +     }
>>>> +     return NULL;
>>>> +}
>>>> +
>>>> +static unsigned int aml_simple_strtoull(const char *cp)
>>>> +{
>>>> +     unsigned int result = 0;
>>>> +     unsigned int value = 0;
>>>> +     unsigned int len = strlen(cp);
>>>> +
>>>> +     while (len != 0) {
>>>> +             len--;
>>>> +             value = isdigit(*cp);
>>>> +             if (value) {
>>>> +                     value = *cp - '0';
>>>> +             } else {
>>>> +                     cp++;
>>>> +                     continue;
>>>> +             }
>>>> +             cp++;
>>>> +             result = result * 10 + value;
>>>> +     }
>>>> +     return result;
>>>> +}
>>>> +
>>>> +static int aml_get_clk_id(const char *name)
>>>> +{
>>>> +     int clk_id = 0;
>>>> +
>>>> +     if (strstr(name, "mst_a"))
>>>> +             clk_id = 0;
>>>> +     else if (strstr(name, "mst_b"))
>>>> +             clk_id = 1;
>>>> +     else if (strstr(name, "mst_c"))
>>>> +             clk_id = 2;
>>>> +     else if (strstr(name, "mst_d"))
>>>> +             clk_id = 3;
>>>> +     else if (strstr(name, "mst_e"))
>>>> +             clk_id = 4;
>>>> +     else if (strstr(name, "mst_f"))
>>>> +             clk_id = 5;
>>>> +
>>>> +     return clk_id;
>>>> +}
>>>> +
>>>> +static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
>>>> +{
>>>> +     struct snd_soc_dai *be;
>>>> +     struct axg_tdm_stream *stream;
>>>> +     struct axg_tdm_iface *iface;
>>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>>> +     struct toacodec *priv = snd_soc_component_get_drvdata(component);
>>>> +     unsigned int tdm_id = TDM_A_PAD;
>>>> +     const char *dai_widget_name;
>>>> +     struct snd_soc_dapm_path *p;
>>>> +     unsigned int lane = 0;
>>>> +     unsigned int val = 0;
>>>> +     struct clk *sclk, *mclk;
>>>> +     char *clk_name;
>>>> +     int mclk_id, sclk_id;
>>>> +
>>>> +     be = tocodec_tdm_get_ahead_be(w);
>>>> +     if (!be) {
>>>> +             dev_err(component->dev, "%s not find the be\n", __func__);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +     stream = snd_soc_dai_dma_data_get_playback(be);
>>>> +     if (!stream) {
>>>> +             dev_err(component->dev, "%s not find the stream\n", __func__);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +     /*we like to use dai id, but it is fixed val*/
>>>> +     dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
>>>> +     if (strstr(dai_widget_name, "TDM_A"))
>>>> +             tdm_id = TDM_A_PAD;
>>>> +     else if (strstr(dai_widget_name, "TDM_B"))
>>>> +             tdm_id = TDM_B_PAD;
>>>> +     else if (strstr(dai_widget_name, "TDM_C"))
>>>> +             tdm_id = TDM_C_PAD;
>>>> +     snd_soc_dapm_widget_for_each_source_path(w, p) {
>>>> +             if (p->connect && p->name) {
>>>> +                     lane = aml_simple_strtoull(p->name);
>>>> +                     val = lane + tdm_id * S4_LANE_OFFSET;
>>>> +                     regmap_field_write(priv->field_dat_sel, val);
>>>> +             }
>>>> +     }
>>>> +     iface = stream->iface;
>>>> +     mclk = iface->mclk;
>>>> +     sclk = iface->sclk;
>>>> +     mclk_id = aml_get_clk_id(__clk_get_name(mclk));
>>>> +     sclk_id = aml_get_clk_id(__clk_get_name(sclk));
>>>> +     regmap_field_write(priv->field_mclk_sel, mclk_id);
>>>> +     regmap_field_write(priv->field_bclk_sel, sclk_id);
>>>> +     regmap_field_write(priv->field_lrclk_sel, sclk_id);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
>>>> +                          struct snd_kcontrol *control,
>>>> +                          int event)
>>>> +{
>>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>>> +     int ret = 0;
>>>> +
>>>> +     switch (event) {
>>>> +     case SND_SOC_DAPM_PRE_PMU:
>>>> +             ret = aml_tocodec_sel_set(w);
>>>> +             break;
>>>> +
>>>> +     case SND_SOC_DAPM_PRE_PMD:
>>>> +             break;
>>>> +
>>>> +     default:
>>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
>>>> +                           struct snd_kcontrol *control,
>>>> +                           int event)
>>>> +{
>>>> +     int ret = 0;
>>>> +     unsigned int mask = 0, val = 0;
>>>> +     struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
>>>> +
>>>> +     snd_soc_component_update_bits(component, TOACODEC_CTRL0,
>>>> +                                   1 << CTRL0_BLK_CAP_INV_SHIFT, 1 << CTRL0_BLK_CAP_INV_SHIFT);
>>>> +     switch (event) {
>>>> +     case SND_SOC_DAPM_PRE_PMU:
>>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>>> +             val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>>>> +             break;
>>>> +     case SND_SOC_DAPM_PRE_PMD:
>>>> +             mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
>>>> +             val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << CTRL0_BCLK_ENABLE_SHIFT;
>>>> +             snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
>>>> +             break;
>>>> +     default:
>>>> +             dev_err(component->dev, "Unexpected event %d\n", event);
>>>> +             return -EINVAL;
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
>>>> +     SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
>>>> +                        &s4_tocodec_lane_sel, tocodec_sel_event,
>>>> +                        (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>>>> +     SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, &s4_tocodec_src_sel),
>>>> +     SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
>>>> +                           &s4_toacodec_out_enable, tocodec_clk_enable,
>>>> +                             (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
>>>> +     SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
>>>> +     SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
>>>> +     SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
>>>> +     SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
>>>> +};
>>>> +
>>>> +static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
>>>> +     { "INPUT SRC", "TDMA", "TDMA"},
>>>> +     { "INPUT SRC", "TDMB", "TDMB"},
>>>> +     { "INPUT SRC", "TDMC", "TDMC"},
>>>> +     { "Lane0", NULL, "INPUT SRC" },
>>>> +     { "Lane1", NULL, "INPUT SRC"},
>>>> +     { "Lane2", NULL, "INPUT SRC"},
>>>> +     { "Lane3", NULL, "INPUT SRC"},
>>>> +     { "Lane4", NULL, "INPUT SRC"},
>>>> +     { "Lane5", NULL, "INPUT SRC"},
>>>> +     { "Lane6", NULL, "INPUT SRC"},
>>>> +     { "Lane7", NULL, "INPUT SRC"},
>>>> +     { "Lane SRC", "Lane0", "Lane0"},
>>>> +     { "Lane SRC", "Lane1", "Lane1"},
>>>> +     { "Lane SRC", "Lane2", "Lane2"},
>>>> +     { "Lane SRC", "Lane3", "Lane3"},
>>>> +     { "Lane SRC", "Lane4", "Lane4"},
>>>> +     { "Lane SRC", "Lane5", "Lane5"},
>>>> +     { "Lane SRC", "Lane6", "Lane6"},
>>>> +     { "Lane SRC", "Lane7", "Lane7"},
>>>> +     { "OUT EN", "Switch", "Lane SRC"},
>>>> +     { "TDM_TO_ACODEC", NULL, "OUT EN"},
>>>> +
>>>> +};
>>>> +
>>>> +static const struct snd_soc_component_driver s4_tocodec_component_drv = {
>>>> +     .dapm_widgets           = s4_toacodec_widgets,
>>>> +     .num_dapm_widgets       = ARRAY_SIZE(s4_toacodec_widgets),
>>>> +     .dapm_routes            = s4_tocodec_dapm_routes,
>>>> +     .num_dapm_routes        = ARRAY_SIZE(s4_tocodec_dapm_routes),
>>>> +};
>>>> +
>>>> +static const struct toacodec_match_data s4_toacodec_match_data = {
>>>> +     .component_drv  = &s4_tocodec_component_drv,
>>>> +     .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 16, 20),
>>>> +     .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
>>>> +     .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
>>>> +     .field_mclk_sel = REG_FIELD(TOACODEC_CTRL0, 0, 2),
>>>> +};
>>>> +
>>>> +static const struct of_device_id s4_tocodec_of_match[] = {
>>>> +     {
>>>> +             .compatible = "amlogic,s4-tocodec",
>>>> +             .data = &s4_toacodec_match_data,
>>>> +     }, {}
>>>> +};
>>>> +
>>>> +MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
>>>> +
>>>> +static int tocodec_probe(struct platform_device *pdev)
>>>> +{
>>>> +     const struct toacodec_match_data *data;
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct toacodec *priv;
>>>> +     void __iomem *regs;
>>>> +     struct regmap *map;
>>>> +     int ret;
>>>> +
>>>> +     data = device_get_match_data(dev);
>>>> +     if (!data)
>>>> +             return dev_err_probe(dev, -ENODEV, "failed to match device\n");
>>>> +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>>> +     if (!priv)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     platform_set_drvdata(pdev, priv);
>>>> +
>>>> +     ret = device_reset(dev);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     regs = devm_platform_ioremap_resource(pdev, 0);
>>>> +     if (IS_ERR(regs))
>>>> +             return PTR_ERR(regs);
>>>> +
>>>> +     map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
>>>> +     if (IS_ERR(map))
>>>> +             return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
>>>> +
>>>> +     priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
>>>> +     if (IS_ERR(priv->field_dat_sel))
>>>> +             return PTR_ERR(priv->field_dat_sel);
>>>> +
>>>> +     priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
>>>> +     if (IS_ERR(priv->field_lrclk_sel))
>>>> +             return PTR_ERR(priv->field_lrclk_sel);
>>>> +
>>>> +     priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
>>>> +     if (IS_ERR(priv->field_bclk_sel))
>>>> +             return PTR_ERR(priv->field_bclk_sel);
>>>> +
>>>> +     priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, data->field_mclk_sel);
>>>> +     if (IS_ERR(priv->field_mclk_sel))
>>>> +             return PTR_ERR(priv->field_mclk_sel);
>>>> +
>>>> +     return devm_snd_soc_register_component(dev,
>>>> +                     data->component_drv, NULL, 0);
>>>> +}
>>>> +
>>>> +static struct platform_driver tocodec_pdrv = {
>>>> +     .probe = tocodec_probe,
>>>> +     .driver = {
>>>> +             .name = "s4-tocodec",
>>>> +             .of_match_table = s4_tocodec_of_match,
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(tocodec_pdrv);
>>>> +
>>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>>> +MODULE_LICENSE("GPL");
>>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>>> index 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 100644
>>>> --- a/sound/soc/meson/t9015.c
>>>> +++ b/sound/soc/meson/t9015.c
>>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>>                 .channels_min = 1,
>>>>                 .channels_max = 2,
>>>>                 .rates = SNDRV_PCM_RATE_8000_96000,
>>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
>>> Again, mixed up changes with zero justification.
>>>
>>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>>> which is known to work and add S32 support on an HW documented as 24bits
>>> only. Can you explain ?
>>>
>>>>         },
>>>>         .ops = &t9015_dai_ops,
>>>>    };
>>> --
>>> Jerome
> --
> Jerome
Jerome Brunet Jan. 14, 2025, 2:05 p.m. UTC | #7
On Tue 14 Jan 2025 at 19:20, Jiebing Chen <jiebing.chen@amlogic.com> wrote:

>>>> +
>>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>>> +MODULE_LICENSE("GPL");
>>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>>> index
>>>> 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648
>>>> 100644
>>>> --- a/sound/soc/meson/t9015.c
>>>> +++ b/sound/soc/meson/t9015.c
>>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>>                .channels_min = 1,
>>>>                .channels_max = 2,
>>>>                .rates = SNDRV_PCM_RATE_8000_96000,
>>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE |
>>>> SNDRV_PCM_FMTBIT_S32_LE),
>>> Again, mixed up changes with zero justification.
>>>
>>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>>> which is known to work and add S32 support on an HW documented as 24bits
>>> only. Can you explain ?
>
> for g12a, sm1 etc, it is use new audio ip, GXL is old ip,

If there are chips difference we did not know about, then you should
introduce those difference, without breaking existing support -
including for GXL, which is what you did IIUC.

> the new ip not support 24 bit,

Are sure about that ? that code has been there for a while.

If sm1 does not support SNDRV_PCM_FMTBIT_S24_LE, you should a fix up patch for
that, with the proper "Fixes:" tag, how to reproduce the problem and
explaining the fix.

>
> usually support 16/32 bit for new audio ip , for SNDRV_PCM_FMTBIT_S24_LE,
> it width =24, phy =32

Yes physical of SNDRV_PCM_FMTBIT_S24_LE, so most chip supporting 32 bits
width would support this S24_LE, unless there is something odd.

>
> it was  treated as 32 bit to send for tdm, so we can only add the S32LE
> base on it , right ?

You are asking me ? How am I suppose to know ?

> but if the gxl not support the 32bit

I don't see a problem with a DAC taking input on 32bits physical
interface and ignoring some bit on processing.

If that's not the case, please send a proper fix change with some explanation

>
> we need add new snd_soc_dai_driver t9015_dai_s4 ?
>

If I understood correctly format depends on the chip and needs to
adjusted including for sm1. 

>>>
>>>>        },
>>>>        .ops = &t9015_dai_ops,
>>>>   };
>>> -- Jerome
Jiebing Chen Jan. 15, 2025, 2:56 a.m. UTC | #8
在 2025/1/14 22:05, Jerome Brunet 写道:
> [ EXTERNAL EMAIL ]
>
> On Tue 14 Jan 2025 at 19:20, Jiebing Chen <jiebing.chen@amlogic.com> wrote:
>
>>>>> +
>>>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>>>> +MODULE_LICENSE("GPL");
>>>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>>>> index
>>>>> 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648
>>>>> 100644
>>>>> --- a/sound/soc/meson/t9015.c
>>>>> +++ b/sound/soc/meson/t9015.c
>>>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>>>                 .channels_min = 1,
>>>>>                 .channels_max = 2,
>>>>>                 .rates = SNDRV_PCM_RATE_8000_96000,
>>>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE |
>>>>> SNDRV_PCM_FMTBIT_S32_LE),
>>>> Again, mixed up changes with zero justification.
>>>>
>>>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>>>> which is known to work and add S32 support on an HW documented as 24bits
>>>> only. Can you explain ?
>> for g12a, sm1 etc, it is use new audio ip, GXL is old ip,
> If there are chips difference we did not know about, then you should
> introduce those difference, without breaking existing support -
> including for GXL, which is what you did IIUC.
>
>> the new ip not support 24 bit,
> Are sure about that ? that code has been there for a while.
>
> If sm1 does not support SNDRV_PCM_FMTBIT_S24_LE, you should a fix up patch for
> that, with the proper "Fixes:" tag, how to reproduce the problem and
> explaining the fix.

maybe there are some gap , we support SNDRV_PCM_FMTBIT_S24, not support the

SNDRV_PCM_FMTBIT_S24_3LE,  for SNDRV_PCM_FMTBIT_S24

it is  Signed, 24-bit (32-bit in memory), little endian , the audio dma 
busrt is 64bit

it can get the full data. we send the 32 bit data  mclk = 32bit* 48k 
*4,  use the clk to send

the  SNDRV_PCM_FMTBIT_S24,   the hadware always send the 32bit data

so, i think we only add the SNDRV_PCM_FMTBIT_S32 base on it

we think the 24 bit is the SNDRV_PCM_FMTBIT_S24_3LE, it is 24bit in memroy,

due to the dma busrt 64 bit limit, it can't align the sample bit, if it 
is 24 bit

so the clock configure can't 24bit clock, by the way, We discuss 
internally for gxl,

it also support the SNDRV_PCM_FMTBIT_S32


>
>> usually support 16/32 bit for new audio ip , for SNDRV_PCM_FMTBIT_S24_LE,
>> it width =24, phy =32
> Yes physical of SNDRV_PCM_FMTBIT_S24_LE, so most chip supporting 32 bits
> width would support this S24_LE, unless there is something odd.
>
>> it was  treated as 32 bit to send for tdm, so we can only add the S32LE
>> base on it , right ?
> You are asking me ? How am I suppose to know ?
>
>> but if the gxl not support the 32bit
> I don't see a problem with a DAC taking input on 32bits physical
> interface and ignoring some bit on processing.
>
> If that's not the case, please send a proper fix change with some explanation
>
>> we need add new snd_soc_dai_driver t9015_dai_s4 ?
>>
> If I understood correctly format depends on the chip and needs to
> adjusted including for sm1.
>
>>>>>         },
>>>>>         .ops = &t9015_dai_ops,
>>>>>    };
>>>> -- Jerome
> --
> Jerome
Jerome Brunet Jan. 15, 2025, 8:36 a.m. UTC | #9
On Wed 15 Jan 2025 at 10:56, Jiebing Chen <jiebing.chen@amlogic.com> wrote:

> 在 2025/1/14 22:05, Jerome Brunet 写道:
>> [ EXTERNAL EMAIL ]
>>
>> On Tue 14 Jan 2025 at 19:20, Jiebing Chen <jiebing.chen@amlogic.com> wrote:
>>
>>>>>> +
>>>>>> +MODULE_DESCRIPTION("Amlogic to codec driver");
>>>>>> +MODULE_AUTHOR("jiebing.chen@amlogic.com");
>>>>>> +MODULE_LICENSE("GPL");
>>>>>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>>>>>> index
>>>>>> 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648
>>>>>> 100644
>>>>>> --- a/sound/soc/meson/t9015.c
>>>>>> +++ b/sound/soc/meson/t9015.c
>>>>>> @@ -89,10 +89,7 @@ static struct snd_soc_dai_driver t9015_dai = {
>>>>>>                 .channels_min = 1,
>>>>>>                 .channels_max = 2,
>>>>>>                 .rates = SNDRV_PCM_RATE_8000_96000,
>>>>>> -             .formats = (SNDRV_PCM_FMTBIT_S8 |
>>>>>> -                         SNDRV_PCM_FMTBIT_S16_LE |
>>>>>> -                         SNDRV_PCM_FMTBIT_S20_LE |
>>>>>> -                         SNDRV_PCM_FMTBIT_S24_LE),
>>>>>> +             .formats = (SNDRV_PCM_FMTBIT_S16_LE |
>>>>>> SNDRV_PCM_FMTBIT_S32_LE),
>>>>> Again, mixed up changes with zero justification.
>>>>>
>>>>> This drops S8 and S16 format support for the existing SoCs (such as GXL)
>>>>> which is known to work and add S32 support on an HW documented as 24bits
>>>>> only. Can you explain ?
>>> for g12a, sm1 etc, it is use new audio ip, GXL is old ip,
>> If there are chips difference we did not know about, then you should
>> introduce those difference, without breaking existing support -
>> including for GXL, which is what you did IIUC.
>>
>>> the new ip not support 24 bit,
>> Are sure about that ? that code has been there for a while.
>>
>> If sm1 does not support SNDRV_PCM_FMTBIT_S24_LE, you should a fix up patch for
>> that, with the proper "Fixes:" tag, how to reproduce the problem and
>> explaining the fix.
>
> maybe there are some gap , we support SNDRV_PCM_FMTBIT_S24, not support the
>
> SNDRV_PCM_FMTBIT_S24_3LE,  for SNDRV_PCM_FMTBIT_S24
>
> it is  Signed, 24-bit (32-bit in memory), little endian , the audio dma
> busrt is 64bit

It makes absolutely no sense to discuss memory layout for the codec.

>
> it can get the full data. we send the 32 bit data  mclk = 32bit* 48k *4, 
> use the clk to send
>
> the  SNDRV_PCM_FMTBIT_S24,   the hadware always send the 32bit data

No it does not. It send 24 bits of data over a 32 bits physical word with
8 bits ignored.

>
> so, i think we only add the SNDRV_PCM_FMTBIT_S32 base on it

That's wrong if the codec does not actually use the full 32bits ... and
I have clear indication that's what the codec is doing, on GXL at least.

>
> we think the 24 bit is the SNDRV_PCM_FMTBIT_S24_3LE, it is 24bit in memroy,
>
> due to the dma busrt 64 bit limit, it can't align the sample bit, if it is
> 24 bit

Again, memory layout makes no sense here.

>
> so the clock configure can't 24bit clock,

I disagree and this has been tested. If you have a test case showing
otherwise please share it.

> by the way, We discuss internally for gxl,
>
> it also support the SNDRV_PCM_FMTBIT_S32
>

Does it really ? If it is just to ignore the 8bits LSB, that not a support.

>
>>
>>> usually support 16/32 bit for new audio ip , for SNDRV_PCM_FMTBIT_S24_LE,
>>> it width =24, phy =32
>> Yes physical of SNDRV_PCM_FMTBIT_S24_LE, so most chip supporting 32 bits
>> width would support this S24_LE, unless there is something odd.
>>
>>> it was  treated as 32 bit to send for tdm, so we can only add the S32LE
>>> base on it , right ?
>> You are asking me ? How am I suppose to know ?
>>
>>> but if the gxl not support the 32bit
>> I don't see a problem with a DAC taking input on 32bits physical
>> interface and ignoring some bit on processing.
>>
>> If that's not the case, please send a proper fix change with some explanation
>>
>>> we need add new snd_soc_dai_driver t9015_dai_s4 ?
>>>
>> If I understood correctly format depends on the chip and needs to
>> adjusted including for sm1.
>>
>>>>>>         },
>>>>>>         .ops = &t9015_dai_ops,
>>>>>>    };
>>>>> -- Jerome
>> --
>> Jerome
diff mbox series

Patch

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 6458d5dc4902f665211bb9e4ae7d274e4bff2fdc..d01e284642fd987cf4bdf88e5bf5f7c9a241af59 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -69,6 +69,8 @@  config SND_MESON_AXG_SOUND_CARD
 	imply SND_MESON_AXG_SPDIFIN
 	imply SND_MESON_AXG_PDM
 	imply SND_MESON_G12A_TOACODEC
+	imply SND_SOC_MESON_PAD_OUT
+	imply SND_SOC_MESON_TOCODEC_CONTROL
 	imply SND_MESON_G12A_TOHDMITX if DRM_MESON_DW_HDMI
 	help
 	  Select Y or M to add support for the AXG SoC sound card
@@ -135,4 +137,18 @@  config SND_SOC_MESON_T9015
 	help
 	  Say Y or M if you want to add support for the internal DAC found
 	  on GXL, G12 and SM1 SoC family.
+
+config SND_SOC_MESON_PAD_OUT
+	tristate "Amlogic PAD OUT"
+	select REGMAP_MMIO
+	help
+	  Say Y or M if you want to add support for the S4 Audio Output from
+	  the data Pad.
+
+config SND_SOC_MESON_TOCODEC_CONTROL
+	tristate "Amlogic TOCODEC CONTROL"
+	select REGMAP_MMIO
+	help
+	 Say Y or M if you want to add support for the internal DAC control
+	 on SM1 SoC family.
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 24078e4396b02d545d8ba4bcb1632979001354e3..afbefcb1313670f9b1365e88b8eb1a0badd7dc1e 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -24,8 +24,11 @@  snd-soc-meson-codec-glue-y := meson-codec-glue.o
 snd-soc-meson-gx-sound-card-y := gx-card.o
 snd-soc-meson-g12a-toacodec-y := g12a-toacodec.o
 snd-soc-meson-g12a-tohdmitx-y := g12a-tohdmitx.o
+snd-soc-meson-s4-padout-objs := s4-pad-out-control.o
+snd-soc-meson-s4-tocodec-control-objs := s4-tocodec-control.o
 snd-soc-meson-t9015-y := t9015.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
@@ -43,4 +46,7 @@  obj-$(CONFIG_SND_MESON_CODEC_GLUE) += snd-soc-meson-codec-glue.o
 obj-$(CONFIG_SND_MESON_GX_SOUND_CARD) += snd-soc-meson-gx-sound-card.o
 obj-$(CONFIG_SND_MESON_G12A_TOACODEC) += snd-soc-meson-g12a-toacodec.o
 obj-$(CONFIG_SND_MESON_G12A_TOHDMITX) += snd-soc-meson-g12a-tohdmitx.o
+obj-$(CONFIG_SND_SOC_MESON_PAD_OUT) += snd-soc-meson-s4-padout.o
+obj-$(CONFIG_SND_SOC_MESON_TOCODEC_CONTROL) += snd-soc-meson-s4-tocodec-control.o
 obj-$(CONFIG_SND_SOC_MESON_T9015) += snd-soc-meson-t9015.o
+
diff --git a/sound/soc/meson/s4-pad-out-control.c b/sound/soc/meson/s4-pad-out-control.c
new file mode 100644
index 0000000000000000000000000000000000000000..a86dcf8a5995926f0ddf8d2911f42006daed0feb
--- /dev/null
+++ b/sound/soc/meson/s4-pad-out-control.c
@@ -0,0 +1,372 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include<linux/kstrtox.h>
+
+#include "axg-tdm.h"
+
+static const struct regmap_config tdmout_pad_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= 0x28,
+};
+
+#define TDM_IFACE 0
+#define TDM_A_PAD 0
+#define TDM_B_PAD 1
+#define TDM_C_PAD 2
+
+#define EE_AUDIO_DAT_PAD_CTRL6 0x0
+#define EE_AUDIO_DAT_PAD_CTRL7 0x4
+#define EE_AUDIO_DAT_PAD_CTRL8 0x8
+#define EE_AUDIO_DAT_PAD_CTRL9 0xc
+#define EE_AUDIO_DAT_PAD_CTRLA 0x10
+#define EE_AUDIO_DAT_PAD_CTRLB 0x14
+#define EE_AUDIO_DAT_PAD_CTRLC 0x1c
+#define EE_AUDIO_DAT_PAD_CTRLD 0x20
+#define EE_AUDIO_DAT_PAD_CTRLE 0x24
+#define EE_AUDIO_DAT_PAD_CTRLF 0x28
+
+#define REG_OFFSET 4
+
+static const char * const s4_tdmout_sel_texts[] = {
+	"TDM_D0", "TDM_D1", "TDM_D2", "TDM_D3", "TDM_D4", "TDM_D5", "TDM_D6", "TDM_D7",
+	"TDM_D8", "TDM_D9", "TDM_D10", "TDM_D11", "TDM_D12", "TDM_D13", "TDM_D14", "TDM_D15",
+	"TDM_D16", "TDM_D17", "TDM_D18", "TDM_D19", "TDM_D20", "TDM_D21", "TDM_D22", "TDM_D23",
+	"TDM_D24", "TDM_D25", "TDM_D26", "TDM_D27", "TDM_D28", "TDM_D29", "TDM_D30", "TDM_D31"
+};
+
+static const struct soc_enum tdmout_sel_enum =
+	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tdmout_sel_texts),
+			s4_tdmout_sel_texts);
+
+static struct snd_soc_dai *tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_source_path(w, p) {
+		if (p->source->id == snd_soc_dapm_dai_in)
+			return (struct snd_soc_dai *)p->source->priv;
+		be = tdm_get_ahead_be(p->source);
+		if (be && be->id == TDM_IFACE)
+			return be;
+	}
+	return NULL;
+}
+
+#define SND_SOC_DAPM_DEMUX_E(wname, wreg, wshift, winvert, wcontrols, \
+	wevent, wflags) \
+((struct snd_soc_dapm_widget) { \
+	.id = snd_soc_dapm_demux, .name = wname, \
+	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+	.kcontrol_news = wcontrols, .num_kcontrols = 1, \
+	.event = wevent, .event_flags = wflags})
+
+static const struct snd_kcontrol_new tdmout_sel_demux[] = {
+	SOC_DAPM_ENUM("TDMOUTA SEL", tdmout_sel_enum),
+	SOC_DAPM_ENUM("TDMOUTB SEL", tdmout_sel_enum),
+	SOC_DAPM_ENUM("TDMOUTC SEL", tdmout_sel_enum),
+};
+
+static unsigned int aml_simple_strtoull(const char *cp)
+{
+	unsigned int result = 0;
+	unsigned int value = 0;
+	unsigned int len =  strlen(cp);
+
+	while (len != 0) {
+		len--;
+		value = isdigit(*cp);
+		if (value) {
+			value = *cp - '0';
+		} else {
+			cp++;
+			continue;
+		}
+		cp++;
+		result = result * 10 + value;
+	}
+	return result;
+}
+
+static int tdm_out_pad_set(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be;
+	struct axg_tdm_stream *stream;
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	unsigned int tdm_id = TDM_A_PAD;
+	const char *dai_widget_name;
+	struct snd_soc_dapm_path *p;
+	unsigned int lane_num = 0;
+	unsigned long pin = 0;
+	unsigned int reg, mask, val = 0;
+	int lane_cnt;
+
+	be = tdm_get_ahead_be(w);
+	if (!be) {
+		dev_err(component->dev, "%s not find the be\n", __func__);
+		return -EINVAL;
+	}
+	stream = snd_soc_dai_dma_data_get_playback(be);
+	if (!stream) {
+		dev_err(component->dev, "%s not find the stream\n", __func__);
+		return -EINVAL;
+	}
+	lane_cnt = (stream->channels - 1) / stream->iface->slots + 1;
+	/*we like to use dai id, but it is fixed val*/
+	dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
+	if (strstr(dai_widget_name, "TDM_A"))
+		tdm_id = TDM_A_PAD;
+	else if (strstr(dai_widget_name, "TDM_B"))
+		tdm_id = TDM_B_PAD;
+	else if (strstr(dai_widget_name, "TDM_C"))
+		tdm_id = TDM_C_PAD;
+	else
+		dev_err(component->dev, "%s not find the be dai widget\n", __func__);
+	dev_dbg(component->dev, "tdm_id:%d, channel:%d, slot:%d\n",
+		tdm_id, stream->channels, stream->iface->slots);
+	snd_soc_dapm_widget_for_each_sink_path(w, p) {
+		if (p->sink->id == snd_soc_dapm_output) {
+			if (p->connect) {
+				pin = aml_simple_strtoull(p->name);
+				reg = (pin / 4) * REG_OFFSET;
+				/*calculate mask pos */
+				mask = 0x1f << ((pin % 4) * 8);
+				val = tdm_id * 8 + lane_num;
+				snd_soc_component_update_bits(component, reg, mask, val);
+				snd_soc_component_update_bits(component, EE_AUDIO_DAT_PAD_CTRLF,
+							      0x1 << pin, 0 << pin);
+				lane_num++;
+				if (lane_num > lane_cnt - 1)
+					break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int tdmout_sel_pad_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *control,
+				int event)
+{
+	int ret = 0;
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		tdm_out_pad_set(w);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		break;
+
+	default:
+		dev_err(component->dev, "Unexpected event %d\n", event);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget s4_tdmout_pad_dapm_widgets[] = {
+	SND_SOC_DAPM_DEMUX_E("TDMA_OUT SEL", SND_SOC_NOPM, 0, 0,
+			     &tdmout_sel_demux[TDM_A_PAD], tdmout_sel_pad_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_DEMUX_E("TDMB_OUT SEL", SND_SOC_NOPM, 0, 0,
+			     &tdmout_sel_demux[TDM_B_PAD], tdmout_sel_pad_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_DEMUX_E("TDMC_OUT SEL", SND_SOC_NOPM, 0, 0,
+			     &tdmout_sel_demux[TDM_C_PAD], tdmout_sel_pad_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_OUTPUT("TDM_D0"),
+	SND_SOC_DAPM_OUTPUT("TDM_D1"),
+	SND_SOC_DAPM_OUTPUT("TDM_D2"),
+	SND_SOC_DAPM_OUTPUT("TDM_D3"),
+	SND_SOC_DAPM_OUTPUT("TDM_D4"),
+	SND_SOC_DAPM_OUTPUT("TDM_D5"),
+	SND_SOC_DAPM_OUTPUT("TDM_D6"),
+	SND_SOC_DAPM_OUTPUT("TDM_D7"),
+	SND_SOC_DAPM_OUTPUT("TDM_D8"),
+	SND_SOC_DAPM_OUTPUT("TDM_D9"),
+	SND_SOC_DAPM_OUTPUT("TDM_D10"),
+	SND_SOC_DAPM_OUTPUT("TDM_D11"),
+	SND_SOC_DAPM_OUTPUT("TDM_D12"),
+	SND_SOC_DAPM_OUTPUT("TDM_D13"),
+	SND_SOC_DAPM_OUTPUT("TDM_D14"),
+	SND_SOC_DAPM_OUTPUT("TDM_D15"),
+	SND_SOC_DAPM_OUTPUT("TDM_D16"),
+	SND_SOC_DAPM_OUTPUT("TDM_D17"),
+	SND_SOC_DAPM_OUTPUT("TDM_D18"),
+	SND_SOC_DAPM_OUTPUT("TDM_D19"),
+	SND_SOC_DAPM_OUTPUT("TDM_D20"),
+	SND_SOC_DAPM_OUTPUT("TDM_D21"),
+	SND_SOC_DAPM_OUTPUT("TDM_D22"),
+	SND_SOC_DAPM_OUTPUT("TDM_D23"),
+	SND_SOC_DAPM_OUTPUT("TDM_D24"),
+	SND_SOC_DAPM_OUTPUT("TDM_D25"),
+	SND_SOC_DAPM_OUTPUT("TDM_D26"),
+	SND_SOC_DAPM_OUTPUT("TDM_D27"),
+	SND_SOC_DAPM_OUTPUT("TDM_D28"),
+	SND_SOC_DAPM_OUTPUT("TDM_D29"),
+	SND_SOC_DAPM_OUTPUT("TDM_D30"),
+	SND_SOC_DAPM_OUTPUT("TDM_D31"),
+};
+
+static const struct snd_soc_dapm_route s4_tdmout_pad_dapm_routes[] = {
+	{ "TDM_D0", "TDM_D0", "TDMA_OUT SEL" },
+	{ "TDM_D1", "TDM_D1", "TDMA_OUT SEL" },
+	{ "TDM_D2", "TDM_D2", "TDMA_OUT SEL" },
+	{ "TDM_D3", "TDM_D3", "TDMA_OUT SEL" },
+	{ "TDM_D4", "TDM_D4", "TDMA_OUT SEL" },
+	{ "TDM_D5", "TDM_D5", "TDMA_OUT SEL" },
+	{ "TDM_D6", "TDM_D6", "TDMA_OUT SEL" },
+	{ "TDM_D7", "TDM_D7", "TDMA_OUT SEL" },
+	{ "TDM_D8", "TDM_D8", "TDMA_OUT SEL" },
+	{ "TDM_D9", "TDM_D9", "TDMA_OUT SEL" },
+	{ "TDM_D10", "TDM_D10", "TDMA_OUT SEL" },
+	{ "TDM_D11", "TDM_D11", "TDMA_OUT SEL" },
+	{ "TDM_D12", "TDM_D12", "TDMA_OUT SEL" },
+	{ "TDM_D13", "TDM_D13", "TDMA_OUT SEL" },
+	{ "TDM_D14", "TDM_D14", "TDMA_OUT SEL" },
+	{ "TDM_D15", "TDM_D15", "TDMA_OUT SEL" },
+	{ "TDM_D16", "TDM_D16", "TDMA_OUT SEL" },
+	{ "TDM_D17", "TDM_D17", "TDMA_OUT SEL" },
+	{ "TDM_D18", "TDM_D18", "TDMA_OUT SEL" },
+	{ "TDM_D19", "TDM_D19", "TDMA_OUT SEL" },
+	{ "TDM_D20", "TDM_D20", "TDMA_OUT SEL" },
+	{ "TDM_D21", "TDM_D21", "TDMA_OUT SEL" },
+	{ "TDM_D22", "TDM_D22", "TDMA_OUT SEL" },
+	{ "TDM_D23", "TDM_D23", "TDMA_OUT SEL" },
+	{ "TDM_D24", "TDM_D24", "TDMA_OUT SEL" },
+	{ "TDM_D25", "TDM_D25", "TDMA_OUT SEL" },
+	{ "TDM_D26", "TDM_D26", "TDMA_OUT SEL" },
+	{ "TDM_D27", "TDM_D27", "TDMA_OUT SEL" },
+	{ "TDM_D28", "TDM_D28", "TDMA_OUT SEL" },
+	{ "TDM_D29", "TDM_D29", "TDMA_OUT SEL" },
+	{ "TDM_D30", "TDM_D30", "TDMA_OUT SEL" },
+	{ "TDM_D31", "TDM_D31", "TDMA_OUT SEL" },
+	{ "TDM_D0", "TDM_D0", "TDMB_OUT SEL" },
+	{ "TDM_D1", "TDM_D1", "TDMB_OUT SEL" },
+	{ "TDM_D2", "TDM_D2", "TDMB_OUT SEL" },
+	{ "TDM_D3", "TDM_D3", "TDMB_OUT SEL" },
+	{ "TDM_D4", "TDM_D4", "TDMB_OUT SEL" },
+	{ "TDM_D5", "TDM_D5", "TDMB_OUT SEL" },
+	{ "TDM_D6", "TDM_D6", "TDMB_OUT SEL" },
+	{ "TDM_D7", "TDM_D7", "TDMB_OUT SEL" },
+	{ "TDM_D8", "TDM_D8", "TDMB_OUT SEL" },
+	{ "TDM_D9", "TDM_D9", "TDMB_OUT SEL" },
+	{ "TDM_D10", "TDM_D10", "TDMB_OUT SEL" },
+	{ "TDM_D11", "TDM_D11", "TDMB_OUT SEL" },
+	{ "TDM_D12", "TDM_D12", "TDMB_OUT SEL" },
+	{ "TDM_D13", "TDM_D13", "TDMB_OUT SEL" },
+	{ "TDM_D14", "TDM_D14", "TDMB_OUT SEL" },
+	{ "TDM_D15", "TDM_D15", "TDMB_OUT SEL" },
+
+	{ "TDM_D16", "TDM_D16", "TDMB_OUT SEL" },
+	{ "TDM_D17", "TDM_D17", "TDMB_OUT SEL" },
+	{ "TDM_D18", "TDM_D18", "TDMB_OUT SEL" },
+	{ "TDM_D19", "TDM_D19", "TDMB_OUT SEL" },
+	{ "TDM_D20", "TDM_D20", "TDMB_OUT SEL" },
+	{ "TDM_D21", "TDM_D21", "TDMB_OUT SEL" },
+	{ "TDM_D22", "TDM_D22", "TDMB_OUT SEL" },
+	{ "TDM_D23", "TDM_D23", "TDMB_OUT SEL" },
+	{ "TDM_D24", "TDM_D24", "TDMB_OUT SEL" },
+	{ "TDM_D25", "TDM_D25", "TDMB_OUT SEL" },
+	{ "TDM_D26", "TDM_D26", "TDMB_OUT SEL" },
+	{ "TDM_D27", "TDM_D27", "TDMB_OUT SEL" },
+	{ "TDM_D28", "TDM_D28", "TDMB_OUT SEL" },
+	{ "TDM_D29", "TDM_D29", "TDMB_OUT SEL" },
+	{ "TDM_D30", "TDM_D30", "TDMB_OUT SEL" },
+	{ "TDM_D31", "TDM_D31", "TDMB_OUT SEL" },
+	{ "TDM_D0", "TDM_D0", "TDMC_OUT SEL" },
+	{ "TDM_D1", "TDM_D1", "TDMC_OUT SEL" },
+	{ "TDM_D2", "TDM_D2", "TDMC_OUT SEL" },
+	{ "TDM_D3", "TDM_D3", "TDMC_OUT SEL" },
+	{ "TDM_D4", "TDM_D4", "TDMC_OUT SEL" },
+	{ "TDM_D5", "TDM_D5", "TDMC_OUT SEL" },
+	{ "TDM_D6", "TDM_D6", "TDMC_OUT SEL" },
+	{ "TDM_D7", "TDM_D7", "TDMC_OUT SEL" },
+	{ "TDM_D8", "TDM_D8", "TDMC_OUT SEL" },
+	{ "TDM_D9", "TDM_D9", "TDMC_OUT SEL" },
+	{ "TDM_D10", "TDM_D10", "TDMC_OUT SEL" },
+	{ "TDM_D11", "TDM_D11", "TDMC_OUT SEL" },
+	{ "TDM_D12", "TDM_D12", "TDMC_OUT SEL" },
+	{ "TDM_D13", "TDM_D13", "TDMC_OUT SEL" },
+	{ "TDM_D14", "TDM_D14", "TDMC_OUT SEL" },
+	{ "TDM_D15", "TDM_D15", "TDMC_OUT SEL" },
+	{ "TDM_D16", "TDM_D16", "TDMC_OUT SEL" },
+	{ "TDM_D17", "TDM_D17", "TDMC_OUT SEL" },
+	{ "TDM_D18", "TDM_D18", "TDMC_OUT SEL" },
+	{ "TDM_D19", "TDM_D19", "TDMC_OUT SEL" },
+	{ "TDM_D20", "TDM_D20", "TDMC_OUT SEL" },
+	{ "TDM_D21", "TDM_D21", "TDMC_OUT SEL" },
+	{ "TDM_D22", "TDM_D22", "TDMC_OUT SEL" },
+	{ "TDM_D23", "TDM_D23", "TDMC_OUT SEL" },
+	{ "TDM_D24", "TDM_D24", "TDMC_OUT SEL" },
+	{ "TDM_D25", "TDM_D25", "TDMC_OUT SEL" },
+	{ "TDM_D26", "TDM_D26", "TDMC_OUT SEL" },
+	{ "TDM_D27", "TDM_D27", "TDMC_OUT SEL" },
+	{ "TDM_D28", "TDM_D28", "TDMC_OUT SEL" },
+	{ "TDM_D29", "TDM_D29", "TDMC_OUT SEL" },
+	{ "TDM_D30", "TDM_D30", "TDMC_OUT SEL" },
+	{ "TDM_D31", "TDM_D31", "TDMC_OUT SEL" },
+};
+
+static const struct snd_soc_component_driver s4_tdmout_pad_component_drv = {
+	.dapm_widgets		= s4_tdmout_pad_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(s4_tdmout_pad_dapm_widgets),
+	.dapm_routes		= s4_tdmout_pad_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(s4_tdmout_pad_dapm_routes),
+
+};
+
+static const struct of_device_id s4_tdmout_pad_of_match[] = {
+	{
+		.compatible = "amlogic,s4-tdmout-pad",
+	}, {}
+};
+
+MODULE_DEVICE_TABLE(of, s4_tdmout_pad_of_match);
+
+static int tdm_pad_out_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct regmap *map;
+	void __iomem *regs;
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	map = devm_regmap_init_mmio(dev, regs, &tdmout_pad_regmap_cfg);
+	if (IS_ERR(map))
+		return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
+
+	return devm_snd_soc_register_component(dev, &s4_tdmout_pad_component_drv,
+					       NULL, 0);
+}
+
+static struct platform_driver tdmout_pad_pdrv = {
+	.probe = tdm_pad_out_probe,
+	.driver = {
+		.name = "s4-pad-out",
+		.of_match_table = s4_tdmout_pad_of_match,
+	},
+};
+
+module_platform_driver(tdmout_pad_pdrv);
+
+MODULE_DESCRIPTION("Amlogic TDM PAD DRIVER");
+MODULE_AUTHOR("jiebing.chen@amlogic.com");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/meson/s4-tocodec-control.c b/sound/soc/meson/s4-tocodec-control.c
new file mode 100644
index 0000000000000000000000000000000000000000..e5d824fae0eba545d38dc36e2566e7cee590e7f5
--- /dev/null
+++ b/sound/soc/meson/s4-tocodec-control.c
@@ -0,0 +1,376 @@ 
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2023 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include<linux/kstrtox.h>
+#include <linux/clk-provider.h>
+#include <linux/reset.h>
+#include "axg-tdm.h"
+
+#define TOACODEC_CTRL0			0x0
+
+#define CTRL0_ENABLE_SHIFT		31
+#define CTRL0_BCLK_ENABLE_SHIFT		30
+#define CTRL0_MCLK_ENABLE_SHIFT		29
+#define CTRL0_BLK_CAP_INV_SHIFT		9
+
+#define TDM_IFACE 0
+#define TDM_A_PAD 0
+#define TDM_B_PAD 1
+#define TDM_C_PAD 2
+
+struct toacodec {
+	struct regmap_field *field_dat_sel;
+	struct regmap_field *field_lrclk_sel;
+	struct regmap_field *field_bclk_sel;
+	struct regmap_field *field_mclk_sel;
+};
+
+struct toacodec_match_data {
+	const struct snd_soc_component_driver *component_drv;
+	const struct reg_field field_dat_sel;
+	const struct reg_field field_lrclk_sel;
+	const struct reg_field field_bclk_sel;
+	const struct reg_field field_mclk_sel;
+};
+
+static const struct regmap_config tocodec_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= 0x1,
+};
+
+#define S4_LANE_OFFSET 8
+
+static const char * const s4_tocodec_lane_sel_texts[] = {
+	"Lane0", "Lane1", "Lane2", "Lane3", "Lane4", "Lane5", "Lane6", "Lane7"
+};
+
+static const struct soc_enum s4_tocodec_lane_sel_enum =
+	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_lane_sel_texts),
+			s4_tocodec_lane_sel_texts);
+
+static const struct snd_kcontrol_new s4_tocodec_lane_sel =
+	SOC_DAPM_ENUM("TOCODEC LANE SEL", s4_tocodec_lane_sel_enum);
+
+static const char * const s4_tocodec_src_sel_texts[] = {
+	"TDMA", "TDMB", "TDMC"
+};
+
+static const struct soc_enum s4_tocodec_src_sel_enum =
+	SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(s4_tocodec_src_sel_texts),
+			s4_tocodec_src_sel_texts);
+
+static const struct snd_kcontrol_new s4_tocodec_src_sel =
+	SOC_DAPM_ENUM("TOCODEC SEL", s4_tocodec_src_sel_enum);
+
+static const struct snd_kcontrol_new s4_toacodec_out_enable =
+	SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
+				    CTRL0_ENABLE_SHIFT, 1, 0);
+
+static struct snd_soc_dai *tocodec_tdm_get_ahead_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_source_path(w, p) {
+		if (!p->connect)
+			continue;
+		if (p->source->id == snd_soc_dapm_dai_in)
+			return (struct snd_soc_dai *)p->source->priv;
+		be = tocodec_tdm_get_ahead_be(p->source);
+		if (be && be->id == TDM_IFACE)
+			return be;
+	}
+	return NULL;
+}
+
+static unsigned int aml_simple_strtoull(const char *cp)
+{
+	unsigned int result = 0;
+	unsigned int value = 0;
+	unsigned int len = strlen(cp);
+
+	while (len != 0) {
+		len--;
+		value = isdigit(*cp);
+		if (value) {
+			value = *cp - '0';
+		} else {
+			cp++;
+			continue;
+		}
+		cp++;
+		result = result * 10 + value;
+	}
+	return result;
+}
+
+static int aml_get_clk_id(const char *name)
+{
+	int clk_id = 0;
+
+	if (strstr(name, "mst_a"))
+		clk_id = 0;
+	else if (strstr(name, "mst_b"))
+		clk_id = 1;
+	else if (strstr(name, "mst_c"))
+		clk_id = 2;
+	else if (strstr(name, "mst_d"))
+		clk_id = 3;
+	else if (strstr(name, "mst_e"))
+		clk_id = 4;
+	else if (strstr(name, "mst_f"))
+		clk_id = 5;
+
+	return clk_id;
+}
+
+static int aml_tocodec_sel_set(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be;
+	struct axg_tdm_stream *stream;
+	struct axg_tdm_iface *iface;
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct toacodec *priv = snd_soc_component_get_drvdata(component);
+	unsigned int tdm_id = TDM_A_PAD;
+	const char *dai_widget_name;
+	struct snd_soc_dapm_path *p;
+	unsigned int lane = 0;
+	unsigned int val = 0;
+	struct clk *sclk, *mclk;
+	char *clk_name;
+	int mclk_id, sclk_id;
+
+	be = tocodec_tdm_get_ahead_be(w);
+	if (!be) {
+		dev_err(component->dev, "%s not find the be\n", __func__);
+		return -EINVAL;
+	}
+	stream = snd_soc_dai_dma_data_get_playback(be);
+	if (!stream) {
+		dev_err(component->dev, "%s not find the stream\n", __func__);
+		return -EINVAL;
+	}
+	/*we like to use dai id, but it is fixed val*/
+	dai_widget_name = be->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
+	if (strstr(dai_widget_name, "TDM_A"))
+		tdm_id = TDM_A_PAD;
+	else if (strstr(dai_widget_name, "TDM_B"))
+		tdm_id = TDM_B_PAD;
+	else if (strstr(dai_widget_name, "TDM_C"))
+		tdm_id = TDM_C_PAD;
+	snd_soc_dapm_widget_for_each_source_path(w, p) {
+		if (p->connect && p->name) {
+			lane = aml_simple_strtoull(p->name);
+			val = lane + tdm_id * S4_LANE_OFFSET;
+			regmap_field_write(priv->field_dat_sel, val);
+		}
+	}
+	iface = stream->iface;
+	mclk = iface->mclk;
+	sclk = iface->sclk;
+	mclk_id = aml_get_clk_id(__clk_get_name(mclk));
+	sclk_id = aml_get_clk_id(__clk_get_name(sclk));
+	regmap_field_write(priv->field_mclk_sel, mclk_id);
+	regmap_field_write(priv->field_bclk_sel, sclk_id);
+	regmap_field_write(priv->field_lrclk_sel, sclk_id);
+
+	return 0;
+}
+
+static int tocodec_sel_event(struct snd_soc_dapm_widget *w,
+			     struct snd_kcontrol *control,
+			     int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		ret = aml_tocodec_sel_set(w);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		break;
+
+	default:
+		dev_err(component->dev, "Unexpected event %d\n", event);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int tocodec_clk_enable(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *control,
+			      int event)
+{
+	int ret = 0;
+	unsigned int mask = 0, val = 0;
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+
+	snd_soc_component_update_bits(component, TOACODEC_CTRL0,
+				      1 << CTRL0_BLK_CAP_INV_SHIFT, 1 << CTRL0_BLK_CAP_INV_SHIFT);
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
+		val = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
+		snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		mask = 1 << CTRL0_MCLK_ENABLE_SHIFT | 1 << CTRL0_BCLK_ENABLE_SHIFT;
+		val = 0 << CTRL0_MCLK_ENABLE_SHIFT | 0 << CTRL0_BCLK_ENABLE_SHIFT;
+		snd_soc_component_update_bits(component, TOACODEC_CTRL0, mask, val);
+		break;
+	default:
+		dev_err(component->dev, "Unexpected event %d\n", event);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget s4_toacodec_widgets[] = {
+	SND_SOC_DAPM_MUX_E("Lane SRC", SND_SOC_NOPM, 0, 0,
+			   &s4_tocodec_lane_sel, tocodec_sel_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_MUX("INPUT SRC", SND_SOC_NOPM, 0, 0, &s4_tocodec_src_sel),
+	SND_SOC_DAPM_SWITCH_E("OUT EN", SND_SOC_NOPM, 0, 0,
+			      &s4_toacodec_out_enable, tocodec_clk_enable,
+				(SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_AIF_IN("TDMA", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("TDMB", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("TDMC", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane0", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane1", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane2", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane3", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane4", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane5", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane6", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Lane7", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("TDM_TO_ACODEC"),
+};
+
+static const struct snd_soc_dapm_route s4_tocodec_dapm_routes[] = {
+	{ "INPUT SRC", "TDMA", "TDMA"},
+	{ "INPUT SRC", "TDMB", "TDMB"},
+	{ "INPUT SRC", "TDMC", "TDMC"},
+	{ "Lane0", NULL, "INPUT SRC" },
+	{ "Lane1", NULL, "INPUT SRC"},
+	{ "Lane2", NULL, "INPUT SRC"},
+	{ "Lane3", NULL, "INPUT SRC"},
+	{ "Lane4", NULL, "INPUT SRC"},
+	{ "Lane5", NULL, "INPUT SRC"},
+	{ "Lane6", NULL, "INPUT SRC"},
+	{ "Lane7", NULL, "INPUT SRC"},
+	{ "Lane SRC", "Lane0", "Lane0"},
+	{ "Lane SRC", "Lane1", "Lane1"},
+	{ "Lane SRC", "Lane2", "Lane2"},
+	{ "Lane SRC", "Lane3", "Lane3"},
+	{ "Lane SRC", "Lane4", "Lane4"},
+	{ "Lane SRC", "Lane5", "Lane5"},
+	{ "Lane SRC", "Lane6", "Lane6"},
+	{ "Lane SRC", "Lane7", "Lane7"},
+	{ "OUT EN", "Switch", "Lane SRC"},
+	{ "TDM_TO_ACODEC", NULL, "OUT EN"},
+
+};
+
+static const struct snd_soc_component_driver s4_tocodec_component_drv = {
+	.dapm_widgets		= s4_toacodec_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(s4_toacodec_widgets),
+	.dapm_routes		= s4_tocodec_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(s4_tocodec_dapm_routes),
+};
+
+static const struct toacodec_match_data s4_toacodec_match_data = {
+	.component_drv	= &s4_tocodec_component_drv,
+	.field_dat_sel	= REG_FIELD(TOACODEC_CTRL0, 16, 20),
+	.field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
+	.field_bclk_sel	= REG_FIELD(TOACODEC_CTRL0, 4, 6),
+	.field_mclk_sel	= REG_FIELD(TOACODEC_CTRL0, 0, 2),
+};
+
+static const struct of_device_id s4_tocodec_of_match[] = {
+	{
+		.compatible = "amlogic,s4-tocodec",
+		.data = &s4_toacodec_match_data,
+	}, {}
+};
+
+MODULE_DEVICE_TABLE(of, s4_tocodec_of_match);
+
+static int tocodec_probe(struct platform_device *pdev)
+{
+	const struct toacodec_match_data *data;
+	struct device *dev = &pdev->dev;
+	struct toacodec *priv;
+	void __iomem *regs;
+	struct regmap *map;
+	int ret;
+
+	data = device_get_match_data(dev);
+	if (!data)
+		return dev_err_probe(dev, -ENODEV, "failed to match device\n");
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = device_reset(dev);
+	if (ret)
+		return ret;
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	map = devm_regmap_init_mmio(dev, regs, &tocodec_regmap_cfg);
+	if (IS_ERR(map))
+		return dev_err_probe(dev, PTR_ERR(map), "failed to init regmap\n");
+
+	priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
+	if (IS_ERR(priv->field_dat_sel))
+		return PTR_ERR(priv->field_dat_sel);
+
+	priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
+	if (IS_ERR(priv->field_lrclk_sel))
+		return PTR_ERR(priv->field_lrclk_sel);
+
+	priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
+	if (IS_ERR(priv->field_bclk_sel))
+		return PTR_ERR(priv->field_bclk_sel);
+
+	priv->field_mclk_sel = devm_regmap_field_alloc(dev, map, data->field_mclk_sel);
+	if (IS_ERR(priv->field_mclk_sel))
+		return PTR_ERR(priv->field_mclk_sel);
+
+	return devm_snd_soc_register_component(dev,
+			data->component_drv, NULL, 0);
+}
+
+static struct platform_driver tocodec_pdrv = {
+	.probe = tocodec_probe,
+	.driver = {
+		.name = "s4-tocodec",
+		.of_match_table = s4_tocodec_of_match,
+	},
+};
+
+module_platform_driver(tocodec_pdrv);
+
+MODULE_DESCRIPTION("Amlogic to codec driver");
+MODULE_AUTHOR("jiebing.chen@amlogic.com");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
index 571f65788c592050abdca264f5656d4d1a9d99f6..2db1cd18cf2cea507f3d7282054e03d953586648 100644
--- a/sound/soc/meson/t9015.c
+++ b/sound/soc/meson/t9015.c
@@ -89,10 +89,7 @@  static struct snd_soc_dai_driver t9015_dai = {
 		.channels_min = 1,
 		.channels_max = 2,
 		.rates = SNDRV_PCM_RATE_8000_96000,
-		.formats = (SNDRV_PCM_FMTBIT_S8 |
-			    SNDRV_PCM_FMTBIT_S16_LE |
-			    SNDRV_PCM_FMTBIT_S20_LE |
-			    SNDRV_PCM_FMTBIT_S24_LE),
+		.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
 	},
 	.ops = &t9015_dai_ops,
 };