All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family
  2025-06-06  6:51 [PATCH v1 0/4] ASoc: tac5x1x: mixer-test report Niranjan H Y
@ 2025-06-06  6:51 ` Niranjan H Y
  2025-06-06 12:47   ` Mark Brown
  0 siblings, 1 reply; 4+ messages in thread
From: Niranjan H Y @ 2025-06-06  6:51 UTC (permalink / raw)
  To: broonie
  Cc: andriy.shevchenko, tiwai, alsa-devel, baojun.xu, jesse-ji,
	shenghao-ding, liam.r.girdwood, navada, v-hampiholi, niranjan.hy

tac5x1x family are series of low-power and high performance
mono/stereo audio codecs consists of ADC and DAC combinations.
The patch adds supports for Codecs(DAC & ADC), ADC only and
DAC only configurations available in the tac5x1x family.

Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
---
 sound/soc/codecs/Kconfig       |   16 +
 sound/soc/codecs/Makefile      |    6 +-
 sound/soc/codecs/tac5x1x-i2c.c |   81 ++
 sound/soc/codecs/tac5x1x.c     | 2389 ++++++++++++++++++++++++++++++++
 sound/soc/codecs/tac5x1x.h     |  302 ++++
 5 files changed, 2793 insertions(+), 1 deletion(-)
 create mode 100644 sound/soc/codecs/tac5x1x-i2c.c
 create mode 100644 sound/soc/codecs/tac5x1x.c
 create mode 100644 sound/soc/codecs/tac5x1x.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 477d33234fdd..74abc0d68237 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -258,6 +258,8 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_STA529
 	imply SND_SOC_STAC9766
 	imply SND_SOC_STI_SAS
+	imply SND_SOC_TAC5X1X
+	imply SND_SOC_TAC5X1X_I2C
 	imply SND_SOC_TAS2552
 	imply SND_SOC_TAS2562
 	imply SND_SOC_TAS2764
@@ -2001,6 +2003,20 @@ config SND_SOC_STAC9766
 config SND_SOC_STI_SAS
 	tristate "codec Audio support for STI SAS codec"
 
+config SND_SOC_TAC5X1X
+	tristate "Texas Instruments TAC5X1X family codecs"
+	depends on I2C && REGMAP_I2C
+
+config SND_SOC_TAC5X1X_I2C
+	tristate "Texas Instruments TAC5X1X family driver based on I2C"
+	depends on I2C && REGMAP_I2C
+	select SND_SOC_TAC5X1X
+	help
+	  Enable support for Texas Instruments TAC5X1X family Audio chips.
+	  The family consists mono/stereo audio codecs, DACs and ADCs.
+	  Includes support for TAC5311-Q1, TAC5411-Q1, TAC5111, TAC5211,
+	  TAA5212, TAA5412-Q1, TAD5112, TAD5212, TAC5312, TAC5412-Q1,
+
 config SND_SOC_TAS2552
 	tristate "Texas Instruments TAS2552 Mono Audio amplifier"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 66e2f2381d40..ec315236f2d9 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -312,6 +312,8 @@ snd-soc-tas2770-y := tas2770.o
 snd-soc-tas2781-comlib-y := tas2781-comlib.o
 snd-soc-tas2781-fmwlib-y := tas2781-fmwlib.o
 snd-soc-tas2781-i2c-y := tas2781-i2c.o
+snd-soc-tac5x1x-y := tac5x1x.o
+snd-soc-tac5x1x-i2c-y := tac5x1x-i2c.o
 snd-soc-tfa9879-y := tfa9879.o
 snd-soc-tfa989x-y := tfa989x.o
 snd-soc-tlv320adc3xxx-y := tlv320adc3xxx.o
@@ -717,6 +719,8 @@ obj-$(CONFIG_SND_SOC_STA32X)   += snd-soc-sta32x.o
 obj-$(CONFIG_SND_SOC_STA350)   += snd-soc-sta350.o
 obj-$(CONFIG_SND_SOC_STA529)   += snd-soc-sta529.o
 obj-$(CONFIG_SND_SOC_STAC9766)	+= snd-soc-stac9766.o
+obj-$(CONFIG_SND_SOC_TAC5X1X)	+= snd-soc-tac5x1x.o
+obj-$(CONFIG_SND_SOC_TAC5X1X_I2C)	+= snd-soc-tac5x1x-i2c.o
 obj-$(CONFIG_SND_SOC_STI_SAS)	+= snd-soc-sti-sas.o
 obj-$(CONFIG_SND_SOC_TAS2552)	+= snd-soc-tas2552.o
 obj-$(CONFIG_SND_SOC_TAS2562)	+= snd-soc-tas2562.o
@@ -851,4 +855,4 @@ obj-$(CONFIG_SND_SOC_LPASS_RX_MACRO)	+= snd-soc-lpass-rx-macro.o
 obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO)	+= snd-soc-lpass-tx-macro.o
 
 # Mux
-obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
\ No newline at end of file
+obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
diff --git a/sound/soc/codecs/tac5x1x-i2c.c b/sound/soc/codecs/tac5x1x-i2c.c
new file mode 100644
index 000000000000..26af55a29b03
--- /dev/null
+++ b/sound/soc/codecs/tac5x1x-i2c.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments TAC5x1x Audio Codec
+//
+// Copyright (C) 2020 - 2025 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// TAC5x1x are a series of low-power and high performance mono or stereo
+// audio codecs, as well as multiple inputs and outputs programmable in
+// single-ended or fully differential configurations. Device supports both
+// Microphone and Line In input on ADC Channel. DAC Output can be configured
+// for either Line Out or Head Phone Load.
+//
+// Author: Kevin Lu <kevin-lu@ti.com>
+// Author: Kokila Karuppusamy <kokila.karuppusamy@ti.com>
+// Author: Niranjan H Y <niranjan.hy@ti.com>
+//
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+
+#include "tac5x1x.h"
+
+static const struct i2c_device_id tac5x1x_id[] = {
+	{ "taa5212", TAA5212 },
+	{ "taa5412", TAA5412 },
+	{ "tac5111", TAC5111 },
+	{ "tac5112", TAC5112 },
+	{ "tac5211", TAC5211 },
+	{ "tac5212", TAC5212 },
+	{ "tac5311", TAC5311 },
+	{ "tac5312", TAC5312 },
+	{ "tac5411", TAC5411 },
+	{ "tac5412", TAC5412 },
+	{ "tad5112", TAD5112 },
+	{ "tad5212", TAD5212 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, tac5x1x_id);
+
+static int tac5x1x_i2c_probe(struct i2c_client *i2c)
+{
+	int ret;
+	enum tac5x1x_type type;
+	struct regmap *regmap;
+	const struct regmap_config *config = &tac5x1x_regmap;
+
+	regmap = devm_regmap_init_i2c(i2c, config);
+	type = (uintptr_t)i2c_get_match_data(i2c);
+
+	dev_info(&i2c->dev, "probing %s codec_type = %d\n",
+		 i2c->name, type);
+
+	ret = tac5x1x_probe(&i2c->dev, regmap, type);
+	if (ret)
+		dev_err(&i2c->dev, "probe failed");
+
+	return ret;
+}
+
+static void tac5x1x_i2c_remove(struct i2c_client *client)
+{
+	tac5x1x_remove(&client->dev);
+}
+
+static struct i2c_driver tac5x1x_i2c_driver = {
+	.driver = {
+		.name		= "tac5x1x-codec",
+		.of_match_table	= of_match_ptr(tac5x1x_of_match),
+	},
+	.probe		= tac5x1x_i2c_probe,
+	.remove		= tac5x1x_i2c_remove,
+	.id_table	= tac5x1x_id,
+};
+
+module_i2c_driver(tac5x1x_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC tac5x1x codec driver");
+MODULE_AUTHOR("Texas Instruments");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tac5x1x.c b/sound/soc/codecs/tac5x1x.c
new file mode 100644
index 000000000000..f0481a2aebd0
--- /dev/null
+++ b/sound/soc/codecs/tac5x1x.c
@@ -0,0 +1,2389 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// tac5x1x.c
+//
+// Copyright (C) 2022 - 2025 Texas Instruments Incorporated
+//
+// Author: Kevin Lu <kevin-lu@ti.com>
+// Author: Kokila Karuppusamy <kokila.karuppusamy@ti.com>
+// Author: Niranjan H Y <niranjan.hy@ti.com>
+//
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/regmap.h>
+#include <linux/device.h>
+#include <sound/tlv.h>
+
+#include "tac5x1x.h"
+
+struct tac5x1x_setup_gpio {
+	s32 gpio_func[3];
+	s32 gpio_drive[3];
+	s32 gpi1_func;
+	s32 gpa_gpio;
+};
+
+struct tac5x1x_input_diag_config {
+	s32 in_ch_en;
+	s32 out_ch_en;
+	s32 incl_se_inm;
+	s32 incl_ac_coup;
+};
+
+struct tac5x1x_irqinfo {
+	struct delayed_work irq_work;
+	s32 irq_gpio;
+	s32 irq;
+	bool irq_enable;
+};
+
+struct tac5x1x_dev {
+	u32 addr;
+	bool is_loading;
+};
+
+struct interrupt_info {
+	const char * const *names;
+	size_t size;
+};
+
+struct tac5x1x_priv {
+	struct snd_soc_component *component;
+	struct regmap *regmap;
+	struct device *dev;
+	enum tac5x1x_type codec_type;
+	bool pll_setup;
+	s32 irq;
+	s32 vref_vg;
+	s32 adc_en;
+	s32 dac_en;
+	s32 micbias_en;
+	s32 micbias_vg;
+	s32 agc_en;
+	s32 uad_en;
+	s32 vad_en;
+	s32 uag_en;
+	s32 dac_mute;
+	s32 dac_dig_vol[4];
+	s32 dac_chns;
+	u32 sysclk;
+	s32 ndev;
+	s32 micbias_threshold[2];
+	s32 gpa_threshold[2];
+	u8 dev_name[20];
+	struct snd_soc_jack *jack;
+	struct regulator *supply_avdd;
+	struct regulator *supply_iovdd;
+	struct tac5x1x_setup_gpio *gpio_setup;
+	struct tac5x1x_irqinfo irqinfo;
+	/* mutex to prevent simulataneous device read */
+	struct mutex dev_lock;
+	struct tac5x1x_dev tac5x1x_dev[2];
+	struct tac5x1x_input_diag_config input_diag_config;
+	void (*irq_work_func)(struct tac5x1x_priv *tac5x1x);
+};
+
+static const char * const int_ltch0[] = {
+	"Clock Error",
+	"PLL Lock",
+	"Boost Over Temperature",
+	"Boost Over Current",
+	"Boost Mode",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+};
+
+static const char * const chx_ltch[] = {
+	"Input Channel1 fault",
+	"Input Channel2 fault",
+	"Output Channel1 fault",
+	"Output Channel2 fault",
+	"Short to VBAT_IN",
+	"Reserved",
+	"Reserved",
+	"Reserved",
+};
+
+static const char * const in_ch1_ltch[] = {
+	"IN_CH1 open Input",
+	"IN_CH1 Input shorted",
+	"IN_CH1 INP shorted to GND",
+	"IN_CH1 INM shorted to GND",
+	"IN_CH1 INP shorted to MICBIAS",
+	"IN_CH1 INM shorted to MICBIAS",
+	"IN_CH1 INP shorted to VBAT_IN",
+	"IN_CH1 INM shorted to VBAT_IN",
+};
+
+static const char * const in_ch2_ltch[] = {
+	"IN_CH2 open Input",
+	"IN_CH2 Input shorted",
+	"IN_CH2 INP shorted to GND",
+	"IN_CH2 INM shorted to GND",
+	"IN_CH2 INP shorted to MICBIAS",
+	"IN_CH2 INM shorted to MICBIAS",
+	"IN_CH2 INP shorted to VBAT_IN",
+	"IN_CH2 INM shorted to VBAT_IN",
+};
+
+static const char * const out_ch1_ltch[] = {
+	"OUT_CH1 OUT1P Short circuit Fault",
+	"OUT_CH1 OUT1M Short circuit Fault",
+	"OUT_CH1 DRVRP Virtual Ground Fault",
+	"OUT_CH1 DRVRM Virtual ground Fault",
+	"OUT_CH1 ADC CH1 Mask",
+	"OUT_CH1 ADC CH2 MASK",
+	"Reserved",
+	"Reserved",
+};
+
+static const char * const out_ch2_ltch[] = {
+	"OUT_CH2 OUT2P Short circuit Fault",
+	"OUT_CH2 OUT2M Short circuit Fault",
+	"OUT_CH2 DRVRP Virtual Ground Fault",
+	"OUT_CH2 DRVRM Virtual ground Fault",
+	"Reserved",
+	"Reserved",
+	"AREG SC Fault Mask",
+	"AREG SC Fault",
+};
+
+static const char * const int_ltch1[] = {
+	"CH1 INP Over Voltage",
+	"CH1 INM Over Voltage",
+	"CH2 INP over Voltage",
+	"CH2 INM Over Voltage",
+	"Headset Insert Detection",
+	"Headset Remove Detection",
+	"Headset Hook",
+	"MIPS Overload",
+};
+
+static const char * const int_ltch2[] = {
+	"GPA Up threashold Fault",
+	"GPA low threashold Fault",
+	"VAD Power up detect",
+	"VAD power down detect",
+	"Micbias short circuit",
+	"Micbias high current fault",
+	"Micbias low current fault",
+	"Micbias Over voltage fault",
+};
+
+static u32 int_reg_array[] = {
+	TAC5X1X_REG_INT_LTCH0,
+	TAC5X1X_REG_CHX_LTCH,
+	TAC5X1X_REG_IN_CH1_LTCH,
+	TAC5X1X_REG_IN_CH2_LTCH,
+	TAC5X1X_REG_OUT_CH1_LTCH,
+	TAC5X1X_REG_OUT_CH2_LTCH,
+	TAC5X1X_REG_INT_LTCH1,
+	TAC5X1X_REG_INT_LTCH2,
+};
+
+static const char * const int_reg_arr_name[] = {
+	"TAC5X1X_REG_INT_LTCH0",
+	"TAC5X1X_REG_CHX_LTCH",
+	"TAC5X1X_REG_IN_CH1_LTCH",
+	"TAC5X1X_REG_IN_CH2_LTCH",
+	"TAC5X1X_REG_OUT_CH1_LTCH",
+	"TAC5X1X_REG_OUT_CH2_LTCH",
+	"TAC5X1X_REG_INT_LTCH1",
+	"TAC5X1X_REG_INT_LTCH2",
+};
+
+static const struct interrupt_info interrupts[] = {
+	{ int_ltch0, ARRAY_SIZE(int_ltch0) },
+	{ chx_ltch, ARRAY_SIZE(chx_ltch) },
+	{ in_ch1_ltch, ARRAY_SIZE(in_ch1_ltch) },
+	{ in_ch2_ltch, ARRAY_SIZE(in_ch2_ltch) },
+	{ out_ch1_ltch, ARRAY_SIZE(out_ch1_ltch) },
+	{ out_ch2_ltch, ARRAY_SIZE(out_ch2_ltch) },
+	{ int_ltch1, ARRAY_SIZE(int_ltch1) },
+	{ int_ltch2, ARRAY_SIZE(int_ltch2) },
+};
+
+static s32 tac5x1x_regmap_write(struct tac5x1x_priv *tac5x1x,
+				u32 reg, u32 value)
+{
+	s32 ret;
+	s32 retry_count = 5;
+
+	while (retry_count--) {
+		ret = regmap_write(tac5x1x->regmap, reg,
+				   value);
+		if (ret >= 0)
+			break;
+		usleep_range(5000, 5050);
+	}
+	if (retry_count == -1)
+		return 3;
+	else
+		return ret;
+}
+
+static s32 tac5x1x_regmap_read(struct tac5x1x_priv *tac5x1x,
+			       u32 reg, u32 *value)
+{
+	s32 ret;
+	s32 retry_count = 5;
+
+	ret = regmap_reinit_cache(tac5x1x->regmap, &tac5x1x_regmap);
+	if (ret) {
+		dev_err(tac5x1x->dev, "Failed to reinit reg cache\n");
+		return ret;
+	}
+
+	while (retry_count--) {
+		ret = regmap_read(tac5x1x->regmap, reg,
+				  value);
+		if (ret >= 0)
+			break;
+		usleep_range(5000, 5050);
+	}
+	if (retry_count == -1)
+		return 3;
+	else
+		return ret;
+}
+
+static s32 tac5x1x_dev_read(struct tac5x1x_priv *tac5x1x,
+			    u32 dev_no, u32 reg,
+			    u32 *ref_value)
+{
+	s32 ret;
+
+	guard(mutex)(&tac5x1x->dev_lock);
+	if (dev_no < tac5x1x->ndev) {
+		ret = tac5x1x_regmap_write(tac5x1x,
+					   TAC_PAGE_SELECT, 0);
+		if (ret < 0) {
+			dev_err(tac5x1x->dev, "%s, E=%d\n",
+				__func__, ret);
+			return ret;
+		}
+
+		ret = tac5x1x_regmap_read(tac5x1x, reg, ref_value);
+		if (ret < 0)
+			dev_err(tac5x1x->dev, "read, ERROR, E=%d\n",
+				ret);
+		else
+			dev_dbg(tac5x1x->dev,
+				"read PAGE:REG 0x%02x:0x%02x,0x%02x\n",
+					TAC_PAGE_ID(reg),
+					TAC_PAGE_REG(reg), *ref_value);
+	} else {
+		dev_err(tac5x1x->dev, "%s, ERROR: no such device(%d)\n",
+			__func__, dev_no);
+	}
+
+	return 0;
+}
+
+static void tac5x1x_enable_irq(struct tac5x1x_priv *tac5x1x, bool is_enable)
+{
+	if (is_enable == tac5x1x->irqinfo.irq_enable &&
+	    !gpio_is_valid(tac5x1x->irqinfo.irq_gpio))
+		return;
+	if (is_enable)
+		enable_irq(tac5x1x->irqinfo.irq);
+	else
+		disable_irq_nosync(tac5x1x->irqinfo.irq);
+	tac5x1x->irqinfo.irq_enable = is_enable;
+}
+
+static void tac5x1x_irq_work_func(struct tac5x1x_priv *tac5x1x)
+{
+	u32 reg_val, array_size, i, index = 0, bit = 0;
+	s32 rc;
+
+	tac5x1x_enable_irq(tac5x1x, false);
+	array_size =  ARRAY_SIZE(int_reg_array);
+	for (i = 0; i < array_size; i++) {
+		rc = tac5x1x_dev_read(tac5x1x, index,
+				      int_reg_array[i], &reg_val);
+		if (!rc) {
+			for (s32 j = 0; j < interrupts[i].size; j++) {
+				bit = interrupts[i].size - 1 - j;
+				if (reg_val & (1 << bit))
+					dev_info(tac5x1x->dev,
+						 "Reg: %s || %s\n",
+						int_reg_arr_name[i],
+						interrupts[i].names[j]);
+			}
+		} else {
+			dev_err(tac5x1x->dev,
+				"%s DEV_NO%d Read Reg 0x%04x error(rc=%d)\n",
+					tac5x1x->dev_name, index,
+					int_reg_array[i], rc);
+		}
+	}
+	tac5x1x_enable_irq(tac5x1x, true);
+}
+
+static const struct regmap_range_cfg tac5x1x_ranges[] = {
+	{
+		.range_min = 0,
+		.range_max = 12 * 128,
+		.selector_reg = TAC_PAGE_SELECT,
+		.selector_mask = GENMASK(7, 0),
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 128,
+	},
+};
+
+const struct regmap_config tac5x1x_regmap = {
+	.max_register = 12 * 128,
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_MAPLE,
+	.ranges = tac5x1x_ranges,
+	.num_ranges = ARRAY_SIZE(tac5x1x_ranges),
+};
+EXPORT_SYMBOL(tac5x1x_regmap);
+
+static void irq_work_routine(struct work_struct *work)
+{
+	struct tac5x1x_priv *tac5x1x =
+		container_of(work, struct tac5x1x_priv, irqinfo.irq_work.work);
+
+	if (tac5x1x->irq_work_func) {
+		dev_dbg(tac5x1x->dev, "Calling irq_work_func\n");
+		tac5x1x->irq_work_func(tac5x1x);
+	} else {
+		dev_dbg(tac5x1x->dev,
+			"%s, irq_work_func is NULL\n", __func__);
+	}
+}
+
+static irqreturn_t tac5x1x_irq_handler(s32 irq, void *dev_id)
+{
+	struct tac5x1x_priv *tac5x1x = (struct tac5x1x_priv *)dev_id;
+
+	/* get IRQ status after 100 ms */
+	schedule_delayed_work(&tac5x1x->irqinfo.irq_work,
+			      msecs_to_jiffies(100));
+	return IRQ_HANDLED;
+}
+
+static s32 tac5x1x_register_interrupt(struct tac5x1x_priv *tac5x1x)
+{
+	struct device_node *np = tac5x1x->dev->of_node;
+	s32 ret;
+
+	mutex_init(&tac5x1x->dev_lock);
+	tac5x1x->irqinfo.irq = of_irq_get(np, 0);
+	INIT_DELAYED_WORK(&tac5x1x->irqinfo.irq_work, irq_work_routine);
+
+	dev_dbg(tac5x1x->dev, "irq = %d\n", tac5x1x->irqinfo.irq);
+	ret = request_threaded_irq(tac5x1x->irqinfo.irq, tac5x1x_irq_handler,
+				   NULL, IRQF_TRIGGER_FALLING, "TAC-IRQ", tac5x1x);
+	if (ret) {
+		dev_err(tac5x1x->dev, "request irq failed, ret %d\n", ret);
+		return ret;
+	}
+	tac5x1x->irq_work_func = tac5x1x_irq_work_func;
+	return 0;
+};
+
+static s32 tac5x1x_set_GPO1_gpio(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 gpio_check, val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPO1);
+	gpio_check = ((val & TAC5X1X_GPIOX_CFG_MASK) >> 0);
+	if (gpio_check != TAC5X1X_GPIO_GPO) {
+		dev_err(component->dev,
+			"%s: GPO1 is not configure as a GPO output\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	if (ucontrol->value.integer.value[0])
+		val = 0;
+	else
+		val = TAC5X1X_GPO1_VAL;
+
+	ucontrol->value.integer.value[0] = (val >> 0);
+	snd_soc_component_update_bits(component, TAC5X1X_GPIOVAL,
+				      TAC5X1X_GPO1_VAL, val);
+
+	return 1;
+};
+
+static s32 tac5x1x_get_GPIO1_gpio(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPIOVAL);
+	ucontrol->value.integer.value[0] = ((val & TAC5X1X_GPIO1_MON) >> 0);
+
+	return 0;
+};
+
+static s32 tac5x1x_set_GPIO1_gpio(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 gpio_check, val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPIO1);
+	gpio_check = ((val & TAC5X1X_GPIOX_CFG_MASK) >> 0);
+	if (gpio_check == TAC5X1X_GPIO_DISABLE) {
+		dev_err(component->dev,
+			"%s: GPIO1 is not configure as a GPO output\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	if (ucontrol->value.integer.value[0])
+		val = 0;
+	else
+		val = TAC5X1X_GPIO1_VAL;
+
+	ucontrol->value.integer.value[0] = (val >> 0);
+	snd_soc_component_update_bits(component, TAC5X1X_GPIOVAL,
+				      TAC5X1X_GPIO1_VAL, val);
+
+	return 1;
+};
+
+static s32 tac5x1x_get_GPIO2_gpio(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPIOVAL);
+	ucontrol->value.integer.value[0] = !!(val & TAC5X1X_GPIO2_MON);
+
+	return 0;
+};
+
+static s32 tac5x1x_set_GPIO2_gpio(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 gpio_check, val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPIO2);
+	gpio_check = ((val & TAC5X1X_GPIOX_CFG_MASK) >> 0);
+	if (gpio_check == TAC5X1X_GPIO_DISABLE) {
+		dev_err(component->dev,
+			"%s: GPIO2 is not configure as a GPO output\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	if (ucontrol->value.integer.value[0])
+		val = 0;
+	else
+		val = TAC5X1X_GPIO2_VAL;
+
+	ucontrol->value.integer.value[0] = (val >> 0);
+	snd_soc_component_update_bits(component, TAC5X1X_GPIOVAL,
+				      TAC5X1X_GPIO2_VAL, val);
+
+	return 1;
+};
+
+static s32 tac5x1x_get_GPI1_gpio(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	s32 val;
+
+	val = snd_soc_component_read(component, TAC5X1X_GPIOVAL);
+	ucontrol->value.integer.value[0] = !!(val & TAC5X1X_GPI1_MON);
+
+	return 0;
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPO1[] = {
+	SOC_SINGLE_BOOL_EXT("GPO1 GPO", 0, NULL, tac5x1x_set_GPO1_gpio),
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPIO1_I[] = {
+	SOC_SINGLE_BOOL_EXT("GPIO1 GPI", 0, tac5x1x_get_GPIO1_gpio, NULL),
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPIO1_O[] = {
+	SOC_SINGLE_BOOL_EXT("GPIO1 GPO", 0, NULL, tac5x1x_set_GPIO1_gpio),
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPIO2_I[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "GPIO2 GPI",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.get = tac5x1x_get_GPIO2_gpio,
+		.info = snd_soc_info_bool_ext,
+	}
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPIO2_O[] = {
+	SOC_SINGLE_BOOL_EXT("GPIO2 GPO", 0, NULL, tac5x1x_set_GPIO2_gpio),
+};
+
+static const struct snd_kcontrol_new tac5x1x_GPI1[] = {
+	SOC_SINGLE_BOOL_EXT("GPI1 GPI", 0, tac5x1x_get_GPI1_gpio, NULL),
+};
+
+/* Record */
+/* ADC Analog/PDM Selection */
+static const char *const tac5x1x_input_source_text[] = {"Analog", "PDM"};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_in1_source_enum, TAC5X1X_INTF4, 7,
+		tac5x1x_input_source_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_in2_source_enum, TAC5X1X_INTF4, 6,
+		tac5x1x_input_source_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_in1_source_control[] = {
+	SOC_DAPM_ENUM("CH1 Source MUX", tac5x1x_in1_source_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_in2_source_control[] = {
+	SOC_DAPM_ENUM("CH2 Source MUX", tac5x1x_in2_source_enum),
+};
+
+static const char *const tad5x1x_input_source_text[] = {"Disable", "PDM"};
+static SOC_ENUM_SINGLE_DECL(tad5x1x_in1_source_enum, TAC5X1X_INTF4, 7,
+		tad5x1x_input_source_text);
+static SOC_ENUM_SINGLE_DECL(tad5x1x_in2_source_enum, TAC5X1X_INTF4, 6,
+		tad5x1x_input_source_text);
+
+static const struct snd_kcontrol_new tad5x1x_dapm_in1_source_control[] = {
+	SOC_DAPM_ENUM("CH1 Source MUX", tad5x1x_in1_source_enum),
+};
+
+static const struct snd_kcontrol_new tad5x1x_dapm_in2_source_control[] = {
+	SOC_DAPM_ENUM("CH2 Source MUX", tad5x1x_in2_source_enum),
+};
+
+/* ADC Analog source Selection */
+static const char *const tac5x1x_input_analog_sel_text[] = {
+	"Differential",
+	"Single-ended",
+	"Single-ended mux INxP",
+	"Single-ended mux INxM",
+};
+
+static const char *const tac5x1x_input_analog2_sel_text[] = {
+	"Differential",
+	"Single-ended",
+};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc1_config_enum, TAC5X1X_ADCCH1C0, 6,
+		tac5x1x_input_analog_sel_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc2_config_enum, TAC5X1X_ADCCH2C0, 6,
+		tac5x1x_input_analog2_sel_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_adc1_config_control[] = {
+	SOC_DAPM_ENUM("ADC1 Analog MUX", tac5x1x_adc1_config_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_adc2_config_control[] = {
+	SOC_DAPM_ENUM("ADC2 Analog MUX", tac5x1x_adc2_config_enum),
+};
+
+/*
+ * ADC full-scale selection
+ * 2/10-VRMS is for TAX52xx/TAX51xx devices
+ * 4/5-VRMS is for TAX54xx/TAX53xx devices
+ */
+static const char *const tac5x1x_adc_fscale_text[] = {"2/10-VRMS",
+	"4/5-VRMS"};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc1_fscale_enum, TAC5X1X_ADCCH1C0, 1,
+		tac5x1x_adc_fscale_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc2_fscale_enum, TAC5X1X_ADCCH2C0, 1,
+		tac5x1x_adc_fscale_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_adc1_fscale_control[] = {
+	SOC_DAPM_ENUM("ADC1 FSCALE MUX", tac5x1x_adc1_fscale_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_adc2_fscale_control[] = {
+	SOC_DAPM_ENUM("ADC2 FSCALE MUX", tac5x1x_adc2_fscale_enum),
+};
+
+/* Impedance Selection */
+static const char *const resistor_text[] = {
+	"5 kOhm",
+	"10 kOhm",
+	"40 kOhm",
+};
+
+static SOC_ENUM_SINGLE_DECL(adc1_resistor_enum, TAC5X1X_ADCCH1C0, 4,
+		resistor_text);
+static SOC_ENUM_SINGLE_DECL(adc2_resistor_enum, TAC5X1X_ADCCH2C0, 4,
+		resistor_text);
+
+/* PDM Data input pin Selection */
+static const char *const pdm_pin_text[] = {
+	"Disable",
+	"GPIO1",
+	"GPIO2",
+	"GPI1",
+};
+
+static SOC_ENUM_SINGLE_DECL(pdm_12_pin_enum, TAC5X1X_INTF4, 2, pdm_pin_text);
+static SOC_ENUM_SINGLE_DECL(pdm_34_pin_enum, TAC5X1X_INTF4, 0, pdm_pin_text);
+static const struct snd_kcontrol_new pdm_12_pin_controls[] = {
+	SOC_DAPM_ENUM("PDM chn12 Datain Select", pdm_12_pin_enum),
+};
+
+static const struct snd_kcontrol_new pdm_34_pin_controls[] = {
+	SOC_DAPM_ENUM("PDM chn34 Datain Select", pdm_34_pin_enum),
+};
+
+static const char *const pdmclk_text[] = {
+	"2.8224 MHz or 3.072 MHz", "1.4112 MHz or 1.536 MHz",
+	"705.6 kHz or 768 kHz", "5.6448 MHz or 6.144 MHz"};
+
+static SOC_ENUM_SINGLE_DECL(pdmclk_select_enum, TAC5X1X_CNTCLK0, 6,
+		pdmclk_text);
+
+/* Digital Volume control. From -80 to 47 dB in 0.5 dB steps */
+static DECLARE_TLV_DB_SCALE(record_dig_vol_tlv, -8000, 50, 0);
+
+/* Gain Calibration control. From -0.8db to 0.7db dB in 0.1 dB steps */
+static DECLARE_TLV_DB_MINMAX(record_gain_cali_tlv, -80, 70);
+
+/* Analog Level control. From -12 to 24 dB in 6 dB steps */
+static DECLARE_TLV_DB_SCALE(playback_analog_level_tlv, -1200, 600, 0);
+
+/* Digital Volume control. From -100 to 27 dB in 0.5 dB steps */
+static DECLARE_TLV_DB_SCALE(dac_dig_vol_tlv, -10000, 50, 0); // mute ?
+
+/* Gain Calibration control. From -0.8db to 0.7db dB in 0.1 dB steps */
+static DECLARE_TLV_DB_MINMAX(playback_gain_cali_tlv, -80, 70);
+
+/* Output Source Selection */
+static const char *const tac5x1x_output_source_text[] = {
+	"Disabled",
+	"DAC Input",
+	"Analog Bypass",
+	"DAC + Analog Bypass Mix",
+	"DAC -> OUTxP, INxP -> OUTxM",
+	"INxM -> OUTxP, DAC -> OUTxM",
+};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_out1_source_enum, TAC5X1X_OUT1CFG0, 5,
+		tac5x1x_output_source_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_out2_source_enum, TAC5X1X_OUT2CFG0, 5,
+		tac5x1x_output_source_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out1_source_control[] = {
+	SOC_DAPM_ENUM("OUT1X MUX", tac5x1x_out1_source_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out2_source_control[] = {
+	SOC_DAPM_ENUM("OUT2X MUX", tac5x1x_out2_source_enum),
+};
+
+/* Output Config Selection */
+static const char *const tac5x1x_output_config_text[] = {
+	"Differential",
+	"Stereo Single-ended",
+	"Mono Single-ended at OUTxP only",
+	"Mono Single-ended at OUTxM only",
+	"Pseudo differential with OUTxM as VCOM",
+	"Pseudo differential with OUTxM as external sensing",
+	"Pseudo differential with OUTxP as VCOM",
+};
+
+static const char *const tac5x1x_output2_config_text[] = {
+	"Differential",
+	"Stereo Single-ended",
+	"Mono Single-ended at OUTxP only",
+	"Mono Single-ended at OUTxM only",
+	"Pseudo differential with OUTxM as VCOM",
+	"Reserved",
+	"Pseudo differential with OUTxP as VCOM",
+};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_out1_config_enum, TAC5X1X_OUT1CFG0, 2,
+			tac5x1x_output_config_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_out2_config_enum, TAC5X1X_OUT2CFG0, 2,
+			tac5x1x_output2_config_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out1_config_control[] = {
+	SOC_DAPM_ENUM("OUT1X Config MUX", tac5x1x_out1_config_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out2_config_control[] = {
+	SOC_DAPM_ENUM("OUT2X Config MUX", tac5x1x_out2_config_enum),
+};
+
+static const char *const tac5x1x_wideband_text[] = {
+	"Audio BW 24-kHz",
+	"Wide BW 96-kHz",
+};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc1_wideband_enum, TAC5X1X_ADCCH1C0, 0,
+		tac5x1x_wideband_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc2_wideband_enum, TAC5X1X_ADCCH2C0, 0,
+		tac5x1x_wideband_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_dac1_wideband_enum, TAC5X1X_OUT1CFG1, 0,
+		tac5x1x_wideband_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_dac2_wideband_enum, TAC5X1X_OUT2CFG1, 0,
+		tac5x1x_wideband_text);
+
+static const char *const tac5x1x_tolerance_text[] = {
+	"AC Coupled with 100mVpp",
+	"AC/DC Coupled with 1Vpp",
+	"AC/DC Coupled with Rail-to-rail",
+};
+
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc1_tolerance_enum, TAC5X1X_ADCCH1C0, 2,
+		tac5x1x_tolerance_text);
+static SOC_ENUM_SINGLE_DECL(tac5x1x_adc2_tolerance_enum, TAC5X1X_ADCCH2C0, 2,
+		tac5x1x_tolerance_text);
+
+/* Output Drive Selection */
+static const char *const tac5x1x_output_driver_text[] = {
+	"Line-out",
+	"Headphone",
+	"4 ohm",
+	"FD Receiver/Debug",
+};
+
+static SOC_ENUM_SINGLE_DECL(out1p_driver_enum, TAC5X1X_OUT1CFG1, 6,
+		tac5x1x_output_driver_text);
+
+static SOC_ENUM_SINGLE_DECL(out2p_driver_enum, TAC5X1X_OUT2CFG1, 6,
+		tac5x1x_output_driver_text);
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out1_driver_control[] = {
+	SOC_DAPM_ENUM("OUT1 driver MUX", out1p_driver_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_dapm_out2_driver_control[] = {
+	SOC_DAPM_ENUM("OUT2 driver MUX", out2p_driver_enum),
+};
+
+/* Decimation Filter Selection */
+static const char *const decimation_filter_text[] = {
+	"Linear Phase", "Low Latency", "Ultra-low Latency"};
+
+static SOC_ENUM_SINGLE_DECL(decimation_filter_record_enum, TAC5X1X_DSP0, 6,
+			decimation_filter_text);
+static SOC_ENUM_SINGLE_DECL(decimation_filter_playback_enum, TAC5X1X_DSP1, 6,
+			    decimation_filter_text);
+
+static const struct snd_kcontrol_new tx_ch1_asi_switch =
+	SOC_DAPM_SINGLE("Capture Switch", TAC5X1X_PASITXCH1, 5, 1, 0);
+static const struct snd_kcontrol_new tx_ch2_asi_switch =
+	SOC_DAPM_SINGLE("Capture Switch", TAC5X1X_PASITXCH2, 5, 1, 0);
+static const struct snd_kcontrol_new tx_ch3_asi_switch =
+	SOC_DAPM_SINGLE("Capture Switch", TAC5X1X_PASITXCH3, 5, 1, 0);
+static const struct snd_kcontrol_new tx_ch4_asi_switch =
+	SOC_DAPM_SINGLE("Capture Switch", TAC5X1X_PASITXCH4, 5, 1, 0);
+
+static const struct snd_kcontrol_new rx_ch1_asi_switch =
+	SOC_DAPM_SINGLE("Switch", TAC5X1X_PASIRXCH1, 5, 1, 0);
+static const struct snd_kcontrol_new rx_ch2_asi_switch =
+	SOC_DAPM_SINGLE("Switch", TAC5X1X_PASIRXCH2, 5, 1, 0);
+
+static const char *const rx_ch3_asi_cfg_text[] = {
+		"Disable",
+		"DAC channel data",
+};
+
+static const char *const rx_ch5_asi_cfg_text[] = {
+		"Disable",
+		"DAC channel data",
+		"ADC channel output loopback",
+};
+
+static const char *const rx_ch6_asi_cfg_text[] = {
+		"Disable",
+		"DAC channel data",
+		"ADC channel output loopback",
+		"Channel Input to ICLA device",
+};
+
+static const char *const tx_ch5_asi_cfg_text[] = {
+		"Tristate",
+		"Input Channel Loopback data",
+		"Echo reference Channel data",
+};
+
+static const char *const tx_ch7_asi_cfg_text[] = {
+		"Tristate",
+		"Vbat_Wlby2,Temp_Wlby2",
+		"echo_ref_ch1,echo_ref_ch2",
+};
+
+static const char *const tx_ch8_asi_cfg_text[] = {
+		"Tristate",
+		"ICLA data",
+};
+
+static const char *const tac5x1x_slot_select_text[] = {
+	"Slot 0", "Slot 1", "Slot 2", "Slot 3",
+	"Slot 4", "Slot 5", "Slot 6", "Slot 7",
+	"Slot 8", "Slot 9", "Slot 10", "Slot 11",
+	"Slot 12", "Slot 13", "Slot 14", "Slot 15",
+	"Slot 16", "Slot 17", "Slot 18", "Slot 19",
+	"Slot 20", "Slot 21", "Slot 22", "Slot 23",
+	"Slot 24", "Slot 25", "Slot 26", "Slot 27",
+	"Slot 28", "Slot 29", "Slot 30", "Slot 31",
+};
+
+static const char *const out2x_vcom_text[] = {
+	"0.6 * Vref",
+	"AVDD by 2",
+};
+
+static const char *const diag_cfg_text[] = {
+	"0mv", "30mv", "60mv", "90mv",
+	"120mv", "150mv", "180mv", "210mv",
+	"240mv", "270mv", "300mv", "330mv",
+	"360mv", "390mv", "420mv", "450mv",
+};
+
+static const char *const diag_cfg_gnd_text[] = {
+	"0mv", "60mv", "120mv", "180mv",
+	"240mv", "300mv", "360mv", "420mv",
+	"480mv", "540mv", "600mv", "660mv",
+	"720mv", "780mv", "840mv", "900mv",
+};
+
+static SOC_ENUM_SINGLE_DECL(out2x_vcom_enum, TAC5X1X_OUT2CFG0, 1,
+		out2x_vcom_text);
+
+static SOC_ENUM_SINGLE_DECL(tx_ch5_asi_cfg_enum, TAC5X1X_PASITXCH5, 5,
+		tx_ch5_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch6_asi_cfg_enum, TAC5X1X_PASITXCH6, 5,
+		tx_ch5_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch7_asi_cfg_enum, TAC5X1X_PASITXCH7, 5,
+		tx_ch7_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch8_asi_cfg_enum, TAC5X1X_PASITXCH8, 5,
+		tx_ch8_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch1_slot_select_enum, TAC5X1X_PASITXCH1, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch2_slot_select_enum, TAC5X1X_PASITXCH2, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch3_slot_select_enum, TAC5X1X_PASITXCH3, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch4_slot_select_enum, TAC5X1X_PASITXCH4, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch5_slot_select_enum, TAC5X1X_PASITXCH5, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch6_slot_select_enum, TAC5X1X_PASITXCH6, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch7_slot_select_enum, TAC5X1X_PASITXCH7, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(tx_ch8_slot_select_enum, TAC5X1X_PASITXCH8, 0,
+		tac5x1x_slot_select_text);
+
+static SOC_ENUM_SINGLE_DECL(rx_ch3_asi_cfg_enum, TAC5X1X_PASIRXCH3, 5,
+		rx_ch3_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch4_asi_cfg_enum, TAC5X1X_PASIRXCH4, 5,
+		rx_ch3_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch5_asi_cfg_enum, TAC5X1X_PASIRXCH5, 5,
+		rx_ch5_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch6_asi_cfg_enum, TAC5X1X_PASIRXCH6, 5,
+		rx_ch6_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch7_asi_cfg_enum, TAC5X1X_PASIRXCH7, 5,
+		rx_ch6_asi_cfg_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch8_asi_cfg_enum, TAC5X1X_PASIRXCH8, 5,
+		rx_ch6_asi_cfg_text);
+
+static SOC_ENUM_SINGLE_DECL(rx_ch1_slot_select_enum, TAC5X1X_PASIRXCH1, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch2_slot_select_enum, TAC5X1X_PASIRXCH2, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch3_slot_select_enum, TAC5X1X_PASIRXCH3, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch4_slot_select_enum, TAC5X1X_PASIRXCH4, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch5_slot_select_enum, TAC5X1X_PASIRXCH5, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch6_slot_select_enum, TAC5X1X_PASIRXCH6, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch7_slot_select_enum, TAC5X1X_PASIRXCH7, 0,
+		tac5x1x_slot_select_text);
+static SOC_ENUM_SINGLE_DECL(rx_ch8_slot_select_enum, TAC5X1X_PASIRXCH8, 0,
+		tac5x1x_slot_select_text);
+
+static SOC_ENUM_SINGLE_DECL(diag_cfg1_sht_term_enum, TAC5X1X_DIAG_CFG1, 4,
+		diag_cfg_text);
+static SOC_ENUM_SINGLE_DECL(diag_cfg1_vbat_in_enum, TAC5X1X_DIAG_CFG1, 0,
+		diag_cfg_text);
+static SOC_ENUM_SINGLE_DECL(diag_cfg2_sht_gnd_enum, TAC5X1X_DIAG_CFG2, 4,
+		diag_cfg_gnd_text);
+static SOC_ENUM_SINGLE_DECL(diag_cfg2_micbias, TAC5X1X_DIAG_CFG2, 0,
+		diag_cfg_text);
+
+static const struct snd_kcontrol_new taa5x1x_controls[] = {
+	SOC_ENUM("Record Decimation Filter",
+		 decimation_filter_record_enum),
+	SOC_ENUM("ADC1 Audio BW", tac5x1x_adc1_wideband_enum),
+
+	SOC_SINGLE_TLV("ADC1 Digital Capture Volume", TAC5X1X_ADCCH1C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+
+	SOC_SINGLE_TLV("ADC1 Fine Capture Volume", TAC5X1X_ADCCH1C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+
+	SOC_SINGLE_RANGE("ADC1 Phase Capture Volume", TAC5X1X_ADCCH1C4,
+			 2, 0, 63, 0),
+
+	SOC_ENUM("ASI_TX_CH5_CFG", tx_ch5_asi_cfg_enum),
+	SOC_ENUM("ASI_TX_CH6_CFG", tx_ch6_asi_cfg_enum),
+	SOC_ENUM("ASI_TX_CH7_CFG", tx_ch7_asi_cfg_enum),
+	SOC_ENUM("ASI_TX_CH8_CFG", tx_ch8_asi_cfg_enum),
+	SOC_ENUM("ASI_TX_CH1_SLOT", tx_ch1_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH2_SLOT", tx_ch2_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH3_SLOT", tx_ch3_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH4_SLOT", tx_ch4_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH5_SLOT", tx_ch5_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH6_SLOT", tx_ch6_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH7_SLOT", tx_ch7_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH8_SLOT", tx_ch8_slot_select_enum),
+	SOC_SINGLE("IN_CH1_EN Capture Switch", TAC5X1X_CH_EN, 7, 1, 0),
+	SOC_SINGLE("IN_CH2_EN Capture Switch", TAC5X1X_CH_EN, 6, 1, 0),
+	SOC_SINGLE("IN_CH3_EN Capture Switch", TAC5X1X_CH_EN, 5, 1, 0),
+	SOC_SINGLE("IN_CH4_EN Capture Switch", TAC5X1X_CH_EN, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new tad5x1x_controls[] = {
+	SOC_ENUM("Playback Decimation Filter",
+		 decimation_filter_playback_enum),
+	SOC_ENUM("DAC1 Audio BW", tac5x1x_dac1_wideband_enum),
+	SOC_SINGLE_TLV("OUT1P Analog Level Playback Volume", TAC5X1X_OUT1CFG1,
+		       3, 6, 1, playback_analog_level_tlv),
+	SOC_SINGLE_TLV("OUT1M Analog Level Playback Volume", TAC5X1X_OUT1CFG2,
+		       3, 6, 1, playback_analog_level_tlv),
+	SOC_SINGLE_TLV("DAC1 CHA Digital Playback Volume", TAC5X1X_DACCH1A0,
+		       0, 0xff, 0, dac_dig_vol_tlv),
+	SOC_SINGLE_TLV("DAC1 CHB Digital Playback Volume", TAC5X1X_DACCH1B0,
+		       0, 0xff, 0, dac_dig_vol_tlv),
+	SOC_SINGLE_TLV("DAC1 CHA Gain Calibration Playback Volume",
+		       TAC5X1X_DACCH1A1, 4, 0xf, 0,
+			playback_gain_cali_tlv),
+	SOC_SINGLE_TLV("DAC1 CHB Gain Calibration Playback Volume",
+		       TAC5X1X_DACCH1B1, 4, 0xf, 0,
+			playback_gain_cali_tlv),
+
+	SOC_ENUM("ASI_RX_CH3_EN Playback", rx_ch3_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH4_EN Playback", rx_ch4_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH5_EN Playback", rx_ch5_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH6_EN Playback", rx_ch6_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH7_EN Playback", rx_ch7_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH8_EN Playback", rx_ch8_asi_cfg_enum),
+	SOC_ENUM("ASI_RX_CH1_SLOT Playback", rx_ch1_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH2_SLOT Playback", rx_ch2_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH3_SLOT Playback", rx_ch3_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH4_SLOT Playback", rx_ch4_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH5_SLOT Playback", rx_ch5_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH6_SLOT Playback", rx_ch6_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH7_SLOT Playback", rx_ch7_slot_select_enum),
+	SOC_ENUM("ASI_RX_CH8_SLOT Playback", rx_ch8_slot_select_enum),
+
+	SOC_SINGLE("OUT_CH1_EN Playback Switch", TAC5X1X_CH_EN, 3, 1, 0),
+	SOC_SINGLE("OUT_CH2_EN Playback Switch", TAC5X1X_CH_EN, 2, 1, 0),
+	SOC_SINGLE("OUT_CH3_EN Playback Switch", TAC5X1X_CH_EN, 1, 1, 0),
+	SOC_SINGLE("OUT_CH4_EN Playback Switch", TAC5X1X_CH_EN, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new tac5x11_controls[] = {
+	SOC_ENUM("ADC1 Common-mode Tolerance", tac5x1x_adc1_tolerance_enum),
+	SOC_ENUM("ADC1 Impedance Select", adc1_resistor_enum),
+};
+
+static const struct snd_kcontrol_new tad5x12_controls[] = {
+	SOC_SINGLE_TLV("OUT2P Analog Level Playback Volume", TAC5X1X_OUT2CFG1,
+		       3, 6, 1, playback_analog_level_tlv),
+	SOC_SINGLE_TLV("OUT2M Analog Level Playback Volume", TAC5X1X_OUT2CFG2,
+		       3, 6, 1, playback_analog_level_tlv),
+	SOC_SINGLE_TLV("DAC2 CHA Digital Playback Volume", TAC5X1X_DACCH2A0,
+		       0, 0xff, 0, dac_dig_vol_tlv),
+	SOC_SINGLE_TLV("DAC2 CHB Digital Playback Volume", TAC5X1X_DACCH2B0,
+		       0, 0xff, 0, dac_dig_vol_tlv),
+	SOC_SINGLE_TLV("DAC2 CHA Gain Calibration Playback Volume",
+		       TAC5X1X_DACCH2A1, 4, 0xf, 0,
+			playback_gain_cali_tlv),
+	SOC_SINGLE_TLV("DAC2 CHB Gain Calibration Playback Volume",
+		       TAC5X1X_DACCH2B1, 4, 0xf, 0,
+			playback_gain_cali_tlv),
+	SOC_ENUM("DAC2 Audio BW", tac5x1x_dac2_wideband_enum),
+	SOC_ENUM("OUT2X_VCOM", out2x_vcom_enum),
+};
+
+static const struct snd_kcontrol_new taa5x12_controls[] = {
+	SOC_ENUM("ADC2 Audio BW", tac5x1x_adc2_wideband_enum),
+
+	SOC_SINGLE_TLV("ADC2 Digital Capture Volume", TAC5X1X_ADCCH2C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+
+	SOC_SINGLE_TLV("ADC2 Fine Capture Volume", TAC5X1X_ADCCH2C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+
+	SOC_SINGLE_RANGE("ADC2 Phase Capture Volume", TAC5X1X_ADCCH2C4,
+			 2, 0, 63, 0),
+};
+
+static const struct snd_kcontrol_new tac5x12_impedance_controls[] = {
+	SOC_ENUM("ADC1 Impedance Select", adc1_resistor_enum),
+	SOC_ENUM("ADC2 Impedance Select", adc2_resistor_enum),
+};
+
+static const struct snd_kcontrol_new tac5x12_tolerance_controls[] = {
+	SOC_ENUM("ADC1 Common-mode Tolerance", tac5x1x_adc1_tolerance_enum),
+	SOC_ENUM("ADC2 Common-mode Tolerance", tac5x1x_adc2_tolerance_enum),
+};
+
+static const struct snd_kcontrol_new tac5x1x_pdm_controls[] = {
+	SOC_ENUM("PDM Clk Divider", pdmclk_select_enum),
+
+	SOC_SINGLE_TLV("PDM1 Digital Capture Volume", TAC5X1X_ADCCH1C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+	SOC_SINGLE_TLV("PDM2 Digital Capture Volume", TAC5X1X_ADCCH2C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+
+	SOC_SINGLE_TLV("PDM1 Fine Capture Volume", TAC5X1X_ADCCH1C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+	SOC_SINGLE_TLV("PDM2 Fine Capture Volume", TAC5X1X_ADCCH2C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+
+	SOC_SINGLE_RANGE("PDM1 Phase Capture Volume", TAC5X1X_ADCCH1C4,
+			 2, 0, 63, 0),
+	SOC_SINGLE_RANGE("PDM2 Phase Capture Volume", TAC5X1X_ADCCH2C4,
+			 2, 0, 63, 0),
+
+	SOC_SINGLE_TLV("PDM3 Digital Capture Volume", TAC5X1X_ADCCH3C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+	SOC_SINGLE_TLV("PDM4 Digital Capture Volume", TAC5X1X_ADCCH4C2,
+		       0, 0xff, 0, record_dig_vol_tlv),
+
+	SOC_SINGLE_TLV("PDM3 Fine Capture Volume", TAC5X1X_ADCCH3C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+	SOC_SINGLE_TLV("PDM4 Fine Capture Volume", TAC5X1X_ADCCH4C3,
+		       0, 0xff, 0, record_gain_cali_tlv),
+
+	SOC_SINGLE_RANGE("PDM3 Phase Capture Volume", TAC5X1X_ADCCH3C4,
+			 2, 0, 63, 0),
+	SOC_SINGLE_RANGE("PDM4 Phase Capture Volume", TAC5X1X_ADCCH4C4,
+			 2, 0, 63, 0),
+};
+
+static const struct snd_kcontrol_new taa5x1x_ip_controls[] = {
+	SOC_ENUM("DIAG_SHT_TERM", diag_cfg1_sht_term_enum),
+	SOC_ENUM("DIAG_SHT_VBAT_IN", diag_cfg1_vbat_in_enum),
+	SOC_ENUM("DIAG_SHT_GND", diag_cfg2_sht_gnd_enum),
+	SOC_ENUM("DIAG_SHT_MICBIAS", diag_cfg2_micbias),
+};
+
+static const struct snd_soc_dapm_widget taa5x1x_dapm_widgets[] = {
+	/* ADC1 */
+	SND_SOC_DAPM_INPUT("AIN1"),
+
+	SND_SOC_DAPM_MUX("ADC1 Full-Scale", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_adc1_fscale_control),
+
+	SND_SOC_DAPM_MUX("ADC1 Config", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_adc1_config_control),
+
+	SND_SOC_DAPM_ADC("CH1_ADC_EN", "CH1 Capture", TAC5X1X_CH_EN, 7, 0),
+
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH1_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch1_asi_switch),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", TAC5X1X_PWR_CFG, 5, 0),
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH2_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch2_asi_switch),
+};
+
+static const struct snd_kcontrol_new tad5x1x_pdm_controls[] = {
+	SOC_ENUM("ASI_TX_CH1_SLOT", tx_ch1_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH2_SLOT", tx_ch2_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH3_SLOT", tx_ch3_slot_select_enum),
+	SOC_ENUM("ASI_TX_CH4_SLOT", tx_ch4_slot_select_enum),
+};
+
+static const struct snd_soc_dapm_widget tad5xx_dapm_widgets[] = {
+	/* pdm capture */
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH1_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch1_asi_switch),
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH2_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch2_asi_switch),
+};
+
+static const struct snd_soc_dapm_widget tad5x1x_dapm_widgets[] = {
+	/* DAC1 */
+	SND_SOC_DAPM_AIF_IN("ASI IN1", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_OUTPUT("OUT1"),
+
+	SND_SOC_DAPM_MUX("OUT1x Source", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out1_source_control),
+
+	SND_SOC_DAPM_MUX("OUT1x Config", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out1_config_control),
+
+	SND_SOC_DAPM_MUX("OUT1x Driver", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out1_driver_control),
+
+	SND_SOC_DAPM_DAC("Left DAC Enable", "Left Playback", TAC5X1X_CH_EN, 3,
+			 0),
+	SND_SOC_DAPM_PGA("Left DAC Power", TAC5X1X_PWR_CFG, 6, 0, NULL, 0),
+
+	SND_SOC_DAPM_SWITCH("ASI_RX_CH1_EN", SND_SOC_NOPM, 0, 0,
+			    &rx_ch1_asi_switch),
+};
+
+static const struct snd_soc_dapm_widget taa5x12_dapm_widgets[] = {
+	/* ADC2 */
+	SND_SOC_DAPM_INPUT("AIN2"),
+
+	SND_SOC_DAPM_MUX("ADC2 Full-Scale", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_adc2_fscale_control),
+
+	SND_SOC_DAPM_MUX("ADC2 Config", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_adc2_config_control),
+
+	SND_SOC_DAPM_ADC("CH2_ADC_EN", "CH2 Capture", TAC5X1X_CH_EN, 6, 0),
+
+};
+
+static const struct snd_soc_dapm_widget tad5x12_dapm_widgets[] = {
+	/* DAC2 */
+	SND_SOC_DAPM_OUTPUT("OUT2"),
+
+	SND_SOC_DAPM_AIF_IN("ASI IN2", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_MUX("OUT2x Source", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out2_source_control),
+
+	SND_SOC_DAPM_MUX("OUT2x Config", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out2_config_control),
+
+	SND_SOC_DAPM_MUX("OUT2x Driver", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_out2_driver_control),
+
+	SND_SOC_DAPM_DAC("Right DAC Enable",
+			 "Right Playback", TAC5X1X_CH_EN, 2, 0),
+	SND_SOC_DAPM_PGA("Right DAC Power", TAC5X1X_PWR_CFG, 6, 0, NULL, 0),
+
+	SND_SOC_DAPM_SWITCH("ASI_RX_CH2_EN", SND_SOC_NOPM, 0, 0,
+			    &rx_ch2_asi_switch),
+};
+
+static const struct snd_soc_dapm_widget tac5x1x_dapm_pdm_widgets[] = {
+	/* PDM */
+	SND_SOC_DAPM_INPUT("DIN1"),
+	SND_SOC_DAPM_INPUT("DIN2"),
+	SND_SOC_DAPM_INPUT("DIN3"),
+	SND_SOC_DAPM_INPUT("DIN4"),
+
+	SND_SOC_DAPM_MUX("PDM ch1 & ch2 Datain Select",
+			 SND_SOC_NOPM, 0, 0, pdm_12_pin_controls),
+	SND_SOC_DAPM_MUX("PDM ch3 & ch4 Datain Select",
+			 SND_SOC_NOPM, 0, 0, pdm_34_pin_controls),
+
+	SND_SOC_DAPM_ADC("CH1_PDM_EN", "PDM CH1 Capture", TAC5X1X_CH_EN, 7, 0),
+	SND_SOC_DAPM_ADC("CH2_PDM_EN", "PDM CH2 Capture", TAC5X1X_CH_EN, 6, 0),
+	SND_SOC_DAPM_ADC("CH3_PDM_EN", "PDM CH3 Capture", TAC5X1X_CH_EN, 5, 0),
+	SND_SOC_DAPM_ADC("CH4_PDM_EN", "PDM CH4 Capture", TAC5X1X_CH_EN, 4, 0),
+
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH3_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch3_asi_switch),
+	SND_SOC_DAPM_SWITCH("ASI_TX_CH4_EN", SND_SOC_NOPM, 0, 0,
+			    &tx_ch4_asi_switch),
+};
+
+static const struct snd_soc_dapm_widget tac5x1x_common_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("IN1 Source Mux", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_in1_source_control),
+	SND_SOC_DAPM_MUX("IN2 Source Mux", SND_SOC_NOPM, 0, 0,
+			 tac5x1x_dapm_in2_source_control),
+	SND_SOC_DAPM_PGA("ADC Power", TAC5X1X_PWR_CFG, 7, 0, NULL, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF OUT", "ASI Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_widget tad5x1x_common_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("IN1 Source Mux", SND_SOC_NOPM, 0, 0,
+			 tad5x1x_dapm_in1_source_control),
+	SND_SOC_DAPM_MUX("IN2 Source Mux", SND_SOC_NOPM, 0, 0,
+			 tad5x1x_dapm_in2_source_control),
+	SND_SOC_DAPM_PGA("ADC Power", TAC5X1X_PWR_CFG, 7, 0, NULL, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF OUT", "ASI Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route taa5x1x_dapm_routes[] = {
+	/* ADC channel1 */
+	{"IN1 Source Mux", "Analog", "AIN1"},
+	{"IN2 Source Mux", "Analog",
+		"IN1 Source Mux"},
+
+	{"CH1_ADC_EN", NULL, "IN2 Source Mux"},
+	{"ASI_TX_CH1_EN", "Capture Switch", "CH1_ADC_EN"},
+
+	{"ADC1 Config", "Differential", "ASI_TX_CH1_EN"},
+	{"ADC1 Config", "Single-ended", "ASI_TX_CH1_EN"},
+	{"ADC1 Config", "Single-ended mux INxP",
+		"ASI_TX_CH1_EN"},
+	{"ADC1 Config", "Single-ended mux INxM",
+		"ASI_TX_CH1_EN"},
+
+	{"ADC1 Full-Scale", "2/10-VRMS",
+		"ADC1 Config"},
+	{"ADC1 Full-Scale", "4/5-VRMS",
+		"ADC1 Config"},
+	{"Mic Bias", NULL, "ADC1 Full-Scale"},
+
+};
+
+static const struct snd_soc_dapm_route tad5x1x_dapm_routes[] = {
+	/* Left Output */
+	{"ASI_RX_CH1_EN", "Switch", "ASI IN1"},
+
+	{"OUT1x Source", "DAC + Analog Bypass Mix", "ASI_RX_CH1_EN"},
+	{"OUT1x Source", "DAC -> OUTxP, INxP -> OUTxM", "ASI_RX_CH1_EN"},
+	{"OUT1x Source", "INxM -> OUTxP, DAC -> OUTxM", "ASI_RX_CH1_EN"},
+
+	{"OUT1x Config", "Differential", "OUT1x Source"},
+	// {"OUT1x Config", "Stereo Single-ended", "OUT1x Source"},
+	{"OUT1x Config", "Mono Single-ended at OUTxP only", "OUT1x Source"},
+	{"OUT1x Config", "Mono Single-ended at OUTxM only", "OUT1x Source"},
+	{"OUT1x Config", "Pseudo differential with OUTxM as VCOM",
+		"OUT1x Source"},
+	{"OUT1x Config", "Pseudo differential with OUTxM as external sensing",
+		"OUT1x Source"},
+	{"OUT1x Config", "Pseudo differential with OUTxP as VCOM",
+		"OUT1x Source"},
+
+	{"OUT1x Driver", "Line-out", "OUT1x Config"},
+	{"OUT1x Driver", "Headphone", "OUT1x Config"},
+
+	{"Left DAC Enable", NULL, "OUT1x Driver"},
+	{"Left DAC Power", NULL, "Left DAC Enable"},
+
+	{"OUT1", NULL, "Left DAC Power"},
+};
+
+static const struct snd_soc_dapm_route taa5x12_dapm_routes[] = {
+	/* ADC channel2 */
+	{"CH2_ADC_EN", NULL, "AIN2"},
+
+	{"ASI_TX_CH2_EN", "Capture Switch", "CH2_ADC_EN"},
+
+	{"ADC2 Config", "Differential", "ASI_TX_CH2_EN"},
+	{"ADC2 Config", "Single-ended", "ASI_TX_CH2_EN"},
+	{"ADC2 Full-Scale", "2/10-VRMS",
+		"ADC2 Config"},
+	{"ADC2 Full-Scale", "4/5-VRMS",
+		"ADC2 Config"},
+
+	{"Mic Bias", NULL, "ADC2 Full-Scale"},
+};
+
+static const struct snd_soc_dapm_route tad5x12_dapm_routes[] = {
+	/* Right Output */
+	{"ASI_RX_CH2_EN", "Switch", "ASI IN2"},
+
+	{"OUT2x Source", "DAC + Analog Bypass Mix", "ASI_RX_CH1_EN"},
+	{"OUT2x Source", "DAC -> OUTxP, INxP -> OUTxM", "ASI_RX_CH1_EN"},
+	{"OUT2x Source", "INxM -> OUTxP, DAC -> OUTxM", "ASI_RX_CH1_EN"},
+
+	{"OUT2x Config", "Differential", "OUT2x Source"},
+	// {"OUT2x Config", "Stereo Single-ended", "OUT2x Source"},
+	{"OUT2x Config", "Mono Single-ended at OUTxP only", "OUT2x Source"},
+	{"OUT2x Config", "Mono Single-ended at OUTxM only", "OUT2x Source"},
+	{"OUT2x Config", "Pseudo differential with OUTxM as VCOM",
+		"OUT2x Source"},
+	{"OUT2x Config", "Pseudo differential with OUTxP as VCOM",
+		"OUT2x Source"},
+
+	{"OUT2x Driver", "Line-out", "OUT2x Config"},
+	{"OUT2x Driver", "Headphone", "OUT2x Config"},
+
+	{"Right DAC Enable", NULL, "OUT2x Driver"},
+
+	{"Right DAC Power", NULL, "Right DAC Enable"},
+
+	{"OUT2", NULL, "Right DAC Power"},
+};
+
+static const struct snd_soc_dapm_route tac5x1x_dapm_pdm_routes[] = {
+	/* PDM channel1 & Channel2 */
+	{"IN1 Source Mux", "PDM", "DIN1"},
+	{"IN2 Source Mux", "PDM", "DIN2"},
+
+	{"ASI_TX_CH1_EN", "Capture Switch",
+		"IN1 Source Mux"},
+	{"ASI_TX_CH2_EN", "Capture Switch",
+		"IN2 Source Mux"},
+
+	{"CH1_PDM_EN", NULL, "ASI_TX_CH1_EN"},
+	{"CH2_PDM_EN", NULL, "ASI_TX_CH2_EN"},
+
+	{"PDM ch1 & ch2 Datain Select", "GPIO1", "CH1_PDM_EN"},
+	{"PDM ch1 & ch2 Datain Select", "GPIO2", "CH1_PDM_EN"},
+	{"PDM ch1 & ch2 Datain Select", "GPI1", "CH1_PDM_EN"},
+
+	{"ADC Power", NULL, "PDM ch1 & ch2 Datain Select"},
+
+	/* PDM channel3 & Channel4 */
+	{"ASI_TX_CH3_EN", "Capture Switch", "DIN3"},
+	{"ASI_TX_CH4_EN", "Capture Switch", "DIN4"},
+
+	{"CH3_PDM_EN", NULL, "ASI_TX_CH3_EN"},
+	{"CH4_PDM_EN", NULL, "ASI_TX_CH4_EN"},
+
+	{"PDM ch3 & ch4 Datain Select", "GPIO1", "CH3_PDM_EN"},
+	{"PDM ch3 & ch4 Datain Select", "GPIO2", "CH3_PDM_EN"},
+	{"PDM ch3 & ch4 Datain Select", "GPI1", "CH3_PDM_EN"},
+
+	{"ADC Power", NULL, "PDM ch3 & ch4 Datain Select"},
+	{"AIF OUT", NULL, "ADC Power"},
+};
+
+static const struct snd_soc_dapm_route tac5x1x_common_dapm_routes[] = {
+	{"ADC Power", NULL, "Mic Bias"},
+	{"AIF OUT", NULL, "ADC Power"},
+};
+
+static const struct reg_default tac5x1x_reg_defaults[] = {
+	{TAC5X1X_PGSEL, 0x00},
+	{TAC5X1X_INT, 0x10},
+	{TAC5X1X_ADCCH1C0, 0x04},
+	{TAC5X1X_ADCCH2C0, 0x04},
+	{TAC5X1X_OUT1CFG0, 0x20},
+	{TAC5X1X_OUT2CFG0, 0x20},
+	{TAC5X1X_CH_EN, 0x00},
+	{TAC5X1X_PASITXCH1, 0x00},
+	{TAC5X1X_PASITXCH2, 0x01},
+	{TAC5X1X_PASIRXCH1, 0x00},
+	{TAC5X1X_PASIRXCH2, 0x01},
+	{},
+};
+
+static s32 tac5x1x_pwr_ctrl(struct snd_soc_component *component,
+			    bool power_state)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 active_ctrl, ret;
+	s32 pwr_ctrl = 0;
+
+	if (power_state) {
+		active_ctrl = TAC5X1X_VREF_SLEEP_ACTIVE_MASK;
+		snd_soc_component_update_bits(component, TAC5X1X_VREFCFG,
+					      TAC5X1X_VREFCFG_MICBIAS_VAL_MASK,
+					      tac5x1x->micbias_vg << 2);
+		snd_soc_component_update_bits(component, TAC5X1X_VREFCFG,
+					      TAC5X1X_VREFCFG_VREF_FSCALE_MASK,
+					      tac5x1x->vref_vg);
+
+		if (tac5x1x->uad_en)
+			pwr_ctrl |= TAC5X1X_PWR_CFG_UAD_EN;
+		if (tac5x1x->vad_en)
+			pwr_ctrl |= TAC5X1X_PWR_CFG_VAD_EN;
+		if (tac5x1x->uag_en)
+			pwr_ctrl |= TAC5X1X_PWR_CFG_UAG_EN;
+	} else {
+		active_ctrl = 0x0;
+	}
+
+	ret = snd_soc_component_update_bits(component, TAC5X1X_VREF,
+					    TAC5X1X_VREF_SLEEP_EXIT_VREF_EN |
+					    TAC5X1X_VREF_SLEEP_ACTIVE_MASK, active_ctrl);
+	if (ret < 0) {
+		dev_err(tac5x1x->dev,
+			"%s, device active or sleep failed!, ret %d/n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = snd_soc_component_update_bits(component, TAC5X1X_PWR_CFG,
+					    TAC5X1X_PWR_CFG_UAD_EN | TAC5X1X_PWR_CFG_UAG_EN |
+					    TAC5X1X_PWR_CFG_VAD_EN, pwr_ctrl);
+	if (ret < 0)
+		dev_err(tac5x1x->dev,
+			"%s, Power control set failed!, ret %d/n",
+			__func__, ret);
+	return ret;
+}
+
+static s32 tac5x1x_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	s32 iface_reg_1 = 0;
+	s32 iface_reg_2 = 0;
+	s32 iface_reg_3 = 0;
+
+	dev_err(component->dev, "[tac5x1x] %s(), fmt=%d\n", __func__, fmt);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBP_CFP:
+		iface_reg_1 |= TAC5X1X_PASI_MODE_MASK;
+		break;
+	case SND_SOC_DAIFMT_CBC_CFC:
+		break;
+	default:
+		dev_err(component->dev,
+			"%s: invalid DAI master/slave interface\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface_reg_2 |= TAC5X1X_PASI_FMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface_reg_2 |= TAC5X1X_PASI_FMT_TDM;
+		iface_reg_3 |= BIT(0); /* add offset 1 */
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface_reg_2 |= TAC5X1X_PASI_FMT_TDM;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface_reg_2 |= TAC5X1X_PASI_FMT_LJ;
+		break;
+	default:
+		dev_err(component->dev,
+			"%s: invalid DAI interface format\n", __func__);
+		return -EINVAL;
+	}
+
+	snd_soc_component_update_bits(component, TAC5X1X_CNTCLK2,
+				      TAC5X1X_PASI_MODE_MASK, iface_reg_1);
+	snd_soc_component_update_bits(component, TAC5X1X_PASI0,
+				      TAC5X1X_PASI_FMT_MASK, iface_reg_2);
+	snd_soc_component_update_bits(component, TAC5X1X_PASITX1,
+				      TAC5X1X_PASITX_OFFSET_MASK, iface_reg_3);
+	snd_soc_component_update_bits(component, TAC5X1X_PASIRX0,
+				      TAC5X1X_PASIRX_OFFSET_MASK, iface_reg_3);
+	return 0;
+}
+
+static s32 tac5x1x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 sample_rate, word_length = 0;
+
+	switch (params_rate(params)) {
+	case 24000:
+		sample_rate = 25;
+		break;
+	case 32000:
+		sample_rate = 23;
+		break;
+	case 44100:
+	case 48000:
+		sample_rate = 20;
+		break;
+	case 64000:
+		sample_rate = 18;
+		break;
+	case 96000:
+		sample_rate = 15;
+		break;
+	case 192000:
+		sample_rate = 10;
+		break;
+	default:
+		/* Auto detect sample rate */
+		sample_rate = 0;
+		break;
+	}
+
+	switch (params_physical_width(params)) {
+	case 16:
+		word_length |= TAC5X1X_WORD_LEN_16BITS;
+		break;
+	case 20:
+		word_length |= TAC5X1X_WORD_LEN_20BITS;
+		break;
+	case 24:
+		word_length |= TAC5X1X_WORD_LEN_24BITS;
+		break;
+	case 32:
+		word_length |= TAC5X1X_WORD_LEN_32BITS;
+		break;
+	default:
+		dev_err(tac5x1x->dev, "%s, set word length failed\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	snd_soc_component_update_bits(component, TAC5X1X_CLK0, TAC5X1X_PASI_SAMP_RATE_MASK,
+				      sample_rate << 2);
+	snd_soc_component_update_bits(component, TAC5X1X_PASI0,
+				      TAC5X1X_PASI_DATALEN_MASK, word_length);
+
+	tac5x1x_pwr_ctrl(component, true);
+	return 0;
+}
+
+static s32 tac5x1x_set_bias_level(struct snd_soc_component *component,
+				  enum snd_soc_bias_level level)
+{
+	s32 ret;
+	struct tac5x1x_priv *tac5x1x = snd_soc_component_get_drvdata(component);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		ret = tac5x1x_pwr_ctrl(component, true);
+		if (ret < 0)
+			dev_err(tac5x1x->dev, "%s, power up failed!/n", __func__);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		break;
+	case SND_SOC_BIAS_OFF:
+		ret = tac5x1x_pwr_ctrl(component, false);
+		if (ret < 0)
+			dev_err(tac5x1x->dev, "%s, power down failed!/n", __func__);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dai_ops tac5x1x_ops = {
+	.hw_params = tac5x1x_hw_params,
+	.set_fmt = tac5x1x_set_dai_fmt,
+	.no_capture_mute = 1,
+};
+
+static struct snd_soc_dai_driver tac5x1x_dai = {
+	.name = "tac5x1x-hifi",
+	.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 4,
+			.rates = TAC5X1X_RATES,
+			.formats = TAC5X1X_FORMATS,},
+	.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 4,
+			.rates = TAC5X1X_RATES,
+			.formats = TAC5X1X_FORMATS,
+			},
+	.ops = &tac5x1x_ops,
+	.symmetric_rate = 1,
+};
+
+static struct snd_soc_dai_driver taa5x1x_dai = {
+	.name = "taa5x1x-hifi",
+	.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 4,
+			.rates = TAC5X1X_RATES,
+			.formats = TAC5X1X_FORMATS,
+			},
+	.ops = &tac5x1x_ops,
+	.symmetric_rate = 1,
+};
+
+static struct snd_soc_dai_driver tad5x1x_dai = {
+	.name = "tad5x1x-hifi",
+	.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 4,
+			.rates = TAC5X1X_RATES,
+			.formats = TAC5X1X_FORMATS,
+			},
+	.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 4,
+			.rates = TAC5X1X_RATES,
+			.formats = TAC5X1X_FORMATS,
+			},
+	.ops = &tac5x1x_ops,
+	.symmetric_rate = 1,
+};
+
+static void tac5x1x_setup_gpios(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 *gpio_drive = tac5x1x->gpio_setup->gpio_drive;
+	s32 *gpio_func = tac5x1x->gpio_setup->gpio_func;
+
+	/* setup GPIO functions */
+	/* GPIO1 */
+	if (gpio_func[0] <= TAC5X1X_GPIO_DAISY_OUT) {
+		snd_soc_component_update_bits(component, TAC5X1X_GPIO1, TAC5X1X_GPIOX_CFG_MASK,
+					      gpio_func[0] << 4);
+		snd_soc_component_update_bits(component, TAC5X1X_GPIO1, TAC5X1X_GPIOX_DRV_MASK,
+					      gpio_drive[0]);
+
+		if (gpio_func[0] == TAC5X1X_GPIO_GPI)
+			snd_soc_add_component_controls(component, tac5x1x_GPIO1_I,
+						       ARRAY_SIZE(tac5x1x_GPIO1_I));
+		else if (gpio_func[0] == TAC5X1X_GPIO_GPO)
+			snd_soc_add_component_controls(component, tac5x1x_GPIO1_O,
+						       ARRAY_SIZE(tac5x1x_GPIO1_O));
+	}
+	/* GPIO2 */
+	if (gpio_func[1] <= TAC5X1X_GPIO_DAISY_OUT) {
+		snd_soc_component_update_bits(component, TAC5X1X_GPIO2, TAC5X1X_GPIOX_CFG_MASK,
+					      gpio_func[1] << 4);
+		snd_soc_component_update_bits(component, TAC5X1X_GPIO2, TAC5X1X_GPIOX_DRV_MASK,
+					      gpio_drive[1]);
+
+		if (gpio_func[1] == TAC5X1X_GPIO_GPI)
+			snd_soc_add_component_controls(component, tac5x1x_GPIO2_I,
+						       ARRAY_SIZE(tac5x1x_GPIO2_I));
+		else if (gpio_func[1] == TAC5X1X_GPIO_GPO)
+			snd_soc_add_component_controls(component, tac5x1x_GPIO2_O,
+						       ARRAY_SIZE(tac5x1x_GPIO2_O));
+	}
+	/* GPO1 */
+	if (gpio_func[2] <= TAC5X1X_GPIO_DAISY_OUT) {
+		snd_soc_component_update_bits(component, TAC5X1X_GPO1, TAC5X1X_GPIOX_CFG_MASK,
+					      gpio_func[2] << 4);
+		snd_soc_component_update_bits(component, TAC5X1X_GPO1, TAC5X1X_GPIOX_DRV_MASK,
+					      gpio_drive[2]);
+
+		if (gpio_func[2] == TAC5X1X_GPIO_GPO)
+			snd_soc_add_component_controls(component, tac5x1x_GPO1,
+						       ARRAY_SIZE(tac5x1x_GPO1));
+	}
+	/* GPI1 */
+	if (tac5x1x->gpio_setup->gpi1_func) {
+		snd_soc_component_update_bits(component, TAC5X1X_GPI1, TAC5X1X_GPI1_CFG_MASK,
+					      TAC5X1X_GPI1_CFG_MASK);
+		snd_soc_add_component_controls(component, tac5x1x_GPI1, ARRAY_SIZE(tac5x1x_GPI1));
+	}
+	/*GPA GPIO*/
+	if (tac5x1x->gpio_setup->gpa_gpio)
+		snd_soc_component_update_bits(component, TAC5X1X_INTF5, TAC5X1X_GPA_CFG_MASK,
+					      TAC5X1X_GPA_CFG_MASK);
+}
+
+static s32 tac5x1x_reset(struct snd_soc_component *component)
+{
+	s32 ret, index;
+
+	ret = snd_soc_component_write(component, TAC5X1X_RESET, 1);
+	if (ret < 0)
+		return ret;
+	/* Wait >= 10 ms after entering sleep mode. */
+	usleep_range(10000, 100000);
+
+	for (index = 0; index < ARRAY_SIZE(tac5x1x_reg_defaults); index++) {
+		ret = snd_soc_component_write(component, tac5x1x_reg_defaults[index].reg,
+					      tac5x1x_reg_defaults[index].def);
+		if (ret < 0)
+			return ret;
+	}
+	return ret;
+}
+
+static s32 tac5x1x_add_controls(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 *gpio_func = tac5x1x->gpio_setup->gpio_func;
+	s32 ret;
+
+	switch (tac5x1x->codec_type) {
+	case TAA5212:
+		ret = snd_soc_add_component_controls(component, tac5x12_impedance_controls,
+						     ARRAY_SIZE(tac5x12_impedance_controls));
+		if (ret)
+			return ret;
+		fallthrough;
+	case TAA5412:
+		ret = snd_soc_add_component_controls(component, tac5x12_tolerance_controls,
+						     ARRAY_SIZE(tac5x12_tolerance_controls));
+		if (ret)
+			return ret;
+		ret = snd_soc_add_component_controls(component, taa5x1x_ip_controls,
+						     ARRAY_SIZE(taa5x1x_ip_controls));
+		if (ret)
+			return ret;
+		break;
+	/* For Mono */
+	case TAC5111:
+	case TAC5211:
+		ret = snd_soc_add_component_controls(component, tac5x11_controls,
+						     ARRAY_SIZE(tac5x11_controls));
+		if (ret)
+			return ret;
+	fallthrough;
+	case TAC5311:
+	case TAC5411:
+		ret = snd_soc_add_component_controls(component, tad5x1x_controls,
+						     ARRAY_SIZE(tad5x1x_controls));
+		if (ret)
+			return ret;
+		break;
+	/* For Stereo */
+	case TAC5112:
+	case TAC5212:
+		ret = snd_soc_add_component_controls(component, tac5x12_impedance_controls,
+						     ARRAY_SIZE(tac5x12_impedance_controls));
+		if (ret)
+			return ret;
+		fallthrough;
+	case TAC5312:
+	case TAC5412:
+		ret = snd_soc_add_component_controls(component, tac5x12_tolerance_controls,
+						     ARRAY_SIZE(tac5x12_tolerance_controls));
+		if (ret)
+			return ret;
+		ret = snd_soc_add_component_controls(component, tad5x1x_controls,
+						     ARRAY_SIZE(tad5x1x_controls));
+		if (ret)
+			return ret;
+
+		ret = snd_soc_add_component_controls(component, taa5x12_controls,
+						     ARRAY_SIZE(taa5x12_controls));
+		if (ret)
+			return ret;
+
+		ret = snd_soc_add_component_controls(component, tad5x12_controls,
+						     ARRAY_SIZE(tad5x12_controls));
+		if (ret)
+			return ret;
+		break;
+	case TAD5212:
+	case TAD5112:
+		ret = snd_soc_add_component_controls(component, tad5x12_controls,
+						     ARRAY_SIZE(tad5x12_controls));
+		if (ret)
+			return ret;
+
+		ret = snd_soc_add_component_controls(component, tad5x1x_pdm_controls,
+						     ARRAY_SIZE(tad5x1x_pdm_controls));
+		if (ret)
+			return ret;
+		break;
+	default:
+		break;
+	}
+
+	/* If enabled PDM GPIO*/
+	if (memchr(gpio_func, TAC5X1X_GPIO_PDMCLK,
+		   sizeof(*gpio_func) / sizeof(gpio_func[0]))) {
+		ret = snd_soc_add_component_controls(component, tac5x1x_pdm_controls,
+						     ARRAY_SIZE(tac5x1x_pdm_controls));
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static s32 tac5x1x_add_ip_diag_controls(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 ret;
+
+	switch (tac5x1x->codec_type) {
+	case TAA5212:
+		break;
+	case TAA5412:
+	case TAC5311:
+	case TAC5312:
+	case TAC5411:
+	case TAC5412:
+		if (tac5x1x->input_diag_config.in_ch_en) {
+			ret = snd_soc_add_component_controls(component, taa5x1x_ip_controls,
+							     ARRAY_SIZE(taa5x1x_ip_controls));
+			if (ret)
+				return ret;
+			snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG0,
+						      TAC5X1X_IN_CH_DIAG_EN_MASK,
+						      TAC5X1X_IN_CH_DIAG_EN_MASK);
+		}
+		if (tac5x1x->input_diag_config.out_ch_en) {
+			snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG0,
+						      TAC5X1X_OUT1P_DIAG_EN_MASK,
+						      TAC5X1X_OUT1P_DIAG_EN_MASK);
+		}
+		if (tac5x1x->input_diag_config.incl_se_inm) {
+			snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG0,
+						      TAC5X1X_INCL_SE_INM_MASK,
+						      TAC5X1X_INCL_SE_INM_MASK);
+		}
+		if (tac5x1x->input_diag_config.incl_ac_coup) {
+			snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG0,
+						      TAC5X1X_INCL_AC_COUP_MASK,
+						      TAC5X1X_INCL_AC_COUP_MASK);
+		}
+		snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG7, 0xff,
+					      tac5x1x->micbias_threshold[0]);
+		snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG6, 0xff,
+					      tac5x1x->micbias_threshold[1]);
+		ret = tac5x1x_register_interrupt(tac5x1x);
+		if (!ret)
+			dev_info(tac5x1x->dev, "Interrupt registration Done");
+		fallthrough;
+	case TAC5111:
+	case TAC5112:
+	case TAC5211:
+	case TAC5212:
+	case TAD5112:
+	case TAD5212:
+		snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG9, 0xff,
+					      tac5x1x->gpa_threshold[0]);
+		snd_soc_component_update_bits(component, TAC5X1X_DIAG_CFG8, 0xff,
+					      tac5x1x->gpa_threshold[1]);
+		break;
+	default:
+		break;
+	};
+
+	return ret;
+};
+
+static s32 tac5x1x_add_widgets(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x = snd_soc_component_get_drvdata(component);
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	s32 *gpio_func = tac5x1x->gpio_setup->gpio_func;
+	s32 ret;
+
+	switch (tac5x1x->codec_type) {
+	case TAC5111:
+	case TAC5211:
+	case TAC5311:
+	case TAC5411:
+		ret = snd_soc_dapm_new_controls(dapm, tad5x1x_dapm_widgets,
+						ARRAY_SIZE(tad5x1x_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, tad5x1x_dapm_routes,
+					      ARRAY_SIZE(tad5x1x_dapm_routes));
+		if (ret)
+			return ret;
+		break;
+	case TAC5112:
+	case TAC5212:
+	case TAC5312:
+	case TAC5412:
+		ret = snd_soc_dapm_new_controls(dapm, tad5x1x_dapm_widgets,
+						ARRAY_SIZE(tad5x1x_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, tad5x1x_dapm_routes,
+					      ARRAY_SIZE(tad5x1x_dapm_routes));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_new_controls(dapm, tad5x12_dapm_widgets,
+						ARRAY_SIZE(tad5x12_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, tad5x12_dapm_routes,
+					      ARRAY_SIZE(tad5x12_dapm_routes));
+		if (ret)
+			return ret;
+		fallthrough;
+	case TAA5212:
+	case TAA5412:
+		ret = snd_soc_dapm_new_controls(dapm, taa5x12_dapm_widgets,
+						ARRAY_SIZE(taa5x12_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, taa5x12_dapm_routes,
+					      ARRAY_SIZE(taa5x12_dapm_routes));
+		if (ret)
+			return ret;
+		break;
+	case TAD5212:
+	case TAD5112:
+		ret = snd_soc_dapm_new_controls(dapm, tad5xx_dapm_widgets,
+						ARRAY_SIZE(tad5xx_dapm_widgets));
+		if (ret)
+			return ret;
+
+		ret = snd_soc_dapm_new_controls(dapm, tad5x12_dapm_widgets,
+						ARRAY_SIZE(tad5x12_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, tad5x12_dapm_routes,
+					      ARRAY_SIZE(tad5x12_dapm_routes));
+		if (ret)
+			return ret;
+
+		break;
+	default:
+		break;
+	}
+
+	if (!(tac5x1x->codec_type == TAD5212 || tac5x1x->codec_type == TAD5112)) {
+		ret = snd_soc_dapm_new_controls(dapm, tac5x1x_common_dapm_widgets,
+						ARRAY_SIZE(tac5x1x_common_dapm_widgets));
+		if (ret)
+			return ret;
+
+		ret = snd_soc_dapm_add_routes(dapm, tac5x1x_common_dapm_routes,
+					      ARRAY_SIZE(tac5x1x_common_dapm_routes));
+		if (ret)
+			return ret;
+	} else {
+		ret = snd_soc_dapm_new_controls(dapm, tad5x1x_common_dapm_widgets,
+						ARRAY_SIZE(tad5x1x_common_dapm_widgets));
+		if (ret)
+			return ret;
+	}
+	/* If enabled PDM GPIO*/
+	if (memchr(gpio_func, TAC5X1X_GPIO_PDMCLK,
+		   sizeof(*gpio_func) / sizeof(gpio_func[0]))) {
+		ret = snd_soc_dapm_new_controls(dapm, tac5x1x_dapm_pdm_widgets,
+						ARRAY_SIZE(tac5x1x_dapm_pdm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm, tac5x1x_dapm_pdm_routes,
+					      ARRAY_SIZE(tac5x1x_dapm_pdm_routes));
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static s32 tac5x1x_component_probe(struct snd_soc_component *component)
+{
+	s32 ret;
+	struct tac5x1x_priv *tac5x1x = snd_soc_component_get_drvdata(component);
+
+	ret = tac5x1x_add_controls(component);
+	if (ret < 0) {
+		dev_err(tac5x1x->dev, "%s, add control failed\n", __func__);
+		return ret;
+	}
+
+	ret = tac5x1x_add_widgets(component);
+	if (ret < 0) {
+		dev_err(tac5x1x->dev, "%s, device widget addition failed\n", __func__);
+		return ret;
+	}
+
+	ret = tac5x1x_reset(component);
+	if (ret < 0) {
+		dev_err(tac5x1x->dev, "%s, device reset failed\n", __func__);
+		return ret;
+	}
+
+	if (tac5x1x->gpio_setup)
+		tac5x1x_setup_gpios(component);
+
+	ret = tac5x1x_add_ip_diag_controls(component);
+	if (ret < 0) {
+		dev_err(tac5x1x->dev, "%s, add Ip diag control failed\n", __func__);
+		return ret;
+	}
+	return ret;
+}
+
+#ifdef CONFIG_PM
+static s32 tac5x1x_soc_suspend(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+
+	regcache_cache_only(tac5x1x->regmap, true);
+	regcache_mark_dirty(tac5x1x->regmap);
+
+	regulator_disable(tac5x1x->supply_avdd);
+	regulator_disable(tac5x1x->supply_iovdd);
+
+	return 0;
+}
+
+static s32 tac5x1x_soc_resume(struct snd_soc_component *component)
+{
+	struct tac5x1x_priv *tac5x1x =
+		snd_soc_component_get_drvdata(component);
+	s32 ret;
+
+	regcache_cache_only(tac5x1x->regmap, false);
+	snd_soc_component_cache_sync(component);
+
+	ret = regulator_enable(tac5x1x->supply_avdd);
+	if (ret) {
+		regulator_disable(tac5x1x->supply_avdd);
+		return ret;
+	}
+
+	ret = regulator_enable(tac5x1x->supply_iovdd);
+	if (ret) {
+		regulator_disable(tac5x1x->supply_iovdd);
+		return ret;
+	}
+	return 0;
+}
+#else
+#define tac5x1x_soc_suspend	NULL
+#define tac5x1x_soc_resume	NULL
+#endif /* CONFIG_PM */
+
+static const struct snd_soc_component_driver soc_component_dev_tac5x1x = {
+	.probe			= tac5x1x_component_probe,
+	.set_bias_level		= tac5x1x_set_bias_level,
+	.suspend		= tac5x1x_soc_suspend,
+	.resume			= tac5x1x_soc_resume,
+	.controls		= taa5x1x_controls,
+	.num_controls		= ARRAY_SIZE(taa5x1x_controls),
+	.dapm_widgets		= taa5x1x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(taa5x1x_dapm_widgets),
+	.dapm_routes		= taa5x1x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(taa5x1x_dapm_routes),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 1,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static const struct snd_soc_component_driver soc_component_dev_taa5x1x = {
+	.probe			= tac5x1x_component_probe,
+	.set_bias_level		= tac5x1x_set_bias_level,
+	.suspend		= tac5x1x_soc_suspend,
+	.resume			= tac5x1x_soc_resume,
+	.controls		= taa5x1x_controls,
+	.num_controls		= ARRAY_SIZE(taa5x1x_controls),
+	.dapm_widgets		= taa5x1x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(taa5x1x_dapm_widgets),
+	.dapm_routes		= taa5x1x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(taa5x1x_dapm_routes),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 1,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static const struct snd_soc_component_driver soc_component_dev_tad5x1x = {
+	.probe			= tac5x1x_component_probe,
+	.set_bias_level		= tac5x1x_set_bias_level,
+	.suspend		= tac5x1x_soc_suspend,
+	.resume			= tac5x1x_soc_resume,
+	.controls		= tad5x1x_controls,
+	.num_controls		= ARRAY_SIZE(tad5x1x_controls),
+	.dapm_widgets		= tad5x1x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(tad5x1x_dapm_widgets),
+	.dapm_routes		= tad5x1x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(tad5x1x_dapm_routes),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 1,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static s32 tac5x1x_parse_dt(struct tac5x1x_priv *tac5x1x,
+			    struct device_node *np)
+{
+	struct tac5x1x_input_diag_config input_config;
+	struct tac5x1x_setup_gpio *tac5x1x_setup;
+	s32 micbias_value = TAC5X1X_MICBIAS_VREF;
+	s32 vref_value = TAC5X1X_VERF_2_5V;
+	s32 ret;
+
+	tac5x1x_setup = devm_kzalloc(tac5x1x->dev, sizeof(*tac5x1x_setup),
+				     GFP_KERNEL);
+	if (!tac5x1x_setup)
+		return -ENOMEM;
+
+	ret = fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,vref", &vref_value);
+	if (ret) {
+		dev_err(tac5x1x->dev, "Fail to get verf E:%d\n", ret);
+		goto out;
+	}
+	ret = fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,micbias-vg", &micbias_value);
+	if (ret) {
+		dev_err(tac5x1x->dev, "Fail to get micbias-vg E:%d\n", ret);
+		goto out;
+	}
+
+	if (micbias_value == TAC5X1X_MICBIAS_AVDD) {
+		tac5x1x->micbias_vg = micbias_value;
+		tac5x1x->vref_vg = TAC5X1X_VERF_2_75V;
+		tac5x1x->micbias_en = true;
+	} else {
+		switch (vref_value) {
+		case TAC5X1X_VERF_2_75V:
+		case TAC5X1X_VERF_2_5V:
+			switch (micbias_value) {
+			case TAC5X1X_MICBIAS_VREF:
+			case TAC5X1X_MICBIAS_0_5VREF:
+				tac5x1x->micbias_vg = micbias_value;
+				break;
+			default:
+				dev_err(tac5x1x->dev, "Bad tac5x1x-micbias-vg value %d\n",
+					micbias_value);
+				tac5x1x->micbias_vg = TAC5X1X_MICBIAS_AVDD;
+				break;
+			}
+			tac5x1x->vref_vg = vref_value;
+			tac5x1x->micbias_en = true;
+			break;
+		case TAC5X1X_VERF_1_375V:
+			if (micbias_value == TAC5X1X_MICBIAS_VREF) {
+				tac5x1x->micbias_vg = micbias_value;
+			} else {
+				dev_err(tac5x1x->dev, "Bad tac5x1x-micbias-vg value %d\n",
+					micbias_value);
+				tac5x1x->micbias_vg = TAC5X1X_MICBIAS_AVDD;
+			}
+			tac5x1x->micbias_en = true;
+			tac5x1x->vref_vg = vref_value;
+			break;
+		default:
+			dev_err(tac5x1x->dev, "Bad tac5x1x-vref-vg value %d\n", vref_value);
+			tac5x1x->vref_vg = TAC5X1X_VERF_2_5V;
+			tac5x1x->micbias_vg = TAC5X1X_MICBIAS_AVDD;
+			tac5x1x->micbias_en = true;
+			break;
+		}
+	}
+
+	if (fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,gpi1-func",
+				     &tac5x1x_setup->gpi1_func))
+		dev_err(tac5x1x->dev, "%s, Fail to get gpi1-func value\n", __func__);
+
+	if (fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,gpa-gpio",
+				     &tac5x1x_setup->gpa_gpio))
+		dev_err(tac5x1x->dev, "%s, Fail to get gpa-gpio value\n", __func__);
+
+	if (fwnode_property_read_u32_array(tac5x1x->dev->fwnode, "ti,gpios-func",
+					   tac5x1x_setup->gpio_func, 3))
+		dev_err(tac5x1x->dev, "%s, Fail to get gpios-func value\n", __func__);
+	if (fwnode_property_read_u32_array(tac5x1x->dev->fwnode, "ti,gpios-drive",
+					   tac5x1x_setup->gpio_drive, 3))
+		dev_err(tac5x1x->dev, "%s, Fail to get gpios-drive value\n", __func__);
+
+	tac5x1x->gpa_threshold[0] = TAC5X1X_GPA_LOW_THRESHOLD;
+	tac5x1x->gpa_threshold[1] = TAC5X1X_GPA_HIGH_THRESHOLD;
+	if (fwnode_property_read_u32_array(tac5x1x->dev->fwnode, "ti,gpa-threshold",
+					   tac5x1x->gpa_threshold, 2))
+		dev_err(tac5x1x->dev, "%s, Fail to get ti,gpa-threshold value\n", __func__);
+
+	tac5x1x->gpio_setup = tac5x1x_setup;
+
+	switch (tac5x1x->codec_type) {
+	case TAA5212:
+	case TAC5111:
+	case TAC5112:
+	case TAC5211:
+	case TAC5212:
+	case TAD5112:
+	case TAD5212:
+		break;
+	case TAA5412:
+	case TAC5411:
+	case TAC5412:
+	case TAC5311:
+	case TAC5312:
+		tac5x1x->input_diag_config.in_ch_en = 0;
+		if (fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,in-ch-en",
+					     &input_config.in_ch_en))
+			dev_err(tac5x1x->dev, "%s, Fail to get ti,in-ch-en value\n", __func__);
+		tac5x1x->input_diag_config.out_ch_en = 0;
+		if (fwnode_property_read_u32(tac5x1x->dev->fwnode,
+					     "ti,out-ch-en", &input_config.in_ch_en))
+			dev_err(tac5x1x->dev, "%s, Fail to get ti,out-ch-en value\n", __func__);
+		tac5x1x->input_diag_config.incl_se_inm = 0;
+		if (fwnode_property_read_u32(tac5x1x->dev->fwnode, "ti,incl-se-inm",
+					     &input_config.incl_se_inm))
+			dev_err(tac5x1x->dev, "%s, Fail to get ti,incl-se-inm value\n", __func__);
+		tac5x1x->input_diag_config.incl_ac_coup = 0;
+		if (fwnode_property_read_u32(tac5x1x->dev->fwnode,
+					     "ti,incl-ac-coup", &input_config.incl_ac_coup))
+			dev_err(tac5x1x->dev, "%s, Fail to get ti,incl-ac-coup value\n", __func__);
+		tac5x1x->input_diag_config = input_config;
+
+		tac5x1x->micbias_threshold[0] = TAC5X1X_MICBIAS_LOW_THRESHOLD;
+		tac5x1x->micbias_threshold[1] =
+					TAC5X1X_MICBIAS_HIGH_THRESHOLD;
+		if (fwnode_property_read_u32_array(tac5x1x->dev->fwnode, "ti,micbias-threshold",
+						   tac5x1x->micbias_threshold, 2))
+			dev_err(tac5x1x->dev, "%s, Fail to get ti,micbias-threshold value\n",
+				__func__);
+		break;
+	}
+out:
+	return ret;
+}
+
+static void tac5x1x_disable_regulators(struct tac5x1x_priv *tac5x1x)
+{
+	if (!IS_ERR(tac5x1x->supply_iovdd))
+		regulator_disable(tac5x1x->supply_iovdd);
+
+	if (!IS_ERR(tac5x1x->supply_avdd))
+		regulator_disable(tac5x1x->supply_avdd);
+}
+
+static s32 tac5x1x_setup_regulators(struct device *dev,
+				    struct tac5x1x_priv *tac5x1x)
+{
+	s32 ret;
+
+	tac5x1x->supply_iovdd = devm_regulator_get(dev, "iovdd");
+	tac5x1x->supply_avdd = devm_regulator_get(dev, "avdd");
+
+	/* Check if the regulator requirements are fulfilled */
+	if (IS_ERR(tac5x1x->supply_iovdd)) {
+		dev_err(dev, "Missing supply 'iovdd'\n");
+		return PTR_ERR(tac5x1x->supply_iovdd);
+	}
+
+	if (IS_ERR(tac5x1x->supply_avdd)) {
+		dev_err(dev, "Missing supply 'avdd'\n");
+		return PTR_ERR(tac5x1x->supply_avdd);
+	}
+
+	ret = regulator_enable(tac5x1x->supply_iovdd);
+	if (ret) {
+		dev_err(dev, "Failed to enable regulator iovdd\n");
+		regulator_disable(tac5x1x->supply_iovdd);
+		return ret;
+	}
+
+	ret = regulator_enable(tac5x1x->supply_avdd);
+	if (ret) {
+		dev_err(dev, "Failed to enable regulator avdd\n");
+		regulator_disable(tac5x1x->supply_avdd);
+		return ret;
+	}
+
+	return 0;
+}
+
+s32 tac5x1x_probe(struct device *dev, struct regmap *regmap,
+		  enum tac5x1x_type type)
+{
+	struct device_node *np = dev->of_node;
+	struct tac5x1x_priv *tac5x1x;
+	s32 ret;
+
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	tac5x1x = devm_kzalloc(dev, sizeof(struct tac5x1x_priv),
+			       GFP_KERNEL);
+	if (!tac5x1x)
+		return -ENOMEM;
+
+	tac5x1x->dev = dev;
+	tac5x1x->codec_type = type;
+	tac5x1x->regmap = regmap;
+	tac5x1x->ndev = 1;
+
+	dev_set_drvdata(dev, tac5x1x);
+	if (np) {
+		ret = tac5x1x_parse_dt(tac5x1x, np);
+		if (ret) {
+			dev_err(dev, "Failed to parse DT node\n");
+			return ret;
+		}
+	} else {
+		dev_err(dev, "%s: Fail to get device node\n", __func__);
+	}
+
+	ret = tac5x1x_setup_regulators(dev, tac5x1x);
+	if (ret) {
+		dev_err(dev, "Failed to setup regulators\n");
+		return ret;
+	}
+
+	switch (tac5x1x->codec_type) {
+	case TAA5212:
+	case TAA5412:
+		ret = devm_snd_soc_register_component(dev,
+						      &soc_component_dev_taa5x1x, &taa5x1x_dai, 1);
+		if (ret) {
+			dev_err(dev, "Failed to register component\n");
+			goto err_disable_regulators;
+		}
+		break;
+	case TAC5111:
+	case TAC5112:
+	case TAC5211:
+	case TAC5212:
+	case TAC5311:
+	case TAC5312:
+	case TAC5411:
+	case TAC5412:
+		ret = devm_snd_soc_register_component(dev,
+						      &soc_component_dev_tac5x1x, &tac5x1x_dai, 1);
+		if (ret) {
+			dev_err(dev, "Failed to register component\n");
+			goto err_disable_regulators;
+		}
+		break;
+	case TAD5112:
+	case TAD5212:
+		ret = devm_snd_soc_register_component(dev,
+						      &soc_component_dev_tad5x1x, &tad5x1x_dai, 1);
+		if (ret) {
+			dev_err(dev, "Failed to register component\n");
+			goto err_disable_regulators;
+		}
+		break;
+	}
+	return 0;
+
+err_disable_regulators:
+	tac5x1x_disable_regulators(tac5x1x);
+
+	return ret;
+}
+EXPORT_SYMBOL(tac5x1x_probe);
+
+s32 tac5x1x_remove(struct device *dev)
+{
+	struct tac5x1x_priv *tac5x1x = dev_get_drvdata(dev);
+
+	tac5x1x_disable_regulators(tac5x1x);
+	if (tac5x1x->irq_work_func) {
+		dev_info(tac5x1x->dev, "Cancelled IRQ\n");
+		free_irq(tac5x1x->irqinfo.irq, tac5x1x);
+		mutex_destroy(&tac5x1x->dev_lock);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(tac5x1x_remove);
+
+const struct of_device_id tac5x1x_of_match[] = {
+	{ .compatible = "ti,taa5212", .data = (void *)TAA5212 },
+	{ .compatible = "ti,taa5412", .data = (void *)TAA5412 },
+	{ .compatible = "ti,tac5111", .data = (void *)TAC5111 },
+	{ .compatible = "ti,tac5112", .data = (void *)TAC5112 },
+	{ .compatible = "ti,tac5211", .data = (void *)TAC5211 },
+	{ .compatible = "ti,tac5212", .data = (void *)TAC5212 },
+	{ .compatible = "ti,tac5311", .data = (void *)TAC5311 },
+	{ .compatible = "ti,tac5312", .data = (void *)TAC5312 },
+	{ .compatible = "ti,tac5411", .data = (void *)TAC5411 },
+	{ .compatible = "ti,tac5412", .data = (void *)TAC5412 },
+	{ .compatible = "ti,tad5112", .data = (void *)TAD5112 },
+	{ .compatible = "ti,tad5212", .data = (void *)TAD5212 },
+	{}
+};
+EXPORT_SYMBOL_GPL(tac5x1x_of_match);
+MODULE_DEVICE_TABLE(of, tac5x1x_of_match);
+
+MODULE_DESCRIPTION("ASoC tac5x1x codec driver");
+MODULE_AUTHOR("Texas Instruments");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tac5x1x.h b/sound/soc/codecs/tac5x1x.h
new file mode 100644
index 000000000000..d3f2d5b4769c
--- /dev/null
+++ b/sound/soc/codecs/tac5x1x.h
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Audio Codec Driver Supporting Devices
+ * TAA5X1X, TAC5X1X, TAD5X1X
+ *
+ * Copyright (C) 2024-2025 Texas Instruments Incorporated - https://www.ti.com
+ *
+ * Author: Kevin Lu <kevin-lu@ti.com>
+ * Author: Kokila Karuppusamy <kokila.karuppusamy@ti.com>
+ * Author: Niranjan H Y <niranjan.hy@ti.com>
+ */
+
+#ifndef _TAC5X1X_H
+#define _TAC5X1X_H
+
+#define	TAC5X1X_RATES	SNDRV_PCM_RATE_8000_192000
+#define	TAC5X1X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+			 | SNDRV_PCM_FMTBIT_S20_3LE \
+			 | SNDRV_PCM_FMTBIT_S24_LE \
+			 | SNDRV_PCM_FMTBIT_S24_3LE \
+			 | SNDRV_PCM_FMTBIT_S32_LE)
+
+/*PAGE Control Register (available in page0 of each book) */
+#define	TAC_PAGE_SELECT		0x00
+#define	TAC_PAGE_ID(reg)	((reg) / 128)
+#define	TAC_PAGE_REG(reg)	((reg) % 128)
+#define	TAC5X1X_REG(page, reg)	(((page) * 128) + (reg))
+
+#define	TAC_PAGE1_SELECT	0x01
+#define	TAC5X1X_PGSEL		TAC5X1X_REG(0, 0x0)
+
+#define	TAC5X1X_RESET		TAC5X1X_REG(0, 0x1)
+#define	TAC5X1X_VREF		TAC5X1X_REG(0, 0x2)
+#define	TAC5X1X_VDDSTS		TAC5X1X_REG(0, 0x3)
+#define	TAC5X1X_MISC		TAC5X1X_REG(0, 0x4)
+#define	TAC5X1X_MISC1		TAC5X1X_REG(0, 0x5)
+#define	TAC5X1X_DACA0		TAC5X1X_REG(0, 0x6)
+#define	TAC5X1X_MISC0		TAC5X1X_REG(0, 0x7)
+#define	TAC5X1X_GPIO1		TAC5X1X_REG(0, 0xa)
+#define	TAC5X1X_GPIO2		TAC5X1X_REG(0, 0xb)
+#define	TAC5X1X_GPO1		TAC5X1X_REG(0, 0xc)
+#define	TAC5X1X_GPI1		TAC5X1X_REG(0, 0xd)
+#define	TAC5X1X_GPIOVAL		TAC5X1X_REG(0, 0xe)
+#define	TAC5X1X_INTF0		TAC5X1X_REG(0, 0xf)
+#define	TAC5X1X_INTF1		TAC5X1X_REG(0, 0x10)
+#define	TAC5X1X_INTF2		TAC5X1X_REG(0, 0x11)
+#define	TAC5X1X_INTF3		TAC5X1X_REG(0, 0x12)
+#define	TAC5X1X_INTF4		TAC5X1X_REG(0, 0x13)
+#define	TAC5X1X_INTF5		TAC5X1X_REG(0, 0x14)
+#define	TAC5X1X_INTF6		TAC5X1X_REG(0, 0x15)
+#define	TAC5X1X_ASI0		TAC5X1X_REG(0, 0x18)
+#define	TAC5X1X_ASI1		TAC5X1X_REG(0, 0x19)
+#define	TAC5X1X_PASI0		TAC5X1X_REG(0, 0x1a)
+#define	TAC5X1X_PASITX0		TAC5X1X_REG(0, 0x1b)
+#define	TAC5X1X_PASITX1		TAC5X1X_REG(0, 0x1c)
+#define	TAC5X1X_PASITX2		TAC5X1X_REG(0, 0x1d)
+#define	TAC5X1X_PASITXCH1	TAC5X1X_REG(0, 0x1e)
+#define	TAC5X1X_PASITXCH2	TAC5X1X_REG(0, 0x1f)
+#define	TAC5X1X_PASITXCH3	TAC5X1X_REG(0, 0x20)
+#define	TAC5X1X_PASITXCH4	TAC5X1X_REG(0, 0x21)
+#define	TAC5X1X_PASITXCH5	TAC5X1X_REG(0, 0x22)
+#define	TAC5X1X_PASITXCH6	TAC5X1X_REG(0, 0x23)
+#define	TAC5X1X_PASITXCH7	TAC5X1X_REG(0, 0x24)
+#define	TAC5X1X_PASITXCH8	TAC5X1X_REG(0, 0x25)
+#define	TAC5X1X_PASIRX0		TAC5X1X_REG(0, 0x26)
+#define	TAC5X1X_PASIRX1		TAC5X1X_REG(0, 0x27)
+#define	TAC5X1X_PASIRXCH1	TAC5X1X_REG(0, 0x28)
+#define	TAC5X1X_PASIRXCH2	TAC5X1X_REG(0, 0x29)
+#define	TAC5X1X_PASIRXCH3	TAC5X1X_REG(0, 0x2a)
+#define	TAC5X1X_PASIRXCH4	TAC5X1X_REG(0, 0x2b)
+#define	TAC5X1X_PASIRXCH5	TAC5X1X_REG(0, 0x2c)
+#define	TAC5X1X_PASIRXCH6	TAC5X1X_REG(0, 0x2d)
+#define	TAC5X1X_PASIRXCH7	TAC5X1X_REG(0, 0x2e)
+#define	TAC5X1X_PASIRXCH8	TAC5X1X_REG(0, 0x2f)
+#define	TAC5X1X_CLK0		TAC5X1X_REG(0, 0x32)
+#define	TAC5X1X_CLK1		TAC5X1X_REG(0, 0x33)
+#define	TAC5X1X_CLK2		TAC5X1X_REG(0, 0x34)
+#define	TAC5X1X_CNTCLK0		TAC5X1X_REG(0, 0x35)
+#define	TAC5X1X_CNTCLK1		TAC5X1X_REG(0, 0x36)
+#define	TAC5X1X_CNTCLK2		TAC5X1X_REG(0, 0x37)
+#define	TAC5X1X_CNTCLK3		TAC5X1X_REG(0, 0x38)
+#define	TAC5X1X_CNTCLK4		TAC5X1X_REG(0, 0x39)
+#define	TAC5X1X_CNTCLK5		TAC5X1X_REG(0, 0x3a)
+#define	TAC5X1X_CNTCLK6		TAC5X1X_REG(0, 0x3b)
+#define	TAC5X1X_CLKERRSTS0	TAC5X1X_REG(0, 0x3c)
+#define	TAC5X1X_CLKERRSTS1	TAC5X1X_REG(0, 0x3d)
+#define	TAC5X1X_CLKDETSTS0	TAC5X1X_REG(0, 0x3e)
+#define	TAC5X1X_CLKDETSTS1	TAC5X1X_REG(0, 0x3f)
+#define	TAC5X1X_CLKDETSTS2	TAC5X1X_REG(0, 0x40)
+#define	TAC5X1X_CLKDETSTS3	TAC5X1X_REG(0, 0x41)
+#define	TAC5X1X_INT		TAC5X1X_REG(0, 0x42)
+#define	TAC5X1X_DAC_FLT		TAC5X1X_REG(0, 0x43)
+#define	TAC5X1X_ADCDACMISC	TAC5X1X_REG(0, 0x4b)
+#define	TAC5X1X_IADC		TAC5X1X_REG(0, 0x4c)
+#define	TAC5X1X_VREFCFG		TAC5X1X_REG(0, 0x4d)
+#define	TAC5X1X_PWRTUNE0	TAC5X1X_REG(0, 0x4e)
+#define	TAC5X1X_PWRTUNE1	TAC5X1X_REG(0, 0x4f)
+#define	TAC5X1X_ADCCH1C0	TAC5X1X_REG(0, 0x50)
+#define	TAC5X1X_ADCCH		TAC5X1X_REG(0, 0x51)
+#define	TAC5X1X_ADCCH1C2	TAC5X1X_REG(0, 0x52)
+#define	TAC5X1X_ADCCH1C3	TAC5X1X_REG(0, 0x53)
+#define	TAC5X1X_ADCCH1C4	TAC5X1X_REG(0, 0x54)
+#define	TAC5X1X_ADCCH2C0	TAC5X1X_REG(0, 0x55)
+#define	TAC5X1X_ADCCH2C2	TAC5X1X_REG(0, 0x57)
+#define	TAC5X1X_ADCCH2C3	TAC5X1X_REG(0, 0x58)
+#define	TAC5X1X_ADCCH2C4	TAC5X1X_REG(0, 0x59)
+#define	TAC5X1X_ADCCH3C0	TAC5X1X_REG(0, 0x5a)
+#define	TAC5X1X_ADCCH3C2	TAC5X1X_REG(0, 0x5b)
+#define	TAC5X1X_ADCCH3C3	TAC5X1X_REG(0, 0x5c)
+#define	TAC5X1X_ADCCH3C4	TAC5X1X_REG(0, 0x5d)
+#define	TAC5X1X_ADCCH4C0	TAC5X1X_REG(0, 0x5e)
+#define	TAC5X1X_ADCCH4C2	TAC5X1X_REG(0, 0x5f)
+#define	TAC5X1X_ADCCH4C3	TAC5X1X_REG(0, 0x60)
+#define	TAC5X1X_ADCCH4C4	TAC5X1X_REG(0, 0x61)
+#define	TAC5X1X_ADCCFG1		TAC5X1X_REG(0, 0x62)
+#define	TAC5X1X_OUT1CFG0	TAC5X1X_REG(0, 0x64)
+#define	TAC5X1X_OUT1CFG1	TAC5X1X_REG(0, 0x65)
+#define	TAC5X1X_OUT1CFG2	TAC5X1X_REG(0, 0x66)
+#define	TAC5X1X_DACCH1A0	TAC5X1X_REG(0, 0x67)
+#define	TAC5X1X_DACCH1A1	TAC5X1X_REG(0, 0x68)
+#define	TAC5X1X_DACCH1B0	TAC5X1X_REG(0, 0x69)
+#define	TAC5X1X_DACCH1B1	TAC5X1X_REG(0, 0x6a)
+#define	TAC5X1X_OUT2CFG0	TAC5X1X_REG(0, 0x6b)
+#define	TAC5X1X_OUT2CFG1	TAC5X1X_REG(0, 0x6c)
+#define	TAC5X1X_OUT2CFG2	TAC5X1X_REG(0, 0x6d)
+#define	TAC5X1X_DACCH2A0	TAC5X1X_REG(0, 0x6e)
+#define	TAC5X1X_DACCH2A1	TAC5X1X_REG(0, 0x6f)
+#define	TAC5X1X_DACCH2B0	TAC5X1X_REG(0, 0x70)
+#define	TAC5X1X_DACCH2B1	TAC5X1X_REG(0, 0x71)
+#define	TAC5X1X_DSP0		TAC5X1X_REG(0, 0x72)
+#define	TAC5X1X_DSP1		TAC5X1X_REG(0, 0x73)
+#define	TAC5X1X_CH_EN		TAC5X1X_REG(0, 0x76)
+#define	TAC5X1X_DYN_PUPD	TAC5X1X_REG(0, 0x77)
+#define	TAC5X1X_PWR_CFG		TAC5X1X_REG(0, 0x78)
+#define	TAC5X1X_DEVSTS0		TAC5X1X_REG(0, 0x79)
+#define	TAC5X1X_DEVSTS1		TAC5X1X_REG(0, 0x7a)
+
+#define	TAC5X1X_CLKCFG0		TAC5X1X_REG(1, 0xd)
+#define	TAC5X1X_MICBIAS1	TAC5X1X_REG(1, 0x16)
+#define	TAC5X1X_AGC_DRC		TAC5X1X_REG(1, 0x24)
+#define	TAC5X1X_PLIM		TAC5X1X_REG(1, 0x2b)
+#define	TAC5X1X_MIXER		TAC5X1X_REG(1, 0x2c)
+
+#define	TAC5X1X_DIAG_CFG0	TAC5X1X_REG(1, 0x46)
+#define	TAC5X1X_DIAG_CFG1	TAC5X1X_REG(1, 0x47)
+#define	TAC5X1X_DIAG_CFG2	TAC5X1X_REG(1, 0x48)
+#define	TAC5X1X_DIAG_CFG8	TAC5X1X_REG(1, 0x4e)
+#define	TAC5X1X_DIAG_CFG9	TAC5X1X_REG(1, 0x4b)
+#define	 TAC5X1X_DIAG_CFG6	TAC5X1X_REG(1, 0x4c)
+#define	TAC5X1X_DIAG_CFG7	TAC5X1X_REG(1, 0x4d)
+/* Bits, masks, and shifts */
+
+#define	TAC5X1X_REG_INT_LTCH0	TAC5X1X_REG(0x1, 0x34)
+#define	TAC5X1X_REG_CHX_LTCH	TAC5X1X_REG(0x1, 0x35)
+#define	TAC5X1X_REG_IN_CH1_LTCH		TAC5X1X_REG(0x1, 0x36)
+#define	TAC5X1X_REG_IN_CH2_LTCH		TAC5X1X_REG(0x1, 0x37)
+#define	TAC5X1X_REG_OUT_CH1_LTCH	TAC5X1X_REG(0x1, 0x38)
+#define	TAC5X1X_REG_OUT_CH2_LTCH	TAC5X1X_REG(0x1, 0x39)
+#define	TAC5X1X_REG_INT_LTCH1	TAC5X1X_REG(0x1, 0x3A)
+#define	TAC5X1X_REG_INT_LTCH2	TAC5X1X_REG(0x1, 0x3B)
+
+/* TAC5X1X_CH_EN */
+
+#define	TAC5X1X_CH_EN_ADC_MASK	GENMASK(7, 4)
+#define	TAC5X1X_CH_EN_ADC_CH1	BIT(7)
+#define	TAC5X1X_CH_EN_ADC_CH2	BIT(6)
+#define	TAC5X1X_CH_EN_ADC_CH3	BIT(5)
+#define	TAC5X1X_CH_EN_ADC_CH4	BIT(4)
+
+#define	TAC5X1X_CH_EN_DAC_MASK	GENMASK(3, 0)
+#define	TAC5X1X_CH_EN_DAC_CH1	BIT(3)
+#define	TAC5X1X_CH_EN_DAC_CH2	BIT(2)
+#define	TAC5X1X_CH_EN_DAC_CH3	BIT(1)
+#define	TAC5X1X_CH_EN_DAC_CH4	BIT(0)
+
+/* TAC5X1X_GPIOVAL */
+#define	TAC5X1X_GPIO1_VAL	BIT(7)
+#define	TAC5X1X_GPIO2_VAL	BIT(6)
+#define	TAC5X1X_GPO1_VAL	BIT(5)
+#define	TAC5X1X_GPIO1_MON	BIT(3)
+#define	TAC5X1X_GPIO2_MON	BIT(2)
+#define	TAC5X1X_GPI1_MON	BIT(1)
+
+/* TAC5X1X_DIAG_CFG0 */
+#define	TAC5X1X_IN_CH_DIAG_EN_MASK	0xc0
+#define	TAC5X1X_INCL_SE_INM_MASK	0x20
+#define	TAC5X1X_INCL_AC_COUP_MASK	0x10
+#define	TAC5X1X_OUT1P_DIAG_EN_MASK	0x0f
+#define	TAC5X1X_MICBIAS_LOW_THRESHOLD	0x48
+#define	TAC5X1X_MICBIAS_HIGH_THRESHOLD	0xa2
+#define	TAC5X1X_GPA_LOW_THRESHOLD	0x4b
+#define	TAC5X1X_GPA_HIGH_THRESHOLD	0xba
+
+/* TAC5X1X_PASI */
+#define	TAC5X1X_PASI_SAMP_RATE_MASK	GENMASK(7, 2)
+#define	TAC5X1X_PASI_FMT_MASK		GENMASK(7, 6)
+#define	TAC5X1X_PASI_FMT_TDM		0x00
+#define	TAC5X1X_PASI_FMT_I2S		0x40
+#define	TAC5X1X_PASI_FMT_LJ		0x80
+
+#define	TAC5X1X_PASI_DATALEN_MASK	GENMASK(5, 4)
+#define	TAC5X1X_WORD_LEN_16BITS		0x00
+#define	TAC5X1X_WORD_LEN_20BITS		0x10
+#define	TAC5X1X_WORD_LEN_24BITS		0x20
+#define	TAC5X1X_WORD_LEN_32BITS		0x30
+
+/* TAC5X1X_CNTCLK2 */
+#define	TAC5X1X_PASI_MODE_MASK		0x10
+#define	TAC5X1X_SASI_MODE_MASK		0x08
+#define	TAC5X1X_ASI_RATE_MASK		0x01
+
+#define	TAC5X1X_PASI_RATE_48000		0x00
+#define	TAC5X1X_PASI_RATE_44100		0x01
+
+/* TAC5X1X_PASITX0 */
+#define	TAC5X1X_PASITX_OFFSET_MASK	0x1f
+
+/* TAC5X1X_PASIRX0 */
+#define	TAC5X1X_PASIRX_OFFSET_MASK	0x1f
+
+/* TAC5X1X_VREF */
+#define	TAC5X1X_VREF_SLEEP_EXIT_VREF_EN		0x80
+#define	TAC5X1X_VREF_SLEEP_ACTIVE_MASK		0x01
+
+/* TAC5X1X_PWRCFG */
+#define	TAC5X1X_PWR_CFG_ADC_PDZ	BIT(7)
+#define	TAC5X1X_PWR_CFG_DAC_PDZ	BIT(6)
+#define	TAC5X1X_PWR_CFG_MICBIAS	BIT(5)
+#define	TAC5X1X_PWR_CFG_UAD_EN	BIT(3)
+#define	TAC5X1X_PWR_CFG_VAD_EN	BIT(2)
+#define	TAC5X1X_PWR_CFG_UAG_EN	BIT(1)
+
+/* TAC5X1X_GPIOx */
+#define	TAC5X1X_GPIO1_DEFAULT_VAL	0x32
+#define	TAC5X1X_GPIO2_DEFAULT_VAL	0x00
+#define	TAC5X1X_GPI1_DEFAULT_VAL	0x00
+#define	TAC5X1X_GPO1_DEFAULT_VAL	0x00
+
+#define	TAC5X1X_GPIOX_CFG_MASK	0xf0
+#define	TAC5X1X_GPIOX_DRV_MASK	0x07
+
+#define	TAC5X1X_GPIO_DISABLE	0
+#define	TAC5X1X_GPIO_GPI	1
+#define	TAC5X1X_GPIO_GPO	2
+#define	TAC5X1X_GPIO_IRQ	3
+#define	TAC5X1X_GPIO_PDMCLK	4
+#define	TAC5X1X_GPIO_P_DOUT	5
+#define	TAC5X1X_GPIO_P_DOUT2	6
+#define	TAC5X1X_GPIO_S_DOUT	7
+#define	TAC5X1X_GPIO_S_DOUT2	8
+#define	TAC5X1X_GPIO_S_BCLK	9
+#define	TAC5X1X_GPIO_S_FSYNC	10
+#define	TAC5X1X_GPIO_CLKOUT	11
+#define	TAC5X1X_GPIO_DOUT_MUX	12
+#define	TAC5X1X_GPIO_DAISY_OUT	13
+
+#define	TAC5X1X_GPIO_DRV_HIZ	0
+#define	TAC5X1X_GPIO_DRV_ALAH	1
+#define	TAC5X1X_GPIO_DRV_ALWH	2
+#define	TAC5X1X_GPIO_DRV_ALHIZ	3
+#define	TAC5X1X_GPIO_DRV_WLAH	4
+#define	TAC5X1X_GPIO_DRV_HIZAH	5
+
+/* TAC5X1X_GPI1 */
+#define	TAC5X1X_GPI1_CFG_MASK	BIT(1)
+#define	TAC5X1X_GPA_CFG_MASK	BIT(0)
+
+/* TAC5X1X_VREFCFG */
+#define	TAC5X1X_VREFCFG_MICBIAS_VAL_MASK	GENMASK(3, 2)
+#define	TAC5X1X_VREFCFG_VREF_FSCALE_MASK	GENMASK(1, 0)
+
+#define	TAC5X1X_MICBIAS_VREF	0
+#define	TAC5X1X_MICBIAS_0_5VREF	1
+#define	TAC5X1X_MICBIAS_AVDD	3
+
+#define	TAC5X1X_VERF_2_75V	0
+#define	TAC5X1X_VERF_2_5V	1
+#define	TAC5X1X_VERF_1_375V	2
+
+enum tac5x1x_type {
+	TAA5212 = 0,
+	TAA5412,
+	TAC5111,
+	TAC5112,
+	TAC5211,
+	TAC5212,
+	TAC5311,
+	TAC5312,
+	TAC5411,
+	TAC5412,
+	TAD5112,
+	TAD5212,
+};
+
+extern const struct regmap_config tac5x1x_regmap;
+extern const struct of_device_id tac5x1x_of_match[];
+
+int tac5x1x_probe(struct device *dev, struct regmap *regmap,
+		  enum tac5x1x_type type);
+int tac5x1x_remove(struct device *dev);
+
+#endif	/* _TAC5X1X_H */
-- 
2.45.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family
  2025-06-06  6:51 ` [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family Niranjan H Y
@ 2025-06-06 12:47   ` Mark Brown
  0 siblings, 0 replies; 4+ messages in thread
From: Mark Brown @ 2025-06-06 12:47 UTC (permalink / raw)
  To: Niranjan H Y
  Cc: andriy.shevchenko, tiwai, alsa-devel, baojun.xu, jesse-ji,
	shenghao-ding, liam.r.girdwood, navada, v-hampiholi

[-- Attachment #1: Type: text/plain, Size: 9381 bytes --]

On Fri, Jun 06, 2025 at 12:21:33PM +0530, Niranjan H Y wrote:
> tac5x1x family are series of low-power and high performance
> mono/stereo audio codecs consists of ADC and DAC combinations.
> The patch adds supports for Codecs(DAC & ADC), ADC only and
> DAC only configurations available in the tac5x1x family.

There's a few issues below but nothing that's *hugely* structural, the
bulk of the code here looks good.

> +config SND_SOC_TAC5X1X
> +	tristate "Texas Instruments TAC5X1X family codecs"
> +	depends on I2C && REGMAP_I2C
> +
> +config SND_SOC_TAC5X1X_I2C
> +	tristate "Texas Instruments TAC5X1X family driver based on I2C"
> +	depends on I2C && REGMAP_I2C
> +	select SND_SOC_TAC5X1X

You need to select REGMAP_I2C if you use it, it can't be turned on
independently.  If the device is I2C only then there's no need to have
the second option for I2C, that's used for devices that support both I2C
and SPI to avoid problems with dependencies on the I2C and SPI core
code.

>  snd-soc-tas2781-i2c-y := tas2781-i2c.o
> +snd-soc-tac5x1x-y := tac5x1x.o
> +snd-soc-tac5x1x-i2c-y := tac5x1x-i2c.o
>  snd-soc-tfa9879-y := tfa9879.o

Please keep these files alphanumerically sorted.

>  obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO)	+= snd-soc-lpass-tx-macro.o
>  
>  # Mux
> -obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
> \ No newline at end of file
> +obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
> diff --git a/sound/soc/codecs/tac5x1x-i2c.c b/sound/soc/codecs/tac5x1x-i2c.c

Looks like this whitespace change crept in accidentally.

> +static int tac5x1x_i2c_probe(struct i2c_client *i2c)
> +{
> +	int ret;
> +	enum tac5x1x_type type;
> +	struct regmap *regmap;
> +	const struct regmap_config *config = &tac5x1x_regmap;
> +
> +	regmap = devm_regmap_init_i2c(i2c, config);
> +	type = (uintptr_t)i2c_get_match_data(i2c);
> +
> +	dev_info(&i2c->dev, "probing %s codec_type = %d\n",
> +		 i2c->name, type);

Just drop this print, it's probably a bit noisy.

> +static const char * const int_ltch0[] = {
> +	"Clock Error",
> +	"PLL Lock",
> +	"Boost Over Temperature",
> +	"Boost Over Current",
> +	"Boost Mode",
> +	"Reserved",
> +	"Reserved",
> +	"Reserved",
> +};

You can just set the maximum value for the enum to be whatever the
maximum valid value is and then not need to list the Reserved entries at
all.

> +static const char * const out_ch2_ltch[] = {
> +	"OUT_CH2 OUT2P Short circuit Fault",
> +	"OUT_CH2 OUT2M Short circuit Fault",
> +	"OUT_CH2 DRVRP Virtual Ground Fault",
> +	"OUT_CH2 DRVRM Virtual ground Fault",
> +	"Reserved",
> +	"Reserved",
> +	"AREG SC Fault Mask",
> +	"AREG SC Fault",
> +};

For ones where the reserved values are in the middle of the set of
values you can use _VAL_ENUM() which lets you skip over the values that
are invalid.

> +static s32 tac5x1x_regmap_write(struct tac5x1x_priv *tac5x1x,
> +				u32 reg, u32 value)
> +{
> +	s32 ret;
> +	s32 retry_count = 5;
> +
> +	while (retry_count--) {
> +		ret = regmap_write(tac5x1x->regmap, reg,
> +				   value);
> +		if (ret >= 0)
> +			break;
> +		usleep_range(5000, 5050);
> +	}
> +	if (retry_count == -1)
> +		return 3;
> +	else
> +		return ret;
> +}

Is the hardware genuinely so unstable that we need to retry all the I/O?
This seems really concerning.

> +static s32 tac5x1x_regmap_read(struct tac5x1x_priv *tac5x1x,
> +			       u32 reg, u32 *value)
> +{
> +	s32 ret;
> +	s32 retry_count = 5;
> +
> +	ret = regmap_reinit_cache(tac5x1x->regmap, &tac5x1x_regmap);
> +	if (ret) {
> +		dev_err(tac5x1x->dev, "Failed to reinit reg cache\n");
> +		return ret;
> +	}
> +
> +	while (retry_count--) {
> +		ret = regmap_read(tac5x1x->regmap, reg,
> +				  value);
> +		if (ret >= 0)
> +			break;
> +		usleep_range(5000, 5050);
> +	}

This seems *really* scary and confusing, why would we reset the register
cache in what looks like a normal read operation.

> +static s32 tac5x1x_dev_read(struct tac5x1x_priv *tac5x1x,
> +			    u32 dev_no, u32 reg,
> +			    u32 *ref_value)
> +{
> +	s32 ret;
> +
> +	guard(mutex)(&tac5x1x->dev_lock);
> +	if (dev_no < tac5x1x->ndev) {
> +		ret = tac5x1x_regmap_write(tac5x1x,
> +					   TAC_PAGE_SELECT, 0);
> +		if (ret < 0) {
> +			dev_err(tac5x1x->dev, "%s, E=%d\n",
> +				__func__, ret);
> +			return ret;
> +		}

This doesn't actually appear to do anything to support or choose between
multiple devices?  The code all looks like it only supports one device,
and this is only used in the interrupt handler.

> +static void tac5x1x_irq_work_func(struct tac5x1x_priv *tac5x1x)
> +{
> +	u32 reg_val, array_size, i, index = 0, bit = 0;
> +	s32 rc;
> +
> +	tac5x1x_enable_irq(tac5x1x, false);
> +	array_size =  ARRAY_SIZE(int_reg_array);
> +	for (i = 0; i < array_size; i++) {
> +		rc = tac5x1x_dev_read(tac5x1x, index,
> +				      int_reg_array[i], &reg_val);

...

> +	}
> +	tac5x1x_enable_irq(tac5x1x, true);

The interrupt disabling here seems odd - what's the story there?  I see
this is a work function rather than an irq thread to add some delay and
introduce some debouncing but it's not clear why the disable during
handling.

> +const struct regmap_config tac5x1x_regmap = {
> +	.max_register = 12 * 128,

> +EXPORT_SYMBOL(tac5x1x_regmap);

EXPORT_SYMBOL_GPL().

> +static s32 tac5x1x_set_GPO1_gpio(struct snd_kcontrol *kcontrol,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
> +	s32 gpio_check, val;
> +
> +	val = snd_soc_component_read(component, TAC5X1X_GPO1);
> +	gpio_check = ((val & TAC5X1X_GPIOX_CFG_MASK) >> 0);
> +	if (gpio_check != TAC5X1X_GPIO_GPO) {
> +		dev_err(component->dev,
> +			"%s: GPO1 is not configure as a GPO output\n",
> +			__func__);
> +		return -EINVAL;
> +	}
> +
> +	if (ucontrol->value.integer.value[0])
> +		val = 0;
> +	else
> +		val = TAC5X1X_GPO1_VAL;

This seems to be exposing a GPIO directly to userspace, which will
prevent using that GPIO with other kernel subsystems.  It would be
better to expose this via gpiolib, then if userspace control is needed
that can be done through gpiolib.

> +/* Impedance Selection */
> +static const char *const resistor_text[] = {
> +	"5 kOhm",
> +	"10 kOhm",
> +	"40 kOhm",
> +};
> +
> +static SOC_ENUM_SINGLE_DECL(adc1_resistor_enum, TAC5X1X_ADCCH1C0, 4,
> +		resistor_text);
> +static SOC_ENUM_SINGLE_DECL(adc2_resistor_enum, TAC5X1X_ADCCH2C0, 4,
> +		resistor_text);

Is this something that would be adjusted at runtime, or should it be a
device tree setting?

> +static const char *const rx_ch3_asi_cfg_text[] = {
> +		"Disable",
> +		"DAC channel data",
> +};

On/off switches should be plain controls ending in "Switch" rather than
enums.

> +static const char *const tac5x1x_slot_select_text[] = {
> +	"Slot 0", "Slot 1", "Slot 2", "Slot 3",
> +	"Slot 4", "Slot 5", "Slot 6", "Slot 7",
> +	"Slot 8", "Slot 9", "Slot 10", "Slot 11",
> +	"Slot 12", "Slot 13", "Slot 14", "Slot 15",
> +	"Slot 16", "Slot 17", "Slot 18", "Slot 19",
> +	"Slot 20", "Slot 21", "Slot 22", "Slot 23",
> +	"Slot 24", "Slot 25", "Slot 26", "Slot 27",
> +	"Slot 28", "Slot 29", "Slot 30", "Slot 31",
> +};

TDM slot control would usually be done via set_tdm_slot().

> +static const char *const out2x_vcom_text[] = {
> +	"0.6 * Vref",
> +	"AVDD by 2",
> +};
> +
> +static const char *const diag_cfg_text[] = {
> +	"0mv", "30mv", "60mv", "90mv",
> +	"120mv", "150mv", "180mv", "210mv",
> +	"240mv", "270mv", "300mv", "330mv",
> +	"360mv", "390mv", "420mv", "450mv",
> +};
> +
> +static const char *const diag_cfg_gnd_text[] = {
> +	"0mv", "60mv", "120mv", "180mv",
> +	"240mv", "300mv", "360mv", "420mv",
> +	"480mv", "540mv", "600mv", "660mv",
> +	"720mv", "780mv", "840mv", "900mv",
> +};

Are these controls that should be device tree data?

> +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {

SND_SOC_DAIFMT_CLOCK_MASK.

> +#ifdef CONFIG_PM
> +static s32 tac5x1x_soc_suspend(struct snd_soc_component *component)
> +{
> +	struct tac5x1x_priv *tac5x1x =
> +		snd_soc_component_get_drvdata(component);
> +
> +	regcache_cache_only(tac5x1x->regmap, true);
> +	regcache_mark_dirty(tac5x1x->regmap);
> +
> +	regulator_disable(tac5x1x->supply_avdd);
> +	regulator_disable(tac5x1x->supply_iovdd);

Consider using the regulator_bulk_ APIs - the current code is fine, but
I don't see any cases where you're controlling the regulators separately
so it'd be a little simpler and allows the core to do things like bring
multiple regulators up in parallel.

> +static s32 tac5x1x_soc_resume(struct snd_soc_component *component)
> +{
> +	struct tac5x1x_priv *tac5x1x =
> +		snd_soc_component_get_drvdata(component);
> +	s32 ret;
> +
> +	regcache_cache_only(tac5x1x->regmap, false);
> +	snd_soc_component_cache_sync(component);
> +
> +	ret = regulator_enable(tac5x1x->supply_avdd);
> +	if (ret) {
> +		regulator_disable(tac5x1x->supply_avdd);
> +		return ret;
> +	}
> +
> +	ret = regulator_enable(tac5x1x->supply_iovdd);
> +	if (ret) {
> +		regulator_disable(tac5x1x->supply_iovdd);
> +		return ret;
> +	}

This will try to do the cache sync with the regulators disabled which
won't work if they were actually turned off.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family
@ 2025-06-10 13:13 Holalu Yogendra, Niranjan
  2025-06-10 14:04 ` Mark Brown
  0 siblings, 1 reply; 4+ messages in thread
From: Holalu Yogendra, Niranjan @ 2025-06-10 13:13 UTC (permalink / raw)
  To: broonie@kernel.org
  Cc: andriy.shevchenko@linux.intel.com, tiwai@suse.de,
	alsa-devel@alsa-project.org, Xu, Baojun, Ji, Jesse,
	Ding, Shenghao, Girdwood, Liam R, Navada Kanyana, Mukund,
	Hampiholi, Vallabha

> From: Mark Brown <broonie@kernel.org>
> Sent: Friday, June 6, 2025 6:17 PM
> Subject: Re: [PATCH v1 1/4] ASoc: tac5x1x: add codec driver
> tac5x1x family
> 
> On Fri, Jun 06, 2025 at 12:21:33PM +0530, Niranjan H Y wrote:
... 
> There's a few issues below but nothing that's *hugely* structural, the
> bulk of the code here looks good.

Thank you for your time and review comments. 
I will add fixes for most the comments in the next patch. 
Request you to take a look at some comments below.

> 
> > +config SND_SOC_TAC5X1X
> > +	tristate "Texas Instruments TAC5X1X family codecs"
> > +	depends on I2C && REGMAP_I2C
> > +
> > +config SND_SOC_TAC5X1X_I2C
> > +	tristate "Texas Instruments TAC5X1X family driver based on I2C"
> > +	depends on I2C && REGMAP_I2C
> > +	select SND_SOC_TAC5X1X
> 
> You need to select REGMAP_I2C if you use it, it can't be turned on
> independently.  If the device is I2C only then there's no need to have
> the second option for I2C, that's used for devices that support both I2C
> and SPI to avoid problems with dependencies on the I2C and SPI core
> code.

Many of these devices support SPI interface as well. So I thought I will make
library and I2C interface file. But currently SPI support is not added in the driver.
Is this okay to still keep this ? please suggest.

...
> 
> > +static const char * const out_ch2_ltch[] = {
> > +	"OUT_CH2 OUT2P Short circuit Fault",
> > +	"OUT_CH2 OUT2M Short circuit Fault",
> > +	"OUT_CH2 DRVRP Virtual Ground Fault",
> > +	"OUT_CH2 DRVRM Virtual ground Fault",
> > +	"Reserved",
> > +	"Reserved",
> > +	"AREG SC Fault Mask",
> > +	"AREG SC Fault",
> > +};
> 
> For ones where the reserved values are in the middle of the set of
> values you can use _VAL_ENUM() which lets you skip over the values that
> are invalid.

Is there some example which I can refer? 
I could not find anything in the kernel source with _VAL_ENUM.
I will try to refactor to avoid this.

> 
> > +static s32 tac5x1x_regmap_write(struct tac5x1x_priv *tac5x1x,
> > +				u32 reg, u32 value)
> > +{
> > +	s32 ret;
> > +	s32 retry_count = 5;
> > +
> > +	while (retry_count--) {
> > +		ret = regmap_write(tac5x1x->regmap, reg,
> > +				   value);
> > +		if (ret >= 0)
> > +			break;
> > +		usleep_range(5000, 5050);
> > +	}
> > +	if (retry_count == -1)
> > +		return 3;
> > +	else
> > +		return ret;
> > +}
> 
> Is the hardware genuinely so unstable that we need to retry all the I/O?
> This seems really concerning.

I think this can be dropped as this is used for some legacy devices for some 
customer platforms. I will remove this in next patch and redo the tests.

> > +static s32 tac5x1x_set_GPO1_gpio(struct snd_kcontrol *kcontrol,
> > +				 struct snd_ctl_elem_value *ucontrol)
> > +{
> > +	struct snd_soc_component *component =
> snd_kcontrol_chip(kcontrol);
> > +	s32 gpio_check, val;
> > +
> > +	val = snd_soc_component_read(component, TAC5X1X_GPO1);
> > +	gpio_check = ((val & TAC5X1X_GPIOX_CFG_MASK) >> 0);
> > +	if (gpio_check != TAC5X1X_GPIO_GPO) {
> > +		dev_err(component->dev,
> > +			"%s: GPO1 is not configure as a GPO output\n",
> > +			__func__);
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (ucontrol->value.integer.value[0])
> > +		val = 0;
> > +	else
> > +		val = TAC5X1X_GPO1_VAL;
> 
> This seems to be exposing a GPIO directly to userspace, which will
> prevent using that GPIO with other kernel subsystems.  It would be
> better to expose this via gpiolib, then if userspace control is needed
> that can be done through gpiolib.

Thanks for the suggestion. But, in our case, codec chip has some GPIO pins
which are specific to the codec. We do not intend to expose to the other 
kernel subsystem. It is more of codec chip configuration to use these
GPIO pins as - for e.g. daisy-chain input, audio data output, PLL input clock source,
interrupt, digital audio input(PDM) etc. Depending on the hardware connection, 
we need to configure some registers so that the pin can function as any one of the 
features which mentioned in the data sheet. More info in section 
"5 Pin Configuration and Functions" & "Table 7-70. Multifunction Pin Assignments" 
in the data sheet. https://www.ti.com/lit/ds/symlink/tac5212.pdf
If it feels right, I can make this also as part of the dts configuration. 
Please suggest if I still need to expose this as gpiolib.

> 
> > +static const char *const tac5x1x_slot_select_text[] = {
> > +	"Slot 0", "Slot 1", "Slot 2", "Slot 3",
> > +	"Slot 4", "Slot 5", "Slot 6", "Slot 7",
> > +	"Slot 8", "Slot 9", "Slot 10", "Slot 11",
> > +	"Slot 12", "Slot 13", "Slot 14", "Slot 15",
> > +	"Slot 16", "Slot 17", "Slot 18", "Slot 19",
> > +	"Slot 20", "Slot 21", "Slot 22", "Slot 23",
> > +	"Slot 24", "Slot 25", "Slot 26", "Slot 27",
> > +	"Slot 28", "Slot 29", "Slot 30", "Slot 31",
> > +};
> 
> TDM slot control would usually be done via set_tdm_slot().

We need the slots to be configurable in case we need to swap capture
channels. Is it okay to keep this ?

> 
> > +static const char *const out2x_vcom_text[] = {
> > +	"0.6 * Vref",
> > +	"AVDD by 2",
> > +};
> > +

This can be device tree setting. I will move it.

> > +static const char *const diag_cfg_text[] = {
> > +	"0mv", "30mv", "60mv", "90mv",
> > +	"120mv", "150mv", "180mv", "210mv",
> > +	"240mv", "270mv", "300mv", "330mv",
> > +	"360mv", "390mv", "420mv", "450mv",
> > +};
> > +
> > +static const char *const diag_cfg_gnd_text[] = {
> > +	"0mv", "60mv", "120mv", "180mv",
> > +	"240mv", "300mv", "360mv", "420mv",
> > +	"480mv", "540mv", "600mv", "660mv",
> > +	"720mv", "780mv", "840mv", "900mv",
> > +};
> 
> Are these controls that should be device tree data?

We need these values to be configurable via mixer controls.

Thanks,
Niranjan H Y

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family
  2025-06-10 13:13 [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family Holalu Yogendra, Niranjan
@ 2025-06-10 14:04 ` Mark Brown
  0 siblings, 0 replies; 4+ messages in thread
From: Mark Brown @ 2025-06-10 14:04 UTC (permalink / raw)
  To: Holalu Yogendra, Niranjan
  Cc: andriy.shevchenko@linux.intel.com, tiwai@suse.de,
	alsa-devel@alsa-project.org, Xu, Baojun, Ji, Jesse,
	Ding, Shenghao, Girdwood, Liam R, Navada Kanyana, Mukund,
	Hampiholi, Vallabha

[-- Attachment #1: Type: text/plain, Size: 4129 bytes --]

On Tue, Jun 10, 2025 at 01:13:23PM +0000, Holalu Yogendra, Niranjan wrote:

> > > +config SND_SOC_TAC5X1X
> > > +	tristate "Texas Instruments TAC5X1X family codecs"
> > > +	depends on I2C && REGMAP_I2C

> > > +config SND_SOC_TAC5X1X_I2C
> > > +	tristate "Texas Instruments TAC5X1X family driver based on I2C"
> > > +	depends on I2C && REGMAP_I2C
> > > +	select SND_SOC_TAC5X1X

> > You need to select REGMAP_I2C if you use it, it can't be turned on
> > independently.  If the device is I2C only then there's no need to have
> > the second option for I2C, that's used for devices that support both I2C
> > and SPI to avoid problems with dependencies on the I2C and SPI core
> > code.

> Many of these devices support SPI interface as well. So I thought I will make
> library and I2C interface file. But currently SPI support is not added in the driver.
> Is this okay to still keep this ? please suggest.

Yes, if some of the devices do have SPI support but it's just not there
yet then it makes sense to do things this way.

> > > +	"OUT_CH2 OUT2P Short circuit Fault",
> > > +	"OUT_CH2 OUT2M Short circuit Fault",
> > > +	"OUT_CH2 DRVRP Virtual Ground Fault",
> > > +	"OUT_CH2 DRVRM Virtual ground Fault",
> > > +	"Reserved",
> > > +	"Reserved",
> > > +	"AREG SC Fault Mask",
> > > +	"AREG SC Fault",
> > > +};

> > For ones where the reserved values are in the middle of the set of
> > values you can use _VAL_ENUM() which lets you skip over the values that
> > are invalid.

> Is there some example which I can refer? 
> I could not find anything in the kernel source with _VAL_ENUM.
> I will try to refactor to avoid this.

Sorry, I meant _VALUE_ENUM (should've actually grepped to confirm).
There's a bunch of examples which should be easy to find with the
correct name, such as adau1372.

> > > +static s32 tac5x1x_set_GPO1_gpio(struct snd_kcontrol *kcontrol,
> > > +				 struct snd_ctl_elem_value *ucontrol)
> > > +{
> > > +	struct snd_soc_component *component =

> > This seems to be exposing a GPIO directly to userspace, which will
> > prevent using that GPIO with other kernel subsystems.  It would be
> > better to expose this via gpiolib, then if userspace control is needed
> > that can be done through gpiolib.

> Thanks for the suggestion. But, in our case, codec chip has some GPIO pins
> which are specific to the codec. We do not intend to expose to the other 
> kernel subsystem. It is more of codec chip configuration to use these
> GPIO pins as - for e.g. daisy-chain input, audio data output, PLL input clock source,
> interrupt, digital audio input(PDM) etc. Depending on the hardware connection, 

You might not intend to use them as generic GPIOs but some customer
might.  For purposes where they're not actually used as GPIOs but rather
are pinmuxed into an audio data output or something then they should be
configured via DT properties since that's not something that will be
changed at runtime but rather something that'd be controlled by the
board design.  Only the actual GPIO functionality should be exposed via
gpiolib.

We do have a specific subsystem for pinmuxing too, but for fixed
function stuff like this it's probably not worth the effort of using it.

> > > +static const char *const tac5x1x_slot_select_text[] = {
> > > +	"Slot 0", "Slot 1", "Slot 2", "Slot 3",
> > > +	"Slot 4", "Slot 5", "Slot 6", "Slot 7",
> > > +	"Slot 8", "Slot 9", "Slot 10", "Slot 11",
> > > +	"Slot 12", "Slot 13", "Slot 14", "Slot 15",
> > > +	"Slot 16", "Slot 17", "Slot 18", "Slot 19",
> > > +	"Slot 20", "Slot 21", "Slot 22", "Slot 23",
> > > +	"Slot 24", "Slot 25", "Slot 26", "Slot 27",
> > > +	"Slot 28", "Slot 29", "Slot 30", "Slot 31",
> > > +};

> > TDM slot control would usually be done via set_tdm_slot().

> We need the slots to be configurable in case we need to swap capture
> channels. Is it okay to keep this ?

If it's runtime configurable then ideally that'd be a control exposed by
the board that uses set_tdm_slot() which offers the specific
combinations of options that make sense on that board.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-06-10 19:35 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-10 13:13 [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family Holalu Yogendra, Niranjan
2025-06-10 14:04 ` Mark Brown
  -- strict thread matches above, loose matches on Subject: below --
2025-06-06  6:51 [PATCH v1 0/4] ASoc: tac5x1x: mixer-test report Niranjan H Y
2025-06-06  6:51 ` [PATCH v1 1/4] ASoc: tac5x1x: add codec driver tac5x1x family Niranjan H Y
2025-06-06 12:47   ` Mark Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.