public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver
       [not found] <message-id-of-your-RFC-cover-letter>
@ 2026-04-01 22:28 ` Sen Wang
  2026-04-01 22:28   ` [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x Sen Wang
                     ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Sen Wang @ 2026-04-01 22:28 UTC (permalink / raw)
  To: linux-sound
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, devicetree, perex,
	tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel, Sen Wang

This series adds support for the TI TAS675x (TAS6754, TAS67524)
quad-channel automotive Class-D amplifiers. The devices have an
integrated DSP and load diagnostics, and are controlled over I2C.

Patch 1 adds the dt-binding, patch 2 the codec driver, patch 3 the
ALSA mixer controls documentation, and patch 4 adds the MAINTAINERS
entry.

Tested on AM62D-EVM with a TAS67CD-AEC daughter card, on setups &
test procedures, refer to the Github repository.

GitHub: https://github.com/SenWang125/tas67-linux

Changes in v2:
 - Remove redundant DAPM event function (2/4)
 - Move IRQ request past power_on, so regs can be set in a clean state (2/4)
 - Add delayed_work at probe time to accomdate no PM configs (2/4)
 - Change .set_fmt and .dapm_routes callbacks to the same tas675x_set_fmt name (1/3)
 - Links to v1: https://lore.kernel.org/all/20260401024210.28542-1-sen@ti.com/

Sen Wang (4):
  dt-bindings: sound: Add ti,tas675x
  ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
  Documentation: sound: Add TAS675x codec mixer controls documentation
  MAINTAINERS: add entry for TAS675x audio amplifier

 .../devicetree/bindings/sound/ti,tas675x.yaml |  278 +++
 Documentation/sound/codecs/index.rst          |    1 +
 Documentation/sound/codecs/tas675x.rst        |  618 +++++
 MAINTAINERS                                   |    4 +
 sound/soc/codecs/Kconfig                      |   12 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/tas675x.c                    | 2158 +++++++++++++++++
 sound/soc/codecs/tas675x.h                    |  367 +++
 8 files changed, 3440 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/ti,tas675x.yaml
 create mode 100644 Documentation/sound/codecs/tas675x.rst
 create mode 100644 sound/soc/codecs/tas675x.c
 create mode 100644 sound/soc/codecs/tas675x.h

-- 
2.43.0


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

* [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-01 22:28 ` [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver Sen Wang
@ 2026-04-01 22:28   ` Sen Wang
  2026-04-02  7:53     ` Krzysztof Kozlowski
  2026-04-01 22:28   ` [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver Sen Wang
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Sen Wang @ 2026-04-01 22:28 UTC (permalink / raw)
  To: linux-sound
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, devicetree, perex,
	tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel, Sen Wang

Add device tree binding for the Texas Instruments TAS675x family
of four-channel Class-D audio amplifiers with integrated DSP.

Signed-off-by: Sen Wang <sen@ti.com>
---
 .../devicetree/bindings/sound/ti,tas675x.yaml | 278 ++++++++++++++++++
 1 file changed, 278 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/ti,tas675x.yaml

diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
new file mode 100644
index 000000000000..23e4cc77b4ae
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
@@ -0,0 +1,278 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments TAS675x Audio Amplifier
+
+maintainers:
+  - Sen Wang <sen@ti.com>
+
+description: |
+  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,
+  automotive Class-D audio amplifiers with load diagnostics and an integrated
+  DSP for audio processing.
+
+allOf:
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    enum:
+      - ti,tas6754
+      - ti,tas67524
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 1
+    description: |
+      The device exposes three DAIs, selected by index.
+        0 - Standard Audio Path (Playback)
+        1 - Low-Latency Playback Path (Playback)
+        2 - Sensory Feedback (Capture - Vpredict and Isense)
+      By default, all four channels of each DAI are active. Runtime
+      reconfiguration is available through DAPM widgets.
+
+  interrupts:
+    maxItems: 1
+    description:
+      Active-low falling-edge interrupt from the FAULT pin. When provided,
+      the driver uses IRQ-driven fault reporting instead of polling.
+
+  pd-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the power-down (PD#) pin, active low. Controls the
+      internal digital circuitry power state. When asserted the device enters
+      full power-down mode and all register state is lost. Can be omitted if
+      PD pin is hardwired or externally controlled.
+
+  stby-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the standby (STBY#) pin, active low. Controls the
+      analog power stage. When asserted the device enters Deep Sleep mode
+      but remains I2C-accessible with registers retained. Can be omitted if
+      STBY pin is tied to PD or hardwired.
+
+  dvdd-supply:
+    description:
+      Digital logic supply (1.62 V to 3.6 V). All three supply rails must
+      be within their recommended operating ranges before the PD pin is
+      released.
+
+  pvdd-supply:
+    description:
+      Output FET power supply (4.5 V to 19 V). All three supply rails must
+      be within their recommended operating ranges before the PD pin is
+      released.
+
+  vbat-supply:
+    description:
+      Battery supply for the Class-D output stage (4.5 V to 19 V). Optional
+      when PVDD and VBAT are connected to the same supply rail. When absent,
+      VBAT is assumed hardwired to PVDD.
+
+  ti,fast-boot:
+    type: boolean
+    description:
+      Skip DC load diagnostic sweep at power-on to reduce boot latency.
+      Automatic diagnostics after fault conditions remain enabled. Hardware
+      overcurrent protection is always active.
+
+  ti,audio-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      TDM slot offset for the standard audio playback path via SDIN1. A value
+      of 4 maps to slot 4. If omitted, slot assignment is derived from the
+      tx_mask provided via set_tdm_slot(). Without either property, no slot
+      mapping is configured.
+
+  ti,llp-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      TDM slot offset for the low-latency playback path via SDIN1. If omitted,
+      slot assignment is derived from the tx_mask provided via set_tdm_slot().
+      Without either property, no slot mapping is configured. Disabled outside
+      of LLP mode, and only relevant for TDM formats.
+
+  ti,vpredict-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      In TDM mode, enables Vpredict output and assigns its starting slot;
+      four consecutive slots carry Vpredict Ch1-4 on SDOUT1. May coexist
+      with ti,isense-slot-no using separate non-overlapping slots.
+
+      In I2S mode, enables Vpredict output on SDOUT1 (Ch1/Ch2) and SDOUT2
+      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
+      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
+      exclusive with ti,isense-slot-no; if both are set, Vpredict takes
+      priority.
+
+      Irrelevant in Left-J and Right-J modes.
+
+  ti,isense-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      In TDM mode, enables Isense output and assigns its starting slot;
+      four consecutive slots carry Isense Ch1-4 on SDOUT1. May coexist
+      with ti,vpredict-slot-no using separate non-overlapping slots.
+
+      In I2S mode, enables Isense output on SDOUT1 (Ch1/Ch2) and SDOUT2
+      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
+      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
+      exclusive with ti,vpredict-slot-no; Vpredict takes priority if both
+      are set.
+
+      Irrelevant in Left-J and Right-J modes.
+
+  ti,gpio1-function:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Function for the GPIO_1 pin. When omitted, GPIO_1 remains in its
+      power-on default state.
+    enum:
+      - low           # Output: driven low
+      - auto-mute     # Output: high when all channels are auto-muted
+      - auto-mute-ch4 # Output: high when channel 4 is auto-muted
+      - auto-mute-ch3 # Output: high when channel 3 is auto-muted
+      - auto-mute-ch2 # Output: high when channel 2 is auto-muted
+      - auto-mute-ch1 # Output: high when channel 1 is auto-muted
+      - sdout2        # Output: Routes secondary serial data output 2
+      - sdout1        # Output: Re-routes secondary serial data output 1
+      - warn          # Output: warning signal (OTW, CBC)
+      - fault         # Output: fault signal (OTSD, OC, DC)
+      - clock-sync    # Output: clock synchronisation
+      - invalid-clock # Output: high when clock is invalid
+      - high          # Output: driven high
+      - mute          # Input: external mute control
+      - phase-sync    # Input: phase synchronisation
+      - sdin2         # Input: secondary SDIN2 for I2S/LJ/RJ ch3/ch4
+      - deep-sleep    # Input: asserted transitions device to Deep Sleep
+      - hiz           # Input: asserted transitions device to Hi-Z
+      - play          # Input: asserted transitions device to Play
+      - sleep         # Input: asserted transitions device to Sleep
+
+  ti,gpio2-function:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Function for the GPIO_2 pin. When omitted, GPIO_2 remains in its
+      power-on default state.
+    enum:
+      - low           # Output: driven low
+      - auto-mute     # Output: high when all channels are auto-muted
+      - auto-mute-ch4 # Output: high when channel 4 is auto-muted
+      - auto-mute-ch3 # Output: high when channel 3 is auto-muted
+      - auto-mute-ch2 # Output: high when channel 2 is auto-muted
+      - auto-mute-ch1 # Output: high when channel 1 is auto-muted
+      - sdout2        # Output: Routes secondary serial data output 2
+      - sdout1        # Output: Re-routes secondary serial data output 1
+      - warn          # Output: warning signal (OTW, CBC)
+      - fault         # Output: fault signal (OTSD, OC, DC)
+      - clock-sync    # Output: clock synchronisation
+      - invalid-clock # Output: high when clock is invalid
+      - high          # Output: driven high
+      - mute          # Input: external mute control
+      - phase-sync    # Input: phase synchronisation
+      - sdin2         # Input: secondary SDIN2 for I2S/LJ/RJ ch3/ch4
+      - deep-sleep    # Input: asserted transitions device to Deep Sleep
+      - hiz           # Input: asserted transitions device to Hi-Z
+      - play          # Input: asserted transitions device to Play
+      - sleep         # Input: asserted transitions device to Sleep
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    properties:
+      port@0:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Standard audio playback port (DAI 0).
+
+      port@1:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Low-latency playback port (LLP) (DAI 1).
+
+      port@2:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Sensory feedback capture port (DAI 2).
+
+  port:
+    $ref: audio-graph-port.yaml#
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - dvdd-supply
+  - pvdd-supply
+
+anyOf:
+  - required: [pd-gpios]
+  - required: [stby-gpios]
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        amplifier@70 {
+            compatible = "ti,tas67524";
+            reg = <0x70>;
+            #sound-dai-cells = <1>;
+            sound-name-prefix = "TAS0";
+
+            stby-gpios = <&main_gpio0 33 GPIO_ACTIVE_LOW>;
+
+            dvdd-supply = <&dvdd_1v8>;
+            pvdd-supply = <&pvdd_12v>;
+            vbat-supply = <&vbat_12v>;
+
+            ti,audio-slot-no = <0>;
+            ti,llp-slot-no = <4>;
+            ti,vpredict-slot-no = <0>;
+            ti,isense-slot-no = <4>;
+
+            ti,gpio2-function = "warn";
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+
+                    tas0_audio_ep: endpoint {
+                        dai-format = "dsp_b";
+                        remote-endpoint = <&be_tas0_audio_ep>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+
+                    tas0_anc_ep: endpoint {
+                        remote-endpoint = <&be_tas0_anc_ep>;
+                    };
+                };
+
+                port@2 {
+                    reg = <2>;
+
+                    tas0_fb_ep: endpoint {
+                        remote-endpoint = <&be_tas0_fb_ep>;
+                    };
+                };
+            };
+        };
+    };
-- 
2.43.0


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

* [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
  2026-04-01 22:28 ` [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver Sen Wang
  2026-04-01 22:28   ` [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x Sen Wang
@ 2026-04-01 22:28   ` Sen Wang
  2026-04-02  7:54     ` Krzysztof Kozlowski
  2026-04-02 17:10     ` Mark Brown
  2026-04-01 22:28   ` [PATCH v2 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
  2026-04-01 22:28   ` [PATCH v2 4/4] MAINTAINERS: add entry for TAS675x audio amplifier Sen Wang
  3 siblings, 2 replies; 13+ messages in thread
From: Sen Wang @ 2026-04-01 22:28 UTC (permalink / raw)
  To: linux-sound
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, devicetree, perex,
	tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel, Sen Wang

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 | 2158 ++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/tas675x.h |  367 ++++++
 4 files changed, 2539 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..fc30128840b7
--- /dev/null
+++ b/sound/soc/codecs/tas675x.c
@@ -0,0 +1,2158 @@
+// 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 const struct snd_soc_dapm_widget tas675x_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("Analog Core", SND_SOC_NOPM, 0, 0, NULL, 0),
+	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_dapm_routes[] = {
+	{ "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_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 SLEEP 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_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_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(tas675x_dapm_routes),
+	.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;
+
+	memcpy(tas->dsp_params, tas675x_dsp_defaults, sizeof(tas->dsp_params));
+	INIT_DELAYED_WORK(&tas->fault_check_work, tas675x_fault_check_work);
+
+	tas->regmap = devm_regmap_init_i2c(client, &cfg);
+	if (IS_ERR(tas->regmap))
+		return PTR_ERR(tas->regmap);
+
+	/* 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");
+
+	ret = tas675x_power_on(tas);
+	if (ret)
+		return ret;
+
+	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) {
+			tas675x_power_off(tas);
+			return dev_err_probe(tas->dev, ret, "Failed to request IRQ\n");
+		}
+	} else {
+		/* Schedule delayed work for fault checking at probe and runtime resume */
+		schedule_delayed_work(&tas->fault_check_work,
+				      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+	}
+
+	/* 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


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

* [PATCH v2 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation
  2026-04-01 22:28 ` [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver Sen Wang
  2026-04-01 22:28   ` [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x Sen Wang
  2026-04-01 22:28   ` [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver Sen Wang
@ 2026-04-01 22:28   ` Sen Wang
  2026-04-01 22:28   ` [PATCH v2 4/4] MAINTAINERS: add entry for TAS675x audio amplifier Sen Wang
  3 siblings, 0 replies; 13+ messages in thread
From: Sen Wang @ 2026-04-01 22:28 UTC (permalink / raw)
  To: linux-sound
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, devicetree, perex,
	tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel, Sen Wang

Add ALSA mixer controls documentation for the TAS675x driver, covering
DSP signal path modes, volume controls, load diagnostics, and fault
monitoring.

Signed-off-by: Sen Wang <sen@ti.com>
---
 Documentation/sound/codecs/index.rst   |   1 +
 Documentation/sound/codecs/tas675x.rst | 618 +++++++++++++++++++++++++
 2 files changed, 619 insertions(+)
 create mode 100644 Documentation/sound/codecs/tas675x.rst

diff --git a/Documentation/sound/codecs/index.rst b/Documentation/sound/codecs/index.rst
index 2cb95d87bbef..7594d0a38d6b 100644
--- a/Documentation/sound/codecs/index.rst
+++ b/Documentation/sound/codecs/index.rst
@@ -7,3 +7,4 @@ Codec-Specific Information
    :maxdepth: 2
 
    cs35l56
+   tas675x
diff --git a/Documentation/sound/codecs/tas675x.rst b/Documentation/sound/codecs/tas675x.rst
new file mode 100644
index 000000000000..334b1d8be3ea
--- /dev/null
+++ b/Documentation/sound/codecs/tas675x.rst
@@ -0,0 +1,618 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================================
+TAS675x Codec Mixer Controls
+======================================
+
+This document describes the ALSA mixer controls for the TAS675x
+4-channel automotive Class-D amplifier driver.
+
+For device tree bindings, see:
+Documentation/devicetree/bindings/sound/ti,tas675x.yaml
+
+DSP Signal Path Mode
+====================
+
+DSP Signal Path Mode
+--------------------
+
+:Description: Signal processing mode selection.
+:Type:        Enumerated
+:Default:     Normal
+:Options:     Normal, LLP, FFLP
+
+Normal
+  Full DSP with all features available.
+
+LLP (Low Latency Path)
+  Bypasses DSP processing. DSP protection features (Thermal Foldback,
+  PVDD Foldback, Clip Detect) and Real-Time Load Diagnostics unavailable.
+
+FFLP (Full Feature Low Latency Path)
+  Reduced latency. Real-Time Load Diagnostics unavailable.
+
+The following controls are unavailable in LLP mode:
+``Thermal Foldback Switch``, ``PVDD Foldback Switch``,
+``DC Blocker Bypass Switch``, ``Clip Detect Switch``, ``Audio SDOUT Switch``.
+
+The following controls require Normal mode (unavailable in FFLP and LLP):
+``CHx RTLDG Switch``, ``RTLDG Clip Mask Switch``, ``ISENSE Calibration Switch``,
+``RTLDG Open Load Threshold``, ``RTLDG Short Load Threshold``,
+``CHx RTLDG Impedance``, ``RTLDG Fault Latched``.
+
+Volume Controls
+===============
+
+Analog Playback Volume
+----------------------
+
+:Description: Analog output gain for all channels (CH1/CH2 and CH3/CH4 pairs).
+:Type:        Volume (TLV)
+:Default:     0 dB
+:Range:       -15.5 dB to 0 dB (0.5 dB steps)
+
+Analog Gain Ramp Step
+---------------------
+
+:Description: Anti-pop ramp step duration for analog gain transitions.
+:Type:        Enumerated
+:Default:     15us
+:Options:     15us, 60us, 200us, 400us
+
+CHx Digital Playback Volume
+---------------------------
+
+:Description: Per-channel digital volume control (x = 1, 2, 3, 4).
+:Type:        Volume (TLV)
+:Default:     0 dB
+:Range:       -103 dB to 0 dB (0.5 dB steps)
+:Bounds:      0x30 (min/mute) to 0xFF (max)
+
+Volume Ramp Down Rate
+---------------------
+
+:Description: Update frequency during mute transition.
+:Type:        Enumerated
+:Default:     16 FS
+:Options:     4 FS, 16 FS, 32 FS, Instant
+
+Volume Ramp Down Step
+---------------------
+
+:Description: dB change per update during mute.
+:Type:        Enumerated
+:Default:     0.5dB
+:Options:     4dB, 2dB, 1dB, 0.5dB
+
+Volume Ramp Up Rate
+-------------------
+
+:Description: Update frequency during unmute transition.
+:Type:        Enumerated
+:Default:     16 FS
+:Options:     4 FS, 16 FS, 32 FS, Instant
+
+Volume Ramp Up Step
+-------------------
+
+:Description: dB change per update during unmute.
+:Type:        Enumerated
+:Default:     0.5dB
+:Options:     4dB, 2dB, 1dB, 0.5dB
+
+CH1/2 Volume Combine
+--------------------
+
+:Description: Links digital volume controls for CH1 and CH2.
+:Type:        Enumerated
+:Default:     Independent
+:Options:     Independent, CH2 follows CH1, CH1 follows CH2
+
+CH3/4 Volume Combine
+--------------------
+
+:Description: Links digital volume controls for CH3 and CH4.
+:Type:        Enumerated
+:Default:     Independent
+:Options:     Independent, CH4 follows CH3, CH3 follows CH4
+
+Auto Mute & Silence Detection
+==============================
+
+CHx Auto Mute Switch
+--------------------
+
+:Description: Enables automatic muting on zero-signal detection (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+Auto Mute Combine Switch
+------------------------
+
+:Description: Coordinated muting behaviour across all channels.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Behavior:    Disabled: channels mute independently when their signal is zero.
+              Enabled: all channels mute together only when all detect zero
+              signal; unmute when any channel has non-zero signal.
+
+CHx Auto Mute Time
+------------------
+
+:Description: Duration of zero signal before muting triggers (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     11.5ms
+:Options:     11.5ms, 53ms, 106.5ms, 266.5ms, 535ms, 1065ms, 2665ms, 5330ms
+:Note:        Values are at 96 kHz. At 48 kHz, times are doubled.
+
+Clock & EMI Management
+======================
+
+Spread Spectrum Mode
+--------------------
+
+:Description: Frequency dithering mode to reduce peak EMI.
+:Type:        Enumerated
+:Default:     Disabled
+:Options:     Disabled, Triangle, Random, Triangle and Random
+
+SS Triangle Range
+-----------------
+
+:Description: Frequency deviation range for Triangle spread spectrum.
+:Type:        Enumerated
+:Default:     6.5%
+:Options:     6.5%, 13.5%, 5%, 10%
+:Note:        Applies only when Spread Spectrum Mode includes Triangle.
+
+SS Random Range
+---------------
+
+:Description: Frequency deviation range for Random spread spectrum.
+:Type:        Enumerated
+:Default:     0.83%
+:Options:     0.83%, 2.50%, 5.83%, 12.50%, 25.83%
+:Note:        Applies only when Spread Spectrum Mode includes Random.
+
+SS Random Dwell Range
+---------------------
+
+:Description: Dwell time range for Random spread spectrum (FSS = spread
+              spectrum modulation frequency).
+:Type:        Enumerated
+:Default:     1/FSS to 2/FSS
+:Options:     1/FSS to 2/FSS, 1/FSS to 4/FSS, 1/FSS to 8/FSS, 1/FSS to 15/FSS
+:Note:        Applies only when Spread Spectrum Mode includes Random.
+
+SS Triangle Dwell Min
+---------------------
+
+:Description: Minimum dwell time for Triangle spread spectrum.
+:Type:        Integer
+:Default:     0
+:Range:       0 to 15 (raw register value)
+
+SS Triangle Dwell Max
+---------------------
+
+:Description: Maximum dwell time for Triangle spread spectrum.
+:Type:        Integer
+:Default:     0
+:Range:       0 to 15 (raw register value)
+
+Hardware Protection
+===================
+
+OTSD Auto Recovery Switch
+--------------------------
+
+:Description: Enables automatic recovery from over-temperature shutdown.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Note:        When disabled, manual fault clearing is required after OTSD events.
+
+Overcurrent Limit Level
+-----------------------
+
+:Description: Current-limit trip point sensitivity.
+:Type:        Enumerated
+:Default:     Level 4 (least sensitive)
+:Options:     Level 4 (least sensitive), Level 3, Level 2, Level 1 (most sensitive)
+
+CHx OTW Threshold
+-----------------
+
+:Description: Over-temperature warning threshold per channel (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     >95C
+:Options:     Disabled, >95C, >110C, >125C, >135C, >145C, >155C, >165C
+
+Temperature and Voltage Monitoring
+===================================
+
+PVDD Sense
+----------
+
+:Description: Supply voltage sense register.
+:Type:        Integer (read-only)
+:Range:       0 to 255
+:Conversion:  value × 0.19 V
+
+Global Temperature
+------------------
+
+:Description: Global die temperature sense register.
+:Type:        Integer (read-only)
+:Range:       0 to 255
+:Conversion:  (value × 0.5 °C) − 50 °C
+
+CHx Temperature Range
+---------------------
+
+:Description: Per-channel coarse temperature range indicator (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0 to 3
+:Mapping:     0 = <80 °C, 1 = 80–100 °C, 2 = 100–120 °C, 3 = >120 °C
+
+Load Diagnostics
+================
+
+The TAS675x provides three load diagnostic modes:
+
+DC Load Diagnostics (DC LDG)
+  Measures DC resistance to detect S2G (short-to-ground), S2P
+  (short-to-power), OL (open load), and SL (shorted load) faults.
+
+AC Load Diagnostics (AC LDG)
+  Measures complex AC impedance at a configurable frequency. Detects
+  capacitive loads and tweeter configurations.
+
+Real-Time Load Diagnostics (RTLDG)
+  Monitors impedance continuously during playback using a pilot tone.
+  Normal DSP mode only, at 48 kHz or 96 kHz.
+
+Fast Boot Mode
+--------------
+
+By default the device runs DC load diagnostics at initialisation before
+accepting audio. Setting ``ti,fast-boot`` in the device tree bypasses this
+initial diagnostic run for faster startup. Automatic diagnostics after
+fault recovery remain enabled::
+
+  codec: tas675x@70 {
+      compatible = "ti,tas67524";
+      reg = <0x70>;
+      ti,fast-boot;
+  };
+
+DC Load Diagnostics
+-------------------
+
+The ``CHx DC LDG Report`` 4-bit fault field uses the following encoding:
+
+  ======  ===========  ===================================================
+  Bit     Fault        Description
+  ======  ===========  ===================================================
+  [3]     S2G          Short-to-Ground
+  [2]     S2P          Short-to-Power
+  [1]     OL           Open Load
+  [0]     SL           Shorted Load
+  ======  ===========  ===================================================
+
+DC LDG Trigger
+~~~~~~~~~~~~~~
+
+:Description: Triggers manual DC load diagnostics on all channels.
+:Type:        Boolean (write-only)
+:Note:        Returns -EBUSY if any DAI stream (playback or capture) is active.
+              The driver manages all channel state transitions. Blocks until
+              diagnostics complete or time out (300 ms).
+
+DC LDG Auto Diagnostics Switch
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Enables automatic DC diagnostics after fault recovery.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+:Note:        When enabled, affected channels re-run diagnostics after fault
+              recovery and retry approximately every 750 ms until resolved.
+
+CHx LO LDG Switch
+~~~~~~~~~~~~~~~~~
+
+:Description: Enables line output load detection per channel (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Note:        When enabled and DC diagnostics report OL, the device tests for
+              a high-impedance line output load.
+
+DC LDG SLOL Ramp Time
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Voltage ramp time for shorted-load and open-load detection.
+:Type:        Enumerated
+:Default:     15 ms
+:Options:     15 ms, 30 ms, 10 ms, 20 ms
+
+DC LDG SLOL Settling Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Settling time for shorted-load and open-load detection.
+:Type:        Enumerated
+:Default:     10 ms
+:Options:     10 ms, 5 ms, 20 ms, 15 ms
+
+DC LDG S2PG Ramp Time
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Voltage ramp time for short-to-power and short-to-ground detection.
+:Type:        Enumerated
+:Default:     5 ms
+:Options:     5 ms, 2.5 ms, 10 ms, 15 ms
+
+DC LDG S2PG Settling Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Settling time for short-to-power and short-to-ground detection.
+:Type:        Enumerated
+:Default:     10 ms
+:Options:     10 ms, 5 ms, 20 ms, 30 ms
+
+CHx DC LDG SL Threshold
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Shorted-load detection threshold per channel (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     1 Ohm
+:Options:     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
+
+DC LDG Result
+~~~~~~~~~~~~~
+
+:Description: Overall DC diagnostic result register.
+:Type:        Integer (read-only)
+:Range:       0x00 to 0xFF
+:Bit Encoding:
+
+  ========  =====================================================
+  Bits      Description
+  ========  =====================================================
+  [7:4]     Line output detection result, one bit per channel
+  [3:0]     DC diagnostic pass/fail per channel (1=pass, 0=fail)
+  ========  =====================================================
+
+CHx DC LDG Report
+~~~~~~~~~~~~~~~~~
+
+:Description: DC diagnostic fault status per channel (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0x0 to 0xF
+:Note:        See fault bit encoding table at the start of this section.
+
+CHx LO LDG Report
+~~~~~~~~~~~~~~~~~
+
+:Description: Line output load detection result per channel (x = 1, 2, 3, 4).
+:Type:        Boolean (read-only)
+:Values:      0 = not detected, 1 = line output load detected
+
+CHx DC Resistance
+~~~~~~~~~~~~~~~~~
+
+:Description: Measured DC load resistance per channel (x = 1, 2, 3, 4).
+:Type:        Float (read-only, displayed in ohms)
+:Resolution:  0.1 ohm per code (10-bit value)
+:Range:       0.0 to 102.3 ohms
+
+AC Load Diagnostics
+-------------------
+
+AC LDG Trigger
+~~~~~~~~~~~~~~
+
+:Description: Triggers AC impedance measurement on all channels.
+:Type:        Boolean (write-only)
+:Note:        Returns -EBUSY if any DAI stream (playback or capture) is active.
+              The driver transitions all channels to SLEEP state before starting
+              the measurement. Blocks until diagnostics complete or time out.
+
+AC DIAG GAIN
+~~~~~~~~~~~~
+
+:Description: Measurement resolution for AC diagnostics.
+:Type:        Boolean Switch
+:Default:     1 (Gain 8)
+:Values:      0 = 0.8 ohm/code (Gain 1), 1 = 0.1 ohm/code (Gain 8)
+:Note:        Gain 8 recommended for load impedances below 8 ohms.
+
+AC LDG Test Frequency
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Test signal frequency for AC impedance measurement.
+:Type:        Integer
+:Default:     200 (0xC8 = 18.75 kHz)
+:Range:       0x01 to 0xFF (0x00 reserved)
+:Formula:     Frequency = 93.75 Hz × register value
+
+CHx AC LDG Real / CHx AC LDG Imag
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Real and imaginary AC impedance components per channel
+              (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0x00 to 0xFF (8-bit signed)
+:Note:        Scale set by AC DIAG GAIN.
+
+Speaker Protection & Detection
+-------------------------------
+
+Tweeter Detection Switch
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Enables tweeter detection using the AC impedance magnitude comparator.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+:Note:        Inverted logic — control value 0 = enabled, 1 = disabled.
+
+Tweeter Detect Threshold
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Magnitude threshold for tweeter detection.
+:Type:        Integer
+:Default:     0
+:Range:       0x00 to 0xFF
+:Resolution:  0.8 ohm/code (AC DIAG GAIN=0) or 0.1 ohm/code (AC DIAG GAIN=1)
+
+CHx Tweeter Detect Report
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Tweeter detection result per channel (x = 1, 2, 3, 4).
+:Type:        Boolean (read-only)
+:Values:      0 = no tweeter detected, 1 = tweeter detected
+
+DSP Protection Features
+=======================
+
+These controls are unavailable in LLP mode.
+
+Thermal Foldback Switch
+-----------------------
+
+:Description: Enables dynamic gain reduction based on die temperature.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+PVDD Foldback Switch
+--------------------
+
+:Description: Enables automatic gain limiting when supply voltage drops
+              (Automatic Gain Limiter).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+DC Blocker Bypass Switch
+------------------------
+
+:Description: Bypasses the DC-blocking high-pass filter.
+:Type:        Boolean Switch
+:Default:     Not bypassed (0)
+
+Clip Detect Switch
+------------------
+
+:Description: Enables DSP-based clip detection (Pseudo-Analog Clip Detect).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+Audio SDOUT Switch
+------------------
+
+:Description: Routes post-processed audio to the SDOUT pin instead of
+              Vpredict data.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Note:        Requires I2S or TDM format. Not supported in Left-justified
+              or DSP mode formats.
+
+Real-Time Load Diagnostics
+===========================
+
+These controls require Normal DSP mode at 48 kHz or 96 kHz. They are
+unavailable at 192 kHz and in FFLP and LLP modes.
+
+The ``RTLDG Fault Latched`` register uses the following encoding:
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [7:4]     Shorted Load faults, CH1–CH4 respectively
+  [3:0]     Open Load faults, CH1–CH4 respectively
+  ========  ==========================================
+
+CHx RTLDG Switch
+----------------
+
+:Description: Enables real-time impedance monitoring during playback
+              (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+RTLDG Clip Mask Switch
+----------------------
+
+:Description: Suppresses impedance updates during clipping events.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+
+ISENSE Calibration Switch
+--------------------------
+
+:Description: Enables current sense calibration for accurate impedance
+              measurements.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+
+RTLDG Open Load Threshold
+--------------------------
+
+:Description: DSP coefficient for open load fault detection threshold.
+:Type:        DSP coefficient (extended control)
+:Location:    DSP book 0x8C, page 0x22
+
+RTLDG Short Load Threshold
+---------------------------
+
+:Description: DSP coefficient for shorted load fault detection threshold.
+:Type:        DSP coefficient (extended control)
+:Location:    DSP book 0x8C, page 0x22
+
+CHx RTLDG Impedance
+-------------------
+
+:Description: Real-time load impedance per channel (x = 1, 2, 3, 4).
+:Type:        Float (read-only, displayed in ohms)
+:Note:        Valid only during PLAY state with RTLDG enabled at 48 or
+              96 kHz. Holds stale data in SLEEP, MUTE, or Hi-Z states.
+
+RTLDG Fault Latched
+-------------------
+
+:Description: Latched fault register for OL and SL conditions detected
+              during playback.
+:Type:        Integer (read-only, read-to-clear)
+:Range:       0x00 to 0xFF
+:Note:        See bit encoding table at the start of this section.
+              Reading the register clears all latched bits.
+
+Known Limitations
+=================
+
+Clock Fault Behaviour
+---------------------
+
+On Stream Stop
+~~~~~~~~~~~~~~
+
+Every time a playback stream stops the FAULT pin briefly asserts.
+The CPU DAI (McASP) stops SCLK during ``trigger(STOP)`` — an atomic
+context where codec I2C writes are not permitted — before the codec can
+transition to sleep. The device detects the clock halt and latches
+``CLK_FAULT_LATCHED``, which asserts the FAULT pin. The driver clears
+the latch in the ``mute_stream`` callback that follows, so the FAULT pin
+flicker lasts only a few milliseconds. Audio output is not affected and
+no kernel log message is produced.
+
+On Rapid Rate Switching
+~~~~~~~~~~~~~~~~~~~~~~~
+
+When streams are started in rapid succession, an intermittent
+``Clock Fault Latched: 0x01`` message may appear in the kernel log.
+A 0.5 second settling gap between sessions eliminates this.
+
+References
+==========
+
+- TAS675x Technical Reference Manual: SLOU589A
+- Device Tree Bindings: Documentation/devicetree/bindings/sound/ti,tas675x.yaml
+- ALSA Control Name Conventions: Documentation/sound/designs/control-names.rst
-- 
2.43.0


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

* [PATCH v2 4/4] MAINTAINERS: add entry for TAS675x audio amplifier
  2026-04-01 22:28 ` [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver Sen Wang
                     ` (2 preceding siblings ...)
  2026-04-01 22:28   ` [PATCH v2 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
@ 2026-04-01 22:28   ` Sen Wang
  3 siblings, 0 replies; 13+ messages in thread
From: Sen Wang @ 2026-04-01 22:28 UTC (permalink / raw)
  To: linux-sound
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, devicetree, perex,
	tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel, Sen Wang

Add Sen Wang as maintainer and register file patterns for the newly
added TAS675x quad-channel amplifier driver.

Signed-off-by: Sen Wang <sen@ti.com>
---
 MAINTAINERS | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index a626dee5c106..ea61bb439652 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26191,17 +26191,20 @@ TEXAS INSTRUMENTS AUDIO (ASoC/HDA) DRIVERS
 M:	Shenghao Ding <shenghao-ding@ti.com>
 M:	Kevin Lu <kevin-lu@ti.com>
 M:	Baojun Xu <baojun.xu@ti.com>
+M:	Sen Wang <sen@ti.com>
 L:	linux-sound@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/sound/ti,tas2552.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas2562.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas2770.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas27xx.yaml
+F:	Documentation/devicetree/bindings/sound/ti,tas675x.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml
 F:	Documentation/devicetree/bindings/sound/ti,pcm1681.yaml
 F:	Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tlv320*.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tlv320adcx140.yaml
+F:	Documentation/sound/codecs/tas675*
 F:	include/sound/tas2*.h
 F:	include/sound/tlv320*.h
 F:	sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
@@ -26215,6 +26218,7 @@ F:	sound/soc/codecs/pcm3168a*.*
 F:	sound/soc/codecs/pcm5102a.c
 F:	sound/soc/codecs/pcm512x*.*
 F:	sound/soc/codecs/tas2*.*
+F:	sound/soc/codecs/tas675*.*
 F:	sound/soc/codecs/tlv320*.*
 F:	sound/soc/codecs/tpa6130a2.*
 
-- 
2.43.0


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

* Re: [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-01 22:28   ` [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x Sen Wang
@ 2026-04-02  7:53     ` Krzysztof Kozlowski
  2026-04-03  0:40       ` Sen Wang
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-04-02  7:53 UTC (permalink / raw)
  To: Sen Wang
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On Wed, Apr 01, 2026 at 05:28:42PM -0500, Sen Wang wrote:
> Add device tree binding for the Texas Instruments TAS675x family
> of four-channel Class-D audio amplifiers with integrated DSP.
> 
> Signed-off-by: Sen Wang <sen@ti.com>

Please use subject prefixes matching the subsystem. You can get them for
example with 'git log --oneline -- DIRECTORY_OR_FILE' on the directory
your patch is touching. For bindings, the preferred subjects are
explained here:
https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters

> ---
>  .../devicetree/bindings/sound/ti,tas675x.yaml | 278 ++++++++++++++++++

Filename matching compatible, one of them.

>  1 file changed, 278 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/ti,tas675x.yaml
> 
> diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
> new file mode 100644
> index 000000000000..23e4cc77b4ae
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
> @@ -0,0 +1,278 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments TAS675x Audio Amplifier
> +
> +maintainers:
> +  - Sen Wang <sen@ti.com>
> +
> +description: |

Do not need '|' unless you need to preserve formatting.

> +  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,

And devices are not compatible?

> +  automotive Class-D audio amplifiers with load diagnostics and an integrated
> +  DSP for audio processing.
> +
> +allOf:
> +  - $ref: dai-common.yaml#
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,tas6754
> +      - ti,tas67524

Keep alphanumerical order (not natural).

> +
> +  reg:
> +    maxItems: 1
> +
> +  '#sound-dai-cells':
> +    const: 1
> +    description: |
> +      The device exposes three DAIs, selected by index.
> +        0 - Standard Audio Path (Playback)
> +        1 - Low-Latency Playback Path (Playback)
> +        2 - Sensory Feedback (Capture - Vpredict and Isense)
> +      By default, all four channels of each DAI are active. Runtime
> +      reconfiguration is available through DAPM widgets.
> +
> +  interrupts:
> +    maxItems: 1
> +    description:
> +      Active-low falling-edge interrupt from the FAULT pin. When provided,
> +      the driver uses IRQ-driven fault reporting instead of polling.
> +
> +  pd-gpios:

Use names from gpio-consumer-common

> +    maxItems: 1
> +    description:
> +      GPIO connected to the power-down (PD#) pin, active low. Controls the
> +      internal digital circuitry power state. When asserted the device enters
> +      full power-down mode and all register state is lost. Can be omitted if
> +      PD pin is hardwired or externally controlled.
> +
> +  stby-gpios:
> +    maxItems: 1
> +    description:
> +      GPIO connected to the standby (STBY#) pin, active low. Controls the
> +      analog power stage. When asserted the device enters Deep Sleep mode
> +      but remains I2C-accessible with registers retained. Can be omitted if
> +      STBY pin is tied to PD or hardwired.
> +
> +  dvdd-supply:
> +    description:
> +      Digital logic supply (1.62 V to 3.6 V). All three supply rails must
> +      be within their recommended operating ranges before the PD pin is
> +      released.
> +
> +  pvdd-supply:
> +    description:
> +      Output FET power supply (4.5 V to 19 V). All three supply rails must
> +      be within their recommended operating ranges before the PD pin is
> +      released.
> +
> +  vbat-supply:
> +    description:
> +      Battery supply for the Class-D output stage (4.5 V to 19 V). Optional
> +      when PVDD and VBAT are connected to the same supply rail. When absent,
> +      VBAT is assumed hardwired to PVDD.
> +
> +  ti,fast-boot:
> +    type: boolean
> +    description:
> +      Skip DC load diagnostic sweep at power-on to reduce boot latency.
> +      Automatic diagnostics after fault conditions remain enabled. Hardware
> +      overcurrent protection is always active.
> +
> +  ti,audio-slot-no:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      TDM slot offset for the standard audio playback path via SDIN1. A value
> +      of 4 maps to slot 4. If omitted, slot assignment is derived from the
> +      tx_mask provided via set_tdm_slot(). Without either property, no slot
> +      mapping is configured.
> +
> +  ti,llp-slot-no:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      TDM slot offset for the low-latency playback path via SDIN1. If omitted,
> +      slot assignment is derived from the tx_mask provided via set_tdm_slot().
> +      Without either property, no slot mapping is configured. Disabled outside
> +      of LLP mode, and only relevant for TDM formats.
> +
> +  ti,vpredict-slot-no:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: |
> +      In TDM mode, enables Vpredict output and assigns its starting slot;
> +      four consecutive slots carry Vpredict Ch1-4 on SDOUT1. May coexist
> +      with ti,isense-slot-no using separate non-overlapping slots.
> +
> +      In I2S mode, enables Vpredict output on SDOUT1 (Ch1/Ch2) and SDOUT2
> +      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
> +      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
> +      exclusive with ti,isense-slot-no; if both are set, Vpredict takes
> +      priority.
> +
> +      Irrelevant in Left-J and Right-J modes.
> +
> +  ti,isense-slot-no:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: |
> +      In TDM mode, enables Isense output and assigns its starting slot;
> +      four consecutive slots carry Isense Ch1-4 on SDOUT1. May coexist
> +      with ti,vpredict-slot-no using separate non-overlapping slots.
> +
> +      In I2S mode, enables Isense output on SDOUT1 (Ch1/Ch2) and SDOUT2
> +      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
> +      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
> +      exclusive with ti,vpredict-slot-no; Vpredict takes priority if both
> +      are set.
> +
> +      Irrelevant in Left-J and Right-J modes.
> +
> +  ti,gpio1-function:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description: |

Do not need '|' unless you need to preserve formatting.

> +      Function for the GPIO_1 pin. When omitted, GPIO_1 remains in its
> +      power-on default state.

Best regards,
Krzysztof


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

* Re: [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
  2026-04-01 22:28   ` [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver Sen Wang
@ 2026-04-02  7:54     ` Krzysztof Kozlowski
  2026-04-02 17:10     ` Mark Brown
  1 sibling, 0 replies; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-04-02  7:54 UTC (permalink / raw)
  To: Sen Wang
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On Wed, Apr 01, 2026 at 05:28:43PM -0500, Sen Wang wrote:
> +
> +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 },

I did not look detailed, but this ^^^

> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, tas675x_of_match);
> +
> +static const struct i2c_device_id tas675x_i2c_id[] = {
> +	{ "tas6754",  TAS6754 },
> +	{ "tas67524", TAS6754 },

... and this ^^^ clearly states devices are compatible, unlike your
binding is stating

> +	{ }

Best regards,
Krzysztof


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

* Re: [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
  2026-04-01 22:28   ` [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver Sen Wang
  2026-04-02  7:54     ` Krzysztof Kozlowski
@ 2026-04-02 17:10     ` Mark Brown
  2026-04-03  1:16       ` Sen Wang
  1 sibling, 1 reply; 13+ messages in thread
From: Mark Brown @ 2026-04-02 17:10 UTC (permalink / raw)
  To: Sen Wang
  Cc: linux-sound, lgirdwood, robh, krzk+dt, conor+dt, devicetree,
	perex, tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel

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

On Wed, Apr 01, 2026 at 05:28:43PM -0500, Sen Wang wrote:
> 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.

One issue that didn't get noticed last time, sorry:

> +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;
> +}

This cancels the work, completing any that's already running, but...

> +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));
> +}

...the work unconditionally rearms itself so we might race and requeue
(we cancel *then* wait) with the device powered off.  There's the
disable_delayed_work_sync() API which should be a better fit.

> +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;
> +}

Also, this should return IRQ_NONE if no faults were seen (to allow for
interrupt sharing and the genirq core's handling of hardware faults).

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

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

* Re: [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-02  7:53     ` Krzysztof Kozlowski
@ 2026-04-03  0:40       ` Sen Wang
  2026-04-03  7:19         ` Krzysztof Kozlowski
  0 siblings, 1 reply; 13+ messages in thread
From: Sen Wang @ 2026-04-03  0:40 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On 4/2/26 02:53, Krzysztof Kozlowski wrote:
> On Wed, Apr 01, 2026 at 05:28:42PM -0500, Sen Wang wrote:
>> Add device tree binding for the Texas Instruments TAS675x family
>> of four-channel Class-D audio amplifiers with integrated DSP.
>>
>> Signed-off-by: Sen Wang <sen@ti.com>
> 

Hi Krzysztof, Thank you for your review.

> Please use subject prefixes matching the subsystem. You can get them for
> example with 'git log --oneline -- DIRECTORY_OR_FILE' on the directory
> your patch is touching. For bindings, the preferred subjects are
> explained here:
> https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters

Understood, ASoC: dt-bindings: Add ti,tas67524. Apologies for the oversight.

> 
>> ---
>>   .../devicetree/bindings/sound/ti,tas675x.yaml | 278 ++++++++++++++++++
> 
> Filename matching compatible, one of them.
> 
>>   1 file changed, 278 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>> new file mode 100644
>> index 000000000000..23e4cc77b4ae
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>> @@ -0,0 +1,278 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Texas Instruments TAS675x Audio Amplifier
>> +
>> +maintainers:
>> +  - Sen Wang <sen@ti.com>
>> +
>> +description: |
> 
> Do not need '|' unless you need to preserve formatting.
> 
>> +  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,
> 
> And devices are not compatible?
> 

They're compatible, and I didn't have any differentiators for this 
initial driver. So I should only keep one.
And also rename ti,tas675x.yaml to ti,tas67524.yaml.

>> +  automotive Class-D audio amplifiers with load diagnostics and an integrated
>> +  DSP for audio processing.
>> +
>> +allOf:
>> +  - $ref: dai-common.yaml#
>> +
>> +properties:
>> +  compatible:
>> +    enum:
>> +      - ti,tas6754
>> +      - ti,tas67524
> 
> Keep alphanumerical order (not natural).
> 
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  '#sound-dai-cells':
>> +    const: 1
>> +    description: |
>> +      The device exposes three DAIs, selected by index.
>> +        0 - Standard Audio Path (Playback)
>> +        1 - Low-Latency Playback Path (Playback)
>> +        2 - Sensory Feedback (Capture - Vpredict and Isense)
>> +      By default, all four channels of each DAI are active. Runtime
>> +      reconfiguration is available through DAPM widgets.
>> +
>> +  interrupts:
>> +    maxItems: 1
>> +    description:
>> +      Active-low falling-edge interrupt from the FAULT pin. When provided,
>> +      the driver uses IRQ-driven fault reporting instead of polling.
>> +
>> +  pd-gpios:
> 
> Use names from gpio-consumer-common
> 

Okay

>> +    maxItems: 1
>> +    description:
>> +      GPIO connected to the power-down (PD#) pin, active low. Controls the
>> +      internal digital circuitry power state. When asserted the device enters
>> +      full power-down mode and all register state is lost. Can be omitted if
>> +      PD pin is hardwired or externally controlled.
>> +
>> +  stby-gpios:
>> +    maxItems: 1
>> +    description:
>> +      GPIO connected to the standby (STBY#) pin, active low. Controls the
>> +      analog power stage. When asserted the device enters Deep Sleep mode
>> +      but remains I2C-accessible with registers retained. Can be omitted if
>> +      STBY pin is tied to PD or hardwired.
>> +
>> +  dvdd-supply:
>> +    description:
>> +      Digital logic supply (1.62 V to 3.6 V). All three supply rails must
>> +      be within their recommended operating ranges before the PD pin is
>> +      released.
>> +
>> +  pvdd-supply:
>> +    description:
>> +      Output FET power supply (4.5 V to 19 V). All three supply rails must
>> +      be within their recommended operating ranges before the PD pin is
>> +      released.
>> +
>> +  vbat-supply:
>> +    description:
>> +      Battery supply for the Class-D output stage (4.5 V to 19 V). Optional
>> +      when PVDD and VBAT are connected to the same supply rail. When absent,
>> +      VBAT is assumed hardwired to PVDD.
>> +
>> +  ti,fast-boot:
>> +    type: boolean
>> +    description:
>> +      Skip DC load diagnostic sweep at power-on to reduce boot latency.
>> +      Automatic diagnostics after fault conditions remain enabled. Hardware
>> +      overcurrent protection is always active.
>> +
>> +  ti,audio-slot-no:
>> +    $ref: /schemas/types.yaml#/definitions/uint32
>> +    description:
>> +      TDM slot offset for the standard audio playback path via SDIN1. A value
>> +      of 4 maps to slot 4. If omitted, slot assignment is derived from the
>> +      tx_mask provided via set_tdm_slot(). Without either property, no slot
>> +      mapping is configured.
>> +
>> +  ti,llp-slot-no:
>> +    $ref: /schemas/types.yaml#/definitions/uint32
>> +    description:
>> +      TDM slot offset for the low-latency playback path via SDIN1. If omitted,
>> +      slot assignment is derived from the tx_mask provided via set_tdm_slot().
>> +      Without either property, no slot mapping is configured. Disabled outside
>> +      of LLP mode, and only relevant for TDM formats.
>> +
>> +  ti,vpredict-slot-no:
>> +    $ref: /schemas/types.yaml#/definitions/uint32
>> +    description: |
>> +      In TDM mode, enables Vpredict output and assigns its starting slot;
>> +      four consecutive slots carry Vpredict Ch1-4 on SDOUT1. May coexist
>> +      with ti,isense-slot-no using separate non-overlapping slots.
>> +
>> +      In I2S mode, enables Vpredict output on SDOUT1 (Ch1/Ch2) and SDOUT2
>> +      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
>> +      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
>> +      exclusive with ti,isense-slot-no; if both are set, Vpredict takes
>> +      priority.
>> +
>> +      Irrelevant in Left-J and Right-J modes.
>> +
>> +  ti,isense-slot-no:
>> +    $ref: /schemas/types.yaml#/definitions/uint32
>> +    description: |
>> +      In TDM mode, enables Isense output and assigns its starting slot;
>> +      four consecutive slots carry Isense Ch1-4 on SDOUT1. May coexist
>> +      with ti,vpredict-slot-no using separate non-overlapping slots.
>> +
>> +      In I2S mode, enables Isense output on SDOUT1 (Ch1/Ch2) and SDOUT2
>> +      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
>> +      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
>> +      exclusive with ti,vpredict-slot-no; Vpredict takes priority if both
>> +      are set.
>> +
>> +      Irrelevant in Left-J and Right-J modes.
>> +
>> +  ti,gpio1-function:
>> +    $ref: /schemas/types.yaml#/definitions/string
>> +    description: |
> 
> Do not need '|' unless you need to preserve formatting.
> 

Understood, thanks a lot for your review and the tip. Will post a V3.

>> +      Function for the GPIO_1 pin. When omitted, GPIO_1 remains in its
>> +      power-on default state.
> 
> Best regards,
> Krzysztof
> 


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

* Re: [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver
  2026-04-02 17:10     ` Mark Brown
@ 2026-04-03  1:16       ` Sen Wang
  0 siblings, 0 replies; 13+ messages in thread
From: Sen Wang @ 2026-04-03  1:16 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-sound, lgirdwood, robh, krzk+dt, conor+dt, devicetree,
	perex, tiwai, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
	l-badrinarayanan, devarsht, v-singh1, linux-kernel

On 4/2/26 12:10, Mark Brown wrote:
> On Wed, Apr 01, 2026 at 05:28:43PM -0500, Sen Wang wrote:
>> 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.
> 
> One issue that didn't get noticed last time, sorry:
> 

The more the merrier :)

>> +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;
>> +}
> 
> This cancels the work, completing any that's already running, but...
> 
>> +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));
>> +}
> 
> ...the work unconditionally rearms itself so we might race and requeue
> (we cancel *then* wait) with the device powered off.  There's the
> disable_delayed_work_sync() API which should be a better fit.
> 

Right the APIs themselves aren't atomic, disable would be a much less 
error-prone approach. Looks like TAS5720 & TAS6424 has a similar issue 
too, where they have cancel work at DAPM events instead of runtime 
suspend. I can test and send these changes in a separate patch.

>> +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;
>> +}
> 
> Also, this should return IRQ_NONE if no faults were seen (to allow for
> interrupt sharing and the genirq core's handling of hardware faults).

Understood, thank you for the context, didn't know IRQ_NONE provides 
additional heuristics to irq core. Will post a follow-up V3 addressing 
the issues.

Best,
Sen Wang

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

* Re: [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-03  0:40       ` Sen Wang
@ 2026-04-03  7:19         ` Krzysztof Kozlowski
  2026-04-04  3:40           ` Sen Wang
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-04-03  7:19 UTC (permalink / raw)
  To: Sen Wang
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On 03/04/2026 02:40, Sen Wang wrote:
>>> diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>> new file mode 100644
>>> index 000000000000..23e4cc77b4ae
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>> @@ -0,0 +1,278 @@
>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Texas Instruments TAS675x Audio Amplifier
>>> +
>>> +maintainers:
>>> +  - Sen Wang <sen@ti.com>
>>> +
>>> +description: |
>>
>> Do not need '|' unless you need to preserve formatting.
>>
>>> +  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,
>>
>> And devices are not compatible?
>>
> 
> They're compatible, and I didn't have any differentiators for this 
> initial driver. So I should only keep one.
> And also rename ti,tas675x.yaml to ti,tas67524.yaml.

No, you should express compatibility. See writing bindings, writing
schema, example schema docs or DTS101 talk.


Best regards,
Krzysztof

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

* Re: [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-03  7:19         ` Krzysztof Kozlowski
@ 2026-04-04  3:40           ` Sen Wang
  2026-04-04  5:20             ` Krzysztof Kozlowski
  0 siblings, 1 reply; 13+ messages in thread
From: Sen Wang @ 2026-04-04  3:40 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On 4/3/26 02:19, Krzysztof Kozlowski wrote:
> On 03/04/2026 02:40, Sen Wang wrote:
>>>> diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>>> new file mode 100644
>>>> index 000000000000..23e4cc77b4ae
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>>> @@ -0,0 +1,278 @@
>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Texas Instruments TAS675x Audio Amplifier
>>>> +
>>>> +maintainers:
>>>> +  - Sen Wang <sen@ti.com>
>>>> +
>>>> +description: |
>>>
>>> Do not need '|' unless you need to preserve formatting.
>>>
>>>> +  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,
>>>
>>> And devices are not compatible?
>>>
>>
>> They're compatible, and I didn't have any differentiators for this
>> initial driver. So I should only keep one.
>> And also rename ti,tas675x.yaml to ti,tas67524.yaml.
> 
> No, you should express compatibility. See writing bindings, writing
> schema, example schema docs or DTS101 talk.
> 

Thank you for clarifying Krzysztof, DTS101 talk is immensely helpful.

Regarding the compatibles, if I use ti,tas67524 as a fallback:

   compatible:
     oneOf:
       - items:
         - enum:
           - ti,tas6754
         - const: ti,tas67524
       - const: ti,tas67524

Does this look correct? I've also seen some bindings use 'enum' even for 
single values under oneOf, but I'm assuming 'const' is preferred for 
single entries as advocated in the example schema?


Also, my apologies for the premature v3 series; Sorry for the noise. 
Lesson learned and will only send a proper v4 once all discussions are 
settled.


Best Regards,
Sen Wang

> 
> Best regards,
> Krzysztof


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

* Re: [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x
  2026-04-04  3:40           ` Sen Wang
@ 2026-04-04  5:20             ` Krzysztof Kozlowski
  0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-04-04  5:20 UTC (permalink / raw)
  To: Sen Wang
  Cc: linux-sound, broonie, lgirdwood, robh, krzk+dt, conor+dt,
	devicetree, perex, tiwai, shenghao-ding, kevin-lu, baojun.xu,
	niranjan.hy, l-badrinarayanan, devarsht, v-singh1, linux-kernel

On 04/04/2026 05:40, Sen Wang wrote:
> On 4/3/26 02:19, Krzysztof Kozlowski wrote:
>> On 03/04/2026 02:40, Sen Wang wrote:
>>>>> diff --git a/Documentation/devicetree/bindings/sound/ti,tas675x.yaml b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>>>> new file mode 100644
>>>>> index 000000000000..23e4cc77b4ae
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/sound/ti,tas675x.yaml
>>>>> @@ -0,0 +1,278 @@
>>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>>> +%YAML 1.2
>>>>> +---
>>>>> +$id: http://devicetree.org/schemas/sound/ti,tas675x.yaml#
>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>>> +
>>>>> +title: Texas Instruments TAS675x Audio Amplifier
>>>>> +
>>>>> +maintainers:
>>>>> +  - Sen Wang <sen@ti.com>
>>>>> +
>>>>> +description: |
>>>>
>>>> Do not need '|' unless you need to preserve formatting.
>>>>
>>>>> +  The TAS675x family (TAS6754, TAS67524) are four-channel, digital-input,
>>>>
>>>> And devices are not compatible?
>>>>
>>>
>>> They're compatible, and I didn't have any differentiators for this
>>> initial driver. So I should only keep one.
>>> And also rename ti,tas675x.yaml to ti,tas67524.yaml.
>>
>> No, you should express compatibility. See writing bindings, writing
>> schema, example schema docs or DTS101 talk.
>>
> 
> Thank you for clarifying Krzysztof, DTS101 talk is immensely helpful.
> 
> Regarding the compatibles, if I use ti,tas67524 as a fallback:
> 
>    compatible:
>      oneOf:
>        - items:
>          - enum:
>            - ti,tas6754
>          - const: ti,tas67524
>        - const: ti,tas67524
> 
> Does this look correct? I've also seen some bindings use 'enum' even for 
> single values under oneOf, but I'm assuming 'const' is preferred for 
> single entries as advocated in the example schema?

Concept is correct. You have incomplete indentation, but this will be
pointed out by yamllint during dt_binding_check, so be sure that
yamlling and dtschema Python packages are installed.

Best regards,
Krzysztof

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

end of thread, other threads:[~2026-04-04  5:20 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <message-id-of-your-RFC-cover-letter>
2026-04-01 22:28 ` [PATCH v2 0/4] ASoC: Add TAS675x quad-channel Class-D amplifier driver Sen Wang
2026-04-01 22:28   ` [PATCH v2 1/4] dt-bindings: sound: Add ti,tas675x Sen Wang
2026-04-02  7:53     ` Krzysztof Kozlowski
2026-04-03  0:40       ` Sen Wang
2026-04-03  7:19         ` Krzysztof Kozlowski
2026-04-04  3:40           ` Sen Wang
2026-04-04  5:20             ` Krzysztof Kozlowski
2026-04-01 22:28   ` [PATCH v2 2/4] ASoC: codecs: Add TAS675x quad-channel audio amplifier driver Sen Wang
2026-04-02  7:54     ` Krzysztof Kozlowski
2026-04-02 17:10     ` Mark Brown
2026-04-03  1:16       ` Sen Wang
2026-04-01 22:28   ` [PATCH v2 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
2026-04-01 22:28   ` [PATCH v2 4/4] MAINTAINERS: add entry for TAS675x audio amplifier Sen Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox