diff mbox series

[12/25] ASoC: meson: t9015: add support for A1 SoC family

Message ID 20240314232201.2102178-13-jan.dakinevich@salutedevices.com
State New
Headers show
Series Introduce support of audio for Amlogic A1 SoC family | expand

Commit Message

Jan Dakinevich March 14, 2024, 11:21 p.m. UTC
A1's internal codec is very close to t9015. The main difference, that it
has ADC. This commit introduces support for capturing from it.

Signed-off-by: Jan Dakinevich <jan.dakinevich@salutedevices.com>
---
 sound/soc/meson/t9015.c | 259 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 259 insertions(+)

Comments

Mark Brown March 15, 2024, 1:36 p.m. UTC | #1
On Fri, Mar 15, 2024 at 02:21:48AM +0300, Jan Dakinevich wrote:

> +static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
> +	"2.3V", "2.5V", "2.8V" };
> +static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };

Why would this be varied at runtime rather than being something fixed
when the system is designed?

> +static const char * const a1_adc_pga_txt[] = { "None", "Differential",
> +	"Positive", "Negative" };
> +static const unsigned int a1_adc_pga_right_values[] = { 0, PGAR_DIFF,
> +	PGAR_POSITIVE, PGAR_NEGATIVE };
> +static const unsigned int a1_adc_pga_left_values[] = { 0, PGAL_DIFF,
> +	PGAL_POSITIVE, PGAL_NEGATIVE };

Similarly here.

> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),

Why would micbias be user controlled rather than a DAPM widget as
normal?
Jan Dakinevich March 17, 2024, 4:27 p.m. UTC | #2
On 3/15/24 16:36, Mark Brown wrote:
> On Fri, Mar 15, 2024 at 02:21:48AM +0300, Jan Dakinevich wrote:
> 
>> +static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
>> +	"2.3V", "2.5V", "2.8V" };
>> +static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };
> 
> Why would this be varied at runtime rather than being something fixed
> when the system is designed?
> 
>> +static const char * const a1_adc_pga_txt[] = { "None", "Differential",
>> +	"Positive", "Negative" };
>> +static const unsigned int a1_adc_pga_right_values[] = { 0, PGAR_DIFF,
>> +	PGAR_POSITIVE, PGAR_NEGATIVE };
>> +static const unsigned int a1_adc_pga_left_values[] = { 0, PGAL_DIFF,
>> +	PGAL_POSITIVE, PGAL_NEGATIVE };
> 
> Similarly here.
> 

Both mic bias and ADC's input mode depends on schematics and should be
configurable. What is the better way to give access to these parameters?
Device tree?

>> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
>> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
> 
> Why would micbias be user controlled rather than a DAPM widget as
> normal?

Yes, I could use SND_SOC_DAPM_SUPPLY, but it supports only raw values,
and doesn't supports enums. Here, I want to use enum to restrict
possible values, because only these values mentioned in the
documentation that I have.
Jerome Brunet March 18, 2024, 10:46 a.m. UTC | #3
On Fri 15 Mar 2024 at 02:21, Jan Dakinevich <jan.dakinevich@salutedevices.com> wrote:

> A1's internal codec is very close to t9015. The main difference, that it
> has ADC. This commit introduces support for capturing from it.

This is mis-leading.

It does not look like the change is A1 specific but rather a extension
of the support for t9015. It also mixes several different topics like line
configuration, capture support, etc ...

Again, the t9015 changes should be a separated series from the rest, and
there should be one patch per topic.

As Mark, if something is meant to be configured based on the HW layout,
then there a good change a kcontrol is not appropriate, and this should
rather be part of the platform description, like DT.

It was also suggested here:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/meson/t9015.c?h=v6.8#n298

>
> Signed-off-by: Jan Dakinevich <jan.dakinevich@salutedevices.com>
> ---
>  sound/soc/meson/t9015.c | 259 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 259 insertions(+)
>
> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
> index 48f6767bd858..365955bfeb78 100644
> --- a/sound/soc/meson/t9015.c
> +++ b/sound/soc/meson/t9015.c
> @@ -19,16 +19,33 @@
>  #define  LOLP_EN	3
>  #define  DACR_EN	4
>  #define  DACL_EN	5
> +#define  ADCR_EN	6
> +#define  ADCL_EN	7
> +#define  PGAR_ZCD_EN	8
> +#define  PGAL_ZCD_EN	9
> +#define  PGAR_EN	10
> +#define  PGAL_EN	11
> +#define  ADCR_INV	16
> +#define  ADCL_INV	17
> +#define  ADCR_SRC	18
> +#define  ADCL_SRC	19
>  #define  DACR_INV	20
>  #define  DACL_INV	21
>  #define  DACR_SRC	22
>  #define  DACL_SRC	23
> +#define  ADC_DEM_EN	26
> +#define  ADC_FILTER_MODE 28
> +#define  ADC_FILTER_EN	29
>  #define  REFP_BUF_EN	BIT(12)
>  #define  BIAS_CURRENT_EN BIT(13)
>  #define  VMID_GEN_FAST	BIT(14)
>  #define  VMID_GEN_EN	BIT(15)
>  #define  I2S_MODE	BIT(30)
>  #define VOL_CTRL0	0x04
> +#define  PGAR_VC	0
> +#define  PGAL_VC	8
> +#define  ADCR_VC	16
> +#define  ADCL_VC	24
>  #define  GAIN_H		31
>  #define  GAIN_L		23
>  #define VOL_CTRL1	0x08
> @@ -46,6 +63,28 @@
>  #define  LOLN_POL	8
>  #define  LOLP_POL	12
>  #define POWER_CFG	0x10
> +#define LINEIN_CFG	0x14
> +#define  MICBIAS_LEVEL	0
> +#define  MICBIAS_EN	3
> +#define  PGAR_CTVMN	8
> +#define  PGAR_CTVMP	9
> +#define  PGAL_CTVMN	10
> +#define  PGAL_CTVMP	11
> +#define  PGAR_CTVIN	12
> +#define  PGAR_CTVIP	13
> +#define  PGAL_CTVIN	14
> +#define  PGAL_CTVIP	15
> +
> +#define PGAR_MASK	(BIT(PGAR_CTVMP) | BIT(PGAR_CTVMN) | \
> +			 BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
> +#define PGAR_DIFF	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
> +#define PGAR_POSITIVE	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVMN))
> +#define PGAR_NEGATIVE	(BIT(PGAR_CTVIN) | BIT(PGAR_CTVMP))
> +#define PGAL_MASK	(BIT(PGAL_CTVMP) | BIT(PGAL_CTVMN) | \
> +			 BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
> +#define PGAL_DIFF	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
> +#define PGAL_POSITIVE	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVMN))
> +#define PGAL_NEGATIVE	(BIT(PGAL_CTVIN) | BIT(PGAL_CTVMP))
>  
>  struct t9015 {
>  	struct regulator *avdd;
> @@ -103,6 +142,31 @@ static struct snd_soc_dai_driver t9015_dai = {
>  	.ops = &t9015_dai_ops,
>  };
>  
> +static struct snd_soc_dai_driver a1_t9015_dai = {
> +	.name = "t9015-hifi",
> +	.playback = {
> +		.stream_name = "Playback",
> +		.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),
> +	},
> +	.capture = {
> +		.stream_name = "Capture",
> +		.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),
> +	},
> +	.ops = &t9015_dai_ops,
> +};
> +
>  static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0);
>  
>  static const char * const ramp_rate_txt[] = { "Fast", "Slow" };
> @@ -179,6 +243,166 @@ static const struct snd_soc_dapm_route t9015_dapm_routes[] = {
>  	{ "LOLP", NULL, "Left+ Driver",  },
>  };
>  
> +static const char * const a1_right_driver_txt[] = { "None", "Right DAC",
> +	"Left DAC Inverted" };
> +static const unsigned int a1_right_driver_values[] = { 0, 2, 4 };
> +
> +static const char * const a1_left_driver_txt[] = { "None", "Left DAC",
> +	"Right DAC Inverted" };
> +static const unsigned int a1_left_driver_values[] = { 0, 2, 4 };
> +
> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_right_driver, LINEOUT_CFG, 12, 0x7,
> +				  a1_right_driver_txt, a1_right_driver_values);
> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_left_driver, LINEOUT_CFG, 4, 0x7,
> +				  a1_left_driver_txt, a1_left_driver_values);
> +
> +static const struct snd_kcontrol_new a1_right_driver_mux =
> +	SOC_DAPM_ENUM("Right Driver+ Source", a1_right_driver);
> +static const struct snd_kcontrol_new a1_left_driver_mux =
> +	SOC_DAPM_ENUM("Left Driver+ Source", a1_left_driver);
> +
> +static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_vol_tlv, -29625, 0);
> +static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_pga_vol_tlv, -1200, 0);
> +
> +static const char * const a1_adc_right_txt[] = { "Right", "Left" };
> +static SOC_ENUM_SINGLE_DECL(a1_adc_right, BLOCK_EN, ADCR_SRC, a1_adc_right_txt);
> +
> +static const char * const a1_adc_left_txt[] = { "Left", "Right" };
> +static SOC_ENUM_SINGLE_DECL(a1_adc_left, BLOCK_EN, ADCL_SRC, a1_adc_left_txt);
> +
> +static const struct snd_kcontrol_new a1_adc_right_mux =
> +	SOC_DAPM_ENUM("ADC Right Source", a1_adc_right);
> +static const struct snd_kcontrol_new a1_adc_left_mux =
> +	SOC_DAPM_ENUM("ADC Left Source", a1_adc_left);
> +
> +static const char * const a1_adc_filter_mode_txt[] = { "Voice", "HiFi"};
> +static SOC_ENUM_SINGLE_DECL(a1_adc_filter_mode, BLOCK_EN, ADC_FILTER_MODE,
> +			    a1_adc_filter_mode_txt);
> +
> +static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
> +	"2.3V", "2.5V", "2.8V" };
> +static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };
> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_mic_bias_level,
> +				  LINEIN_CFG, MICBIAS_LEVEL, 0x7,
> +				  a1_adc_mic_bias_level_txt,
> +				  a1_adc_mic_bias_level_values);
> +
> +static const char * const a1_adc_pga_txt[] = { "None", "Differential",
> +	"Positive", "Negative" };
> +static const unsigned int a1_adc_pga_right_values[] = { 0, PGAR_DIFF,
> +	PGAR_POSITIVE, PGAR_NEGATIVE };
> +static const unsigned int a1_adc_pga_left_values[] = { 0, PGAL_DIFF,
> +	PGAL_POSITIVE, PGAL_NEGATIVE };
> +
> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_right, LINEIN_CFG, 0, PGAR_MASK,
> +				  a1_adc_pga_txt, a1_adc_pga_right_values);
> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_left, LINEIN_CFG, 0, PGAL_MASK,
> +				  a1_adc_pga_txt, a1_adc_pga_left_values);
> +
> +static const struct snd_kcontrol_new a1_adc_pga_right_mux =
> +	SOC_DAPM_ENUM("ADC PGA Right Source", a1_adc_pga_right);
> +static const struct snd_kcontrol_new a1_adc_pga_left_mux =
> +	SOC_DAPM_ENUM("ADC PGA Left Source", a1_adc_pga_left);
> +
> +static const struct snd_kcontrol_new a1_t9015_snd_controls[] = {
> +	/* Volume Controls */
> +	SOC_ENUM("Playback Channel Mode", mono_enum),
> +	SOC_SINGLE("Playback Switch", VOL_CTRL1, DAC_SOFT_MUTE, 1, 1),
> +	SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1, DACL_VC, DACR_VC,
> +		       0xff, 0, dac_vol_tlv),
> +
> +	/* Ramp Controls */
> +	SOC_ENUM("Ramp Rate", ramp_rate_enum),
> +	SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1, VC_RAMP_MODE, 1, 0),
> +	SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1, MUTE_MODE, 1, 0),
> +	SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
> +
> +	/* ADC Controls */
> +	SOC_DOUBLE_TLV("ADC Volume", VOL_CTRL0, ADCL_VC, ADCR_VC,
> +		       0x7f, 0, a1_adc_vol_tlv),
> +	SOC_SINGLE("ADC Filter Switch", BLOCK_EN, ADC_FILTER_EN, 1, 0),
> +	SOC_ENUM("ADC Filter Mode", a1_adc_filter_mode),
> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
> +	SOC_SINGLE("ADC DEM Switch", BLOCK_EN, ADC_DEM_EN, 1, 0),
> +	SOC_DOUBLE_TLV("ADC PGA Volume", VOL_CTRL0, PGAR_VC, PGAL_VC,
> +		       0x1f, 0, a1_adc_pga_vol_tlv),
> +	SOC_DOUBLE("ADC PGA Zero Cross-detection Switch", BLOCK_EN,
> +		   PGAL_ZCD_EN, PGAR_ZCD_EN, 1, 0),
> +};
> +
> +static const struct snd_soc_dapm_widget a1_t9015_dapm_widgets[] = {
> +	SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM, 0, 0,
> +			 &t9015_right_dac_mux),
> +	SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM, 0, 0,
> +			 &t9015_left_dac_mux),
> +	SND_SOC_DAPM_DAC("Right DAC", NULL, BLOCK_EN, DACR_EN, 0),
> +	SND_SOC_DAPM_DAC("Left DAC",  NULL, BLOCK_EN, DACL_EN, 0),
> +	SND_SOC_DAPM_MUX("Right+ Driver Sel", SND_SOC_NOPM, 0, 0,
> +			 &a1_right_driver_mux),
> +	SND_SOC_DAPM_MUX("Left+ Driver Sel", SND_SOC_NOPM, 0, 0,
> +			 &a1_left_driver_mux),
> +	SND_SOC_DAPM_OUT_DRV("Right+ Driver", BLOCK_EN, LORP_EN, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("Left+ Driver",  BLOCK_EN, LOLP_EN, 0, NULL, 0),
> +	SND_SOC_DAPM_OUTPUT("LORP"),
> +	SND_SOC_DAPM_OUTPUT("LOLP"),
> +
> +	SND_SOC_DAPM_INPUT("ADC IN Right"),
> +	SND_SOC_DAPM_INPUT("ADC IN Left"),
> +	SND_SOC_DAPM_MUX("ADC PGA Right Sel", SND_SOC_NOPM, 0, 0,
> +			 &a1_adc_pga_right_mux),
> +	SND_SOC_DAPM_MUX("ADC PGA Left Sel", SND_SOC_NOPM, 0, 0,
> +			 &a1_adc_pga_left_mux),
> +	SND_SOC_DAPM_PGA("ADC PGA Right", BLOCK_EN, PGAR_EN, 0, NULL, 0),
> +	SND_SOC_DAPM_PGA("ADC PGA Left", BLOCK_EN, PGAL_EN, 0, NULL, 0),
> +	SND_SOC_DAPM_ADC("ADC Right", NULL, BLOCK_EN, ADCR_EN, 0),
> +	SND_SOC_DAPM_ADC("ADC Left", NULL, BLOCK_EN, ADCL_EN, 0),
> +	SND_SOC_DAPM_MUX("ADC Right Sel", SND_SOC_NOPM, 0, 0, &a1_adc_right_mux),
> +	SND_SOC_DAPM_MUX("ADC Left Sel", SND_SOC_NOPM, 0, 0, &a1_adc_left_mux),
> +	SND_SOC_DAPM_AIF_OUT("ADC OUT Right", NULL, 0, SND_SOC_NOPM, 0, 0),
> +	SND_SOC_DAPM_AIF_OUT("ADC OUT Left", NULL, 0, SND_SOC_NOPM, 0, 0),
> +};
> +
> +static const struct snd_soc_dapm_route a1_t9015_dapm_routes[] = {
> +	{ "Right IN", NULL, "Playback" },
> +	{ "Left IN", NULL, "Playback" },
> +	{ "Right DAC Sel", "Right", "Right IN" },
> +	{ "Right DAC Sel", "Left", "Left IN" },
> +	{ "Left DAC Sel", "Right", "Right IN" },
> +	{ "Left DAC Sel", "Left", "Left IN" },
> +	{ "Right DAC", NULL, "Right DAC Sel" },
> +	{ "Left DAC", NULL, "Left DAC Sel" },
> +	{ "Right+ Driver Sel", "Right DAC", "Right DAC" },
> +	{ "Right+ Driver Sel", "Left DAC Inverted", "Right DAC" },
> +	{ "Left+ Driver Sel", "Left DAC", "Left DAC" },
> +	{ "Left+ Driver Sel", "Right DAC Inverted", "Left DAC" },
> +	{ "Right+ Driver", NULL, "Right+ Driver Sel" },
> +	{ "Left+ Driver", NULL, "Left+ Driver Sel" },
> +	{ "LORP", NULL, "Right+ Driver", },
> +	{ "LOLP", NULL, "Left+ Driver", },
> +
> +	{ "ADC PGA Right Sel", "Differential", "ADC IN Right" },
> +	{ "ADC PGA Right Sel", "Positive", "ADC IN Right" },
> +	{ "ADC PGA Right Sel", "Negative", "ADC IN Right" },
> +	{ "ADC PGA Left Sel", "Differential", "ADC IN Left" },
> +	{ "ADC PGA Left Sel", "Positive", "ADC IN Left" },
> +	{ "ADC PGA Left Sel", "Negative", "ADC IN Left" },
> +	{ "ADC PGA Right", NULL, "ADC PGA Right Sel" },
> +	{ "ADC PGA Left", NULL, "ADC PGA Left Sel" },
> +	{ "ADC Right", NULL, "ADC PGA Right" },
> +	{ "ADC Left", NULL, "ADC PGA Left" },
> +	{ "ADC Right Sel", "Right", "ADC Right" },
> +	{ "ADC Right Sel", "Left", "ADC Left" },
> +	{ "ADC Left Sel", "Right", "ADC Right" },
> +	{ "ADC Left Sel", "Left", "ADC Left" },
> +	{ "ADC OUT Right", NULL, "ADC Right Sel" },
> +	{ "ADC OUT Left", NULL, "ADC Left Sel" },
> +	{ "Capture", NULL, "ADC OUT Right" },
> +	{ "Capture", NULL, "ADC OUT Left" },
> +};
> +
>  static int t9015_set_bias_level(struct snd_soc_component *component,
>  				enum snd_soc_bias_level level)
>  {
> @@ -241,6 +465,18 @@ static int t9015_component_probe(struct snd_soc_component *component)
>  	return 0;
>  }
>  
> +static int a1_t9015_component_probe(struct snd_soc_component *component)
> +{
> +	/*
> +	 * This configuration was stealed from original Amlogic's driver to
> +	 * reproduce the behavior of the driver more accurately. However, it is
> +	 * not known for certain what it actually affects.
> +	 */
> +	snd_soc_component_write(component, POWER_CFG, 0x00010000);
> +
> +	return 0;
> +}
> +
>  static const struct snd_soc_component_driver t9015_codec_driver = {
>  	.probe			= t9015_component_probe,
>  	.set_bias_level		= t9015_set_bias_level,
> @@ -254,6 +490,19 @@ static const struct snd_soc_component_driver t9015_codec_driver = {
>  	.endianness		= 1,
>  };
>  
> +static const struct snd_soc_component_driver a1_t9015_codec_driver = {
> +	.probe			= a1_t9015_component_probe,
> +	.set_bias_level		= t9015_set_bias_level,
> +	.controls		= a1_t9015_snd_controls,
> +	.num_controls		= ARRAY_SIZE(a1_t9015_snd_controls),
> +	.dapm_widgets		= a1_t9015_dapm_widgets,
> +	.num_dapm_widgets	= ARRAY_SIZE(a1_t9015_dapm_widgets),
> +	.dapm_routes		= a1_t9015_dapm_routes,
> +	.num_dapm_routes	= ARRAY_SIZE(a1_t9015_dapm_routes),
> +	.suspend_bias_off	= 1,
> +	.endianness		= 1,
> +};
> +
>  static int t9015_probe(struct platform_device *pdev)
>  {
>  	struct device *dev = &pdev->dev;
> @@ -315,11 +564,21 @@ static const struct t9015_match_data t9015_match_data = {
>  	.max_register = POWER_CFG,
>  };
>  
> +static const struct t9015_match_data a1_t9015_match_data = {
> +	.component_drv = &a1_t9015_codec_driver,
> +	.dai_drv = &a1_t9015_dai,
> +	.max_register = LINEIN_CFG,
> +};
> +
>  static const struct of_device_id t9015_ids[] __maybe_unused = {
>  	{
>  		.compatible = "amlogic,t9015",
>  		.data = &t9015_match_data,
>  	},
> +	{
> +		.compatible = "amlogic,t9015-a1",
> +		.data = &a1_t9015_match_data,
> +	},
>  	{ }
>  };
>  MODULE_DEVICE_TABLE(of, t9015_ids);
Mark Brown March 18, 2024, 1:48 p.m. UTC | #4
On Sun, Mar 17, 2024 at 07:27:14PM +0300, Jan Dakinevich wrote:

> Both mic bias and ADC's input mode depends on schematics and should be
> configurable. What is the better way to give access to these parameters?
> Device tree?

Yes.

> >> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
> >> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),

> > Why would micbias be user controlled rather than a DAPM widget as
> > normal?

> Yes, I could use SND_SOC_DAPM_SUPPLY, but it supports only raw values,
> and doesn't supports enums. Here, I want to use enum to restrict
> possible values, because only these values mentioned in the
> documentation that I have.

A supply is an on/off switch not an enum.  Users should not be selecting
values at all.
Jan Dakinevich March 18, 2024, 10:43 p.m. UTC | #5
On 3/18/24 16:48, Mark Brown wrote:
> On Sun, Mar 17, 2024 at 07:27:14PM +0300, Jan Dakinevich wrote:
> 
>> Both mic bias and ADC's input mode depends on schematics and should be
>> configurable. What is the better way to give access to these parameters?
>> Device tree?
> 
> Yes.
> 
>>>> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
>>>> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
> 
>>> Why would micbias be user controlled rather than a DAPM widget as
>>> normal?
> 
>> Yes, I could use SND_SOC_DAPM_SUPPLY, but it supports only raw values,
>> and doesn't supports enums. Here, I want to use enum to restrict
>> possible values, because only these values mentioned in the
>> documentation that I have.
> 
> A supply is an on/off switch not an enum.  Users should not be selecting
> values at all.

Ok. For me it is great if I am free to move these kcontrols to device tree.
Jan Dakinevich March 19, 2024, 12:17 a.m. UTC | #6
On 3/18/24 13:46, Jerome Brunet wrote:
> 
> On Fri 15 Mar 2024 at 02:21, Jan Dakinevich <jan.dakinevich@salutedevices.com> wrote:
> 
>> A1's internal codec is very close to t9015. The main difference, that it
>> has ADC. This commit introduces support for capturing from it.
> 
> This is mis-leading.
> 
> It does not look like the change is A1 specific but rather a extension
> of the support for t9015. It also mixes several different topics like line
> configuration, capture support, etc ...
> 
First, it is not only extentsion. Some bits are changed comparing to
existing t9015, so new compatible string is still required.

Second, I don't know anything about about ADC in t9015 on other SoCs and
even don't sure that it exist there (may be I am inattentive, but I'm
unable to find audio input pin on sm1/g12a's pinout).

> Again, the t9015 changes should be a separated series from the rest, and
> there should be one patch per topic.
> 
> As Mark, if something is meant to be configured based on the HW layout,
> then there a good change a kcontrol is not appropriate, and this should
> rather be part of the platform description, like DT.
> 
> It was also suggested here:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/meson/t9015.c?h=v6.8#n298
> 

Ok. By the way, on a1 LINEOUT_CFG would have another value.

>>
>> Signed-off-by: Jan Dakinevich <jan.dakinevich@salutedevices.com>
>> ---
>>  sound/soc/meson/t9015.c | 259 ++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 259 insertions(+)
>>
>> diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
>> index 48f6767bd858..365955bfeb78 100644
>> --- a/sound/soc/meson/t9015.c
>> +++ b/sound/soc/meson/t9015.c
>> @@ -19,16 +19,33 @@
>>  #define  LOLP_EN	3
>>  #define  DACR_EN	4
>>  #define  DACL_EN	5
>> +#define  ADCR_EN	6
>> +#define  ADCL_EN	7
>> +#define  PGAR_ZCD_EN	8
>> +#define  PGAL_ZCD_EN	9
>> +#define  PGAR_EN	10
>> +#define  PGAL_EN	11
>> +#define  ADCR_INV	16
>> +#define  ADCL_INV	17
>> +#define  ADCR_SRC	18
>> +#define  ADCL_SRC	19
>>  #define  DACR_INV	20
>>  #define  DACL_INV	21
>>  #define  DACR_SRC	22
>>  #define  DACL_SRC	23
>> +#define  ADC_DEM_EN	26
>> +#define  ADC_FILTER_MODE 28
>> +#define  ADC_FILTER_EN	29
>>  #define  REFP_BUF_EN	BIT(12)
>>  #define  BIAS_CURRENT_EN BIT(13)
>>  #define  VMID_GEN_FAST	BIT(14)
>>  #define  VMID_GEN_EN	BIT(15)
>>  #define  I2S_MODE	BIT(30)
>>  #define VOL_CTRL0	0x04
>> +#define  PGAR_VC	0
>> +#define  PGAL_VC	8
>> +#define  ADCR_VC	16
>> +#define  ADCL_VC	24
>>  #define  GAIN_H		31
>>  #define  GAIN_L		23
>>  #define VOL_CTRL1	0x08
>> @@ -46,6 +63,28 @@
>>  #define  LOLN_POL	8
>>  #define  LOLP_POL	12
>>  #define POWER_CFG	0x10
>> +#define LINEIN_CFG	0x14
>> +#define  MICBIAS_LEVEL	0
>> +#define  MICBIAS_EN	3
>> +#define  PGAR_CTVMN	8
>> +#define  PGAR_CTVMP	9
>> +#define  PGAL_CTVMN	10
>> +#define  PGAL_CTVMP	11
>> +#define  PGAR_CTVIN	12
>> +#define  PGAR_CTVIP	13
>> +#define  PGAL_CTVIN	14
>> +#define  PGAL_CTVIP	15
>> +
>> +#define PGAR_MASK	(BIT(PGAR_CTVMP) | BIT(PGAR_CTVMN) | \
>> +			 BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
>> +#define PGAR_DIFF	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
>> +#define PGAR_POSITIVE	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVMN))
>> +#define PGAR_NEGATIVE	(BIT(PGAR_CTVIN) | BIT(PGAR_CTVMP))
>> +#define PGAL_MASK	(BIT(PGAL_CTVMP) | BIT(PGAL_CTVMN) | \
>> +			 BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
>> +#define PGAL_DIFF	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
>> +#define PGAL_POSITIVE	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVMN))
>> +#define PGAL_NEGATIVE	(BIT(PGAL_CTVIN) | BIT(PGAL_CTVMP))
>>  
>>  struct t9015 {
>>  	struct regulator *avdd;
>> @@ -103,6 +142,31 @@ static struct snd_soc_dai_driver t9015_dai = {
>>  	.ops = &t9015_dai_ops,
>>  };
>>  
>> +static struct snd_soc_dai_driver a1_t9015_dai = {
>> +	.name = "t9015-hifi",
>> +	.playback = {
>> +		.stream_name = "Playback",
>> +		.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),
>> +	},
>> +	.capture = {
>> +		.stream_name = "Capture",
>> +		.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),
>> +	},
>> +	.ops = &t9015_dai_ops,
>> +};
>> +
>>  static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0);
>>  
>>  static const char * const ramp_rate_txt[] = { "Fast", "Slow" };
>> @@ -179,6 +243,166 @@ static const struct snd_soc_dapm_route t9015_dapm_routes[] = {
>>  	{ "LOLP", NULL, "Left+ Driver",  },
>>  };
>>  
>> +static const char * const a1_right_driver_txt[] = { "None", "Right DAC",
>> +	"Left DAC Inverted" };
>> +static const unsigned int a1_right_driver_values[] = { 0, 2, 4 };
>> +
>> +static const char * const a1_left_driver_txt[] = { "None", "Left DAC",
>> +	"Right DAC Inverted" };
>> +static const unsigned int a1_left_driver_values[] = { 0, 2, 4 };
>> +
>> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_right_driver, LINEOUT_CFG, 12, 0x7,
>> +				  a1_right_driver_txt, a1_right_driver_values);
>> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_left_driver, LINEOUT_CFG, 4, 0x7,
>> +				  a1_left_driver_txt, a1_left_driver_values);
>> +
>> +static const struct snd_kcontrol_new a1_right_driver_mux =
>> +	SOC_DAPM_ENUM("Right Driver+ Source", a1_right_driver);
>> +static const struct snd_kcontrol_new a1_left_driver_mux =
>> +	SOC_DAPM_ENUM("Left Driver+ Source", a1_left_driver);
>> +
>> +static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_vol_tlv, -29625, 0);
>> +static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_pga_vol_tlv, -1200, 0);
>> +
>> +static const char * const a1_adc_right_txt[] = { "Right", "Left" };
>> +static SOC_ENUM_SINGLE_DECL(a1_adc_right, BLOCK_EN, ADCR_SRC, a1_adc_right_txt);
>> +
>> +static const char * const a1_adc_left_txt[] = { "Left", "Right" };
>> +static SOC_ENUM_SINGLE_DECL(a1_adc_left, BLOCK_EN, ADCL_SRC, a1_adc_left_txt);
>> +
>> +static const struct snd_kcontrol_new a1_adc_right_mux =
>> +	SOC_DAPM_ENUM("ADC Right Source", a1_adc_right);
>> +static const struct snd_kcontrol_new a1_adc_left_mux =
>> +	SOC_DAPM_ENUM("ADC Left Source", a1_adc_left);
>> +
>> +static const char * const a1_adc_filter_mode_txt[] = { "Voice", "HiFi"};
>> +static SOC_ENUM_SINGLE_DECL(a1_adc_filter_mode, BLOCK_EN, ADC_FILTER_MODE,
>> +			    a1_adc_filter_mode_txt);
>> +
>> +static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
>> +	"2.3V", "2.5V", "2.8V" };
>> +static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };
>> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_mic_bias_level,
>> +				  LINEIN_CFG, MICBIAS_LEVEL, 0x7,
>> +				  a1_adc_mic_bias_level_txt,
>> +				  a1_adc_mic_bias_level_values);
>> +
>> +static const char * const a1_adc_pga_txt[] = { "None", "Differential",
>> +	"Positive", "Negative" };
>> +static const unsigned int a1_adc_pga_right_values[] = { 0, PGAR_DIFF,
>> +	PGAR_POSITIVE, PGAR_NEGATIVE };
>> +static const unsigned int a1_adc_pga_left_values[] = { 0, PGAL_DIFF,
>> +	PGAL_POSITIVE, PGAL_NEGATIVE };
>> +
>> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_right, LINEIN_CFG, 0, PGAR_MASK,
>> +				  a1_adc_pga_txt, a1_adc_pga_right_values);
>> +static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_left, LINEIN_CFG, 0, PGAL_MASK,
>> +				  a1_adc_pga_txt, a1_adc_pga_left_values);
>> +
>> +static const struct snd_kcontrol_new a1_adc_pga_right_mux =
>> +	SOC_DAPM_ENUM("ADC PGA Right Source", a1_adc_pga_right);
>> +static const struct snd_kcontrol_new a1_adc_pga_left_mux =
>> +	SOC_DAPM_ENUM("ADC PGA Left Source", a1_adc_pga_left);
>> +
>> +static const struct snd_kcontrol_new a1_t9015_snd_controls[] = {
>> +	/* Volume Controls */
>> +	SOC_ENUM("Playback Channel Mode", mono_enum),
>> +	SOC_SINGLE("Playback Switch", VOL_CTRL1, DAC_SOFT_MUTE, 1, 1),
>> +	SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1, DACL_VC, DACR_VC,
>> +		       0xff, 0, dac_vol_tlv),
>> +
>> +	/* Ramp Controls */
>> +	SOC_ENUM("Ramp Rate", ramp_rate_enum),
>> +	SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1, VC_RAMP_MODE, 1, 0),
>> +	SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1, MUTE_MODE, 1, 0),
>> +	SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
>> +
>> +	/* ADC Controls */
>> +	SOC_DOUBLE_TLV("ADC Volume", VOL_CTRL0, ADCL_VC, ADCR_VC,
>> +		       0x7f, 0, a1_adc_vol_tlv),
>> +	SOC_SINGLE("ADC Filter Switch", BLOCK_EN, ADC_FILTER_EN, 1, 0),
>> +	SOC_ENUM("ADC Filter Mode", a1_adc_filter_mode),
>> +	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
>> +	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
>> +	SOC_SINGLE("ADC DEM Switch", BLOCK_EN, ADC_DEM_EN, 1, 0),
>> +	SOC_DOUBLE_TLV("ADC PGA Volume", VOL_CTRL0, PGAR_VC, PGAL_VC,
>> +		       0x1f, 0, a1_adc_pga_vol_tlv),
>> +	SOC_DOUBLE("ADC PGA Zero Cross-detection Switch", BLOCK_EN,
>> +		   PGAL_ZCD_EN, PGAR_ZCD_EN, 1, 0),
>> +};
>> +
>> +static const struct snd_soc_dapm_widget a1_t9015_dapm_widgets[] = {
>> +	SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +	SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +	SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM, 0, 0,
>> +			 &t9015_right_dac_mux),
>> +	SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM, 0, 0,
>> +			 &t9015_left_dac_mux),
>> +	SND_SOC_DAPM_DAC("Right DAC", NULL, BLOCK_EN, DACR_EN, 0),
>> +	SND_SOC_DAPM_DAC("Left DAC",  NULL, BLOCK_EN, DACL_EN, 0),
>> +	SND_SOC_DAPM_MUX("Right+ Driver Sel", SND_SOC_NOPM, 0, 0,
>> +			 &a1_right_driver_mux),
>> +	SND_SOC_DAPM_MUX("Left+ Driver Sel", SND_SOC_NOPM, 0, 0,
>> +			 &a1_left_driver_mux),
>> +	SND_SOC_DAPM_OUT_DRV("Right+ Driver", BLOCK_EN, LORP_EN, 0, NULL, 0),
>> +	SND_SOC_DAPM_OUT_DRV("Left+ Driver",  BLOCK_EN, LOLP_EN, 0, NULL, 0),
>> +	SND_SOC_DAPM_OUTPUT("LORP"),
>> +	SND_SOC_DAPM_OUTPUT("LOLP"),
>> +
>> +	SND_SOC_DAPM_INPUT("ADC IN Right"),
>> +	SND_SOC_DAPM_INPUT("ADC IN Left"),
>> +	SND_SOC_DAPM_MUX("ADC PGA Right Sel", SND_SOC_NOPM, 0, 0,
>> +			 &a1_adc_pga_right_mux),
>> +	SND_SOC_DAPM_MUX("ADC PGA Left Sel", SND_SOC_NOPM, 0, 0,
>> +			 &a1_adc_pga_left_mux),
>> +	SND_SOC_DAPM_PGA("ADC PGA Right", BLOCK_EN, PGAR_EN, 0, NULL, 0),
>> +	SND_SOC_DAPM_PGA("ADC PGA Left", BLOCK_EN, PGAL_EN, 0, NULL, 0),
>> +	SND_SOC_DAPM_ADC("ADC Right", NULL, BLOCK_EN, ADCR_EN, 0),
>> +	SND_SOC_DAPM_ADC("ADC Left", NULL, BLOCK_EN, ADCL_EN, 0),
>> +	SND_SOC_DAPM_MUX("ADC Right Sel", SND_SOC_NOPM, 0, 0, &a1_adc_right_mux),
>> +	SND_SOC_DAPM_MUX("ADC Left Sel", SND_SOC_NOPM, 0, 0, &a1_adc_left_mux),
>> +	SND_SOC_DAPM_AIF_OUT("ADC OUT Right", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +	SND_SOC_DAPM_AIF_OUT("ADC OUT Left", NULL, 0, SND_SOC_NOPM, 0, 0),
>> +};
>> +
>> +static const struct snd_soc_dapm_route a1_t9015_dapm_routes[] = {
>> +	{ "Right IN", NULL, "Playback" },
>> +	{ "Left IN", NULL, "Playback" },
>> +	{ "Right DAC Sel", "Right", "Right IN" },
>> +	{ "Right DAC Sel", "Left", "Left IN" },
>> +	{ "Left DAC Sel", "Right", "Right IN" },
>> +	{ "Left DAC Sel", "Left", "Left IN" },
>> +	{ "Right DAC", NULL, "Right DAC Sel" },
>> +	{ "Left DAC", NULL, "Left DAC Sel" },
>> +	{ "Right+ Driver Sel", "Right DAC", "Right DAC" },
>> +	{ "Right+ Driver Sel", "Left DAC Inverted", "Right DAC" },
>> +	{ "Left+ Driver Sel", "Left DAC", "Left DAC" },
>> +	{ "Left+ Driver Sel", "Right DAC Inverted", "Left DAC" },
>> +	{ "Right+ Driver", NULL, "Right+ Driver Sel" },
>> +	{ "Left+ Driver", NULL, "Left+ Driver Sel" },
>> +	{ "LORP", NULL, "Right+ Driver", },
>> +	{ "LOLP", NULL, "Left+ Driver", },
>> +
>> +	{ "ADC PGA Right Sel", "Differential", "ADC IN Right" },
>> +	{ "ADC PGA Right Sel", "Positive", "ADC IN Right" },
>> +	{ "ADC PGA Right Sel", "Negative", "ADC IN Right" },
>> +	{ "ADC PGA Left Sel", "Differential", "ADC IN Left" },
>> +	{ "ADC PGA Left Sel", "Positive", "ADC IN Left" },
>> +	{ "ADC PGA Left Sel", "Negative", "ADC IN Left" },
>> +	{ "ADC PGA Right", NULL, "ADC PGA Right Sel" },
>> +	{ "ADC PGA Left", NULL, "ADC PGA Left Sel" },
>> +	{ "ADC Right", NULL, "ADC PGA Right" },
>> +	{ "ADC Left", NULL, "ADC PGA Left" },
>> +	{ "ADC Right Sel", "Right", "ADC Right" },
>> +	{ "ADC Right Sel", "Left", "ADC Left" },
>> +	{ "ADC Left Sel", "Right", "ADC Right" },
>> +	{ "ADC Left Sel", "Left", "ADC Left" },
>> +	{ "ADC OUT Right", NULL, "ADC Right Sel" },
>> +	{ "ADC OUT Left", NULL, "ADC Left Sel" },
>> +	{ "Capture", NULL, "ADC OUT Right" },
>> +	{ "Capture", NULL, "ADC OUT Left" },
>> +};
>> +
>>  static int t9015_set_bias_level(struct snd_soc_component *component,
>>  				enum snd_soc_bias_level level)
>>  {
>> @@ -241,6 +465,18 @@ static int t9015_component_probe(struct snd_soc_component *component)
>>  	return 0;
>>  }
>>  
>> +static int a1_t9015_component_probe(struct snd_soc_component *component)
>> +{
>> +	/*
>> +	 * This configuration was stealed from original Amlogic's driver to
>> +	 * reproduce the behavior of the driver more accurately. However, it is
>> +	 * not known for certain what it actually affects.
>> +	 */
>> +	snd_soc_component_write(component, POWER_CFG, 0x00010000);
>> +
>> +	return 0;
>> +}
>> +
>>  static const struct snd_soc_component_driver t9015_codec_driver = {
>>  	.probe			= t9015_component_probe,
>>  	.set_bias_level		= t9015_set_bias_level,
>> @@ -254,6 +490,19 @@ static const struct snd_soc_component_driver t9015_codec_driver = {
>>  	.endianness		= 1,
>>  };
>>  
>> +static const struct snd_soc_component_driver a1_t9015_codec_driver = {
>> +	.probe			= a1_t9015_component_probe,
>> +	.set_bias_level		= t9015_set_bias_level,
>> +	.controls		= a1_t9015_snd_controls,
>> +	.num_controls		= ARRAY_SIZE(a1_t9015_snd_controls),
>> +	.dapm_widgets		= a1_t9015_dapm_widgets,
>> +	.num_dapm_widgets	= ARRAY_SIZE(a1_t9015_dapm_widgets),
>> +	.dapm_routes		= a1_t9015_dapm_routes,
>> +	.num_dapm_routes	= ARRAY_SIZE(a1_t9015_dapm_routes),
>> +	.suspend_bias_off	= 1,
>> +	.endianness		= 1,
>> +};
>> +
>>  static int t9015_probe(struct platform_device *pdev)
>>  {
>>  	struct device *dev = &pdev->dev;
>> @@ -315,11 +564,21 @@ static const struct t9015_match_data t9015_match_data = {
>>  	.max_register = POWER_CFG,
>>  };
>>  
>> +static const struct t9015_match_data a1_t9015_match_data = {
>> +	.component_drv = &a1_t9015_codec_driver,
>> +	.dai_drv = &a1_t9015_dai,
>> +	.max_register = LINEIN_CFG,
>> +};
>> +
>>  static const struct of_device_id t9015_ids[] __maybe_unused = {
>>  	{
>>  		.compatible = "amlogic,t9015",
>>  		.data = &t9015_match_data,
>>  	},
>> +	{
>> +		.compatible = "amlogic,t9015-a1",
>> +		.data = &a1_t9015_match_data,
>> +	},
>>  	{ }
>>  };
>>  MODULE_DEVICE_TABLE(of, t9015_ids);
> 
>
diff mbox series

Patch

diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c
index 48f6767bd858..365955bfeb78 100644
--- a/sound/soc/meson/t9015.c
+++ b/sound/soc/meson/t9015.c
@@ -19,16 +19,33 @@ 
 #define  LOLP_EN	3
 #define  DACR_EN	4
 #define  DACL_EN	5
+#define  ADCR_EN	6
+#define  ADCL_EN	7
+#define  PGAR_ZCD_EN	8
+#define  PGAL_ZCD_EN	9
+#define  PGAR_EN	10
+#define  PGAL_EN	11
+#define  ADCR_INV	16
+#define  ADCL_INV	17
+#define  ADCR_SRC	18
+#define  ADCL_SRC	19
 #define  DACR_INV	20
 #define  DACL_INV	21
 #define  DACR_SRC	22
 #define  DACL_SRC	23
+#define  ADC_DEM_EN	26
+#define  ADC_FILTER_MODE 28
+#define  ADC_FILTER_EN	29
 #define  REFP_BUF_EN	BIT(12)
 #define  BIAS_CURRENT_EN BIT(13)
 #define  VMID_GEN_FAST	BIT(14)
 #define  VMID_GEN_EN	BIT(15)
 #define  I2S_MODE	BIT(30)
 #define VOL_CTRL0	0x04
+#define  PGAR_VC	0
+#define  PGAL_VC	8
+#define  ADCR_VC	16
+#define  ADCL_VC	24
 #define  GAIN_H		31
 #define  GAIN_L		23
 #define VOL_CTRL1	0x08
@@ -46,6 +63,28 @@ 
 #define  LOLN_POL	8
 #define  LOLP_POL	12
 #define POWER_CFG	0x10
+#define LINEIN_CFG	0x14
+#define  MICBIAS_LEVEL	0
+#define  MICBIAS_EN	3
+#define  PGAR_CTVMN	8
+#define  PGAR_CTVMP	9
+#define  PGAL_CTVMN	10
+#define  PGAL_CTVMP	11
+#define  PGAR_CTVIN	12
+#define  PGAR_CTVIP	13
+#define  PGAL_CTVIN	14
+#define  PGAL_CTVIP	15
+
+#define PGAR_MASK	(BIT(PGAR_CTVMP) | BIT(PGAR_CTVMN) | \
+			 BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
+#define PGAR_DIFF	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVIN))
+#define PGAR_POSITIVE	(BIT(PGAR_CTVIP) | BIT(PGAR_CTVMN))
+#define PGAR_NEGATIVE	(BIT(PGAR_CTVIN) | BIT(PGAR_CTVMP))
+#define PGAL_MASK	(BIT(PGAL_CTVMP) | BIT(PGAL_CTVMN) | \
+			 BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
+#define PGAL_DIFF	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVIN))
+#define PGAL_POSITIVE	(BIT(PGAL_CTVIP) | BIT(PGAL_CTVMN))
+#define PGAL_NEGATIVE	(BIT(PGAL_CTVIN) | BIT(PGAL_CTVMP))
 
 struct t9015 {
 	struct regulator *avdd;
@@ -103,6 +142,31 @@  static struct snd_soc_dai_driver t9015_dai = {
 	.ops = &t9015_dai_ops,
 };
 
+static struct snd_soc_dai_driver a1_t9015_dai = {
+	.name = "t9015-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.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),
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.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),
+	},
+	.ops = &t9015_dai_ops,
+};
+
 static const DECLARE_TLV_DB_MINMAX_MUTE(dac_vol_tlv, -9525, 0);
 
 static const char * const ramp_rate_txt[] = { "Fast", "Slow" };
@@ -179,6 +243,166 @@  static const struct snd_soc_dapm_route t9015_dapm_routes[] = {
 	{ "LOLP", NULL, "Left+ Driver",  },
 };
 
+static const char * const a1_right_driver_txt[] = { "None", "Right DAC",
+	"Left DAC Inverted" };
+static const unsigned int a1_right_driver_values[] = { 0, 2, 4 };
+
+static const char * const a1_left_driver_txt[] = { "None", "Left DAC",
+	"Right DAC Inverted" };
+static const unsigned int a1_left_driver_values[] = { 0, 2, 4 };
+
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_right_driver, LINEOUT_CFG, 12, 0x7,
+				  a1_right_driver_txt, a1_right_driver_values);
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_left_driver, LINEOUT_CFG, 4, 0x7,
+				  a1_left_driver_txt, a1_left_driver_values);
+
+static const struct snd_kcontrol_new a1_right_driver_mux =
+	SOC_DAPM_ENUM("Right Driver+ Source", a1_right_driver);
+static const struct snd_kcontrol_new a1_left_driver_mux =
+	SOC_DAPM_ENUM("Left Driver+ Source", a1_left_driver);
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_vol_tlv, -29625, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(a1_adc_pga_vol_tlv, -1200, 0);
+
+static const char * const a1_adc_right_txt[] = { "Right", "Left" };
+static SOC_ENUM_SINGLE_DECL(a1_adc_right, BLOCK_EN, ADCR_SRC, a1_adc_right_txt);
+
+static const char * const a1_adc_left_txt[] = { "Left", "Right" };
+static SOC_ENUM_SINGLE_DECL(a1_adc_left, BLOCK_EN, ADCL_SRC, a1_adc_left_txt);
+
+static const struct snd_kcontrol_new a1_adc_right_mux =
+	SOC_DAPM_ENUM("ADC Right Source", a1_adc_right);
+static const struct snd_kcontrol_new a1_adc_left_mux =
+	SOC_DAPM_ENUM("ADC Left Source", a1_adc_left);
+
+static const char * const a1_adc_filter_mode_txt[] = { "Voice", "HiFi"};
+static SOC_ENUM_SINGLE_DECL(a1_adc_filter_mode, BLOCK_EN, ADC_FILTER_MODE,
+			    a1_adc_filter_mode_txt);
+
+static const char * const a1_adc_mic_bias_level_txt[] = { "2.0V", "2.1V",
+	"2.3V", "2.5V", "2.8V" };
+static const unsigned int a1_adc_mic_bias_level_values[] = { 0, 1, 2, 3, 7 };
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_mic_bias_level,
+				  LINEIN_CFG, MICBIAS_LEVEL, 0x7,
+				  a1_adc_mic_bias_level_txt,
+				  a1_adc_mic_bias_level_values);
+
+static const char * const a1_adc_pga_txt[] = { "None", "Differential",
+	"Positive", "Negative" };
+static const unsigned int a1_adc_pga_right_values[] = { 0, PGAR_DIFF,
+	PGAR_POSITIVE, PGAR_NEGATIVE };
+static const unsigned int a1_adc_pga_left_values[] = { 0, PGAL_DIFF,
+	PGAL_POSITIVE, PGAL_NEGATIVE };
+
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_right, LINEIN_CFG, 0, PGAR_MASK,
+				  a1_adc_pga_txt, a1_adc_pga_right_values);
+static SOC_VALUE_ENUM_SINGLE_DECL(a1_adc_pga_left, LINEIN_CFG, 0, PGAL_MASK,
+				  a1_adc_pga_txt, a1_adc_pga_left_values);
+
+static const struct snd_kcontrol_new a1_adc_pga_right_mux =
+	SOC_DAPM_ENUM("ADC PGA Right Source", a1_adc_pga_right);
+static const struct snd_kcontrol_new a1_adc_pga_left_mux =
+	SOC_DAPM_ENUM("ADC PGA Left Source", a1_adc_pga_left);
+
+static const struct snd_kcontrol_new a1_t9015_snd_controls[] = {
+	/* Volume Controls */
+	SOC_ENUM("Playback Channel Mode", mono_enum),
+	SOC_SINGLE("Playback Switch", VOL_CTRL1, DAC_SOFT_MUTE, 1, 1),
+	SOC_DOUBLE_TLV("Playback Volume", VOL_CTRL1, DACL_VC, DACR_VC,
+		       0xff, 0, dac_vol_tlv),
+
+	/* Ramp Controls */
+	SOC_ENUM("Ramp Rate", ramp_rate_enum),
+	SOC_SINGLE("Volume Ramp Switch", VOL_CTRL1, VC_RAMP_MODE, 1, 0),
+	SOC_SINGLE("Mute Ramp Switch", VOL_CTRL1, MUTE_MODE, 1, 0),
+	SOC_SINGLE("Unmute Ramp Switch", VOL_CTRL1, UNMUTE_MODE, 1, 0),
+
+	/* ADC Controls */
+	SOC_DOUBLE_TLV("ADC Volume", VOL_CTRL0, ADCL_VC, ADCR_VC,
+		       0x7f, 0, a1_adc_vol_tlv),
+	SOC_SINGLE("ADC Filter Switch", BLOCK_EN, ADC_FILTER_EN, 1, 0),
+	SOC_ENUM("ADC Filter Mode", a1_adc_filter_mode),
+	SOC_SINGLE("ADC Mic Bias Switch", LINEIN_CFG, MICBIAS_EN, 1, 0),
+	SOC_ENUM("ADC Mic Bias Level", a1_adc_mic_bias_level),
+	SOC_SINGLE("ADC DEM Switch", BLOCK_EN, ADC_DEM_EN, 1, 0),
+	SOC_DOUBLE_TLV("ADC PGA Volume", VOL_CTRL0, PGAR_VC, PGAL_VC,
+		       0x1f, 0, a1_adc_pga_vol_tlv),
+	SOC_DOUBLE("ADC PGA Zero Cross-detection Switch", BLOCK_EN,
+		   PGAL_ZCD_EN, PGAR_ZCD_EN, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget a1_t9015_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("Right IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("Left IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("Right DAC Sel", SND_SOC_NOPM, 0, 0,
+			 &t9015_right_dac_mux),
+	SND_SOC_DAPM_MUX("Left DAC Sel", SND_SOC_NOPM, 0, 0,
+			 &t9015_left_dac_mux),
+	SND_SOC_DAPM_DAC("Right DAC", NULL, BLOCK_EN, DACR_EN, 0),
+	SND_SOC_DAPM_DAC("Left DAC",  NULL, BLOCK_EN, DACL_EN, 0),
+	SND_SOC_DAPM_MUX("Right+ Driver Sel", SND_SOC_NOPM, 0, 0,
+			 &a1_right_driver_mux),
+	SND_SOC_DAPM_MUX("Left+ Driver Sel", SND_SOC_NOPM, 0, 0,
+			 &a1_left_driver_mux),
+	SND_SOC_DAPM_OUT_DRV("Right+ Driver", BLOCK_EN, LORP_EN, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("Left+ Driver",  BLOCK_EN, LOLP_EN, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("LORP"),
+	SND_SOC_DAPM_OUTPUT("LOLP"),
+
+	SND_SOC_DAPM_INPUT("ADC IN Right"),
+	SND_SOC_DAPM_INPUT("ADC IN Left"),
+	SND_SOC_DAPM_MUX("ADC PGA Right Sel", SND_SOC_NOPM, 0, 0,
+			 &a1_adc_pga_right_mux),
+	SND_SOC_DAPM_MUX("ADC PGA Left Sel", SND_SOC_NOPM, 0, 0,
+			 &a1_adc_pga_left_mux),
+	SND_SOC_DAPM_PGA("ADC PGA Right", BLOCK_EN, PGAR_EN, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("ADC PGA Left", BLOCK_EN, PGAL_EN, 0, NULL, 0),
+	SND_SOC_DAPM_ADC("ADC Right", NULL, BLOCK_EN, ADCR_EN, 0),
+	SND_SOC_DAPM_ADC("ADC Left", NULL, BLOCK_EN, ADCL_EN, 0),
+	SND_SOC_DAPM_MUX("ADC Right Sel", SND_SOC_NOPM, 0, 0, &a1_adc_right_mux),
+	SND_SOC_DAPM_MUX("ADC Left Sel", SND_SOC_NOPM, 0, 0, &a1_adc_left_mux),
+	SND_SOC_DAPM_AIF_OUT("ADC OUT Right", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("ADC OUT Left", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route a1_t9015_dapm_routes[] = {
+	{ "Right IN", NULL, "Playback" },
+	{ "Left IN", NULL, "Playback" },
+	{ "Right DAC Sel", "Right", "Right IN" },
+	{ "Right DAC Sel", "Left", "Left IN" },
+	{ "Left DAC Sel", "Right", "Right IN" },
+	{ "Left DAC Sel", "Left", "Left IN" },
+	{ "Right DAC", NULL, "Right DAC Sel" },
+	{ "Left DAC", NULL, "Left DAC Sel" },
+	{ "Right+ Driver Sel", "Right DAC", "Right DAC" },
+	{ "Right+ Driver Sel", "Left DAC Inverted", "Right DAC" },
+	{ "Left+ Driver Sel", "Left DAC", "Left DAC" },
+	{ "Left+ Driver Sel", "Right DAC Inverted", "Left DAC" },
+	{ "Right+ Driver", NULL, "Right+ Driver Sel" },
+	{ "Left+ Driver", NULL, "Left+ Driver Sel" },
+	{ "LORP", NULL, "Right+ Driver", },
+	{ "LOLP", NULL, "Left+ Driver", },
+
+	{ "ADC PGA Right Sel", "Differential", "ADC IN Right" },
+	{ "ADC PGA Right Sel", "Positive", "ADC IN Right" },
+	{ "ADC PGA Right Sel", "Negative", "ADC IN Right" },
+	{ "ADC PGA Left Sel", "Differential", "ADC IN Left" },
+	{ "ADC PGA Left Sel", "Positive", "ADC IN Left" },
+	{ "ADC PGA Left Sel", "Negative", "ADC IN Left" },
+	{ "ADC PGA Right", NULL, "ADC PGA Right Sel" },
+	{ "ADC PGA Left", NULL, "ADC PGA Left Sel" },
+	{ "ADC Right", NULL, "ADC PGA Right" },
+	{ "ADC Left", NULL, "ADC PGA Left" },
+	{ "ADC Right Sel", "Right", "ADC Right" },
+	{ "ADC Right Sel", "Left", "ADC Left" },
+	{ "ADC Left Sel", "Right", "ADC Right" },
+	{ "ADC Left Sel", "Left", "ADC Left" },
+	{ "ADC OUT Right", NULL, "ADC Right Sel" },
+	{ "ADC OUT Left", NULL, "ADC Left Sel" },
+	{ "Capture", NULL, "ADC OUT Right" },
+	{ "Capture", NULL, "ADC OUT Left" },
+};
+
 static int t9015_set_bias_level(struct snd_soc_component *component,
 				enum snd_soc_bias_level level)
 {
@@ -241,6 +465,18 @@  static int t9015_component_probe(struct snd_soc_component *component)
 	return 0;
 }
 
+static int a1_t9015_component_probe(struct snd_soc_component *component)
+{
+	/*
+	 * This configuration was stealed from original Amlogic's driver to
+	 * reproduce the behavior of the driver more accurately. However, it is
+	 * not known for certain what it actually affects.
+	 */
+	snd_soc_component_write(component, POWER_CFG, 0x00010000);
+
+	return 0;
+}
+
 static const struct snd_soc_component_driver t9015_codec_driver = {
 	.probe			= t9015_component_probe,
 	.set_bias_level		= t9015_set_bias_level,
@@ -254,6 +490,19 @@  static const struct snd_soc_component_driver t9015_codec_driver = {
 	.endianness		= 1,
 };
 
+static const struct snd_soc_component_driver a1_t9015_codec_driver = {
+	.probe			= a1_t9015_component_probe,
+	.set_bias_level		= t9015_set_bias_level,
+	.controls		= a1_t9015_snd_controls,
+	.num_controls		= ARRAY_SIZE(a1_t9015_snd_controls),
+	.dapm_widgets		= a1_t9015_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(a1_t9015_dapm_widgets),
+	.dapm_routes		= a1_t9015_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(a1_t9015_dapm_routes),
+	.suspend_bias_off	= 1,
+	.endianness		= 1,
+};
+
 static int t9015_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -315,11 +564,21 @@  static const struct t9015_match_data t9015_match_data = {
 	.max_register = POWER_CFG,
 };
 
+static const struct t9015_match_data a1_t9015_match_data = {
+	.component_drv = &a1_t9015_codec_driver,
+	.dai_drv = &a1_t9015_dai,
+	.max_register = LINEIN_CFG,
+};
+
 static const struct of_device_id t9015_ids[] __maybe_unused = {
 	{
 		.compatible = "amlogic,t9015",
 		.data = &t9015_match_data,
 	},
+	{
+		.compatible = "amlogic,t9015-a1",
+		.data = &a1_t9015_match_data,
+	},
 	{ }
 };
 MODULE_DEVICE_TABLE(of, t9015_ids);