* [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver
@ 2026-04-03 5:06 Sen Wang
2026-04-03 5:06 ` [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524 Sen Wang
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Sen Wang @ 2026-04-03 5:06 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 v3:
- Renamed ALL tas675x filenames to tas67524, removed tas6754 compatible instance
- Changed pd-gpios to powerdown-gpios, cleanup unnessary .yaml formatting (1/4)
- Opt to use disable delayed_work and re-enable on runtime suspend/resume,
similarly, use disable/enable IRQ on system suspend/resume. (2/4)
- Include IRQ_NONE on ISR returns. (2/4)
- Clarify _check_faults() function which now returns need_clear boolean (2/4)
- Added register section (3/4)
- Added addintional notes: for clarification (3/4)
- Links to v2: https://lore.kernel.org/all/20260401223239.1638881-1-sen@ti.com/
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 (2/4)
- Links to v1: https://lore.kernel.org/all/20260401024210.28542-1-sen@ti.com/
Sen Wang (4):
ASoC: dt-bindings: Add ti,tas67524
ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver
Documentation: sound: Add TAS675x codec mixer controls documentation
MAINTAINERS: add entry for TAS67524 audio amplifier
.../bindings/sound/ti,tas67524.yaml | 277 +++
Documentation/sound/codecs/index.rst | 1 +
Documentation/sound/codecs/tas67524.rst | 686 ++++++
MAINTAINERS | 4 +
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tas67524.c | 2173 +++++++++++++++++
sound/soc/codecs/tas67524.h | 367 +++
8 files changed, 3521 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/ti,tas67524.yaml
create mode 100644 Documentation/sound/codecs/tas67524.rst
create mode 100644 sound/soc/codecs/tas67524.c
create mode 100644 sound/soc/codecs/tas67524.h
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524
2026-04-03 5:06 [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver Sen Wang
@ 2026-04-03 5:06 ` Sen Wang
2026-04-03 7:20 ` Krzysztof Kozlowski
2026-04-03 5:06 ` [PATCH v3 2/4] ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver Sen Wang
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Sen Wang @ 2026-04-03 5:06 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 TAS67524 family
of four-channel Class-D audio amplifiers with integrated DSP.
Signed-off-by: Sen Wang <sen@ti.com>
---
Changes in v3:
- Renamed ti,tas675x to ti,tas67524.yaml
- Removed tas6754 compatible instance
- Changed pd-gpios to powerdown-gpios
- Cleanup unnessary "|" formatting
Changes in v2:
- None
.../bindings/sound/ti,tas67524.yaml | 277 ++++++++++++++++++
1 file changed, 277 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/ti,tas67524.yaml
diff --git a/Documentation/devicetree/bindings/sound/ti,tas67524.yaml b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
new file mode 100644
index 000000000000..b8da1360e698
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
@@ -0,0 +1,277 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/ti,tas67524.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments TAS67524 Audio Amplifier
+
+maintainers:
+ - Sen Wang <sen@ti.com>
+
+description:
+ The TAS67524 is a four-channel, digital-input, automotive
+ Class-D audio amplifier with load diagnostics and an integrated
+ DSP for audio processing.
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - 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.
+
+ powerdown-gpios:
+ maxItems: 1
+ description:
+ GPIO connected to the 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.
+
+ standby-gpios:
+ maxItems: 1
+ description:
+ GPIO connected to the 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: [powerdown-gpios]
+ - required: [standby-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";
+
+ standby-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] 6+ messages in thread
* [PATCH v3 2/4] ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver
2026-04-03 5:06 [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver Sen Wang
2026-04-03 5:06 ` [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524 Sen Wang
@ 2026-04-03 5:06 ` Sen Wang
2026-04-03 5:06 ` [PATCH v3 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
2026-04-03 5:06 ` [PATCH v3 4/4] MAINTAINERS: add entry for TAS67524 audio amplifier Sen Wang
3 siblings, 0 replies; 6+ messages in thread
From: Sen Wang @ 2026-04-03 5:06 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>
---
Changes in v3:
- Use disable delayed_work and re-enable on runtime suspend/resume
- Similarly, use disable/enable IRQ on system suspend/resume
- Include IRQ_NONE on ISR returns
- Clarify _check_faults() which now returns need_clear boolean
Changes in v2:
- Remove redundant DAPM event function
- Move IRQ request past power_on(2/4)
- Add delayed_work at probe time to accomdate no PM config
- Change .set_fmt and .dapm_routes callbacks to the same tas675x_set_fmt name
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tas67524.c | 2173 +++++++++++++++++++++++++++++++++++
sound/soc/codecs/tas67524.h | 367 ++++++
4 files changed, 2553 insertions(+)
create mode 100644 sound/soc/codecs/tas67524.c
create mode 100644 sound/soc/codecs/tas67524.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f9e6a83e55c6..167abe626ecb 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_TAS67524
imply SND_SOC_TDA7419
imply SND_SOC_TFA9879
imply SND_SOC_TFA989X
@@ -2240,6 +2241,16 @@ config SND_SOC_TAS6424
Enable support for Texas Instruments TAS6424 high-efficiency
digital input quad-channel Class-D audio power amplifiers.
+config SND_SOC_TAS67524
+ tristate "Texas Instruments TAS67524 Quad-Channel Audio Amplifier"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Enable support for Texas Instruments TAS67524 quad-channel Class-D
+ audio power amplifier. The device supports I2S and TDM interfaces
+ with real-time voltage and current sense feedback via SDOUT, and
+ provides 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..04769532afa4 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-tas67524-y := tas67524.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_TAS67524) += snd-soc-tas67524.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/tas67524.c b/sound/soc/codecs/tas67524.c
new file mode 100644
index 000000000000..2a1b4a7daaa2
--- /dev/null
+++ b/sound/soc/codecs/tas67524.c
@@ -0,0 +1,2173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC Texas Instruments TAS67524 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 "tas67524.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 9
+ 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 LDG 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);
+ }
+
+ /*
+ * Configure fault and warning event routing:
+ *
+ * ROUTING_1: CP fault/UVLO latch, OUTM soft short latch
+ * ROUTING_2: CBC latch, OTSD latch, OTSD, power fault
+ * ROUTING_3: CBC latch, OTSD latch, power latch, DC LDG,
+ * OTSD, power warnings
+ * ROUTING_4: OC latch, DC latch, protection shutdown
+ * OTW latch, OTW, clip latch
+ * ROUTING_5: clock latch+non-latch, RTLDG latch
+ * CBC warning, clip warning
+ */
+ regmap_write(regmap, TAS675X_REPORT_ROUTING_1_REG, 0x70);
+ regmap_write(regmap, TAS675X_REPORT_ROUTING_2_REG, 0xA3);
+ regmap_write(regmap, TAS675X_REPORT_ROUTING_3_REG, 0xBB);
+ 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_STATUS_1_REG, &val);
+ 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);
+
+ disable_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) {
+ enable_delayed_work(&tas->fault_check_work);
+ 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;
+
+ if (to_i2c_client(dev)->irq)
+ disable_irq(to_i2c_client(dev)->irq);
+
+ 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;
+
+ if (to_i2c_client(dev)->irq)
+ enable_irq(to_i2c_client(dev)->irq);
+
+ 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_STATUS_1_REG, TAS675X_FAULT_TRACK,
+ "CP / OUTM Fault" },
+ { 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 fault register needs to be cleared.
+ */
+static bool tas675x_check_faults(struct tas675x_priv *tas)
+{
+ struct device *dev = tas->dev;
+ bool needs_clear = 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, ®);
+ if (ret) {
+ if (f->flags & TAS675X_FAULT_CRITICAL) {
+ dev_err(dev, "failed to read %s: %d\n", f->name, ret);
+ return needs_clear;
+ }
+ continue;
+ }
+
+ if (reg)
+ needs_clear = true;
+
+ /* Skip logging stream-dependent events when no stream is active */
+ if ((f->flags & TAS675X_FAULT_ACTIVE) &&
+ !READ_ONCE(tas->active_playback_dais) &&
+ !READ_ONCE(tas->active_capture_dais))
+ continue;
+
+ /* 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 needs_clear;
+}
+
+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;
+
+ if (!tas675x_check_faults(tas))
+ return IRQ_NONE;
+
+ 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, "powerdown", GPIOD_OUT_HIGH);
+ if (IS_ERR(tas->pd_gpio))
+ return dev_err_probe(tas->dev, PTR_ERR(tas->pd_gpio), "Failed powerdown-gpios\n");
+
+ tas->stby_gpio = devm_gpiod_get_optional(tas->dev, "standby", GPIOD_OUT_HIGH);
+ if (IS_ERR(tas->stby_gpio))
+ return dev_err_probe(tas->dev, PTR_ERR(tas->stby_gpio), "Failed standby-gpios\n");
+
+ if (!tas->pd_gpio && !tas->stby_gpio)
+ return dev_err_probe(tas->dev, -EINVAL,
+ "At least one of powerdown-gpios or standby-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,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 = "tas67524",
+ .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 TAS67524 Audio Amplifier Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas67524.h b/sound/soc/codecs/tas67524.h
new file mode 100644
index 000000000000..95330064b686
--- /dev/null
+++ b/sound/soc/codecs/tas67524.h
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC Texas Instruments TAS67524 Quad-Channel Audio Amplifier
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ * Author: Sen Wang <sen@ti.com>
+ */
+
+#ifndef __TAS67524_H__
+#define __TAS67524_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] 6+ messages in thread
* [PATCH v3 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation
2026-04-03 5:06 [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver Sen Wang
2026-04-03 5:06 ` [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524 Sen Wang
2026-04-03 5:06 ` [PATCH v3 2/4] ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver Sen Wang
@ 2026-04-03 5:06 ` Sen Wang
2026-04-03 5:06 ` [PATCH v3 4/4] MAINTAINERS: add entry for TAS67524 audio amplifier Sen Wang
3 siblings, 0 replies; 6+ messages in thread
From: Sen Wang @ 2026-04-03 5:06 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 TAS67524 driver, covering
DSP signal path modes, volume controls, load diagnostics, and fault
monitoring.
Signed-off-by: Sen Wang <sen@ti.com>
---
Changes in v3:
- Added register section
- Added few addintional notes for clarification
Changes in v2:
- None
Documentation/sound/codecs/index.rst | 1 +
Documentation/sound/codecs/tas67524.rst | 686 ++++++++++++++++++++++++
2 files changed, 687 insertions(+)
create mode 100644 Documentation/sound/codecs/tas67524.rst
diff --git a/Documentation/sound/codecs/index.rst b/Documentation/sound/codecs/index.rst
index 2cb95d87bbef..f7e842c68407 100644
--- a/Documentation/sound/codecs/index.rst
+++ b/Documentation/sound/codecs/index.rst
@@ -7,3 +7,4 @@ Codec-Specific Information
:maxdepth: 2
cs35l56
+ tas67524
diff --git a/Documentation/sound/codecs/tas67524.rst b/Documentation/sound/codecs/tas67524.rst
new file mode 100644
index 000000000000..3b99c31593cf
--- /dev/null
+++ b/Documentation/sound/codecs/tas67524.rst
@@ -0,0 +1,686 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================================
+TAS675x Codec Mixer Controls
+======================================
+
+This document describes the ALSA mixer controls for the TAS675x
+4-channel amplifier driver.
+
+For device tree bindings, see:
+Documentation/devicetree/bindings/sound/ti,tas67524.yaml
+
+DSP Signal Path Mode
+====================
+
+DSP Signal Path Mode
+--------------------
+
+:Description: Signal processing mode selection.
+:Type: Enumerated
+:Default: Normal
+:Options: Normal, LLP, FFLP
+:Register: 0x32 bits [1:0]
+
+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)
+:Register: 0x4A (CH1/CH2), 0x4B (CH3/CH4)
+
+Analog Gain Ramp Step
+---------------------
+
+:Description: Anti-pop ramp step duration for analog gain transitions.
+:Type: Enumerated
+:Default: 15us
+:Options: 15us, 60us, 200us, 400us
+:Register: 0x4E bits [3:2]
+
+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)
+:Register: 0x40 (CH1), 0x41 (CH2), 0x42 (CH3), 0x43 (CH4)
+
+Volume Ramp Down Rate
+---------------------
+
+:Description: Update frequency during mute transition.
+:Type: Enumerated
+:Default: 16 FS
+:Options: 4 FS, 16 FS, 32 FS, Instant
+:Register: 0x44 bits [7:6]
+
+Volume Ramp Down Step
+---------------------
+
+:Description: dB change per update during mute.
+:Type: Enumerated
+:Default: 0.5dB
+:Options: 4dB, 2dB, 1dB, 0.5dB
+:Register: 0x44 bits [5:4]
+
+Volume Ramp Up Rate
+-------------------
+
+:Description: Update frequency during unmute transition.
+:Type: Enumerated
+:Default: 16 FS
+:Options: 4 FS, 16 FS, 32 FS, Instant
+:Register: 0x44 bits [3:2]
+
+Volume Ramp Up Step
+-------------------
+
+:Description: dB change per update during unmute.
+:Type: Enumerated
+:Default: 0.5dB
+:Options: 4dB, 2dB, 1dB, 0.5dB
+:Register: 0x44 bits [1:0]
+
+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
+:Register: 0x46 bits [1:0]
+
+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
+:Register: 0x46 bits [3:2]
+
+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)
+:Register: 0x47 bit [0] (CH1), bit [1] (CH2), bit [2] (CH3), bit [3] (CH4)
+
+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.
+:Register: 0x47 bit [4]
+
+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
+:Register: 0x48 bits [7:4] (CH1), bits [3:0] (CH2),
+ 0x49 bits [7:4] (CH3), bits [3:0] (CH4)
+: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
+:Register: 0x61 bits [1:0]
+
+SS Triangle Range
+-----------------
+
+:Description: Frequency deviation range for Triangle spread spectrum.
+:Type: Enumerated
+:Default: 6.5%
+:Options: 6.5%, 13.5%, 5%, 10%
+:Register: 0x62 bits [1:0]
+: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%
+:Register: 0x62 bits [6:4]
+: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
+:Register: 0x62 bits [3:2]
+:Note: Applies only when Spread Spectrum Mode includes Random.
+
+SS Triangle Dwell Min
+---------------------
+
+:Description: Minimum dwell time at Triangle spread spectrum frequency extremes.
+:Type: Integer
+:Default: 0
+:Range: 0 to 15 (0 = feature disabled)
+:Register: 0x66 bits [7:4]
+:Note: Counts in FSS clock cycles. The modulator holds the extreme
+ frequency for at least this many FSS cycles before reversing.
+ When Dwell Min equals Dwell Max, the dwell feature is inactive.
+ For FSS values at each PWM frequency refer to the "Spread
+ Spectrum" section of the TRM.
+
+SS Triangle Dwell Max
+---------------------
+
+:Description: Maximum dwell time at Triangle spread spectrum frequency extremes.
+:Type: Integer
+:Default: 0
+:Range: 0 to 15 (0 = feature disabled)
+:Register: 0x66 bits [3:0]
+:Note: Counts in FSS clock cycles. Must be >= Dwell Min. When Dwell Max
+ equals Dwell Min, the dwell feature is inactive.
+
+Hardware Protection
+===================
+
+OTSD Auto Recovery Switch
+--------------------------
+
+:Description: Enables automatic recovery from over-temperature shutdown.
+:Type: Boolean Switch
+:Default: Disabled (0)
+:Register: 0x8F bit [1]
+: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
+:Options: Level 4, Level 3, Level 2, Level 1
+:Register: 0x55 bits [1:0]
+:Note: Level 4 is the least sensitive (highest trip current); Level 1 is
+ the most sensitive. The exact ILIM values depend on operating
+ conditions (PVDD voltage, switching frequency, and temperature).
+ Refer to the Electrical Characteristics table and the
+ "Overcurrent Limit (Cycle-By-Cycle)" section of the TRM.
+
+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
+:Register: 0xE2 bits [6:4] (CH1), bits [2:0] (CH2),
+ 0xE3 bits [6:4] (CH3), bits [2:0] (CH4)
+
+Temperature and Voltage Monitoring
+===================================
+
+PVDD Sense
+----------
+
+:Description: Supply voltage sense register.
+:Type: Integer (read-only)
+:Range: 0 to 255
+:Conversion: value × 0.19 V
+:Register: 0x74
+
+Global Temperature
+------------------
+
+:Description: Global die temperature sense register.
+:Type: Integer (read-only)
+:Range: 0 to 255
+:Conversion: (value × 0.5 °C) − 50 °C
+:Register: 0x75
+
+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
+:Register: 0xBB bits [7:6] (CH1), bits [5:4] (CH2),
+ 0xBC bits [3:2] (CH3), bits [1:0] (CH4)
+
+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 initialization 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.
+
+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)
+:Register: 0xB0 bit [0]
+:Note: Active-low, 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)
+:Register: 0xB1 bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+: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
+:Register: 0xB2 bits [7:6]
+
+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
+:Register: 0xB2 bits [5:4]
+
+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
+:Register: 0xB2 bits [3:2]
+
+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
+:Register: 0xB2 bits [1:0]
+
+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
+:Register: 0xB3 bits [7:4] (CH1), bits [3:0] (CH2),
+ 0xB4 bits [7:4] (CH3), bits [3:0] (CH4)
+
+DC LDG Result
+~~~~~~~~~~~~~
+
+:Description: Overall DC diagnostic result register.
+:Type: Integer (read-only)
+:Range: 0x00 to 0xFF
+:Register: 0xC2
+: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
+:Register: 0xC0 bits [7:4] (CH1), bits [3:0] (CH2),
+ 0xC1 bits [7:4] (CH3), bits [3:0] (CH4)
+: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
+:Register: 0xC2 bit [7] (CH1), bit [6] (CH2), bit [5] (CH3), bit [4] (CH4)
+
+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
+:Register: 0xD9 bits [7:6]/[5:4]/[3:2]/[1:0] (MSB for CH1–CH4),
+ 0xDA (CH1 LSB), 0xDB (CH2 LSB), 0xDC (CH3 LSB), 0xDD (CH4 LSB)
+
+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 LDG 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)
+:Register: 0xB5 bit [4]
+: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
+:Register: 0xB8
+
+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)
+:Register: 0xC3 (CH1 Real), 0xC4 (CH1 Imag), 0xC5 (CH2 Real), 0xC6 (CH2 Imag),
+ 0xC7 (CH3 Real), 0xC8 (CH3 Imag), 0xC9 (CH4 Real), 0xCA (CH4 Imag)
+:Note: Scale set by AC LDG Gain.
+
+Speaker Protection & Detection
+-------------------------------
+
+Tweeter Detection Switch
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Enables tweeter detection using the AC impedance magnitude comparator.
+:Type: Boolean Switch
+:Default: Enabled (1)
+:Register: 0xB6 bit [0]
+:Note: The underlying register bit is TWEETER DETECT DISABLE (active-low).
+ Control value 1 = detection enabled (register bit 0), 0 = disabled
+ (register bit 1).
+
+Tweeter Detect Threshold
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Magnitude threshold for tweeter detection.
+:Type: Integer
+:Default: 0
+:Range: 0x00 to 0xFF
+:Resolution: 0.8 ohm/code (AC LDG Gain=0) or 0.1 ohm/code (AC LDG Gain=1)
+:Register: 0xB7
+
+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
+:Register: 0xCB bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+
+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)
+:Register: 0x3A bit [0]
+
+PVDD Foldback Switch
+--------------------
+
+:Description: Enables automatic gain limiting when supply voltage drops
+ (Automatic Gain Limiter).
+:Type: Boolean Switch
+:Default: Disabled (0)
+:Register: 0x3A bit [4]
+
+DC Blocker Bypass Switch
+------------------------
+
+:Description: Bypasses the DC-blocking high-pass filter.
+:Type: Boolean Switch
+:Default: Not bypassed (0)
+:Register: 0x39 bit [0]
+
+Clip Detect Switch
+------------------
+
+:Description: Enables DSP-based clip detection (Pseudo-Analog Clip Detect).
+:Type: Boolean Switch
+:Default: Disabled (0)
+:Register: 0x93 bit [6]
+
+Audio SDOUT Switch
+------------------
+
+:Description: Routes post-processed audio to the SDOUT pin instead of
+ Vpredict data.
+:Type: Boolean Switch
+:Default: Disabled (0)
+:Register: 0x3A bit [5]
+:Note: When enabled, replaces Vpredict data on SDOUT with post-processed
+ SDIN. All SDOUT configurations that apply to Vpredict also apply
+ to SDIN-to-SDOUT transmission.
+
+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)
+:Register: 0x37 bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+
+RTLDG Clip Mask Switch
+----------------------
+
+:Description: Suppresses impedance updates during clipping events.
+:Type: Boolean Switch
+:Default: Enabled (1)
+:Register: 0x37 bit [4]
+
+ISENSE Calibration Switch
+--------------------------
+
+:Description: Enables current sense calibration for accurate impedance
+ measurements.
+:Type: Boolean Switch
+:Default: Disabled (0)
+:Register: 0x5B bit [3]
+
+RTLDG Open Load Threshold
+--------------------------
+
+:Description: DSP coefficient for open load fault detection threshold.
+:Type: DSP coefficient (extended control)
+:Register: DSP Book 0x8C, page 0x22, 0x98
+
+RTLDG Short Load Threshold
+---------------------------
+
+:Description: DSP coefficient for shorted load fault detection threshold.
+:Type: DSP coefficient (extended control)
+:Register: DSP Book 0x8C, page 0x22, 0x9C
+
+CHx RTLDG Impedance
+-------------------
+
+:Description: Real-time load impedance per channel (x = 1, 2, 3, 4).
+:Type: Float (read-only, displayed in ohms)
+:Register: 0xD1–0xD2 (CH1), 0xD3–0xD4 (CH2), 0xD5–0xD6 (CH3), 0xD7–0xD8 (CH4)
+: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
+:Register: 0x8B
+:Note: See bit encoding table at the start of this section.
+ Reading the register clears all latched bits.
+
+Driver 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,tas67524.yaml
+- ALSA Control Name Conventions: Documentation/sound/designs/control-names.rst
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 4/4] MAINTAINERS: add entry for TAS67524 audio amplifier
2026-04-03 5:06 [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver Sen Wang
` (2 preceding siblings ...)
2026-04-03 5:06 ` [PATCH v3 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
@ 2026-04-03 5:06 ` Sen Wang
3 siblings, 0 replies; 6+ messages in thread
From: Sen Wang @ 2026-04-03 5:06 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 TAS67524 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..a3bf005e38b1 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,tas67524.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/tas67524*
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/tas67524*.*
F: sound/soc/codecs/tlv320*.*
F: sound/soc/codecs/tpa6130a2.*
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524
2026-04-03 5:06 ` [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524 Sen Wang
@ 2026-04-03 7:20 ` Krzysztof Kozlowski
0 siblings, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2026-04-03 7:20 UTC (permalink / raw)
To: Sen Wang, 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
On 03/04/2026 07:06, Sen Wang wrote:
> Add device tree binding for the Texas Instruments TAS67524 family
> of four-channel Class-D audio amplifiers with integrated DSP.
>
> Signed-off-by: Sen Wang <sen@ti.com>
> ---
> Changes in v3:
> - Renamed ti,tas675x to ti,tas67524.yaml
> - Removed tas6754 compatible instance
> - Changed pd-gpios to powerdown-gpios
> - Cleanup unnessary "|" formatting
>
> Changes in v2:
> - None
>
> .../bindings/sound/ti,tas67524.yaml | 277 ++++++++++++++++++
> 1 file changed, 277 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/sound/ti,tas67524.yaml
>
> diff --git a/Documentation/devicetree/bindings/sound/ti,tas67524.yaml b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
> new file mode 100644
> index 000000000000..b8da1360e698
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
> @@ -0,0 +1,277 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/ti,tas67524.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments TAS67524 Audio Amplifier
> +
> +maintainers:
> + - Sen Wang <sen@ti.com>
> +
> +description:
> + The TAS67524 is a four-channel, digital-input, automotive
> + Class-D audio amplifier with load diagnostics and an integrated
> + DSP for audio processing.
> +
> +allOf:
> + - $ref: dai-common.yaml#
> +
> +properties:
> + compatible:
> + enum:
> + - ti,tas67524
And where did you give me chance to answer to your comment? You answered
2:40 AM of my time and four hours later - 7 AM my time - you send v3.
No, read again the feedback. I am not reviewing the rest.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-03 7:20 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 5:06 [PATCH v3 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver Sen Wang
2026-04-03 5:06 ` [PATCH v3 1/4] ASoC: dt-bindings: Add ti,tas67524 Sen Wang
2026-04-03 7:20 ` Krzysztof Kozlowski
2026-04-03 5:06 ` [PATCH v3 2/4] ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver Sen Wang
2026-04-03 5:06 ` [PATCH v3 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation Sen Wang
2026-04-03 5:06 ` [PATCH v3 4/4] MAINTAINERS: add entry for TAS67524 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