[PATCH 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver

Sen Wang posted 4 patches 6 hours ago
[PATCH 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
Posted by Sen Wang 6 hours ago
The TAS675x (TAS6754, TAS67524) are quad-channel, digital-input
Class-D amplifiers with an integrated DSP, controlled over I2C.
They support I2S and TDM serial audio interfaces.

The driver exposes three DAI endpoints: standard playback, a
low-latency DSP bypass path, and a sense capture DAI for real-time
voltage and current feedback. DC, AC and real-time load diagnostics
and hardware fault monitoring are supported.

Link: https://www.ti.com/product/TAS6754-Q1
Signed-off-by: Sen Wang <sen@ti.com>
---
 sound/soc/codecs/Kconfig   |   12 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/tas675x.c | 2172 ++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/tas675x.h |  367 ++++++
 4 files changed, 2553 insertions(+)
 create mode 100644 sound/soc/codecs/tas675x.c
 create mode 100644 sound/soc/codecs/tas675x.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f9e6a83e55c6..0ca7c7e2283b 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -275,6 +275,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TAS571X
 	imply SND_SOC_TAS5720
 	imply SND_SOC_TAS6424
+	imply SND_SOC_TAS675X
 	imply SND_SOC_TDA7419
 	imply SND_SOC_TFA9879
 	imply SND_SOC_TFA989X
@@ -2240,6 +2241,17 @@ config SND_SOC_TAS6424
 	  Enable support for Texas Instruments TAS6424 high-efficiency
 	  digital input quad-channel Class-D audio power amplifiers.
 
+config SND_SOC_TAS675X
+	tristate "Texas Instruments TAS675x Quad-Channel Audio Amplifier"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Enable support for Texas Instruments TAS675x quad-channel Class-D
+	  audio power amplifiers (TAS6754, TAS67524). The devices support I2S
+	  and TDM interfaces with real-time voltage and current sense feedback
+	  via SDOUT, and provide DC/AC/real-time load diagnostics for speaker
+	  monitoring.
+
 config SND_SOC_TDA7419
 	tristate "ST TDA7419 audio processor"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 172861d17cfd..106fdc140d42 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -318,6 +318,7 @@ snd-soc-tas571x-y := tas571x.o
 snd-soc-tas5720-y := tas5720.o
 snd-soc-tas5805m-y := tas5805m.o
 snd-soc-tas6424-y := tas6424.o
+snd-soc-tas675x-y := tas675x.o
 snd-soc-tda7419-y := tda7419.o
 snd-soc-tas2770-y := tas2770.o
 snd-soc-tas2781-comlib-y := tas2781-comlib.o
@@ -760,6 +761,7 @@ obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o
 obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o
 obj-$(CONFIG_SND_SOC_TAS5805M)	+= snd-soc-tas5805m.o
 obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o
+obj-$(CONFIG_SND_SOC_TAS675X)	+= snd-soc-tas675x.o
 obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o
 obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c
new file mode 100644
index 000000000000..cf150a115bb3
--- /dev/null
+++ b/sound/soc/codecs/tas675x.c
@@ -0,0 +1,2172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC Texas Instruments TAS675x Quad-Channel Audio Amplifier
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ *	Author: Sen Wang <sen@ti.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>
+#include <linux/iopoll.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+
+#include "tas675x.h"
+
+#define TAS675X_FAULT_CHECK_INTERVAL_MS  200
+
+enum tas675x_type {
+	TAS6754,
+};
+
+struct tas675x_reg_param {
+	u8  page;
+	u8  reg;
+	u32 val;
+};
+
+struct tas675x_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	enum tas675x_type dev_type;
+	struct mutex io_lock;
+
+	struct gpio_desc *pd_gpio;
+	struct gpio_desc *stby_gpio;
+	struct regulator_bulk_data supplies[2];
+	struct regulator *vbat;
+	bool fast_boot;
+
+	int audio_slot;
+	int llp_slot;
+	int vpredict_slot;
+	int isense_slot;
+	int bclk_offset;
+	int slot_width;
+	unsigned int tx_mask;
+
+	int gpio1_func;
+	int gpio2_func;
+
+	unsigned long active_playback_dais;
+	unsigned long active_capture_dais;
+	unsigned int rate;
+	unsigned int saved_rtldg_en;
+#define TAS675X_DSP_PARAM_NUM	2
+	struct tas675x_reg_param dsp_params[TAS675X_DSP_PARAM_NUM];
+
+	/* Fault monitor, disabled when Fault IRQ is used */
+	struct delayed_work fault_check_work;
+#define TAS675X_FAULT_REGS_NUM	8
+	unsigned int last_status[TAS675X_FAULT_REGS_NUM];
+};
+
+static const char * const tas675x_supply_names[] = {
+	"dvdd",		/* Digital power supply */
+	"pvdd",		/* Output powerstage supply */
+};
+
+/* Page 1 setup initialization defaults */
+static const struct reg_sequence tas675x_page1_init[] = {
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC8), 0x20),	/* Charge pump clock */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x2F), 0x90),	/* VBAT idle */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x29), 0x40),	/* OC/CBC threshold */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x2E), 0x0C),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC5), 0x02),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC6), 0x10),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x1F), 0x20),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x16), 0x01),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x1E), 0x04),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC1), 0x00),	/* CH1 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC2), 0x04),	/* CH2 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC3), 0x00),	/* CH3 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC4), 0x00),	/* CH4 DC fault */
+};
+
+static inline const char *tas675x_state_name(unsigned int state)
+{
+	switch (state & 0x0F) {
+	case TAS675X_STATE_DEEPSLEEP:	return "DEEPSLEEP";
+	case TAS675X_STATE_LOAD_DIAG:	return "LOAD_DIAG";
+	case TAS675X_STATE_SLEEP:	return "SLEEP";
+	case TAS675X_STATE_HIZ:		return "HIZ";
+	case TAS675X_STATE_PLAY:	return "PLAY";
+	case TAS675X_STATE_FAULT:	return "FAULT";
+	case TAS675X_STATE_AUTOREC:	return "AUTOREC";
+	default:			return "UNKNOWN";
+	}
+}
+
+static inline int tas675x_set_state_all(struct tas675x_priv *tas, u8 state)
+{
+	const struct reg_sequence seq[] = {
+		REG_SEQ0(TAS675X_STATE_CTRL_CH1_CH2_REG, state),
+		REG_SEQ0(TAS675X_STATE_CTRL_CH3_CH4_REG, state),
+	};
+
+	return regmap_multi_reg_write(tas->regmap, seq, ARRAY_SIZE(seq));
+}
+
+static inline int tas675x_select_book(struct regmap *regmap, u8 book)
+{
+	int ret;
+
+	/* Reset page to 0 before switching books */
+	ret = regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
+	if (!ret)
+		ret = regmap_write(regmap, TAS675X_BOOK_CTRL_REG, book);
+
+	return ret;
+}
+
+/* Raw I2C version of tas675x_select_book, must be called with io_lock held */
+static inline int __tas675x_select_book(struct tas675x_priv *tas, u8 book)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	int ret;
+
+	/* Reset page to 0 before switching books */
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, 0x00);
+	if (ret)
+		return ret;
+
+	return i2c_smbus_write_byte_data(client, TAS675X_BOOK_CTRL_REG, book);
+}
+
+static int tas675x_dsp_mem_write(struct tas675x_priv *tas, u8 page, u8 reg, u32 val)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	u8 buf[4];
+	int ret;
+
+	/* DSP registers are 24-bit values, zero-padded MSB to 32-bit */
+	buf[0] = 0x00;
+	buf[1] = (val >> 16) & 0xFF;
+	buf[2] = (val >> 8) & 0xFF;
+	buf[3] = val & 0xFF;
+
+	/*
+	 * DSP regs in a different book, therefore block
+	 * regmap access before completion.
+	 */
+	mutex_lock(&tas->io_lock);
+
+	ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_i2c_block_data(client, reg, sizeof(buf), buf);
+
+out:
+	__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
+	mutex_unlock(&tas->io_lock);
+
+	return ret;
+}
+
+static int tas675x_dsp_mem_read(struct tas675x_priv *tas, u8 page, u8 reg, u32 *val)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	u8 buf[4];
+	int ret;
+
+	/*
+	 * DSP regs in a different book, therefore block
+	 * regmap access before completion.
+	 */
+	mutex_lock(&tas->io_lock);
+
+	ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
+	if (ret == sizeof(buf)) {
+		*val = (buf[1] << 16) | (buf[2] << 8) | buf[3];
+		ret = 0;
+	} else if (ret >= 0) {
+		ret = -EIO;
+	}
+
+out:
+	__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
+	mutex_unlock(&tas->io_lock);
+
+	return ret;
+}
+
+static const struct {
+	const char *name;
+	int val;
+} tas675x_gpio_func_map[] = {
+	/* Output functions */
+	{ "low",            TAS675X_GPIO_SEL_LOW },
+	{ "auto-mute",      TAS675X_GPIO_SEL_AUTO_MUTE_ALL },
+	{ "auto-mute-ch4",  TAS675X_GPIO_SEL_AUTO_MUTE_CH4 },
+	{ "auto-mute-ch3",  TAS675X_GPIO_SEL_AUTO_MUTE_CH3 },
+	{ "auto-mute-ch2",  TAS675X_GPIO_SEL_AUTO_MUTE_CH2 },
+	{ "auto-mute-ch1",  TAS675X_GPIO_SEL_AUTO_MUTE_CH1 },
+	{ "sdout2",         TAS675X_GPIO_SEL_SDOUT2 },
+	{ "sdout1",         TAS675X_GPIO_SEL_SDOUT1 },
+	{ "warn",           TAS675X_GPIO_SEL_WARN },
+	{ "fault",          TAS675X_GPIO_SEL_FAULT },
+	{ "clock-sync",     TAS675X_GPIO_SEL_CLOCK_SYNC },
+	{ "invalid-clock",  TAS675X_GPIO_SEL_INVALID_CLK },
+	{ "high",           TAS675X_GPIO_SEL_HIGH },
+	/* Input functions */
+	{ "mute",           TAS675X_GPIO_IN_MUTE },
+	{ "phase-sync",     TAS675X_GPIO_IN_PHASE_SYNC },
+	{ "sdin2",          TAS675X_GPIO_IN_SDIN2 },
+	{ "deep-sleep",     TAS675X_GPIO_IN_DEEP_SLEEP },
+	{ "hiz",            TAS675X_GPIO_IN_HIZ },
+	{ "play",           TAS675X_GPIO_IN_PLAY },
+	{ "sleep",          TAS675X_GPIO_IN_SLEEP },
+};
+
+static int tas675x_gpio_func_parse(struct device *dev, const char *propname)
+{
+	const char *str;
+	int i, ret;
+
+	ret = device_property_read_string(dev, propname, &str);
+	if (ret)
+		return -1;
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_gpio_func_map); i++) {
+		if (!strcmp(str, tas675x_gpio_func_map[i].name))
+			return tas675x_gpio_func_map[i].val;
+	}
+
+	dev_warn(dev, "Invalid %s value '%s'\n", propname, str);
+	return -1;
+}
+
+static const struct {
+	unsigned int reg;
+	unsigned int mask;
+} tas675x_gpio_input_table[TAS675X_GPIO_IN_NUM] = {
+	[TAS675X_GPIO_IN_ID_MUTE] = {
+		TAS675X_GPIO_INPUT_MUTE_REG, TAS675X_GPIO_IN_MUTE_MASK },
+	[TAS675X_GPIO_IN_ID_PHASE_SYNC] = {
+		TAS675X_GPIO_INPUT_SYNC_REG, TAS675X_GPIO_IN_SYNC_MASK },
+	[TAS675X_GPIO_IN_ID_SDIN2] = {
+		TAS675X_GPIO_INPUT_SDIN2_REG, TAS675X_GPIO_IN_SDIN2_MASK },
+	[TAS675X_GPIO_IN_ID_DEEP_SLEEP] = {
+		TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_DEEP_SLEEP_MASK },
+	[TAS675X_GPIO_IN_ID_HIZ] = {
+		TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_HIZ_MASK },
+	[TAS675X_GPIO_IN_ID_PLAY] = {
+		TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_PLAY_MASK },
+	[TAS675X_GPIO_IN_ID_SLEEP] = {
+		TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_SLEEP_MASK },
+};
+
+static void tas675x_config_gpio_pin(struct regmap *regmap, int func_id,
+				    unsigned int out_sel_reg,
+				    unsigned int pin_idx,
+				    unsigned int *gpio_ctrl)
+{
+	int id;
+
+	if (func_id < 0)
+		return;
+
+	if (func_id & TAS675X_GPIO_FUNC_INPUT) {
+		/* 3-bit mux: 0 = disabled, 0b1 = GPIO1, 0b10 = GPIO2 */
+		id = func_id & ~TAS675X_GPIO_FUNC_INPUT;
+		regmap_update_bits(regmap,
+				   tas675x_gpio_input_table[id].reg,
+				   tas675x_gpio_input_table[id].mask,
+				   (pin_idx + 1) << __ffs(tas675x_gpio_input_table[id].mask));
+	} else {
+		/* Output GPIO, update selection register and enable bit */
+		regmap_write(regmap, out_sel_reg, func_id);
+		*gpio_ctrl |= pin_idx ? TAS675X_GPIO2_OUTPUT_EN : TAS675X_GPIO1_OUTPUT_EN;
+	}
+}
+
+static int tas675x_rtldg_thresh_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	/* Accepts 32-bit values, even though 8bit MSB is ignored */
+	uinfo->value.integer.max = 0xFFFFFFFF;
+	return 0;
+}
+
+static int tas675x_set_rtldg_thresh(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	const struct tas675x_reg_param *t =
+		(const struct tas675x_reg_param *)kcontrol->private_value;
+	u32 val = ucontrol->value.integer.value[0];
+	int ret;
+
+	ret = tas675x_dsp_mem_write(tas, t->page, t->reg, val);
+
+	/* Cache the value */
+	if (!ret) {
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
+			if (tas->dsp_params[i].page == t->page &&
+			    tas->dsp_params[i].reg == t->reg) {
+				tas->dsp_params[i].val = val;
+				break;
+			}
+		}
+	}
+
+	/* Return 1 to notify change, or propagate error */
+	return ret ? ret : 1;
+}
+
+static int tas675x_get_rtldg_thresh(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	const struct tas675x_reg_param *t =
+		(const struct tas675x_reg_param *)kcontrol->private_value;
+	u32 val = 0;
+	int ret;
+
+	ret = tas675x_dsp_mem_read(tas, t->page, t->reg, &val);
+	if (!ret)
+		ucontrol->value.integer.value[0] = val;
+
+	return ret;
+}
+
+static const struct tas675x_reg_param tas675x_dsp_defaults[] = {
+	[TAS675X_DSP_PARAM_ID_OL_THRESH] = {
+		TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_OL_THRESH_REG },
+	[TAS675X_DSP_PARAM_ID_SL_THRESH] = {
+		TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_SL_THRESH_REG },
+};
+
+static int tas675x_set_dcldg_trigger(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int state, state34;
+	int ret;
+
+	if (!ucontrol->value.integer.value[0])
+		return 0;
+
+	if (snd_soc_component_active(comp))
+		return -EBUSY;
+
+	ret = pm_runtime_resume_and_get(tas->dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Abort automatic DC LDG retry loops (startup or init-after-fault)
+	 * and clear faults before manual diagnostics.
+	 */
+	regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
+	regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	/* Wait for LOAD_DIAG to exit */
+	regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				 state, (state & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
+					(state >> 4) != TAS675X_STATE_LOAD_DIAG,
+				 TAS675X_POLL_INTERVAL_US,
+				 TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+				 state34, (state34 & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
+					  (state34 >> 4) != TAS675X_STATE_LOAD_DIAG,
+				 TAS675X_POLL_INTERVAL_US,
+				 TAS675X_STATE_TRANSITION_TIMEOUT_US);
+
+	/* Transition to HIZ state */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
+	if (ret)
+		goto out_restore_ldg_ctrl;
+
+	/* Set LOAD_DIAG state for manual DC LDG */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_LOAD_DIAG_BOTH);
+	if (ret)
+		goto out_restore_ldg_ctrl;
+
+	/* Wait for device to transition to LOAD_DIAG state */
+	regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				 state, state == TAS675X_STATE_LOAD_DIAG_BOTH,
+				 TAS675X_POLL_INTERVAL_US,
+				 TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+				 state34, state34 == TAS675X_STATE_LOAD_DIAG_BOTH,
+				 TAS675X_POLL_INTERVAL_US,
+				 TAS675X_STATE_TRANSITION_TIMEOUT_US);
+
+	/* Clear ABORT and BYPASS bits to enable manual DC LDG */
+	ret = regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+				 TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				 0);
+	if (ret)
+		goto out_restore_hiz;
+
+	dev_dbg(tas->dev, "DC LDG: Started\n");
+
+	/* Poll all channels for SLEEP state */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				      state, state == TAS675X_STATE_SLEEP_BOTH,
+				      TAS675X_POLL_INTERVAL_US,
+				      TAS675X_DC_LDG_TIMEOUT_US);
+	ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+				       state34, state34 == TAS675X_STATE_SLEEP_BOTH,
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_DC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"DC LDG: timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
+			state, tas675x_state_name(state), tas675x_state_name(state >> 4),
+			state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
+		goto out_restore_hiz;
+	}
+
+	dev_dbg(tas->dev, "DC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
+		state, state34);
+
+out_restore_hiz:
+	tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
+
+out_restore_ldg_ctrl:
+	regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+			   0);
+
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_put_autosuspend(tas->dev);
+
+	return ret;
+}
+
+static int tas675x_set_acldg_trigger(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int state, state34;
+	int ret;
+
+	if (!ucontrol->value.integer.value[0])
+		return 0;
+
+	if (snd_soc_component_active(comp))
+		return -EBUSY;
+
+	ret = pm_runtime_resume_and_get(tas->dev);
+	if (ret < 0)
+		return ret;
+
+	/* AC Load Diagnostics requires SLEEP state */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+	if (ret) {
+		dev_err(tas->dev, "AC LDG: Failed to set SLEEP state: %d\n", ret);
+		goto out;
+	}
+
+	/* Start AC LDG on all 4 channels (0x0F) */
+	ret = regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x0F);
+	if (ret) {
+		dev_err(tas->dev, "AC LDG: Failed to start: %d\n", ret);
+		goto out;
+	}
+
+	dev_dbg(tas->dev, "AC LDG: Started\n");
+
+	/* Poll all channels for SLEEP state */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+					state, (state == TAS675X_STATE_SLEEP_BOTH),
+					TAS675X_POLL_INTERVAL_US,
+					TAS675X_AC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"AC LDG: CH1/CH2 timeout: %d (state=0x%02x [%s/%s])\n",
+			ret, state, tas675x_state_name(state),
+			tas675x_state_name(state >> 4));
+		regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+		goto out;
+	}
+
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+					state34, (state34 == TAS675X_STATE_SLEEP_BOTH),
+					TAS675X_POLL_INTERVAL_US,
+					TAS675X_AC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"AC LDG: CH3/CH4 timeout: %d (state=0x%02x [%s/%s])\n",
+			ret, state34, tas675x_state_name(state34),
+			tas675x_state_name(state34 >> 4));
+		regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+		goto out;
+	}
+
+	dev_dbg(tas->dev, "AC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
+		state, state34);
+	regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+	ret = 0;
+
+out:
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_put_autosuspend(tas->dev);
+
+	return ret;
+}
+
+static int tas675x_rtldg_impedance_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xFFFF;
+	return 0;
+}
+
+static int tas675x_get_rtldg_impedance(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int msb_reg = (unsigned int)kcontrol->private_value;
+	unsigned int msb, lsb;
+	int ret;
+
+	ret = regmap_read(tas->regmap, msb_reg, &msb);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(tas->regmap, msb_reg + 1, &lsb);
+	if (ret)
+		return ret;
+
+	ucontrol->value.integer.value[0] = (msb << 8) | lsb;
+	return 0;
+}
+
+static int tas675x_dc_resistance_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	/* 10-bit: 2-bit MSB + 8-bit LSB, 0.1 ohm/code, 0-102.3 ohm */
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1023;
+	return 0;
+}
+
+static int tas675x_get_dc_resistance(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int lsb_reg = (unsigned int)kcontrol->private_value;
+	unsigned int msb, lsb, shift;
+	int ret;
+
+	ret = regmap_read(tas->regmap, TAS675X_DC_LDG_DCR_MSB_REG, &msb);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(tas->regmap, lsb_reg, &lsb);
+	if (ret)
+		return ret;
+
+	/* 2-bit MSB: CH1=[7:6], CH2=[5:4], CH3=[3:2], CH4=[1:0] */
+	shift = 6 - (lsb_reg - TAS675X_CH1_DC_LDG_DCR_LSB_REG) * 2;
+	msb = (msb >> shift) & 0x3;
+
+	ucontrol->value.integer.value[0] = (msb << 8) | lsb;
+	return 0;
+}
+
+/* Counterparts with read-only access */
+#define SOC_SINGLE_RO(xname, xreg, xshift, xmax) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_get_volsw, \
+	.private_value = SOC_SINGLE_VALUE(xreg, xshift, 0, xmax, 0, 0) }
+#define SOC_DC_RESIST_RO(xname, xlsb_reg) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = tas675x_dc_resistance_info, \
+	.get = tas675x_get_dc_resistance, \
+	.private_value = (xlsb_reg) }
+#define SOC_RTLDG_IMP_RO(xname, xreg) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = tas675x_rtldg_impedance_info, \
+	.get = tas675x_get_rtldg_impedance, \
+	.private_value = (xreg) }
+
+#define SOC_DSP_THRESH_EXT(xname, xthresh) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.info = tas675x_rtldg_thresh_info, \
+	.get = tas675x_get_rtldg_thresh, \
+	.put = tas675x_set_rtldg_thresh, \
+	.private_value = (unsigned long)&(xthresh) }
+
+/*
+ * DAC digital volumes. From -103 to 0 dB in 0.5 dB steps, -103.5 dB means mute.
+ * DAC analog gain. From -15.5 to 0 dB in 0.5 dB steps, no mute.
+ */
+static const DECLARE_TLV_DB_SCALE(tas675x_dig_vol_tlv, -10350, 50, 1);
+static const DECLARE_TLV_DB_SCALE(tas675x_ana_gain_tlv, -1550, 50, 0);
+
+static const char * const tas675x_ss_texts[] = {
+	"Disabled", "Triangle", "Random", "Triangle and Random"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_enum, TAS675X_SS_CTRL_REG, 0, tas675x_ss_texts);
+
+static const char * const tas675x_ss_tri_range_texts[] = {
+	"6.5%", "13.5%", "5%", "10%"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_tri_range_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 0,
+			    tas675x_ss_tri_range_texts);
+
+static const char * const tas675x_ss_rdm_range_texts[] = {
+	"0.83%", "2.50%", "5.83%", "12.50%", "25.83%"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_range_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 4,
+			    tas675x_ss_rdm_range_texts);
+
+static const char * const tas675x_ss_rdm_dwell_texts[] = {
+	"1/FSS to 2/FSS", "1/FSS to 4/FSS", "1/FSS to 8/FSS", "1/FSS to 15/FSS"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_dwell_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 2,
+			    tas675x_ss_rdm_dwell_texts);
+
+static const char * const tas675x_oc_limit_texts[] = {
+	"Level 4", "Level 3", "Level 2", "Level 1"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_oc_limit_enum, TAS675X_CURRENT_LIMIT_CTRL_REG,
+			    0, tas675x_oc_limit_texts);
+
+static const char * const tas675x_otw_texts[] = {
+	"Disabled", ">95C", ">110C", ">125C", ">135C", ">145C", ">155C", ">165C"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_otw_enum,
+			    TAS675X_OTW_CTRL_CH1_CH2_REG, 4,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_otw_enum,
+			    TAS675X_OTW_CTRL_CH1_CH2_REG, 0,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_otw_enum,
+			    TAS675X_OTW_CTRL_CH3_CH4_REG, 4,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_otw_enum,
+			    TAS675X_OTW_CTRL_CH3_CH4_REG, 0,
+			    tas675x_otw_texts);
+
+static const char * const tas675x_dc_ldg_sl_texts[] = {
+	"0.5 Ohm", "1 Ohm", "1.5 Ohm", "2 Ohm", "2.5 Ohm",
+	"3 Ohm", "3.5 Ohm", "4 Ohm", "4.5 Ohm", "5 Ohm"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 4,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 4,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0,
+			    tas675x_dc_ldg_sl_texts);
+
+static const char * const tas675x_dc_slol_ramp_texts[] = {
+	"15 ms", "30 ms", "10 ms", "20 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_ramp_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 6,
+			    tas675x_dc_slol_ramp_texts);
+
+static const char * const tas675x_dc_slol_settling_texts[] = {
+	"10 ms", "5 ms", "20 ms", "15 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_settling_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 4,
+			    tas675x_dc_slol_settling_texts);
+
+static const char * const tas675x_dc_s2pg_ramp_texts[] = {
+	"5 ms", "2.5 ms", "10 ms", "15 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_ramp_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 2,
+			    tas675x_dc_s2pg_ramp_texts);
+
+static const char * const tas675x_dc_s2pg_settling_texts[] = {
+	"10 ms", "5 ms", "20 ms", "30 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_settling_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 0,
+			    tas675x_dc_s2pg_settling_texts);
+
+static const char * const tas675x_dsp_mode_texts[] = {
+	"Normal", "LLP", "FFLP"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dsp_mode_enum,
+			    TAS675X_LL_EN_REG, 0,
+			    tas675x_dsp_mode_texts);
+
+static const char * const tas675x_ana_ramp_texts[] = {
+	"15us", "60us", "200us", "400us"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ana_ramp_enum,
+			    TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 2,
+			    tas675x_ana_ramp_texts);
+
+static const char * const tas675x_ramp_rate_texts[] = {
+	"4 FS", "16 FS", "32 FS", "Instant"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_rate_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 6,
+			    tas675x_ramp_rate_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_rate_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 2,
+			    tas675x_ramp_rate_texts);
+
+static const char * const tas675x_ramp_step_texts[] = {
+	"4dB", "2dB", "1dB", "0.5dB"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_step_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 4,
+			    tas675x_ramp_step_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_step_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 0,
+			    tas675x_ramp_step_texts);
+
+static const char * const tas675x_vol_combine_ch12_texts[] = {
+	"Independent", "CH2 follows CH1", "CH1 follows CH2"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch12_enum,
+			    TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0,
+			    tas675x_vol_combine_ch12_texts);
+
+static const char * const tas675x_vol_combine_ch34_texts[] = {
+	"Independent", "CH4 follows CH3", "CH3 follows CH4"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch34_enum,
+			    TAS675X_DIG_VOL_COMBINE_CTRL_REG, 2,
+			    tas675x_vol_combine_ch34_texts);
+
+static const char * const tas675x_auto_mute_time_texts[] = {
+	"11.5ms", "53ms", "106.5ms", "266.5ms",
+	"535ms", "1065ms", "2665ms", "5330ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 4,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 4,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0,
+			    tas675x_auto_mute_time_texts);
+
+/*
+ * ALSA Mixer Controls
+ *
+ * For detailed documentation of each control see:
+ * Documentation/sound/codecs/tas675x.rst
+ */
+static const struct snd_kcontrol_new tas675x_snd_controls[] = {
+	/* Volume & Gain Control */
+	SOC_DOUBLE_R_TLV("Analog Playback Volume", TAS675X_ANALOG_GAIN_CH1_CH2_REG,
+			 TAS675X_ANALOG_GAIN_CH3_CH4_REG, 1, 0x1F, 1, tas675x_ana_gain_tlv),
+	SOC_ENUM("Analog Gain Ramp Step", tas675x_ana_ramp_enum),
+	SOC_SINGLE_RANGE_TLV("CH1 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH1_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH2 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH2_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH3 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH3_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH4 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH4_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_ENUM("Volume Ramp Down Rate", tas675x_ramp_down_rate_enum),
+	SOC_ENUM("Volume Ramp Down Step", tas675x_ramp_down_step_enum),
+	SOC_ENUM("Volume Ramp Up Rate", tas675x_ramp_up_rate_enum),
+	SOC_ENUM("Volume Ramp Up Step", tas675x_ramp_up_step_enum),
+	SOC_ENUM("CH1/2 Volume Combine", tas675x_vol_combine_ch12_enum),
+	SOC_ENUM("CH3/4 Volume Combine", tas675x_vol_combine_ch34_enum),
+
+	/* Auto Mute & Silence Detection */
+	SOC_SINGLE("CH1 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 0, 1, 0),
+	SOC_SINGLE("CH2 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 1, 1, 0),
+	SOC_SINGLE("CH3 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 2, 1, 0),
+	SOC_SINGLE("CH4 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 3, 1, 0),
+	SOC_SINGLE("Auto Mute Combine Switch", TAS675X_AUTO_MUTE_EN_REG, 4, 1, 0),
+	SOC_ENUM("CH1 Auto Mute Time", tas675x_ch1_mute_time_enum),
+	SOC_ENUM("CH2 Auto Mute Time", tas675x_ch2_mute_time_enum),
+	SOC_ENUM("CH3 Auto Mute Time", tas675x_ch3_mute_time_enum),
+	SOC_ENUM("CH4 Auto Mute Time", tas675x_ch4_mute_time_enum),
+
+	/* Clock & EMI Management */
+	SOC_ENUM("Spread Spectrum Mode", tas675x_ss_enum),
+	SOC_ENUM("SS Triangle Range", tas675x_ss_tri_range_enum),
+	SOC_ENUM("SS Random Range", tas675x_ss_rdm_range_enum),
+	SOC_ENUM("SS Random Dwell Range", tas675x_ss_rdm_dwell_enum),
+	SOC_SINGLE("SS Triangle Dwell Min", TAS675X_SS_DWELL_CTRL_REG, 4, 15, 0),
+	SOC_SINGLE("SS Triangle Dwell Max", TAS675X_SS_DWELL_CTRL_REG, 0, 15, 0),
+
+	/* Hardware Protection */
+	SOC_SINGLE("OTSD Auto Recovery Switch", TAS675X_OTSD_RECOVERY_EN_REG, 1, 1, 0),
+	SOC_ENUM("Overcurrent Limit Level", tas675x_oc_limit_enum),
+	SOC_ENUM("CH1 OTW Threshold", tas675x_ch1_otw_enum),
+	SOC_ENUM("CH2 OTW Threshold", tas675x_ch2_otw_enum),
+	SOC_ENUM("CH3 OTW Threshold", tas675x_ch3_otw_enum),
+	SOC_ENUM("CH4 OTW Threshold", tas675x_ch4_otw_enum),
+
+	/* DSP Signal Path & Mode */
+	SOC_ENUM("DSP Signal Path Mode", tas675x_dsp_mode_enum),
+
+	/* DC Load Diagnostics */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC LDG Trigger",
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.put = tas675x_set_dcldg_trigger,
+	},
+	SOC_SINGLE("DC LDG Auto Diagnostics Switch", TAS675X_DC_LDG_CTRL_REG, 0, 1, 1),
+	SOC_SINGLE("CH1 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 3, 1, 0),
+	SOC_SINGLE("CH2 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 2, 1, 0),
+	SOC_SINGLE("CH3 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 1, 1, 0),
+	SOC_SINGLE("CH4 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 0, 1, 0),
+	SOC_ENUM("DC LDG SLOL Ramp Time", tas675x_dc_slol_ramp_enum),
+	SOC_ENUM("DC LDG SLOL Settling Time", tas675x_dc_slol_settling_enum),
+	SOC_ENUM("DC LDG S2PG Ramp Time", tas675x_dc_s2pg_ramp_enum),
+	SOC_ENUM("DC LDG S2PG Settling Time", tas675x_dc_s2pg_settling_enum),
+	SOC_ENUM("CH1 DC LDG SL Threshold", tas675x_ch1_dc_ldg_sl_enum),
+	SOC_ENUM("CH2 DC LDG SL Threshold", tas675x_ch2_dc_ldg_sl_enum),
+	SOC_ENUM("CH3 DC LDG SL Threshold", tas675x_ch3_dc_ldg_sl_enum),
+	SOC_ENUM("CH4 DC LDG SL Threshold", tas675x_ch4_dc_ldg_sl_enum),
+	SOC_SINGLE_RO("DC LDG Result", TAS675X_DC_LDG_RESULT_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 4, 0x0F),
+	SOC_SINGLE_RO("CH2 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 0, 0x0F),
+	SOC_SINGLE_RO("CH3 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 4, 0x0F),
+	SOC_SINGLE_RO("CH4 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 0, 0x0F),
+	SOC_SINGLE_RO("CH1 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 7, 1),
+	SOC_SINGLE_RO("CH2 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 6, 1),
+	SOC_SINGLE_RO("CH3 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 5, 1),
+	SOC_SINGLE_RO("CH4 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 4, 1),
+	SOC_DC_RESIST_RO("CH1 DC Resistance", TAS675X_CH1_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH2 DC Resistance", TAS675X_CH2_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH3 DC Resistance", TAS675X_CH3_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH4 DC Resistance", TAS675X_CH4_DC_LDG_DCR_LSB_REG),
+
+	/* AC Load Diagnostics */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "AC LDG Trigger",
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.put = tas675x_set_acldg_trigger,
+	},
+	SOC_SINGLE("AC DIAG GAIN", TAS675X_AC_LDG_CTRL_REG, 4, 1, 0),
+	SOC_SINGLE("AC LDG Test Frequency", TAS675X_AC_LDG_FREQ_CTRL_REG, 0, 0xFF, 0),
+	SOC_SINGLE_RO("CH1 AC LDG Real", TAS675X_AC_LDG_REPORT_CH1_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH1_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH2 AC LDG Real", TAS675X_AC_LDG_REPORT_CH2_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH2 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH2_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH3 AC LDG Real", TAS675X_AC_LDG_REPORT_CH3_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH3 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH3_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH4 AC LDG Real", TAS675X_AC_LDG_REPORT_CH4_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH4 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH4_I_REG, 0, 0xFF),
+
+	/* Temperature and Voltage Monitoring */
+	SOC_SINGLE_RO("PVDD Sense", TAS675X_PVDD_SENSE_REG, 0, 0xFF),
+	SOC_SINGLE_RO("Global Temperature", TAS675X_TEMP_GLOBAL_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 6, 3),
+	SOC_SINGLE_RO("CH2 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 4, 3),
+	SOC_SINGLE_RO("CH3 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 2, 3),
+	SOC_SINGLE_RO("CH4 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 0, 3),
+
+	/* Speaker Protection & Detection */
+	SOC_SINGLE("Tweeter Detection Switch", TAS675X_TWEETER_DETECT_CTRL_REG, 0, 1, 1),
+	SOC_SINGLE("Tweeter Detect Threshold", TAS675X_TWEETER_DETECT_THRESH_REG, 0, 0xFF, 0),
+	SOC_SINGLE_RO("CH1 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 3, 1),
+	SOC_SINGLE_RO("CH2 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 2, 1),
+	SOC_SINGLE_RO("CH3 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 1, 1),
+	SOC_SINGLE_RO("CH4 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 0, 1),
+
+	/*
+	 * Unavailable in LLP, available in Normal & FFLP
+	 */
+	SOC_SINGLE("Thermal Foldback Switch", TAS675X_DSP_CTRL_REG, 0, 1, 0),
+	SOC_SINGLE("PVDD Foldback Switch", TAS675X_DSP_CTRL_REG, 4, 1, 0),
+	SOC_SINGLE("DC Blocker Bypass Switch", TAS675X_DC_BLOCK_BYP_REG, 0, 1, 0),
+	SOC_SINGLE("Clip Detect Switch", TAS675X_CLIP_DETECT_CTRL_REG, 6, 1, 0),
+	SOC_SINGLE("Audio SDOUT Switch", TAS675X_DSP_CTRL_REG, 5, 1, 0),
+
+	/*
+	 * Unavailable in both FFLP and LLP, Normal mode only
+	 */
+
+	/* Real-Time Load Diagnostics */
+	SOC_SINGLE("CH1 RTLDG Switch", TAS675X_RTLDG_EN_REG, 3, 1, 0),
+	SOC_SINGLE("CH2 RTLDG Switch", TAS675X_RTLDG_EN_REG, 2, 1, 0),
+	SOC_SINGLE("CH3 RTLDG Switch", TAS675X_RTLDG_EN_REG, 1, 1, 0),
+	SOC_SINGLE("CH4 RTLDG Switch", TAS675X_RTLDG_EN_REG, 0, 1, 0),
+	SOC_SINGLE("RTLDG Clip Mask Switch", TAS675X_RTLDG_EN_REG, 4, 1, 0),
+	SOC_SINGLE("ISENSE Calibration Switch", TAS675X_ISENSE_CAL_REG, 3, 1, 0),
+	SOC_DSP_THRESH_EXT("RTLDG Open Load Threshold",
+			   tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_OL_THRESH]),
+	SOC_DSP_THRESH_EXT("RTLDG Short Load Threshold",
+			   tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_SL_THRESH]),
+	SOC_RTLDG_IMP_RO("CH1 RTLDG Impedance", TAS675X_CH1_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH2 RTLDG Impedance", TAS675X_CH2_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH3 RTLDG Impedance", TAS675X_CH3_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH4 RTLDG Impedance", TAS675X_CH4_RTLDG_IMP_MSB_REG),
+	SOC_SINGLE_RO("RTLDG Fault Latched", TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG, 0, 0xFF),
+};
+
+static const struct snd_kcontrol_new tas675x_audio_path_switch =
+	SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
+
+static const struct snd_kcontrol_new tas675x_anc_path_switch =
+	SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
+
+static int tas675x_dapm_sleep_event(struct snd_soc_dapm_widget *widget,
+				    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		ret = pm_runtime_resume_and_get(component->dev);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		pm_runtime_mark_last_busy(component->dev);
+		pm_runtime_put_autosuspend(component->dev);
+		break;
+	}
+	return ret < 0 ? ret : 0;
+}
+
+static const struct snd_soc_dapm_widget tas675x_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("Analog Core", SND_SOC_NOPM, 0, 0,
+			    tas675x_dapm_sleep_event,
+			    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_SUPPLY("SDOUT Vpredict", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("SDOUT Isense", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("Audio DAC", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ANC DAC", "ANC Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("Feedback ADC", "Feedback Capture", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_SWITCH("Audio Path", SND_SOC_NOPM, 0, 0,
+			     &tas675x_audio_path_switch),
+	SND_SOC_DAPM_SWITCH("ANC Path", SND_SOC_NOPM, 0, 0,
+			     &tas675x_anc_path_switch),
+
+	/*
+	 * Even though all channels are coupled in terms of power control,
+	 * use logical outputs for each channel to allow independent routing
+	 * and DAPM controls if needed.
+	 */
+	SND_SOC_DAPM_OUTPUT("OUT_CH1"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH2"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH3"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH4"),
+	SND_SOC_DAPM_INPUT("SPEAKER_LOAD"),
+};
+
+static const struct snd_soc_dapm_route tas675x_audio_map[] = {
+	{ "Audio DAC", NULL, "Analog Core" },
+	{ "Audio Path", "Switch", "Audio DAC" },
+	{ "OUT_CH1", NULL, "Audio Path" },
+	{ "OUT_CH2", NULL, "Audio Path" },
+	{ "OUT_CH3", NULL, "Audio Path" },
+	{ "OUT_CH4", NULL, "Audio Path" },
+
+	{ "ANC DAC", NULL, "Analog Core" },
+	{ "ANC Path", "Switch", "ANC DAC" },
+	{ "OUT_CH1", NULL, "ANC Path" },
+	{ "OUT_CH2", NULL, "ANC Path" },
+	{ "OUT_CH3", NULL, "ANC Path" },
+	{ "OUT_CH4", NULL, "ANC Path" },
+
+	{ "Feedback ADC", NULL, "Analog Core" },
+	{ "Feedback ADC", NULL, "SDOUT Vpredict" },
+	{ "Feedback ADC", NULL, "SDOUT Isense" },
+	{ "Feedback ADC", NULL, "SPEAKER_LOAD" },
+};
+
+static void tas675x_program_slot_offsets(struct tas675x_priv *tas,
+					 int dai_id, int slot_width)
+{
+	int offset = 0;
+
+	switch (dai_id) {
+	case 0:
+	/* Standard Audio on SDIN */
+		if (tas->audio_slot >= 0)
+			offset = tas->audio_slot * slot_width;
+		else if (tas->tx_mask)
+			offset = __ffs(tas->tx_mask) * slot_width;
+		else
+			return;
+		offset += tas->bclk_offset;
+		regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
+				   TAS675X_SDIN_AUDIO_OFF_MSB_MASK,
+				   FIELD_PREP(TAS675X_SDIN_AUDIO_OFF_MSB_MASK, offset >> 8));
+		regmap_write(tas->regmap, TAS675X_SDIN_AUDIO_OFFSET_REG,
+			     offset & 0xFF);
+		break;
+	case 1:
+	/*
+	 * Low-Latency Playback on SDIN, **only** enabled in LLP mode
+	 * and to be mixed with main audio before output amplification
+	 * to achieve ANC/RNC.
+	 */
+		if (tas->llp_slot >= 0)
+			offset = tas->llp_slot * slot_width;
+		else if (tas->tx_mask)
+			offset = __ffs(tas->tx_mask) * slot_width;
+		else
+			return;
+		offset += tas->bclk_offset;
+		regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
+				   TAS675X_SDIN_LL_OFF_MSB_MASK,
+				   FIELD_PREP(TAS675X_SDIN_LL_OFF_MSB_MASK, offset >> 8));
+		regmap_write(tas->regmap, TAS675X_SDIN_LL_OFFSET_REG,
+			     offset & 0xFF);
+		break;
+	case 2:
+	/* SDOUT Data Output (Vpredict + Isense feedback) */
+		if (!tas->slot_width)
+			break;
+		if (tas->vpredict_slot >= 0) {
+			offset = tas->vpredict_slot * slot_width;
+			offset += tas->bclk_offset;
+			regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
+					   TAS675X_SDOUT_VP_OFF_MSB_MASK,
+					   FIELD_PREP(TAS675X_SDOUT_VP_OFF_MSB_MASK, offset >> 8));
+			regmap_write(tas->regmap, TAS675X_VPREDICT_OFFSET_REG,
+				     offset & 0xFF);
+		}
+		if (tas->isense_slot >= 0) {
+			offset = tas->isense_slot * slot_width;
+			offset += tas->bclk_offset;
+			regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
+					   TAS675X_SDOUT_IS_OFF_MSB_MASK,
+					   FIELD_PREP(TAS675X_SDOUT_IS_OFF_MSB_MASK, offset >> 8));
+			regmap_write(tas->regmap, TAS675X_ISENSE_OFFSET_REG,
+				     offset & 0xFF);
+		}
+		break;
+	}
+
+	if (offset > 511)
+		dev_warn(tas->dev,
+			 "DAI %d slot offset %d exceeds 511 SCLK limit\n",
+			 dai_id, offset);
+}
+
+static int tas675x_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 tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	unsigned int rate = params_rate(params);
+	u8 word_length;
+
+	/*
+	 * Single clock domain: SDIN and SDOUT share one SCLK/FSYNC pair,
+	 * so all active DAIs must use the same sample rate.
+	 */
+	if ((tas->active_playback_dais || tas->active_capture_dais) &&
+	    tas->rate && tas->rate != rate) {
+		dev_err(component->dev,
+			"Rate %u conflicts with active rate %u\n",
+			rate, tas->rate);
+		return -EINVAL;
+	}
+
+	switch (params_width(params)) {
+	case 16:
+		word_length = TAS675X_WL_16BIT;
+		break;
+	case 20:
+		word_length = TAS675X_WL_20BIT;
+		break;
+	case 24:
+		word_length = TAS675X_WL_24BIT;
+		break;
+	case 32:
+		word_length = TAS675X_WL_32BIT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/*
+		 * RTLDG is not supported above 96kHz. Auto-disable to
+		 * prevent DSP overload and restore when rate drops back.
+		 */
+		if (params_rate(params) > 96000) {
+			unsigned int val;
+
+			regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
+				    &val);
+			if (val & TAS675X_RTLDG_CH_EN_MASK) {
+				tas->saved_rtldg_en = val;
+				dev_dbg(component->dev,
+					 "Sample rate %dHz > 96kHz: Auto-disabling RTLDG\n",
+					 params_rate(params));
+				regmap_update_bits(component->regmap,
+						   TAS675X_RTLDG_EN_REG,
+						   TAS675X_RTLDG_CH_EN_MASK,
+						   0x00);
+			}
+		} else if (tas->saved_rtldg_en) {
+			unsigned int cur;
+
+			/*
+			 * Respect overrides and only restore if RTLDG is still auto-disabled
+			 */
+			regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
+				    &cur);
+			if (!(cur & TAS675X_RTLDG_CH_EN_MASK)) {
+				dev_dbg(component->dev,
+					 "Restoring RTLDG config after high-rate stream\n");
+				regmap_update_bits(component->regmap,
+						   TAS675X_RTLDG_EN_REG,
+						   TAS675X_RTLDG_CH_EN_MASK,
+						   TAS675X_RTLDG_CH_EN_MASK &
+							tas->saved_rtldg_en);
+			}
+			tas->saved_rtldg_en = 0;
+		}
+
+		/* Set SDIN word length (audio path + low-latency path) */
+		regmap_update_bits(component->regmap, TAS675X_SDIN_CTRL_REG,
+				   TAS675X_SDIN_WL_MASK,
+				   FIELD_PREP(TAS675X_SDIN_AUDIO_WL_MASK, word_length) |
+				   FIELD_PREP(TAS675X_SDIN_LL_WL_MASK, word_length));
+	} else {
+		/* Set SDOUT word length (VPREDICT + ISENSE) for capture */
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_WL_MASK,
+				   FIELD_PREP(TAS675X_SDOUT_VP_WL_MASK, word_length) |
+				   FIELD_PREP(TAS675X_SDOUT_IS_WL_MASK, word_length));
+	}
+
+	tas675x_program_slot_offsets(tas, dai->id,
+				    tas->slot_width ?: params_width(params));
+
+	tas->rate = rate;
+
+	return 0;
+}
+
+static int tas675x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	bool tdm_mode = false, i2s_mode = false;
+
+	/* Enforce Clocking Direction (Codec is strictly a consumer) */
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BC_FC:
+		break;
+	default:
+		dev_err(component->dev, "Unsupported clock provider format\n");
+		return -EINVAL;
+	}
+
+	/* SCLK polarity: NB_NF or IB_NF only (no FSYNC inversion support) */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
+				   TAS675X_SCLK_INV_MASK, 0x00);
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
+				   TAS675X_SCLK_INV_MASK, TAS675X_SCLK_INV_MASK);
+		break;
+	default:
+		dev_err(component->dev, "Unsupported clock inversion\n");
+		return -EINVAL;
+	}
+
+	/* Configure Audio Format and TDM Enable */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		i2s_mode = true;
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_I2S);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_RIGHT_J);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_LEFT_J);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		tdm_mode = true;
+		tas->bclk_offset = 1;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
+				   TAS675X_FS_PULSE_SHORT);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_TDM_SDOUT1);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		tdm_mode = true;
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
+				   TAS675X_FS_PULSE_SHORT);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_TDM_SDOUT1);
+		break;
+	default:
+		dev_err(component->dev, "Unsupported DAI format\n");
+		return -EINVAL;
+	}
+
+	/* Setup Vpredict and Isense outputs */
+	if (dai->id == 2) {
+		unsigned int sdout_en = 0;
+
+		if (tdm_mode) {
+			/* TDM: Vpredict and Isense may coexist on separate slots */
+			if (tas->vpredict_slot >= 0)
+				sdout_en |= TAS675X_SDOUT_EN_VPREDICT;
+			if (tas->isense_slot >= 0)
+				sdout_en |= TAS675X_SDOUT_EN_ISENSE;
+			regmap_update_bits(component->regmap,
+					   TAS675X_SDOUT_EN_REG,
+					   TAS675X_SDOUT_EN_VPREDICT |
+					   TAS675X_SDOUT_EN_ISENSE,
+					   sdout_en);
+			if (tas->vpredict_slot >= 0 && tas->isense_slot >= 0 &&
+			    abs(tas->vpredict_slot - tas->isense_slot) < 4)
+				dev_warn(component->dev,
+					 "ti,vpredict-slot-no and ti,isense-slot-no overlaps (each occupies 4 consecutive slots)\n");
+		} else if (i2s_mode) {
+			/* I2S: only one source at a time; Vpredict takes priority */
+			if (tas->vpredict_slot >= 0)
+				sdout_en = TAS675X_SDOUT_NON_TDM_SEL_VPREDICT |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL;
+			else if (tas->isense_slot >= 0)
+				sdout_en = TAS675X_SDOUT_NON_TDM_SEL_ISENSE |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL;
+			regmap_update_bits(component->regmap,
+					   TAS675X_SDOUT_EN_REG,
+					   TAS675X_SDOUT_NON_TDM_SEL_MASK |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL,
+					   sdout_en);
+			if (sdout_en &&
+			    tas->gpio1_func != TAS675X_GPIO_SEL_SDOUT2 &&
+			    tas->gpio2_func != TAS675X_GPIO_SEL_SDOUT2)
+				dev_warn(component->dev,
+					 "sdout enabled in I2S mode but no GPIO configured as SDOUT2; Ch3/Ch4 will be absent\n");
+		}
+	}
+
+	return 0;
+}
+
+static int tas675x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+				unsigned int rx_mask, int slots, int slot_width)
+{
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(dai->component);
+
+	if (slots == 0) {
+		tas->slot_width = 0;
+		tas->tx_mask = 0;
+		return 0;
+	}
+
+	/* No rx_mask as hardware does not support channel muxing for capture */
+	tas->slot_width = slot_width;
+	tas->tx_mask = tx_mask;
+	return 0;
+}
+
+static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	unsigned int discard;
+	int ret;
+
+	if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+		if (mute)
+			clear_bit(dai->id, &tas->active_capture_dais);
+		else
+			set_bit(dai->id, &tas->active_capture_dais);
+		return 0;
+	}
+
+	/*
+	 * Track which playback DAIs are active.
+	 * The TAS675x has two playback DAIs (main audio and LLP).
+	 * Only transition to Hi-Z when ALL are muted.
+	 */
+	if (mute)
+		clear_bit(dai->id, &tas->active_playback_dais);
+	else
+		set_bit(dai->id, &tas->active_playback_dais);
+
+	/* Last playback stream */
+	if (mute && !tas->active_playback_dais) {
+		ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+		regmap_read(tas->regmap, TAS675X_CLK_FAULT_LATCHED_REG, &discard);
+		return ret;
+	}
+
+	return tas675x_set_state_all(tas,
+				     tas->active_playback_dais ?
+					TAS675X_STATE_PLAY_BOTH :
+					TAS675X_STATE_SLEEP_BOTH);
+}
+
+static const struct snd_soc_dai_ops tas675x_dai_ops = {
+	.hw_params	= tas675x_hw_params,
+	.set_fmt	= tas675x_set_dai_fmt,
+	.set_tdm_slot	= tas675x_set_tdm_slot,
+	.mute_stream	= tas675x_mute_stream,
+};
+
+static struct snd_soc_dai_driver tas675x_dais[] = {
+	{
+		.name = "tas675x-audio",
+		.id = 0,
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 2,
+			.channels_max = 4,
+			.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	},
+	/* Only available when Low Latency Path (LLP) is enabled */
+	{
+		.name = "tas675x-anc",
+		.id = 1,
+		.playback = {
+			.stream_name = "ANC Playback",
+			.channels_min = 2,
+			.channels_max = 4,
+			.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	},
+	{
+		.name = "tas675x-feedback",
+		.id = 2,
+		.capture = {
+			.stream_name = "Feedback Capture",
+			.channels_min = 2,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	}
+};
+
+/**
+ * Hardware power sequencing
+ * Handles regulator enable and GPIO deassertion.
+ * The device is not be I2C-accessible until boot wait completes.
+ */
+static int tas675x_hw_enable(struct tas675x_priv *tas)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(tas->supplies), tas->supplies);
+	if (ret) {
+		dev_err(tas->dev, "Failed to enable regulators: %d\n", ret);
+		return ret;
+	}
+
+	if (!IS_ERR(tas->vbat)) {
+		ret = regulator_enable(tas->vbat);
+		if (ret) {
+			dev_err(tas->dev, "Failed to enable vbat: %d\n", ret);
+			regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
+			return ret;
+		}
+	}
+
+	if (tas->pd_gpio && tas->stby_gpio) {
+		/*
+		 * Independent Pin Control
+		 * Deassert PD first to boot digital, then STBY for analog.
+		 */
+		/* Min 4ms digital boot wait */
+		gpiod_set_value_cansleep(tas->pd_gpio, 0);
+		usleep_range(4000, 5000);
+
+		/* ~2ms analog stabilization */
+		gpiod_set_value_cansleep(tas->stby_gpio, 0);
+		usleep_range(2000, 3000);
+	} else if (tas->pd_gpio) {
+		/*
+		 * Simultaneous Pin Release
+		 * STBY tied to PD or hardwired HIGH.
+		 */
+		/* 6ms wait for simultaneous release transition */
+		gpiod_set_value_cansleep(tas->pd_gpio, 0);
+		usleep_range(6000, 7000);
+	} else {
+		/*
+		 * PD hardwired, device in DEEP_SLEEP.
+		 * Digital core already booted, I2C active. Deassert STBY
+		 * to bring up the analog output stage.
+		 */
+		/* ~2ms analog stabilization */
+		gpiod_set_value_cansleep(tas->stby_gpio, 0);
+		usleep_range(2000, 3000);
+	}
+
+	return 0;
+}
+
+static void tas675x_hw_disable(struct tas675x_priv *tas)
+{
+	if (tas->stby_gpio)
+		gpiod_set_value_cansleep(tas->stby_gpio, 1);
+
+	if (tas->pd_gpio)
+		gpiod_set_value_cansleep(tas->pd_gpio, 1);
+
+	/*
+	 * Hold PD/STBY asserted for at least 10ms
+	 * before removing PVDD, VBAT or DVDD.
+	 */
+	usleep_range(10000, 11000);
+
+	if (!IS_ERR(tas->vbat))
+		regulator_disable(tas->vbat);
+
+	regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
+}
+
+/**
+ * Device start-up defaults
+ * Must be called after tas675x_hw_enable() and after regcache is enabled.
+ */
+static int tas675x_init_device(struct tas675x_priv *tas)
+{
+	struct regmap *regmap = tas->regmap;
+	unsigned int val;
+	int ret, i;
+
+	/* Clear POR fault flag to prevent IRQ storm */
+	regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
+
+	/* Bypass DC Load Diagnostics for fast boot */
+	if (tas->fast_boot)
+		regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
+
+	tas675x_select_book(regmap, TAS675X_BOOK_DEFAULT);
+
+	/* Enter setup mode */
+	ret = regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_ENTER_VAL1);
+	if (ret)
+		goto err;
+	ret = regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_ENTER_VAL2);
+	if (ret)
+		goto err;
+
+	/* Set all channels to Sleep (required before Page 1 config) */
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	/* Set DAC clock per TRM startup script */
+	regmap_write(regmap, TAS675X_DAC_CLK_REG, 0x00);
+
+	/*
+	 * Switch to Page 1 for safety-critical OC/CBC configuration,
+	 * while bypassing regcache. (Page 1 not accessible post setup)
+	 */
+	regcache_cache_bypass(regmap, true);
+	ret = regmap_multi_reg_write(regmap, tas675x_page1_init,
+				    ARRAY_SIZE(tas675x_page1_init));
+	regcache_cache_bypass(regmap, false);
+	if (ret)
+		goto err_setup;
+
+	/* Resync regmap's cached page selector */
+	regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
+
+	/* Exit setup mode */
+	regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
+	regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
+
+	/* Write DSP parameters if cached */
+	for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
+		if (tas->dsp_params[i].val)
+			tas675x_dsp_mem_write(tas,
+					      tas->dsp_params[i].page,
+					      tas->dsp_params[i].reg,
+					      tas->dsp_params[i].val);
+	}
+
+	/*
+	 * Route fault events to FAULT pin for IRQ handler
+	 *
+	 * ROUTING_1: latched CP fault, CP UVLO, OUTM soft short
+	 * ROUTING_2: non-latching power fault events (+ default OTSD, CBC)
+	 * ROUTING_4: OTW, clip warning, protection shutdown, OC, DC
+	 * ROUTING_5: clock, CBC warning, RTLDG, clip detect
+	 */
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_1_REG, 0x70);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_2_REG, 0xA3);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_4_REG, 0x7E);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_5_REG, 0xF3);
+
+	/* Configure GPIO pins if specified in DT */
+	if (tas->gpio1_func >= 0 || tas->gpio2_func >= 0) {
+		unsigned int gpio_ctrl = TAS675X_GPIO_CTRL_RSTVAL;
+
+		tas675x_config_gpio_pin(regmap, tas->gpio1_func,
+					TAS675X_GPIO1_OUTPUT_SEL_REG,
+					0, &gpio_ctrl);
+		tas675x_config_gpio_pin(regmap, tas->gpio2_func,
+					TAS675X_GPIO2_OUTPUT_SEL_REG,
+					1, &gpio_ctrl);
+		regmap_write(regmap, TAS675X_GPIO_CTRL_REG, gpio_ctrl);
+	}
+
+	/* Clear fast boot bits */
+	if (tas->fast_boot)
+		regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				   0);
+
+	/* Clear any stale faults from the boot sequence */
+	regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
+	regmap_read(regmap, TAS675X_CLK_FAULT_LATCHED_REG, &val);
+	regmap_write(regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	return 0;
+
+err_setup:
+	regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
+	regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
+err:
+	dev_err(tas->dev, "Init device failed: %d\n", ret);
+	return ret;
+}
+
+static void tas675x_power_off(struct tas675x_priv *tas)
+{
+	regcache_cache_only(tas->regmap, true);
+	regcache_mark_dirty(tas->regmap);
+	tas675x_hw_disable(tas);
+}
+
+static int tas675x_power_on(struct tas675x_priv *tas)
+{
+	int ret;
+
+	ret = tas675x_hw_enable(tas);
+	if (ret)
+		return ret;
+
+	regcache_cache_only(tas->regmap, false);
+	regcache_mark_dirty(tas->regmap);
+
+	ret = tas675x_init_device(tas);
+	if (ret)
+		goto err_disable;
+
+	ret = regcache_sync(tas->regmap);
+	if (ret) {
+		dev_err(tas->dev, "Failed to sync regcache: %d\n", ret);
+		goto err_disable;
+	}
+
+	/* Reset fault tracking */
+	memset(tas->last_status, 0, sizeof(tas->last_status));
+
+	return 0;
+
+err_disable:
+	tas675x_power_off(tas);
+	return ret;
+}
+
+static int tas675x_runtime_suspend(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&tas->fault_check_work);
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	return 0;
+}
+
+static int tas675x_runtime_resume(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	if (!to_i2c_client(dev)->irq)
+		schedule_delayed_work(&tas->fault_check_work,
+				      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+
+	return 0;
+}
+
+static int tas675x_system_suspend(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tas675x_runtime_suspend(dev);
+	if (ret)
+		return ret;
+
+	tas675x_power_off(tas);
+	return 0;
+}
+
+static int tas675x_system_resume(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tas675x_power_on(tas);
+	if (ret)
+		return ret;
+
+	return tas675x_runtime_resume(dev);
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_tas675x = {
+	.controls		= tas675x_snd_controls,
+	.num_controls		= ARRAY_SIZE(tas675x_snd_controls),
+	.dapm_widgets		= tas675x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(tas675x_dapm_widgets),
+	.dapm_routes		= tas675x_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(tas675x_audio_map),
+	.endianness		= 1,
+};
+
+/* Fault register flags */
+#define TAS675X_FAULT_CRITICAL	BIT(0)	/* causes FAULT state, FAULT_CLEAR required */
+#define TAS675X_FAULT_TRACK	BIT(1)	/* track last value, only log on change */
+#define TAS675X_FAULT_ACTIVE	BIT(2)	/* skip when no stream is active */
+
+struct tas675x_fault_reg {
+	unsigned int reg;
+	unsigned int flags;
+	const char *name;
+};
+
+static const struct tas675x_fault_reg tas675x_fault_table[] = {
+	/* Critical */
+	{ TAS675X_OTSD_LATCHED_REG,              TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Overtemperature Shutdown" },
+	{ TAS675X_OC_DC_FAULT_LATCHED_REG,       TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Overcurrent / DC Fault" },
+	{ TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Real-Time Load Diagnostic Fault" },
+	{ TAS675X_CBC_FAULT_WARN_LATCHED_REG,    TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "CBC Fault/Warning" },
+	/* Warning */
+	{ TAS675X_POWER_FAULT_LATCHED_REG,       TAS675X_FAULT_TRACK,
+	  "Power Fault" },
+	{ TAS675X_CLK_FAULT_LATCHED_REG,         TAS675X_FAULT_TRACK | TAS675X_FAULT_ACTIVE,
+	  "Clock Fault" },
+	{ TAS675X_OTW_LATCHED_REG,               TAS675X_FAULT_TRACK,
+	  "Overtemperature Warning" },
+	{ TAS675X_CLIP_WARN_LATCHED_REG,         TAS675X_FAULT_ACTIVE,
+	  "Clip Warning" },
+};
+
+static_assert(ARRAY_SIZE(tas675x_fault_table) == TAS675X_FAULT_REGS_NUM);
+
+/**
+ * Read and log all latched fault registers
+ * Shared by both the polled fault_check_work and IRQ handler paths
+ * (which are mutually exclusive, only one is active per device).
+ *
+ * Returns true if any critical fault was detected that needs FAULT_CLEAR.
+ */
+static bool tas675x_check_faults(struct tas675x_priv *tas)
+{
+	struct device *dev = tas->dev;
+	bool is_latched = false;
+	unsigned int reg;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_fault_table); i++) {
+		const struct tas675x_fault_reg *f = &tas675x_fault_table[i];
+
+		ret = regmap_read(tas->regmap, f->reg, &reg);
+		if (ret) {
+			if (f->flags & TAS675X_FAULT_CRITICAL) {
+				dev_err(dev, "failed to read %s: %d\n", f->name, ret);
+				return is_latched;
+			}
+			continue;
+		}
+
+		if ((f->flags & TAS675X_FAULT_ACTIVE) &&
+		    !READ_ONCE(tas->active_playback_dais) &&
+		    !READ_ONCE(tas->active_capture_dais))
+			continue;
+
+		if ((f->flags & TAS675X_FAULT_CRITICAL) && reg)
+			is_latched = true;
+
+		/* Log on change or on every non-zero read */
+		if (reg && (!(f->flags & TAS675X_FAULT_TRACK) ||
+			    reg != tas->last_status[i])) {
+			if (f->flags & TAS675X_FAULT_CRITICAL)
+				dev_crit(dev, "%s Latched: 0x%02x\n", f->name, reg);
+			else
+				dev_warn(dev, "%s Latched: 0x%02x\n", f->name, reg);
+		}
+
+		if (f->flags & TAS675X_FAULT_TRACK)
+			tas->last_status[i] = reg;
+	}
+
+	return is_latched;
+}
+
+static void tas675x_fault_check_work(struct work_struct *work)
+{
+	struct tas675x_priv *tas = container_of(work, struct tas675x_priv,
+						fault_check_work.work);
+
+	if (tas675x_check_faults(tas))
+		regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	schedule_delayed_work(&tas->fault_check_work,
+			      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+}
+
+static irqreturn_t tas675x_irq_handler(int irq, void *data)
+{
+	struct tas675x_priv *tas = data;
+
+	tas675x_check_faults(tas);
+
+	/* Clear the FAULT pin latch as something latched */
+	regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	return IRQ_HANDLED;
+}
+
+static const struct reg_default tas675x_reg_defaults[] = {
+	{ TAS675X_PAGE_CTRL_REG,           0x00 },
+	{ TAS675X_OUTPUT_CTRL_REG,         0x00 },
+	{ TAS675X_STATE_CTRL_CH1_CH2_REG,  TAS675X_STATE_SLEEP_BOTH },
+	{ TAS675X_STATE_CTRL_CH3_CH4_REG,  TAS675X_STATE_SLEEP_BOTH },
+	{ TAS675X_ISENSE_CTRL_REG,         0x0F },
+	{ TAS675X_DC_DETECT_CTRL_REG,      0x00 },
+	{ TAS675X_SCLK_INV_CTRL_REG,       0x00 },
+	{ TAS675X_AUDIO_IF_CTRL_REG,       0x00 },
+	{ TAS675X_SDIN_CTRL_REG,           0x0A },
+	{ TAS675X_SDOUT_CTRL_REG,          0x1A },
+	{ TAS675X_SDIN_OFFSET_MSB_REG,     0x00 },
+	{ TAS675X_SDIN_AUDIO_OFFSET_REG,   0x00 },
+	{ TAS675X_SDIN_LL_OFFSET_REG,      0x60 },
+	{ TAS675X_SDIN_CH_SWAP_REG,        0x00 },
+	{ TAS675X_SDOUT_OFFSET_MSB_REG,    0xCF },
+	{ TAS675X_VPREDICT_OFFSET_REG,     0xFF },
+	{ TAS675X_ISENSE_OFFSET_REG,       0x00 },
+	{ TAS675X_SDOUT_EN_REG,            0x00 },
+	{ TAS675X_LL_EN_REG,               0x00 },
+	{ TAS675X_RTLDG_EN_REG,            0x10 },
+	{ TAS675X_DC_BLOCK_BYP_REG,        0x00 },
+	{ TAS675X_DSP_CTRL_REG,            0x00 },
+	{ TAS675X_PAGE_AUTO_INC_REG,       0x00 },
+	{ TAS675X_DIG_VOL_CH1_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH2_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH3_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH4_REG,         0x30 },
+	{ TAS675X_DIG_VOL_RAMP_CTRL_REG,   0x77 },
+	{ TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0x00 },
+	{ TAS675X_AUTO_MUTE_EN_REG,        0x00 },
+	{ TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0x00 },
+	{ TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_CH1_CH2_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_CH3_CH4_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 0x00 },
+	{ TAS675X_PULSE_INJECTION_EN_REG,  0x03 },
+	{ TAS675X_CBC_CTRL_REG,            0x07 },
+	{ TAS675X_CURRENT_LIMIT_CTRL_REG,  0x00 },
+	{ TAS675X_ISENSE_CAL_REG,          0x00 },
+	{ TAS675X_PWM_PHASE_CTRL_REG,      0x00 },
+	{ TAS675X_SS_CTRL_REG,             0x00 },
+	{ TAS675X_SS_RANGE_CTRL_REG,       0x00 },
+	{ TAS675X_SS_DWELL_CTRL_REG,       0x00 },
+	{ TAS675X_RAMP_PHASE_CTRL_GPO_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH1_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 },
+	{ TAS675X_DC_LDG_CTRL_REG,         0x00 },
+	{ TAS675X_DC_LDG_LO_CTRL_REG,      0x00 },
+	{ TAS675X_DC_LDG_TIME_CTRL_REG,    0x00 },
+	{ TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 },
+	{ TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 },
+	{ TAS675X_AC_LDG_CTRL_REG,         0x10 },
+	{ TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 },
+	{ TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 },
+	{ TAS675X_AC_LDG_FREQ_CTRL_REG,    0xC8 },
+	{ TAS675X_REPORT_ROUTING_1_REG,    0x00 },
+	{ TAS675X_OTSD_RECOVERY_EN_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_2_REG,    0xA2 },
+	{ TAS675X_REPORT_ROUTING_3_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_4_REG,    0x06 },
+	{ TAS675X_CLIP_DETECT_CTRL_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_5_REG,    0x00 },
+	{ TAS675X_GPIO1_OUTPUT_SEL_REG,    0x00 },
+	{ TAS675X_GPIO2_OUTPUT_SEL_REG,    0x00 },
+	{ TAS675X_GPIO_CTRL_REG,           TAS675X_GPIO_CTRL_RSTVAL },
+	{ TAS675X_OTW_CTRL_CH1_CH2_REG,    0x11 },
+	{ TAS675X_OTW_CTRL_CH3_CH4_REG,    0x11 },
+};
+
+static bool tas675x_is_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TAS675X_RESET_REG:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool tas675x_is_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TAS675X_RESET_REG:
+	case TAS675X_BOOK_CTRL_REG:
+	case TAS675X_AUTO_MUTE_STATUS_REG:
+	case TAS675X_STATE_REPORT_CH1_CH2_REG:
+	case TAS675X_STATE_REPORT_CH3_CH4_REG:
+	case TAS675X_PVDD_SENSE_REG:
+	case TAS675X_TEMP_GLOBAL_REG:
+	case TAS675X_TEMP_CH1_CH2_REG:
+	case TAS675X_TEMP_CH3_CH4_REG:
+	case TAS675X_FS_MON_REG:
+	case TAS675X_SCLK_MON_REG:
+	case TAS675X_POWER_FAULT_STATUS_1_REG:
+	case TAS675X_POWER_FAULT_STATUS_2_REG:
+	case TAS675X_OT_FAULT_REG:
+	case TAS675X_OTW_STATUS_REG:
+	case TAS675X_CLIP_WARN_STATUS_REG:
+	case TAS675X_CBC_WARNING_STATUS_REG:
+	case TAS675X_POWER_FAULT_LATCHED_REG:
+	case TAS675X_OTSD_LATCHED_REG:
+	case TAS675X_OTW_LATCHED_REG:
+	case TAS675X_CLIP_WARN_LATCHED_REG:
+	case TAS675X_CLK_FAULT_LATCHED_REG:
+	case TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG:
+	case TAS675X_CBC_FAULT_WARN_LATCHED_REG:
+	case TAS675X_OC_DC_FAULT_LATCHED_REG:
+	case TAS675X_WARN_OT_MAX_FLAG_REG:
+	case TAS675X_DC_LDG_REPORT_CH1_CH2_REG ... TAS675X_TWEETER_REPORT_REG:
+	case TAS675X_CH1_RTLDG_IMP_MSB_REG ... TAS675X_CH4_DC_LDG_DCR_LSB_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_range_cfg tas675x_ranges[] = {
+	{
+		.name = "Pages",
+		.range_min = 0,
+		.range_max = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
+		.selector_reg = TAS675X_PAGE_CTRL_REG,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = TAS675X_PAGE_SIZE,
+	},
+};
+
+static void tas675x_regmap_lock(void *lock_arg)
+{
+	struct tas675x_priv *tas = lock_arg;
+
+	mutex_lock(&tas->io_lock);
+}
+
+static void tas675x_regmap_unlock(void *lock_arg)
+{
+	struct tas675x_priv *tas = lock_arg;
+
+	mutex_unlock(&tas->io_lock);
+}
+
+static const struct regmap_config tas675x_regmap_config = {
+	.reg_bits         = 8,
+	.val_bits         = 8,
+	.max_register     = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
+	.ranges           = tas675x_ranges,
+	.num_ranges       = ARRAY_SIZE(tas675x_ranges),
+	.cache_type       = REGCACHE_MAPLE,
+	.reg_defaults     = tas675x_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(tas675x_reg_defaults),
+	.readable_reg     = tas675x_is_readable_register,
+	.volatile_reg     = tas675x_is_volatile_register,
+};
+
+static int tas675x_i2c_probe(struct i2c_client *client)
+{
+	struct regmap_config cfg = tas675x_regmap_config;
+	struct tas675x_priv *tas;
+	u32 val;
+	int i, ret;
+
+	tas = devm_kzalloc(&client->dev, sizeof(*tas), GFP_KERNEL);
+	if (!tas)
+		return -ENOMEM;
+
+	tas->dev = &client->dev;
+	i2c_set_clientdata(client, tas);
+
+	mutex_init(&tas->io_lock);
+	cfg.lock     = tas675x_regmap_lock;
+	cfg.unlock   = tas675x_regmap_unlock;
+	cfg.lock_arg = tas;
+	tas->regmap = devm_regmap_init_i2c(client, &cfg);
+	if (IS_ERR(tas->regmap))
+		return PTR_ERR(tas->regmap);
+
+	memcpy(tas->dsp_params, tas675x_dsp_defaults, sizeof(tas->dsp_params));
+
+	/* Keep regmap cache-only until hardware is powered on */
+	regcache_cache_only(tas->regmap, true);
+
+	tas->dev_type = (enum tas675x_type)(unsigned long)device_get_match_data(tas->dev);
+	tas->fast_boot = device_property_read_bool(tas->dev, "ti,fast-boot");
+
+	tas->audio_slot = -1;
+	tas->llp_slot = -1;
+	tas->vpredict_slot = -1;
+	tas->isense_slot = -1;
+	if (!device_property_read_u32(tas->dev, "ti,audio-slot-no", &val))
+		tas->audio_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,llp-slot-no", &val))
+		tas->llp_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,vpredict-slot-no", &val))
+		tas->vpredict_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,isense-slot-no", &val))
+		tas->isense_slot = val;
+
+	tas->gpio1_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio1-function");
+	tas->gpio2_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio2-function");
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_supply_names); i++)
+		tas->supplies[i].supply = tas675x_supply_names[i];
+
+	ret = devm_regulator_bulk_get(tas->dev, ARRAY_SIZE(tas->supplies), tas->supplies);
+	if (ret)
+		return dev_err_probe(tas->dev, ret, "Failed to request supplies\n");
+
+	tas->vbat = devm_regulator_get_optional(tas->dev, "vbat");
+	if (IS_ERR(tas->vbat) && PTR_ERR(tas->vbat) != -ENODEV)
+		return dev_err_probe(tas->dev, PTR_ERR(tas->vbat),
+				     "Failed to get vbat supply\n");
+
+	tas->pd_gpio = devm_gpiod_get_optional(tas->dev, "pd", GPIOD_OUT_HIGH);
+	if (IS_ERR(tas->pd_gpio))
+		return dev_err_probe(tas->dev, PTR_ERR(tas->pd_gpio), "Failed pd-gpios\n");
+
+	tas->stby_gpio = devm_gpiod_get_optional(tas->dev, "stby", GPIOD_OUT_HIGH);
+	if (IS_ERR(tas->stby_gpio))
+		return dev_err_probe(tas->dev, PTR_ERR(tas->stby_gpio), "Failed stby-gpios\n");
+
+	if (!tas->pd_gpio && !tas->stby_gpio)
+		return dev_err_probe(tas->dev, -EINVAL,
+				     "At least one of pd-gpios or stby-gpios is required\n");
+
+	if (client->irq) {
+		ret = devm_request_threaded_irq(tas->dev, client->irq, NULL,
+						tas675x_irq_handler,
+						IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+						"tas675x-fault", tas);
+		if (ret)
+			return dev_err_probe(tas->dev, ret, "Failed to request IRQ\n");
+	}
+
+	INIT_DELAYED_WORK(&tas->fault_check_work, tas675x_fault_check_work);
+
+	ret = tas675x_power_on(tas);
+	if (ret)
+		return ret;
+
+	/* Enable runtime PM with 2s autosuspend */
+	pm_runtime_set_autosuspend_delay(tas->dev, 2000);
+	pm_runtime_use_autosuspend(tas->dev);
+	pm_runtime_set_active(tas->dev);
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_enable(tas->dev);
+
+	ret = devm_snd_soc_register_component(tas->dev, &soc_codec_dev_tas675x,
+					       tas675x_dais, ARRAY_SIZE(tas675x_dais));
+	if (ret)
+		goto err_pm_disable;
+
+	return 0;
+
+err_pm_disable:
+	pm_runtime_force_suspend(tas->dev);
+	pm_runtime_disable(tas->dev);
+	tas675x_power_off(tas);
+	return ret;
+}
+
+static void tas675x_i2c_remove(struct i2c_client *client)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(&client->dev);
+
+	pm_runtime_force_suspend(&client->dev);
+	pm_runtime_disable(&client->dev);
+	tas675x_power_off(tas);
+}
+
+static const struct dev_pm_ops tas675x_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(tas675x_system_suspend, tas675x_system_resume)
+	RUNTIME_PM_OPS(tas675x_runtime_suspend, tas675x_runtime_resume, NULL)
+};
+
+static const struct of_device_id tas675x_of_match[] = {
+	{ .compatible = "ti,tas6754",  .data = (void *)TAS6754 },
+	{ .compatible = "ti,tas67524", .data = (void *)TAS6754 },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tas675x_of_match);
+
+static const struct i2c_device_id tas675x_i2c_id[] = {
+	{ "tas6754",  TAS6754 },
+	{ "tas67524", TAS6754 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tas675x_i2c_id);
+
+static struct i2c_driver tas675x_i2c_driver = {
+	.driver = {
+		.name = "tas675x",
+		.of_match_table = tas675x_of_match,
+		.pm = pm_ptr(&tas675x_pm_ops),
+	},
+	.probe = tas675x_i2c_probe,
+	.remove = tas675x_i2c_remove,
+	.id_table = tas675x_i2c_id,
+};
+
+module_i2c_driver(tas675x_i2c_driver);
+
+MODULE_AUTHOR("Sen Wang <sen@ti.com>");
+MODULE_DESCRIPTION("ASoC TAS675x Audio Amplifier Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas675x.h b/sound/soc/codecs/tas675x.h
new file mode 100644
index 000000000000..db29bb377336
--- /dev/null
+++ b/sound/soc/codecs/tas675x.h
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC Texas Instruments TAS675x Quad-Channel Audio Amplifier
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ *	Author: Sen Wang <sen@ti.com>
+ */
+
+#ifndef __TAS675X_H__
+#define __TAS675X_H__
+
+/*
+ * Book 0, Page 0 — Register Addresses
+ */
+
+#define TAS675X_PAGE_SIZE                    256
+#define TAS675X_PAGE_REG(page, reg)  ((page) * TAS675X_PAGE_SIZE + (reg))
+
+/* Page Control & Basic Config */
+#define TAS675X_PAGE_CTRL_REG                0x00
+#define TAS675X_RESET_REG                    0x01
+#define TAS675X_OUTPUT_CTRL_REG              0x02
+#define TAS675X_STATE_CTRL_CH1_CH2_REG       0x03
+#define TAS675X_STATE_CTRL_CH3_CH4_REG       0x04
+#define TAS675X_ISENSE_CTRL_REG              0x05
+#define TAS675X_DC_DETECT_CTRL_REG           0x06
+
+/* Serial Audio Port */
+#define TAS675X_SCLK_INV_CTRL_REG            0x20
+#define TAS675X_AUDIO_IF_CTRL_REG            0x21
+#define TAS675X_SDIN_CTRL_REG                0x23
+#define TAS675X_SDOUT_CTRL_REG               0x25
+#define TAS675X_SDIN_OFFSET_MSB_REG          0x27
+#define TAS675X_SDIN_AUDIO_OFFSET_REG        0x28
+#define TAS675X_SDIN_LL_OFFSET_REG           0x29
+#define TAS675X_SDIN_CH_SWAP_REG             0x2A
+#define TAS675X_SDOUT_OFFSET_MSB_REG         0x2C
+#define TAS675X_VPREDICT_OFFSET_REG          0x2D
+#define TAS675X_ISENSE_OFFSET_REG            0x2E
+#define TAS675X_SDOUT_EN_REG                 0x31
+#define TAS675X_LL_EN_REG                    0x32
+
+/* DSP & Core Audio Control */
+#define TAS675X_RTLDG_EN_REG                 0x37
+#define TAS675X_DC_BLOCK_BYP_REG             0x39
+#define TAS675X_DSP_CTRL_REG                 0x3A
+#define TAS675X_PAGE_AUTO_INC_REG            0x3B
+
+/* Volume & Mute */
+#define TAS675X_DIG_VOL_CH1_REG              0x40
+#define TAS675X_DIG_VOL_CH2_REG              0x41
+#define TAS675X_DIG_VOL_CH3_REG              0x42
+#define TAS675X_DIG_VOL_CH4_REG              0x43
+#define TAS675X_DIG_VOL_RAMP_CTRL_REG        0x44
+#define TAS675X_DIG_VOL_COMBINE_CTRL_REG     0x46
+#define TAS675X_AUTO_MUTE_EN_REG             0x47
+#define TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG 0x48
+#define TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG 0x49
+
+/* Analog Gain & Power Stage */
+#define TAS675X_ANALOG_GAIN_CH1_CH2_REG      0x4A
+#define TAS675X_ANALOG_GAIN_CH3_CH4_REG      0x4B
+#define TAS675X_ANALOG_GAIN_RAMP_CTRL_REG    0x4E
+#define TAS675X_PULSE_INJECTION_EN_REG       0x52
+#define TAS675X_CBC_CTRL_REG                 0x54
+#define TAS675X_CURRENT_LIMIT_CTRL_REG       0x55
+#define TAS675X_DAC_CLK_REG                  0x5A
+#define TAS675X_ISENSE_CAL_REG               0x5B
+
+/* Spread Spectrum & PWM Phase */
+#define TAS675X_PWM_PHASE_CTRL_REG           0x60
+#define TAS675X_SS_CTRL_REG                  0x61
+#define TAS675X_SS_RANGE_CTRL_REG            0x62
+#define TAS675X_SS_DWELL_CTRL_REG            0x66
+#define TAS675X_RAMP_PHASE_CTRL_GPO_REG      0x68
+#define TAS675X_PWM_PHASE_M_CTRL_CH1_REG     0x69
+#define TAS675X_PWM_PHASE_M_CTRL_CH2_REG     0x6A
+#define TAS675X_PWM_PHASE_M_CTRL_CH3_REG     0x6B
+#define TAS675X_PWM_PHASE_M_CTRL_CH4_REG     0x6C
+
+/* Status & Reporting */
+#define TAS675X_AUTO_MUTE_STATUS_REG         0x71
+#define TAS675X_STATE_REPORT_CH1_CH2_REG     0x72
+#define TAS675X_STATE_REPORT_CH3_CH4_REG     0x73
+#define TAS675X_PVDD_SENSE_REG               0x74
+#define TAS675X_TEMP_GLOBAL_REG              0x75
+#define TAS675X_FS_MON_REG                   0x76
+#define TAS675X_SCLK_MON_REG                 0x77
+#define TAS675X_REPORT_ROUTING_1_REG         0x7C
+
+/* Memory Paging & Book Control */
+#define TAS675X_SETUP_REG1                   0x7D
+#define TAS675X_SETUP_REG2                   0x7E
+#define TAS675X_BOOK_CTRL_REG                0x7F
+
+/* Fault Status */
+#define TAS675X_POWER_FAULT_STATUS_1_REG     0x7D
+#define TAS675X_POWER_FAULT_STATUS_2_REG     0x80
+#define TAS675X_OT_FAULT_REG                 0x81
+#define TAS675X_OTW_STATUS_REG               0x82
+#define TAS675X_CLIP_WARN_STATUS_REG         0x83
+#define TAS675X_CBC_WARNING_STATUS_REG       0x85
+
+/* Latched Fault Registers */
+#define TAS675X_POWER_FAULT_LATCHED_REG      0x86
+#define TAS675X_OTSD_LATCHED_REG             0x87
+#define TAS675X_OTW_LATCHED_REG              0x88
+#define TAS675X_CLIP_WARN_LATCHED_REG        0x89
+#define TAS675X_CLK_FAULT_LATCHED_REG        0x8A
+#define TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG 0x8B
+#define TAS675X_CBC_FAULT_WARN_LATCHED_REG   0x8D
+#define TAS675X_OC_DC_FAULT_LATCHED_REG      0x8E
+#define TAS675X_OTSD_RECOVERY_EN_REG         0x8F
+
+/* Protection & Routing Controls */
+#define TAS675X_REPORT_ROUTING_2_REG         0x90
+#define TAS675X_REPORT_ROUTING_3_REG         0x91
+#define TAS675X_REPORT_ROUTING_4_REG         0x92
+#define TAS675X_CLIP_DETECT_CTRL_REG         0x93
+#define TAS675X_REPORT_ROUTING_5_REG         0x94
+
+/* GPIO Pin Configuration */
+#define TAS675X_GPIO1_OUTPUT_SEL_REG         0x95
+#define TAS675X_GPIO2_OUTPUT_SEL_REG         0x96
+#define TAS675X_GPIO_INPUT_SLEEP_HIZ_REG     0x9B
+#define TAS675X_GPIO_INPUT_PLAY_SLEEP_REG    0x9C
+#define TAS675X_GPIO_INPUT_MUTE_REG          0x9D
+#define TAS675X_GPIO_INPUT_SYNC_REG          0x9E
+#define TAS675X_GPIO_INPUT_SDIN2_REG         0x9F
+#define TAS675X_GPIO_CTRL_REG                0xA0
+#define TAS675X_GPIO_INVERT_REG              0xA1
+
+/* Load Diagnostics Config */
+#define TAS675X_DC_LDG_CTRL_REG              0xB0
+#define TAS675X_DC_LDG_LO_CTRL_REG           0xB1
+#define TAS675X_DC_LDG_TIME_CTRL_REG         0xB2
+#define TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG   0xB3
+#define TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG   0xB4
+#define TAS675X_AC_LDG_CTRL_REG              0xB5
+#define TAS675X_TWEETER_DETECT_CTRL_REG      0xB6
+#define TAS675X_TWEETER_DETECT_THRESH_REG    0xB7
+#define TAS675X_AC_LDG_FREQ_CTRL_REG         0xB8
+#define TAS675X_TEMP_CH1_CH2_REG             0xBB
+#define TAS675X_TEMP_CH3_CH4_REG             0xBC
+#define TAS675X_WARN_OT_MAX_FLAG_REG         0xBD
+
+/* DC Load Diagnostic Reports */
+#define TAS675X_DC_LDG_REPORT_CH1_CH2_REG    0xC0
+#define TAS675X_DC_LDG_REPORT_CH3_CH4_REG    0xC1
+#define TAS675X_DC_LDG_RESULT_REG            0xC2
+#define TAS675X_AC_LDG_REPORT_CH1_R_REG      0xC3
+#define TAS675X_AC_LDG_REPORT_CH1_I_REG      0xC4
+#define TAS675X_AC_LDG_REPORT_CH2_R_REG      0xC5
+#define TAS675X_AC_LDG_REPORT_CH2_I_REG      0xC6
+#define TAS675X_AC_LDG_REPORT_CH3_R_REG      0xC7
+#define TAS675X_AC_LDG_REPORT_CH3_I_REG      0xC8
+#define TAS675X_AC_LDG_REPORT_CH4_R_REG      0xC9
+#define TAS675X_AC_LDG_REPORT_CH4_I_REG      0xCA
+#define TAS675X_TWEETER_REPORT_REG           0xCB
+
+/* RTLDG Impedance */
+#define TAS675X_CH1_RTLDG_IMP_MSB_REG        0xD1
+#define TAS675X_CH1_RTLDG_IMP_LSB_REG        0xD2
+#define TAS675X_CH2_RTLDG_IMP_MSB_REG        0xD3
+#define TAS675X_CH2_RTLDG_IMP_LSB_REG        0xD4
+#define TAS675X_CH3_RTLDG_IMP_MSB_REG        0xD5
+#define TAS675X_CH3_RTLDG_IMP_LSB_REG        0xD6
+#define TAS675X_CH4_RTLDG_IMP_MSB_REG        0xD7
+#define TAS675X_CH4_RTLDG_IMP_LSB_REG        0xD8
+
+/* DC Load Diagnostic Resistance */
+#define TAS675X_DC_LDG_DCR_MSB_REG           0xD9
+#define TAS675X_CH1_DC_LDG_DCR_LSB_REG       0xDA
+#define TAS675X_CH2_DC_LDG_DCR_LSB_REG       0xDB
+#define TAS675X_CH3_DC_LDG_DCR_LSB_REG       0xDC
+#define TAS675X_CH4_DC_LDG_DCR_LSB_REG       0xDD
+
+/* Over-Temperature Warning */
+#define TAS675X_OTW_CTRL_CH1_CH2_REG         0xE2
+#define TAS675X_OTW_CTRL_CH3_CH4_REG         0xE3
+
+/* RESET_REG (all bits auto-clear) */
+#define TAS675X_DEVICE_RESET                 BIT(4)
+#define TAS675X_FAULT_CLEAR                  BIT(3)
+#define TAS675X_REGISTER_RESET               BIT(0)
+
+/* STATE_CTRL and STATE_REPORT — Channel state values */
+#define TAS675X_STATE_DEEPSLEEP              0x00
+#define TAS675X_STATE_LOAD_DIAG              0x01
+#define TAS675X_STATE_SLEEP                  0x02
+#define TAS675X_STATE_HIZ                    0x03
+#define TAS675X_STATE_PLAY                   0x04
+
+/* Additional STATE_REPORT values */
+#define TAS675X_STATE_FAULT                  0x05
+#define TAS675X_STATE_AUTOREC                0x06
+
+/* Combined values for both channel pairs in one register */
+#define TAS675X_STATE_DEEPSLEEP_BOTH \
+	(TAS675X_STATE_DEEPSLEEP | (TAS675X_STATE_DEEPSLEEP << 4))
+#define TAS675X_STATE_LOAD_DIAG_BOTH \
+	(TAS675X_STATE_LOAD_DIAG | (TAS675X_STATE_LOAD_DIAG << 4))
+#define TAS675X_STATE_SLEEP_BOTH \
+	(TAS675X_STATE_SLEEP | (TAS675X_STATE_SLEEP << 4))
+#define TAS675X_STATE_HIZ_BOTH \
+	(TAS675X_STATE_HIZ | (TAS675X_STATE_HIZ << 4))
+#define TAS675X_STATE_PLAY_BOTH \
+	(TAS675X_STATE_PLAY | (TAS675X_STATE_PLAY << 4))
+#define TAS675X_STATE_FAULT_BOTH \
+	(TAS675X_STATE_FAULT | (TAS675X_STATE_FAULT << 4))
+
+/* STATE_CTRL_CH1_CH2 / STATE_CTRL_CH3_CH4 — mute bits */
+#define TAS675X_CH1_MUTE_BIT                 BIT(7)
+#define TAS675X_CH2_MUTE_BIT                 BIT(3)
+#define TAS675X_CH_MUTE_BOTH                 (TAS675X_CH1_MUTE_BIT | TAS675X_CH2_MUTE_BIT)
+
+/* SCLK_INV_CTRL_REG */
+#define TAS675X_SCLK_INV_TX_BIT             BIT(5)
+#define TAS675X_SCLK_INV_RX_BIT             BIT(4)
+#define TAS675X_SCLK_INV_MASK               (TAS675X_SCLK_INV_TX_BIT | TAS675X_SCLK_INV_RX_BIT)
+
+/* AUDIO_IF_CTRL_REG */
+#define TAS675X_TDM_EN_BIT                   BIT(4)
+#define TAS675X_SAP_FMT_MASK                 GENMASK(3, 2)
+#define TAS675X_SAP_FMT_I2S                  (0x00 << 2)
+#define TAS675X_SAP_FMT_TDM                  (0x01 << 2)
+#define TAS675X_SAP_FMT_RIGHT_J              (0x02 << 2)
+#define TAS675X_SAP_FMT_LEFT_J               (0x03 << 2)
+#define TAS675X_FS_PULSE_MASK                GENMASK(1, 0)
+#define TAS675X_FS_PULSE_SHORT               0x01
+
+/* SDIN_CTRL_REG */
+#define TAS675X_SDIN_AUDIO_WL_MASK           GENMASK(3, 2)
+#define TAS675X_SDIN_LL_WL_MASK              GENMASK(1, 0)
+#define TAS675X_SDIN_WL_MASK                 (TAS675X_SDIN_AUDIO_WL_MASK | TAS675X_SDIN_LL_WL_MASK)
+
+/* SDOUT_CTRL_REG */
+#define TAS675X_SDOUT_SELECT_MASK            GENMASK(7, 4)
+#define TAS675X_SDOUT_SELECT_TDM_SDOUT1      0x00
+#define TAS675X_SDOUT_SELECT_NON_TDM         0x10
+#define TAS675X_SDOUT_VP_WL_MASK             GENMASK(3, 2)
+#define TAS675X_SDOUT_IS_WL_MASK             GENMASK(1, 0)
+#define TAS675X_SDOUT_WL_MASK                (TAS675X_SDOUT_VP_WL_MASK | TAS675X_SDOUT_IS_WL_MASK)
+
+/* SDOUT_EN_REG */
+#define TAS675X_SDOUT_NON_TDM_SEL_MASK      GENMASK(5, 4)
+#define TAS675X_SDOUT_NON_TDM_SEL_VPREDICT  (0x0 << 4)
+#define TAS675X_SDOUT_NON_TDM_SEL_ISENSE    (0x1 << 4)
+#define TAS675X_SDOUT_EN_VPREDICT           BIT(0)
+#define TAS675X_SDOUT_EN_ISENSE             BIT(1)
+#define TAS675X_SDOUT_EN_NON_TDM_ALL        GENMASK(1, 0)
+
+/* Word length values (shared by SDIN_CTRL and SDOUT_CTRL) */
+#define TAS675X_WL_16BIT                     0x00
+#define TAS675X_WL_20BIT                     0x01
+#define TAS675X_WL_24BIT                     0x02
+#define TAS675X_WL_32BIT                     0x03
+
+/* SDIN_OFFSET_MSB_REG */
+#define TAS675X_SDIN_AUDIO_OFF_MSB_MASK      GENMASK(7, 6)
+#define TAS675X_SDIN_LL_OFF_MSB_MASK         GENMASK(5, 4)
+
+/* SDOUT_OFFSET_MSB_REG */
+#define TAS675X_SDOUT_VP_OFF_MSB_MASK        GENMASK(7, 6)
+#define TAS675X_SDOUT_IS_OFF_MSB_MASK        GENMASK(5, 4)
+
+/* RTLDG_EN_REG */
+#define TAS675X_RTLDG_CLIP_MASK_BIT          BIT(4)
+#define TAS675X_RTLDG_CH_EN_MASK             GENMASK(3, 0)
+
+/* DC_LDG_CTRL_REG */
+#define TAS675X_LDG_ABORT_BIT                BIT(7)
+#define TAS675X_LDG_BUFFER_WAIT_MASK         GENMASK(6, 5)
+#define TAS675X_LDG_WAIT_BYPASS_BIT          BIT(2)
+#define TAS675X_SLOL_DISABLE_BIT             BIT(1)
+#define TAS675X_LDG_BYPASS_BIT               BIT(0)
+
+/* DC_LDG_TIME_CTRL_REG */
+#define TAS675X_LDG_RAMP_SLOL_MASK           GENMASK(7, 6)
+#define TAS675X_LDG_SETTLING_SLOL_MASK       GENMASK(5, 4)
+#define TAS675X_LDG_RAMP_S2PG_MASK           GENMASK(3, 2)
+#define TAS675X_LDG_SETTLING_S2PG_MASK       GENMASK(1, 0)
+
+/* AC_LDG_CTRL_REG */
+#define TAS675X_AC_DIAG_GAIN_BIT             BIT(4)
+#define TAS675X_AC_DIAG_START_MASK           GENMASK(3, 0)
+
+/* DC_LDG_RESULT_REG */
+#define TAS675X_DC_LDG_LO_RESULT_MASK        GENMASK(7, 4)
+#define TAS675X_DC_LDG_PASS_MASK             GENMASK(3, 0)
+
+/* Load Diagnostics Timing Constants */
+#define TAS675X_POLL_INTERVAL_US             10000
+#define TAS675X_STATE_TRANSITION_TIMEOUT_US  50000
+#define TAS675X_DC_LDG_TIMEOUT_US            300000
+#define TAS675X_AC_LDG_TIMEOUT_US            400000
+
+/* GPIO_CTRL_REG */
+#define TAS675X_GPIO1_OUTPUT_EN              BIT(7)
+#define TAS675X_GPIO2_OUTPUT_EN              BIT(6)
+#define TAS675X_GPIO_CTRL_RSTVAL             0x22
+
+/* GPIO output select values */
+#define TAS675X_GPIO_SEL_LOW                 0x00
+#define TAS675X_GPIO_SEL_AUTO_MUTE_ALL       0x02
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH4       0x03
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH3       0x04
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH2       0x05
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH1       0x06
+#define TAS675X_GPIO_SEL_SDOUT2              0x08
+#define TAS675X_GPIO_SEL_SDOUT1              0x09
+#define TAS675X_GPIO_SEL_WARN                0x0A
+#define TAS675X_GPIO_SEL_FAULT               0x0B
+#define TAS675X_GPIO_SEL_CLOCK_SYNC          0x0E
+#define TAS675X_GPIO_SEL_INVALID_CLK         0x0F
+#define TAS675X_GPIO_SEL_HIGH                0x13
+
+/* GPIO input function encoding (flag bit | function ID) */
+#define TAS675X_GPIO_FUNC_INPUT              0x100
+
+/* Input Function IDs */
+#define TAS675X_GPIO_IN_ID_MUTE              0
+#define TAS675X_GPIO_IN_ID_PHASE_SYNC        1
+#define TAS675X_GPIO_IN_ID_SDIN2             2
+#define TAS675X_GPIO_IN_ID_DEEP_SLEEP        3
+#define TAS675X_GPIO_IN_ID_HIZ               4
+#define TAS675X_GPIO_IN_ID_PLAY              5
+#define TAS675X_GPIO_IN_ID_SLEEP             6
+#define TAS675X_GPIO_IN_NUM                  7
+
+#define TAS675X_GPIO_IN_MUTE                 (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_MUTE)
+#define TAS675X_GPIO_IN_PHASE_SYNC \
+	(TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PHASE_SYNC)
+#define TAS675X_GPIO_IN_SDIN2                (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SDIN2)
+#define TAS675X_GPIO_IN_DEEP_SLEEP \
+	(TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_DEEP_SLEEP)
+#define TAS675X_GPIO_IN_HIZ                  (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_HIZ)
+#define TAS675X_GPIO_IN_PLAY                 (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PLAY)
+#define TAS675X_GPIO_IN_SLEEP                (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SLEEP)
+
+/* GPIO input 3-bit mux field masks */
+#define TAS675X_GPIO_IN_MUTE_MASK            GENMASK(2, 0)
+#define TAS675X_GPIO_IN_SYNC_MASK            GENMASK(2, 0)
+#define TAS675X_GPIO_IN_SDIN2_MASK           GENMASK(6, 4)
+#define TAS675X_GPIO_IN_DEEP_SLEEP_MASK      GENMASK(6, 4)
+#define TAS675X_GPIO_IN_HIZ_MASK             GENMASK(2, 0)
+#define TAS675X_GPIO_IN_PLAY_MASK            GENMASK(6, 4)
+#define TAS675X_GPIO_IN_SLEEP_MASK           GENMASK(2, 0)
+
+/* Book addresses for tas675x_select_book() */
+#define TAS675X_BOOK_DEFAULT                 0x00
+#define TAS675X_BOOK_DSP                     0x8C
+
+/* DSP memory addresses (DSP Book) */
+#define TAS675X_DSP_PAGE_RTLDG               0x22
+#define TAS675X_DSP_RTLDG_OL_THRESH_REG      0x98
+#define TAS675X_DSP_RTLDG_SL_THRESH_REG      0x9C
+
+#define TAS675X_DSP_PARAM_ID_OL_THRESH       0
+#define TAS675X_DSP_PARAM_ID_SL_THRESH       1
+
+/* Setup Mode Entry/Exit*/
+#define TAS675X_SETUP_ENTER_VAL1             0x11
+#define TAS675X_SETUP_ENTER_VAL2             0xFF
+#define TAS675X_SETUP_EXIT_VAL               0x00
+
+#endif /* __TAS675X_H__ */
-- 
2.43.0