diff options
| author | Mark Brown <broonie@kernel.org> | 2026-04-27 02:36:18 +0300 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-04-27 02:36:18 +0300 |
| commit | a960faa424ae459a7843da2734fa51580814b89a (patch) | |
| tree | 82a0d46867459b2830f8313052334510cfdbe957 | |
| parent | eb0d565514734383a3fbcb71e5c2c250d155d653 (diff) | |
| parent | 3507e568d63486ebbc857a8e74e49a04e6311bc2 (diff) | |
| download | linux-a960faa424ae459a7843da2734fa51580814b89a.tar.xz | |
ASoC: Add TAS67524 quad-channel Class-D amplifier driver
Sen Wang <sen@ti.com> says:
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. For setup &
test procedures, refer to the GitHub repository.
GitHub: https://github.com/SenWang125/tas67-linux
| -rw-r--r-- | Documentation/devicetree/bindings/sound/ti,tas67524.yaml | 280 | ||||
| -rw-r--r-- | Documentation/sound/codecs/tas675x.rst | 820 | ||||
| -rw-r--r-- | MAINTAINERS | 4 | ||||
| -rw-r--r-- | sound/soc/codecs/Kconfig | 12 | ||||
| -rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
| -rw-r--r-- | sound/soc/codecs/tas675x.c | 2194 | ||||
| -rw-r--r-- | sound/soc/codecs/tas675x.h | 367 |
7 files changed, 3679 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/sound/ti,tas67524.yaml b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml new file mode 100644 index 000000000000..812a4d39e2a5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml @@ -0,0 +1,280 @@ +# 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: + oneOf: + - items: + - enum: + - ti,tas6754 + - const: ti,tas67524 + - const: 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. + + 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>; + }; + }; + }; + }; + }; diff --git a/Documentation/sound/codecs/tas675x.rst b/Documentation/sound/codecs/tas675x.rst new file mode 100644 index 000000000000..36bcdf18d238 --- /dev/null +++ b/Documentation/sound/codecs/tas675x.rst @@ -0,0 +1,820 @@ +.. 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``. + +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. + + +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. + +Fault Monitoring +================ + +The driver monitors faults via IRQ or periodic polling (fallback when +no ``interrupts`` DT property is present). Detected faults are logged +and cleared so affected channels can recover. + +Kernel log messages use the format:: + + tas675x <addr>: <Name> Latched: 0x<value> + +Critical faults are logged at ``CRIT`` level; warnings at ``WARN``. + +The following fault registers are monitored: + +Critical Faults +--------------- + +These faults place affected channels into the FAULT state. The driver +issues fault clear (register 0x01 bit 3) to allow recovery. + +Overtemperature Shutdown (0x87) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [4] Global OTSD + [3:0] CH1-CH4 OTSD respectively + ======== ========================================== + +Fires when die temperature exceeds the OTSD threshold. If auto-recovery +is enabled (``OTSD Auto Recovery Switch``), channels return to their +previous state after cooling. Otherwise fault clear is required. + +Overcurrent / DC Fault (0x8E) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [7:4] Overcurrent Shutdown, CH1-CH4 respectively + [3:0] DC Fault, CH1-CH4 respectively + ======== ========================================== + +Overcurrent shutdown occurs when output current reaches the shutdown +threshold (e.g. output short to GND). DC fault fires when DC offset +exceeds the DCFAULT threshold. + +Real-Time Load Diagnostic Fault (0x8B) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [7:4] Shorted Load faults, CH1-CH4 respectively + [3:0] Open Load faults, CH1-CH4 respectively + ======== ========================================== + +Reported during playback when RTLDG detects impedance outside the +configured open/short thresholds. Requires RTLDG and current sense to +be enabled, and a sampling rate of 48 kHz or 96 kHz. + +CBC Fault/Warning (0x8D) +~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [7:4] Load current warning, CH1-CH4 respectively + [3:0] Load current fault, CH1-CH4 respectively + ======== ========================================== + +Cycle-by-cycle current limiting events. A warning fires when the +current limit is active for at least 25% of a 21.3 ms window. A fault +fires if the warning persists for 170.4 ms continuously. + +Warnings +-------- + +These conditions are logged but do not place channels into FAULT state. +Corresponding register bits are cleared when read. Nonetheless, the driver +still clears all events as warnings also latch the (mapped) FAULT pin. + +Power Fault (0x86) +~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [7] DVDD Power-On-Reset + [4] DVDD Undervoltage + [3] PVDD Overvoltage + [2] VBAT Overvoltage + [1] PVDD Undervoltage + [0] VBAT Undervoltage + ======== ========================================== + +Supply-related events. DVDD POR at bit 7 is expected after every +power-up and can be ignored. + +CP / OUTM Soft Short Fault (0x7D) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [5:2] OUTM soft short, CH4-CH1 respectively + [1] Charge Pump UVLO (latched) + [0] Charge Pump UVLO (unlatched/live status) + ======== ========================================== + +Per-channel out_minus soft short faults and charge pump undervoltage lockout. + +Clock Fault (0x8A) +~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [0] Clock error + ======== ========================================== + +Fires on clock halt or unsupported SCLK/FSYNC ratio. Only logged when +a stream is active (suppressed during idle to avoid noise from normal +stream stop/start transitions). See `Clock Fault Behaviour`_ below. + +Overtemperature Warning (0x88) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [4] Global OTW + [3:0] CH1-CH4 OTW respectively + ======== ========================================== + +Fires when temperature crosses the OTW threshold. The device continues +to operate. Only logged on change to avoid log spam. + +Clip Warning (0x89) +~~~~~~~~~~~~~~~~~~~ + + ======== ========================================== + Bits Description + ======== ========================================== + [3:0] CH1-CH4 clip detected respectively + ======== ========================================== + +Fires when the audio signal magnitude exceeds the clip detection +threshold. Requires Clip Detect to be enabled. Only logged when a +stream is active. + +Driver Known Limitations +======================== + +Clock Fault Behaviour +--------------------- + +On Stream Stop +~~~~~~~~~~~~~~ + +Every time a playback stream stops the FAULT pin briefly asserts. The +ASoC PCM trigger stop sequence calls DAIs in reverse order, so the CPU +DAI stops SCLK 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 (conservative) ~0.5 second 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 diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..4f9b28a780be 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26321,17 +26321,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/tas675x* F: include/sound/tas2*.h F: include/sound/tlv320*.h F: sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -26345,6 +26348,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/tas675x*.* F: sound/soc/codecs/tlv320*.* F: sound/soc/codecs/tpa6130a2.* diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cf94a1c756e0..c43861b7d41e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -275,6 +275,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_TAS571X imply SND_SOC_TAS5720 imply SND_SOC_TAS6424 + imply SND_SOC_TAS675X imply SND_SOC_TDA7419 imply SND_SOC_TFA9879 imply SND_SOC_TFA989X @@ -2237,6 +2238,17 @@ config SND_SOC_TAS6424 Enable support for Texas Instruments TAS6424 high-efficiency digital input quad-channel Class-D audio power amplifiers. +config SND_SOC_TAS675X + tristate "Texas Instruments TAS675x Quad-Channel Audio Amplifier" + depends on I2C + select REGMAP_I2C + help + Enable support for Texas Instruments TAS675x quad-channel Class-D + audio power amplifiers (TAS6754, TAS67524). The devices support I2S + and TDM interfaces with real-time voltage and current sense feedback + via SDOUT, and provide DC/AC/real-time load diagnostics for speaker + monitoring. + config SND_SOC_TDA7419 tristate "ST TDA7419 audio processor" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 172861d17cfd..106fdc140d42 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -318,6 +318,7 @@ snd-soc-tas571x-y := tas571x.o snd-soc-tas5720-y := tas5720.o snd-soc-tas5805m-y := tas5805m.o snd-soc-tas6424-y := tas6424.o +snd-soc-tas675x-y := tas675x.o snd-soc-tda7419-y := tda7419.o snd-soc-tas2770-y := tas2770.o snd-soc-tas2781-comlib-y := tas2781-comlib.o @@ -760,6 +761,7 @@ obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o obj-$(CONFIG_SND_SOC_TAS5805M) += snd-soc-tas5805m.o obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o +obj-$(CONFIG_SND_SOC_TAS675X) += snd-soc-tas675x.o obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c new file mode 100644 index 000000000000..6f89a422f3c6 --- /dev/null +++ b/sound/soc/codecs/tas675x.c @@ -0,0 +1,2194 @@ +// 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 "tas675x.h" + +#define TAS675X_FAULT_CHECK_INTERVAL_MS 200 + +enum tas675x_type { + TAS67524, +}; + +struct tas675x_reg_param { + u8 page; + u8 reg; + u32 val; +}; + +struct tas675x_priv { + struct device *dev; + struct regmap *regmap; + enum tas675x_type dev_type; + /* Custom regmap lock; protects writes across books */ + 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 32 bit big-endian */ + buf[0] = (val >> 24) & 0xFF; + 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[0] << 24) | (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; + /* threshold reg ranges up to 24bit */ + uinfo->value.integer.max = 0x00FFFFFF; + 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_assert(ARRAY_SIZE(tas675x_dsp_defaults) == TAS675X_DSP_PARAM_NUM); + +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 */ + ret = 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); + ret |= 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); + if (ret) { + dev_err(tas->dev, + "DC LDG: abort 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_ldg_ctrl; + } + + /* 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 */ + ret = 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); + ret |= 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); + if (ret) { + dev_err(tas->dev, + "DC LDG: LOAD_DIAG 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; + } + + /* 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: SLEEP 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); + +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; + u8 buf[2]; + int ret; + + ret = regmap_bulk_read(tas->regmap, msb_reg, buf, 2); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (buf[0] << 8) | buf[1]; + 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), +}; + +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 (rate > 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", + rate); + 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, + } +}; + +/* + * Enable regulators and release hardware reset GPIOs. + * The device is not I2C-accessible until this returns. + */ +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); +} + +/* + * Write 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. + * + * For deciphering fault messages, see "Fault Monitoring" in + * Documentation/sound/codecs/tas675x.rst + */ +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; + irqreturn_t ret = IRQ_NONE; + + if (pm_runtime_resume_and_get(tas->dev) < 0) + return IRQ_NONE; + + if (tas675x_check_faults(tas)) { + regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR); + ret = IRQ_HANDLED; + } + + pm_runtime_mark_last_busy(tas->dev); + pm_runtime_put_autosuspend(tas->dev); + return ret; +} + +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); + + disable_delayed_work_sync(&tas->fault_check_work); + if (client->irq) + disable_irq(client->irq); + + 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 *)TAS67524 }, + { } +}; +MODULE_DEVICE_TABLE(of, tas675x_of_match); + +static const struct i2c_device_id tas675x_i2c_id[] = { + { "tas67524", TAS67524 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas675x_i2c_id); + +static struct i2c_driver tas675x_i2c_driver = { + .driver = { + .name = "tas675x", + .of_match_table = tas675x_of_match, + .pm = pm_ptr(&tas675x_pm_ops), + }, + .probe = tas675x_i2c_probe, + .remove = tas675x_i2c_remove, + .id_table = tas675x_i2c_id, +}; + +module_i2c_driver(tas675x_i2c_driver); + +MODULE_AUTHOR("Sen Wang <sen@ti.com>"); +MODULE_DESCRIPTION("ASoC TAS675x Audio Amplifier Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas675x.h b/sound/soc/codecs/tas675x.h new file mode 100644 index 000000000000..db29bb377336 --- /dev/null +++ b/sound/soc/codecs/tas675x.h @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA SoC Texas Instruments TAS675x Quad-Channel Audio Amplifier + * + * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Sen Wang <sen@ti.com> + */ + +#ifndef __TAS675X_H__ +#define __TAS675X_H__ + +/* + * Book 0, Page 0 — Register Addresses + */ + +#define TAS675X_PAGE_SIZE 256 +#define TAS675X_PAGE_REG(page, reg) ((page) * TAS675X_PAGE_SIZE + (reg)) + +/* Page Control & Basic Config */ +#define TAS675X_PAGE_CTRL_REG 0x00 +#define TAS675X_RESET_REG 0x01 +#define TAS675X_OUTPUT_CTRL_REG 0x02 +#define TAS675X_STATE_CTRL_CH1_CH2_REG 0x03 +#define TAS675X_STATE_CTRL_CH3_CH4_REG 0x04 +#define TAS675X_ISENSE_CTRL_REG 0x05 +#define TAS675X_DC_DETECT_CTRL_REG 0x06 + +/* Serial Audio Port */ +#define TAS675X_SCLK_INV_CTRL_REG 0x20 +#define TAS675X_AUDIO_IF_CTRL_REG 0x21 +#define TAS675X_SDIN_CTRL_REG 0x23 +#define TAS675X_SDOUT_CTRL_REG 0x25 +#define TAS675X_SDIN_OFFSET_MSB_REG 0x27 +#define TAS675X_SDIN_AUDIO_OFFSET_REG 0x28 +#define TAS675X_SDIN_LL_OFFSET_REG 0x29 +#define TAS675X_SDIN_CH_SWAP_REG 0x2A +#define TAS675X_SDOUT_OFFSET_MSB_REG 0x2C +#define TAS675X_VPREDICT_OFFSET_REG 0x2D +#define TAS675X_ISENSE_OFFSET_REG 0x2E +#define TAS675X_SDOUT_EN_REG 0x31 +#define TAS675X_LL_EN_REG 0x32 + +/* DSP & Core Audio Control */ +#define TAS675X_RTLDG_EN_REG 0x37 +#define TAS675X_DC_BLOCK_BYP_REG 0x39 +#define TAS675X_DSP_CTRL_REG 0x3A +#define TAS675X_PAGE_AUTO_INC_REG 0x3B + +/* Volume & Mute */ +#define TAS675X_DIG_VOL_CH1_REG 0x40 +#define TAS675X_DIG_VOL_CH2_REG 0x41 +#define TAS675X_DIG_VOL_CH3_REG 0x42 +#define TAS675X_DIG_VOL_CH4_REG 0x43 +#define TAS675X_DIG_VOL_RAMP_CTRL_REG 0x44 +#define TAS675X_DIG_VOL_COMBINE_CTRL_REG 0x46 +#define TAS675X_AUTO_MUTE_EN_REG 0x47 +#define TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG 0x48 +#define TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG 0x49 + +/* Analog Gain & Power Stage */ +#define TAS675X_ANALOG_GAIN_CH1_CH2_REG 0x4A +#define TAS675X_ANALOG_GAIN_CH3_CH4_REG 0x4B +#define TAS675X_ANALOG_GAIN_RAMP_CTRL_REG 0x4E +#define TAS675X_PULSE_INJECTION_EN_REG 0x52 +#define TAS675X_CBC_CTRL_REG 0x54 +#define TAS675X_CURRENT_LIMIT_CTRL_REG 0x55 +#define TAS675X_DAC_CLK_REG 0x5A +#define TAS675X_ISENSE_CAL_REG 0x5B + +/* Spread Spectrum & PWM Phase */ +#define TAS675X_PWM_PHASE_CTRL_REG 0x60 +#define TAS675X_SS_CTRL_REG 0x61 +#define TAS675X_SS_RANGE_CTRL_REG 0x62 +#define TAS675X_SS_DWELL_CTRL_REG 0x66 +#define TAS675X_RAMP_PHASE_CTRL_GPO_REG 0x68 +#define TAS675X_PWM_PHASE_M_CTRL_CH1_REG 0x69 +#define TAS675X_PWM_PHASE_M_CTRL_CH2_REG 0x6A +#define TAS675X_PWM_PHASE_M_CTRL_CH3_REG 0x6B +#define TAS675X_PWM_PHASE_M_CTRL_CH4_REG 0x6C + +/* Status & Reporting */ +#define TAS675X_AUTO_MUTE_STATUS_REG 0x71 +#define TAS675X_STATE_REPORT_CH1_CH2_REG 0x72 +#define TAS675X_STATE_REPORT_CH3_CH4_REG 0x73 +#define TAS675X_PVDD_SENSE_REG 0x74 +#define TAS675X_TEMP_GLOBAL_REG 0x75 +#define TAS675X_FS_MON_REG 0x76 +#define TAS675X_SCLK_MON_REG 0x77 +#define TAS675X_REPORT_ROUTING_1_REG 0x7C + +/* Memory Paging & Book Control */ +#define TAS675X_SETUP_REG1 0x7D +#define TAS675X_SETUP_REG2 0x7E +#define TAS675X_BOOK_CTRL_REG 0x7F + +/* Fault Status */ +#define TAS675X_POWER_FAULT_STATUS_1_REG 0x7D +#define TAS675X_POWER_FAULT_STATUS_2_REG 0x80 +#define TAS675X_OT_FAULT_REG 0x81 +#define TAS675X_OTW_STATUS_REG 0x82 +#define TAS675X_CLIP_WARN_STATUS_REG 0x83 +#define TAS675X_CBC_WARNING_STATUS_REG 0x85 + +/* Latched Fault Registers */ +#define TAS675X_POWER_FAULT_LATCHED_REG 0x86 +#define TAS675X_OTSD_LATCHED_REG 0x87 +#define TAS675X_OTW_LATCHED_REG 0x88 +#define TAS675X_CLIP_WARN_LATCHED_REG 0x89 +#define TAS675X_CLK_FAULT_LATCHED_REG 0x8A +#define TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG 0x8B +#define TAS675X_CBC_FAULT_WARN_LATCHED_REG 0x8D +#define TAS675X_OC_DC_FAULT_LATCHED_REG 0x8E +#define TAS675X_OTSD_RECOVERY_EN_REG 0x8F + +/* Protection & Routing Controls */ +#define TAS675X_REPORT_ROUTING_2_REG 0x90 +#define TAS675X_REPORT_ROUTING_3_REG 0x91 +#define TAS675X_REPORT_ROUTING_4_REG 0x92 +#define TAS675X_CLIP_DETECT_CTRL_REG 0x93 +#define TAS675X_REPORT_ROUTING_5_REG 0x94 + +/* GPIO Pin Configuration */ +#define TAS675X_GPIO1_OUTPUT_SEL_REG 0x95 +#define TAS675X_GPIO2_OUTPUT_SEL_REG 0x96 +#define TAS675X_GPIO_INPUT_SLEEP_HIZ_REG 0x9B +#define TAS675X_GPIO_INPUT_PLAY_SLEEP_REG 0x9C +#define TAS675X_GPIO_INPUT_MUTE_REG 0x9D +#define TAS675X_GPIO_INPUT_SYNC_REG 0x9E +#define TAS675X_GPIO_INPUT_SDIN2_REG 0x9F +#define TAS675X_GPIO_CTRL_REG 0xA0 +#define TAS675X_GPIO_INVERT_REG 0xA1 + +/* Load Diagnostics Config */ +#define TAS675X_DC_LDG_CTRL_REG 0xB0 +#define TAS675X_DC_LDG_LO_CTRL_REG 0xB1 +#define TAS675X_DC_LDG_TIME_CTRL_REG 0xB2 +#define TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG 0xB3 +#define TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG 0xB4 +#define TAS675X_AC_LDG_CTRL_REG 0xB5 +#define TAS675X_TWEETER_DETECT_CTRL_REG 0xB6 +#define TAS675X_TWEETER_DETECT_THRESH_REG 0xB7 +#define TAS675X_AC_LDG_FREQ_CTRL_REG 0xB8 +#define TAS675X_TEMP_CH1_CH2_REG 0xBB +#define TAS675X_TEMP_CH3_CH4_REG 0xBC +#define TAS675X_WARN_OT_MAX_FLAG_REG 0xBD + +/* DC Load Diagnostic Reports */ +#define TAS675X_DC_LDG_REPORT_CH1_CH2_REG 0xC0 +#define TAS675X_DC_LDG_REPORT_CH3_CH4_REG 0xC1 +#define TAS675X_DC_LDG_RESULT_REG 0xC2 +#define TAS675X_AC_LDG_REPORT_CH1_R_REG 0xC3 +#define TAS675X_AC_LDG_REPORT_CH1_I_REG 0xC4 +#define TAS675X_AC_LDG_REPORT_CH2_R_REG 0xC5 +#define TAS675X_AC_LDG_REPORT_CH2_I_REG 0xC6 +#define TAS675X_AC_LDG_REPORT_CH3_R_REG 0xC7 +#define TAS675X_AC_LDG_REPORT_CH3_I_REG 0xC8 +#define TAS675X_AC_LDG_REPORT_CH4_R_REG 0xC9 +#define TAS675X_AC_LDG_REPORT_CH4_I_REG 0xCA +#define TAS675X_TWEETER_REPORT_REG 0xCB + +/* RTLDG Impedance */ +#define TAS675X_CH1_RTLDG_IMP_MSB_REG 0xD1 +#define TAS675X_CH1_RTLDG_IMP_LSB_REG 0xD2 +#define TAS675X_CH2_RTLDG_IMP_MSB_REG 0xD3 +#define TAS675X_CH2_RTLDG_IMP_LSB_REG 0xD4 +#define TAS675X_CH3_RTLDG_IMP_MSB_REG 0xD5 +#define TAS675X_CH3_RTLDG_IMP_LSB_REG 0xD6 +#define TAS675X_CH4_RTLDG_IMP_MSB_REG 0xD7 +#define TAS675X_CH4_RTLDG_IMP_LSB_REG 0xD8 + +/* DC Load Diagnostic Resistance */ +#define TAS675X_DC_LDG_DCR_MSB_REG 0xD9 +#define TAS675X_CH1_DC_LDG_DCR_LSB_REG 0xDA +#define TAS675X_CH2_DC_LDG_DCR_LSB_REG 0xDB +#define TAS675X_CH3_DC_LDG_DCR_LSB_REG 0xDC +#define TAS675X_CH4_DC_LDG_DCR_LSB_REG 0xDD + +/* Over-Temperature Warning */ +#define TAS675X_OTW_CTRL_CH1_CH2_REG 0xE2 +#define TAS675X_OTW_CTRL_CH3_CH4_REG 0xE3 + +/* RESET_REG (all bits auto-clear) */ +#define TAS675X_DEVICE_RESET BIT(4) +#define TAS675X_FAULT_CLEAR BIT(3) +#define TAS675X_REGISTER_RESET BIT(0) + +/* STATE_CTRL and STATE_REPORT — Channel state values */ +#define TAS675X_STATE_DEEPSLEEP 0x00 +#define TAS675X_STATE_LOAD_DIAG 0x01 +#define TAS675X_STATE_SLEEP 0x02 +#define TAS675X_STATE_HIZ 0x03 +#define TAS675X_STATE_PLAY 0x04 + +/* Additional STATE_REPORT values */ +#define TAS675X_STATE_FAULT 0x05 +#define TAS675X_STATE_AUTOREC 0x06 + +/* Combined values for both channel pairs in one register */ +#define TAS675X_STATE_DEEPSLEEP_BOTH \ + (TAS675X_STATE_DEEPSLEEP | (TAS675X_STATE_DEEPSLEEP << 4)) +#define TAS675X_STATE_LOAD_DIAG_BOTH \ + (TAS675X_STATE_LOAD_DIAG | (TAS675X_STATE_LOAD_DIAG << 4)) +#define TAS675X_STATE_SLEEP_BOTH \ + (TAS675X_STATE_SLEEP | (TAS675X_STATE_SLEEP << 4)) +#define TAS675X_STATE_HIZ_BOTH \ + (TAS675X_STATE_HIZ | (TAS675X_STATE_HIZ << 4)) +#define TAS675X_STATE_PLAY_BOTH \ + (TAS675X_STATE_PLAY | (TAS675X_STATE_PLAY << 4)) +#define TAS675X_STATE_FAULT_BOTH \ + (TAS675X_STATE_FAULT | (TAS675X_STATE_FAULT << 4)) + +/* STATE_CTRL_CH1_CH2 / STATE_CTRL_CH3_CH4 — mute bits */ +#define TAS675X_CH1_MUTE_BIT BIT(7) +#define TAS675X_CH2_MUTE_BIT BIT(3) +#define TAS675X_CH_MUTE_BOTH (TAS675X_CH1_MUTE_BIT | TAS675X_CH2_MUTE_BIT) + +/* SCLK_INV_CTRL_REG */ +#define TAS675X_SCLK_INV_TX_BIT BIT(5) +#define TAS675X_SCLK_INV_RX_BIT BIT(4) +#define TAS675X_SCLK_INV_MASK (TAS675X_SCLK_INV_TX_BIT | TAS675X_SCLK_INV_RX_BIT) + +/* AUDIO_IF_CTRL_REG */ +#define TAS675X_TDM_EN_BIT BIT(4) +#define TAS675X_SAP_FMT_MASK GENMASK(3, 2) +#define TAS675X_SAP_FMT_I2S (0x00 << 2) +#define TAS675X_SAP_FMT_TDM (0x01 << 2) +#define TAS675X_SAP_FMT_RIGHT_J (0x02 << 2) +#define TAS675X_SAP_FMT_LEFT_J (0x03 << 2) +#define TAS675X_FS_PULSE_MASK GENMASK(1, 0) +#define TAS675X_FS_PULSE_SHORT 0x01 + +/* SDIN_CTRL_REG */ +#define TAS675X_SDIN_AUDIO_WL_MASK GENMASK(3, 2) +#define TAS675X_SDIN_LL_WL_MASK GENMASK(1, 0) +#define TAS675X_SDIN_WL_MASK (TAS675X_SDIN_AUDIO_WL_MASK | TAS675X_SDIN_LL_WL_MASK) + +/* SDOUT_CTRL_REG */ +#define TAS675X_SDOUT_SELECT_MASK GENMASK(7, 4) +#define TAS675X_SDOUT_SELECT_TDM_SDOUT1 0x00 +#define TAS675X_SDOUT_SELECT_NON_TDM 0x10 +#define TAS675X_SDOUT_VP_WL_MASK GENMASK(3, 2) +#define TAS675X_SDOUT_IS_WL_MASK GENMASK(1, 0) +#define TAS675X_SDOUT_WL_MASK (TAS675X_SDOUT_VP_WL_MASK | TAS675X_SDOUT_IS_WL_MASK) + +/* SDOUT_EN_REG */ +#define TAS675X_SDOUT_NON_TDM_SEL_MASK GENMASK(5, 4) +#define TAS675X_SDOUT_NON_TDM_SEL_VPREDICT (0x0 << 4) +#define TAS675X_SDOUT_NON_TDM_SEL_ISENSE (0x1 << 4) +#define TAS675X_SDOUT_EN_VPREDICT BIT(0) +#define TAS675X_SDOUT_EN_ISENSE BIT(1) +#define TAS675X_SDOUT_EN_NON_TDM_ALL GENMASK(1, 0) + +/* Word length values (shared by SDIN_CTRL and SDOUT_CTRL) */ +#define TAS675X_WL_16BIT 0x00 +#define TAS675X_WL_20BIT 0x01 +#define TAS675X_WL_24BIT 0x02 +#define TAS675X_WL_32BIT 0x03 + +/* SDIN_OFFSET_MSB_REG */ +#define TAS675X_SDIN_AUDIO_OFF_MSB_MASK GENMASK(7, 6) +#define TAS675X_SDIN_LL_OFF_MSB_MASK GENMASK(5, 4) + +/* SDOUT_OFFSET_MSB_REG */ +#define TAS675X_SDOUT_VP_OFF_MSB_MASK GENMASK(7, 6) +#define TAS675X_SDOUT_IS_OFF_MSB_MASK GENMASK(5, 4) + +/* RTLDG_EN_REG */ +#define TAS675X_RTLDG_CLIP_MASK_BIT BIT(4) +#define TAS675X_RTLDG_CH_EN_MASK GENMASK(3, 0) + +/* DC_LDG_CTRL_REG */ +#define TAS675X_LDG_ABORT_BIT BIT(7) +#define TAS675X_LDG_BUFFER_WAIT_MASK GENMASK(6, 5) +#define TAS675X_LDG_WAIT_BYPASS_BIT BIT(2) +#define TAS675X_SLOL_DISABLE_BIT BIT(1) +#define TAS675X_LDG_BYPASS_BIT BIT(0) + +/* DC_LDG_TIME_CTRL_REG */ +#define TAS675X_LDG_RAMP_SLOL_MASK GENMASK(7, 6) +#define TAS675X_LDG_SETTLING_SLOL_MASK GENMASK(5, 4) +#define TAS675X_LDG_RAMP_S2PG_MASK GENMASK(3, 2) +#define TAS675X_LDG_SETTLING_S2PG_MASK GENMASK(1, 0) + +/* AC_LDG_CTRL_REG */ +#define TAS675X_AC_DIAG_GAIN_BIT BIT(4) +#define TAS675X_AC_DIAG_START_MASK GENMASK(3, 0) + +/* DC_LDG_RESULT_REG */ +#define TAS675X_DC_LDG_LO_RESULT_MASK GENMASK(7, 4) +#define TAS675X_DC_LDG_PASS_MASK GENMASK(3, 0) + +/* Load Diagnostics Timing Constants */ +#define TAS675X_POLL_INTERVAL_US 10000 +#define TAS675X_STATE_TRANSITION_TIMEOUT_US 50000 +#define TAS675X_DC_LDG_TIMEOUT_US 300000 +#define TAS675X_AC_LDG_TIMEOUT_US 400000 + +/* GPIO_CTRL_REG */ +#define TAS675X_GPIO1_OUTPUT_EN BIT(7) +#define TAS675X_GPIO2_OUTPUT_EN BIT(6) +#define TAS675X_GPIO_CTRL_RSTVAL 0x22 + +/* GPIO output select values */ +#define TAS675X_GPIO_SEL_LOW 0x00 +#define TAS675X_GPIO_SEL_AUTO_MUTE_ALL 0x02 +#define TAS675X_GPIO_SEL_AUTO_MUTE_CH4 0x03 +#define TAS675X_GPIO_SEL_AUTO_MUTE_CH3 0x04 +#define TAS675X_GPIO_SEL_AUTO_MUTE_CH2 0x05 +#define TAS675X_GPIO_SEL_AUTO_MUTE_CH1 0x06 +#define TAS675X_GPIO_SEL_SDOUT2 0x08 +#define TAS675X_GPIO_SEL_SDOUT1 0x09 +#define TAS675X_GPIO_SEL_WARN 0x0A +#define TAS675X_GPIO_SEL_FAULT 0x0B +#define TAS675X_GPIO_SEL_CLOCK_SYNC 0x0E +#define TAS675X_GPIO_SEL_INVALID_CLK 0x0F +#define TAS675X_GPIO_SEL_HIGH 0x13 + +/* GPIO input function encoding (flag bit | function ID) */ +#define TAS675X_GPIO_FUNC_INPUT 0x100 + +/* Input Function IDs */ +#define TAS675X_GPIO_IN_ID_MUTE 0 +#define TAS675X_GPIO_IN_ID_PHASE_SYNC 1 +#define TAS675X_GPIO_IN_ID_SDIN2 2 +#define TAS675X_GPIO_IN_ID_DEEP_SLEEP 3 +#define TAS675X_GPIO_IN_ID_HIZ 4 +#define TAS675X_GPIO_IN_ID_PLAY 5 +#define TAS675X_GPIO_IN_ID_SLEEP 6 +#define TAS675X_GPIO_IN_NUM 7 + +#define TAS675X_GPIO_IN_MUTE (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_MUTE) +#define TAS675X_GPIO_IN_PHASE_SYNC \ + (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PHASE_SYNC) +#define TAS675X_GPIO_IN_SDIN2 (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SDIN2) +#define TAS675X_GPIO_IN_DEEP_SLEEP \ + (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_DEEP_SLEEP) +#define TAS675X_GPIO_IN_HIZ (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_HIZ) +#define TAS675X_GPIO_IN_PLAY (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PLAY) +#define TAS675X_GPIO_IN_SLEEP (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SLEEP) + +/* GPIO input 3-bit mux field masks */ +#define TAS675X_GPIO_IN_MUTE_MASK GENMASK(2, 0) +#define TAS675X_GPIO_IN_SYNC_MASK GENMASK(2, 0) +#define TAS675X_GPIO_IN_SDIN2_MASK GENMASK(6, 4) +#define TAS675X_GPIO_IN_DEEP_SLEEP_MASK GENMASK(6, 4) +#define TAS675X_GPIO_IN_HIZ_MASK GENMASK(2, 0) +#define TAS675X_GPIO_IN_PLAY_MASK GENMASK(6, 4) +#define TAS675X_GPIO_IN_SLEEP_MASK GENMASK(2, 0) + +/* Book addresses for tas675x_select_book() */ +#define TAS675X_BOOK_DEFAULT 0x00 +#define TAS675X_BOOK_DSP 0x8C + +/* DSP memory addresses (DSP Book) */ +#define TAS675X_DSP_PAGE_RTLDG 0x22 +#define TAS675X_DSP_RTLDG_OL_THRESH_REG 0x98 +#define TAS675X_DSP_RTLDG_SL_THRESH_REG 0x9C + +#define TAS675X_DSP_PARAM_ID_OL_THRESH 0 +#define TAS675X_DSP_PARAM_ID_SL_THRESH 1 + +/* Setup Mode Entry/Exit*/ +#define TAS675X_SETUP_ENTER_VAL1 0x11 +#define TAS675X_SETUP_ENTER_VAL2 0xFF +#define TAS675X_SETUP_EXIT_VAL 0x00 + +#endif /* __TAS675X_H__ */ |
