Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 0/2] ASoC: add Qualcomm WSA885X codec support
@ 2026-07-01 13:59 Prasad Kumpatla
  2026-07-01 13:59 ` [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings Prasad Kumpatla
  2026-07-01 13:59 ` [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver Prasad Kumpatla
  0 siblings, 2 replies; 5+ messages in thread
From: Prasad Kumpatla @ 2026-07-01 13:59 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, Prasad Kumpatla

Add support for the Qualcomm WSA885X Stereo smart speaker amplifier
accessed over I2C.

The series first documents the WSA885X devicetree binding,
including supplies, GPIOs, Interrupt and battery configuration.

It then adds the ASoC codec driver with register programming, serial
interface setup, clock handling, mute and gain control, reset handling,
interrupt support, runtime TDM slot-count configuration, and stream-time
power-state sequencing.

validated speaker playback on Shikra and Hawi platforms.

Changes in v2:
	- Drop "-i2c" suffix from the binding filename.
	- Convert qcom,battery-config to string-based configuration with improved documentation.
	- Remove register/value initialization data from DT and move it to the driver.
	- Replace interrupt-gpios with interrupts.
	- Address all review comments and update the binding to follow upstream DT conventions.
	- Removed redundant GPIO abstraction layer and direct GPIO API wrapper.
	- Dropped unnecessary NULL checks on driver private data.
	- Switched regulator management to devm_regulator_get_enable().
	- Converted I2C device ID table entries to use named initializers.
	- Cleaned up code per review feedback and aligned with upstream kernel conventions.

Prasad Kumpatla (2):
  dt-bindings: sound: add WSA8855 bindings
  ASoC: codecs: add Qualcomm WSA885X codec driver

 .../bindings/sound/qcom,wsa8855.yaml          |   83 +
 sound/soc/codecs/Kconfig                      |   11 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/WSA885X.c                    | 1490 +++++++++++++++++
 4 files changed, 1586 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml
 create mode 100644 sound/soc/codecs/WSA885X.c

-- 
2.34.1


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

* [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings
  2026-07-01 13:59 [PATCH v2 0/2] ASoC: add Qualcomm WSA885X codec support Prasad Kumpatla
@ 2026-07-01 13:59 ` Prasad Kumpatla
  2026-07-01 16:39   ` Rob Herring (Arm)
  2026-07-01 13:59 ` [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver Prasad Kumpatla
  1 sibling, 1 reply; 5+ messages in thread
From: Prasad Kumpatla @ 2026-07-01 13:59 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, Prasad Kumpatla

Document the Qualcomm WSA8855 stereo smart speaker amplifier.

The device uses a PCM audio interface and is controlled over I2C. Add the
required supplies, powerdown GPIO, interrupt line and optional battery
topology property.

Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
---
 .../bindings/sound/qcom,wsa8855.yaml          | 83 +++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml

diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml
new file mode 100644
index 000000000..b0a18ead3
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/qcom,wsa8855.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WSA8855 stereo smart speaker amplifier
+
+maintainers:
+  - Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+
+description:
+  WSA885X is a Qualcomm Aqstic stereo smart speaker amplifier. It uses a PCM
+  audio interface, an I2C control interface, and a Class-H amplifier path for
+  high efficiency, low output noise, and low idle power consumption.
+
+allOf:
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: qcom,wsa8855
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 0
+
+  powerdown-gpios:
+    description: GPIO controlling the SD_N powerdown pin.
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  vdd-1p8-supply: true
+
+  vdd-io-supply: true
+
+  qcom,battery-config:
+    description:
+      Battery topology connected to the speaker amplifier. 1S indicates one
+      battery cell in series, while 2S indicates two battery cells in series.
+    $ref: /schemas/types.yaml#/definitions/string
+    default: 1s
+    enum:
+      - 1s
+      - 2s
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - powerdown-gpios
+  - interrupts
+  - vdd-1p8-supply
+  - vdd-io-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        speaker@c {
+            compatible = "qcom,wsa8855";
+            reg = <0x0c>;
+            #sound-dai-cells = <0>;
+            powerdown-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>;
+            interrupt-parent = <&tlmm>;
+            interrupts = <77 IRQ_TYPE_EDGE_FALLING>;
+            vdd-1p8-supply = <&vreg_l2g_1p8>;
+            vdd-io-supply = <&vreg_l1g_1p2>;
+            qcom,battery-config = "1s";
+        };
+    };
+...
-- 
2.34.1


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

* [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver
  2026-07-01 13:59 [PATCH v2 0/2] ASoC: add Qualcomm WSA885X codec support Prasad Kumpatla
  2026-07-01 13:59 ` [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings Prasad Kumpatla
@ 2026-07-01 13:59 ` Prasad Kumpatla
  2026-07-01 14:14   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Prasad Kumpatla @ 2026-07-01 13:59 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, Prasad Kumpatla

Add an ASoC codec driver for the Qualcomm WSA885X stereo smart speaker
amplifier controlled over I2C.

The driver programs the register map, handles reset and interrupt support,
exposes DAI operations for PCM/TDM playback, and provides mixer controls
for usage mode, speaker volume and RX slot mask. Also program the TDM
control slot-count field from the runtime slot configuration so the
same codec path can be used with 2-slot, 4-slot, or 8-slot
Audio IF backends.

Keep stream-time power-state sequencing in the DAI callbacks and use regmap
for the control path.

Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
---
 sound/soc/codecs/Kconfig   |   11 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/wsa885x.c | 1490 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1503 insertions(+)
 create mode 100644 sound/soc/codecs/wsa885x.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index e9de333c5..8325a5af3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -367,6 +367,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_WSA881X
 	imply SND_SOC_WSA883X
 	imply SND_SOC_WSA884X
+	imply SND_SOC_WSA885X
 	imply SND_SOC_ZL38060
 	help
 	  Normally ASoC codec drivers are only built if a machine driver which
@@ -2770,6 +2771,16 @@ config SND_SOC_WSA884X
 	  This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D
 	  Smart Speaker Amplifier.
 
+config SND_SOC_WSA885X
+	tristate "WSA885X Codec"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  This enables support for Qualcomm WSA885X Stereo Smart Speaker
+	  Amplifier. The codec driver programs the amplifier register
+	  map and exposes the DAI and mixer controls used by Qualcomm
+	  audio machine drivers.
+
 config SND_SOC_ZL38060
 	tristate "Microsemi ZL38060 Connected Home Audio Processor"
 	depends on SPI_MASTER
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index aa0396e5b..7a37f4472 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -426,6 +426,7 @@ snd-soc-wm-hubs-y := wm_hubs.o
 snd-soc-wsa881x-y := wsa881x.o
 snd-soc-wsa883x-y := wsa883x.o
 snd-soc-wsa884x-y := wsa884x.o
+snd-soc-wsa885x-y := wsa885x.o
 snd-soc-zl38060-y := zl38060.o
 # Amp
 snd-soc-max9877-y := max9877.o
@@ -876,6 +877,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS)	+= snd-soc-wm-hubs.o
 obj-$(CONFIG_SND_SOC_WSA881X)	+= snd-soc-wsa881x.o
 obj-$(CONFIG_SND_SOC_WSA883X)	+= snd-soc-wsa883x.o
 obj-$(CONFIG_SND_SOC_WSA884X)	+= snd-soc-wsa884x.o
+obj-$(CONFIG_SND_SOC_WSA885X)	+= snd-soc-wsa885x.o
 obj-$(CONFIG_SND_SOC_ZL38060)	+= snd-soc-zl38060.o
 
 # Amp
diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c
new file mode 100644
index 000000000..7a3f6373f
--- /dev/null
+++ b/sound/soc/codecs/wsa885x.c
@@ -0,0 +1,1490 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+/* WSA885X codec driver */
+
+#include <linux/bitfield.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+/* Control Registers - Audio Processing */
+#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S    0x0000
+#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK       0x0004
+#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX   0x0140
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID   0x0208
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX       0x0240
+#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER        0x0340
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0    0x4405
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1    0x4406
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB      0x4409
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB      0x6409
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB      0x440a
+#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB      0x640a
+#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS       0x0a04
+#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS       0x0a40
+#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE         0x0b10
+#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX       0x0e40
+
+/* Analog Top Registers - Power and Clock Control */
+#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d
+#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG      0x8024
+#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG      0x8025
+#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL        0x8034
+
+/* Analog PLL Registers */
+#define WSA885X_ANA_PLL_DIV_CTL_0      0x8090
+#define WSA885X_ANA_PLL_DIV_CTL_1      0x8091
+#define WSA885X_ANA_TOP_PLL_VCO_CTL    0x8092
+#define WSA885X_ANA_TOP_PLL_LOOPFILT_0         0x8093
+#define WSA885X_ANA_TOP_PLL_OVRD_CTL   0x8098
+#define WSA885X_ANA_TOP_PLL_STATUS_0   0x809a
+#define WSA885X_ANA_TOP_PLL_STATUS_1   0x809b
+
+/* Analog Boost Control Registers */
+#define WSA885X_ANA_TOP_BOOST_STB_CTRL2        0x805b
+#define WSA885X_ANA_TOP_BOOST_STB_CTRL3        0x805c
+#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2        0x805e
+#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3        0x805f
+#define WSA885X_ANA_TOP_BOOST_MISC     0x8063
+#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2   0x8065
+#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4   0x8067
+
+/* Analog IV Sense ADC Registers */
+#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2  0x80ca
+#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3  0x80cb
+#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL    0x80cc
+#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2      0x80d0
+
+/* Analog Speaker Power Stage Registers */
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3       0x8108
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3       0x810b
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3       0x810e
+#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3       0x8111
+#define WSA885X_ANA_TOP_SPK_TOP_SPARE3       0x813c
+#define WSA885X_SPK_TOP_LF_CH1_CTRL11       0x811c
+#define WSA885X_SPK_TOP_LF_CH1_TUNE1       0x811d
+#define WSA885X_SPK_TOP_LF_CH2_TUNE1       0x8129
+#define WSA885X_SPK_TOP_LF_CH1_CTRL9       0x811a
+#define WSA885X_SPK_TOP_LF_CH2_CTRL9       0x8126
+#define WSA885X_SPK_TOP_LF_CH2_CTRL11       0x8128
+#define WSA885X_SPK_TOP_COMMON_CTRL2        0x8102
+#define WSA885X_SPK_TOP_COMMON_TUNE1       0x8103
+#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1       0x80ba
+#define WSA885X_DIG_CTRL0_TOP_CLK_CFG  0x8418
+#define WSA885X_DIG_CTRL0_SDCA_COMMIT          0x8419
+#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE    0x841a
+#define WSA885X_DIG_CTRL0_SYS_CLK_SEL          0x841b
+#define WSA885X_DIG_CTRL0_CDC_CLK_CTL          0x841c
+#define WSA885X_DIG_CTRL0_PA_FSM_CTL   0x8420
+#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0       0x8423
+#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1       0x8424
+#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1         0x842b
+#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1         0x8435
+#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL    0x8458
+#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL   0x8470
+#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1      0x84b4
+#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1      0x84b7
+#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1    0x84A0
+#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1    0x84A4
+
+/* Digital Control 1 Registers - I2S/TDM Interface */
+#define WSA885X_DIG_CTRL1_I2S_CTL0     0x85A0
+#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX      0x85A2
+#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX      0x85A3
+#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7
+#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9
+#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX        0x85AA
+#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX        0x85AB
+#define WSA885X_DIG_CTRL1_I2S_RESET_CTL        0x85AE
+
+/* CDC RX Path Registers - Audio Data Path */
+#define WSA885X_CDC_RX0_RX_PATH_CFG0   0x8601
+#define WSA885X_CDC_RX0_RX_PATH_CFG1   0x8602
+#define WSA885X_CDC_RX0_RX_PATH_CTL    0x8606
+#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613
+#define WSA885X_CDC_RX1_RX_PATH_CFG0   0x8621
+#define WSA885X_CDC_RX1_RX_PATH_CFG1   0x8622
+#define WSA885X_CDC_RX1_RX_PATH_CTL    0x8626
+#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633
+
+/* CDC Compander Registers - Dynamic Range Control */
+#define WSA885X_CDC_COMPANDER0_CTL0    0x8640
+#define WSA885X_CDC_COMPANDER0_CTL7    0x8647
+#define WSA885X_CDC_COMPANDER1_CTL0    0x8660
+#define WSA885X_CDC_COMPANDER1_CTL7    0x8667
+
+/* CDC Speaker Protection Registers - IV Sense */
+#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1
+#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1
+#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9
+#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9
+
+/* CDC Class-H Registers - Headroom Control */
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL1  0x86CD
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL0  0x86CC
+#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0      0x86C7
+#define WSA885X_CDC_CLSH_CLSH_V_HD_PA  0x86C3
+#define WSA885X_CDC_CLSH_V1P8_BP_CTL2  0x86CE
+
+/* Driver Constants */
+#define WSA885X_CLK_RATE_FIXED 73728000
+#define WSA885X_NUM_REGS       0x03
+
+/* Interrupt Registers */
+#define WSA885X_INTR_STATUS0   0x8584
+#define WSA885X_INTR_MASK0     0x8581
+#define WSA885X_INTR_CLEAR0    0x8587
+
+/* Power and PA FSM Control Registers */
+#define WSA885X_PA0_FSM_CTL0   0x842A
+#define WSA885X_PA1_FSM_CTL0   0x8434
+
+/* Digital Control GPIO and Interrupt Registers */
+#define WSA885X_DIG_CTRL1_PIN_CT       0x8510
+#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL   0x8518
+#define WSA885X_DIG_CTRL1_INTR_MODE    0x8580
+
+#define WSA885X_I2S_CTL0_PCM_RATE_MASK         GENMASK(4, 1)
+#define WSA885X_I2S_CTL0_ENABLE_MASK           BIT(0)
+#define WSA885X_I2S_CTL0_PCM_RATE(v)           \
+	FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v))
+#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ         0x0
+#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ        0x1
+#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ        0x2
+#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ  0x3
+#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ  0x4
+#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5
+#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK     GENMASK(2, 0)
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK     GENMASK(6, 4)
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v))
+#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v))
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK     GENMASK(2, 0)
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK     GENMASK(6, 4)
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v))
+#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v)       \
+	FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v))
+#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK   BIT(0)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1)
+#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8    \
+	FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3)
+#define WSA885X_I2S_TDM_CH_TX_CH0_EN           BIT(0)
+#define WSA885X_I2S_TDM_CH_TX_CH1_EN           BIT(1)
+#define WSA885X_I2S_TDM_CH_TX_CH2_EN           BIT(2)
+#define WSA885X_I2S_TDM_CH_TX_CH3_EN           BIT(3)
+#define WSA885X_I2S_RESET_CTL_RESET_MASK       BIT(0)
+#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2)
+#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3)
+#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK   BIT(2)
+
+#define WSA885X_I2S_TX_SLOT_ISENSE0            0x1
+#define WSA885X_I2S_TX_SLOT_ISENSE1            0x2
+#define WSA885X_I2S_TX_SLOT_CUR_SENSE0         0x5
+#define WSA885X_I2S_TX_SLOT_CUR_SENSE1         0x6
+
+/* RX Sample Rate Index Values - Audio Playback Path */
+#define WSA885X_RX_RATE_8000HZ          0x00
+#define WSA885X_RX_RATE_16000HZ         0x01
+#define WSA885X_RX_RATE_32000HZ         0x02
+#define WSA885X_RX_RATE_44100HZ         0x03
+#define WSA885X_RX_RATE_48000HZ         0x04
+#define WSA885X_RX_RATE_96000HZ         0x05
+#define WSA885X_RX_RATE_192000HZ        0x06
+#define WSA885X_RX_RATE_384000HZ        0x07
+
+/* VI Sample Rate Index Values - Voltage/Current Sensing Path */
+#define WSA885X_VI_RATE_8000HZ          0x00
+#define WSA885X_VI_RATE_16000HZ         0x01
+#define WSA885X_VI_RATE_44100HZ         0x02
+#define WSA885X_VI_RATE_48000HZ         0x03
+#define WSA885X_VI_RATE_96000HZ         0x04
+#define WSA885X_VI_RATE_22050HZ         0x05
+#define WSA885X_VI_RATE_24000HZ         0x06
+#define WSA885X_VI_RATE_192000HZ        0x07
+#define WSA885X_VI_RATE_384000HZ        0x08
+
+/* Channel Configuration Masks */
+#define WSA885X_CHANNEL_STEREO          0x03
+#define WSA885X_CHANNEL_MONO_LEFT       0x01
+#define WSA885X_CHANNEL_MONO_RIGHT      0x02
+
+#define WSA885X_PLL_LOCK_BIT            BIT(0)
+
+#define WSA885X_FU21_VOL_STEPS 124
+#define WSA885X_USAGE_MODE_MAX 8
+static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0);
+
+static bool wsa885x_is_valid_rx_slot_mask(u32 mask)
+{
+	return mask == WSA885X_CHANNEL_MONO_LEFT ||
+	       mask == WSA885X_CHANNEL_MONO_RIGHT ||
+	       mask == WSA885X_CHANNEL_STEREO;
+}
+
+static const char *const wsa885x_supply_name[] = {
+	"vdd-1p8",
+	"vdd-io",
+};
+
+enum {
+	WSA885X_BATT_1S = 1,
+	WSA885X_BATT_2S,
+};
+
+enum {
+	WSA885X_IRQ_INT_SAF2WAR = 0,
+	WSA885X_IRQ_INT_WAR2SAF,
+	WSA885X_IRQ_INT_DISABLE,
+	WSA885X_IRQ_INT_PA0_OCP,
+	WSA885X_IRQ_INT_PA1_OCP,
+	WSA885X_IRQ_INT_CLIP0,
+	WSA885X_IRQ_INT_CLIP1,
+	WSA885X_IRQ_INT_CLK_WD,
+	WSA885X_IRQ_INT_INTR_GPIO1_PIN,
+	WSA885X_IRQ_INT_INTR_GPIO2_PIN,
+	WSA885X_IRQ_INT_UVLO,
+	WSA885X_IRQ_INT_BOP,
+	WSA885X_IRQ_INT_PA0_FSM_ERR,
+	WSA885X_IRQ_INT_PA1_FSM_ERR,
+	WSA885X_IRQ_INT_MAIN_FSM_ERR,
+	WSA885X_IRQ_INT_PCM_DATA0_WD,
+	WSA885X_IRQ_INT_PCM_DATA1_WD,
+	WSA885X_IRQ_INT_PCM_DATA0_DC,
+	WSA885X_IRQ_INT_PCM_DATA1_DC,
+	WSA885X_IRQ_INT_PLL_UNLOCKED,
+	WSA885X_IRQ_INT_PROT_MODE_CHANGE,
+	WSA885X_IRQ_INT_PB_CLOCK_VALID,
+	WSA885X_IRQ_INT_SENSE_CLOCK_VALID,
+	WSA885X_IRQ_MAX,
+};
+
+struct wsa885x_priv {
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct device *dev;
+	struct snd_soc_component *component;
+	struct gpio_desc *sd_n;
+	u32 usage_mode;
+	u32 rx_slot_mask;
+	u32 batt_conf;
+	int stereo_vol_db;
+	struct mutex state_lock; /* protects mutable control state */
+};
+
+struct wsa885x_reg_update {
+	unsigned int reg;
+	unsigned int mask;
+	unsigned int val;
+};
+
+static const struct regmap_range_cfg wsa885x_regmap_ranges[] = {
+	{
+		.range_min = 0,
+		.range_max = 0x88ff,
+		.selector_reg = 0x0,
+		.selector_mask = 0xFF,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 0x100,
+	},
+};
+
+static const struct reg_default wsa885x_codec_reg_defaults[] = {
+	{WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac},
+	{WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03},
+	{WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03},
+	{WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03},
+	{WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00},
+	{WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19},
+	{WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22},
+	{WSA885X_ANA_PLL_DIV_CTL_0, 0x0c},
+	{WSA885X_ANA_PLL_DIV_CTL_1, 0x50},
+	{WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00},
+	{WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4},
+	{WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00},
+	{WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00},
+	{WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03},
+	{WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c},
+	{WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5},
+	{WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13},
+	{WSA885X_ANA_TOP_BOOST_MISC, 0x79},
+	{WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00},
+	{WSA885X_SPK_TOP_COMMON_CTRL2, 0x08},
+	{WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09},
+	{WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00},
+	{WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00},
+	{WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09},
+	{WSA885X_SPK_TOP_COMMON_TUNE1, 0x03},
+	{WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00},
+	{WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e},
+	{WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40},
+	{WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff},
+	{WSA885X_ANA_TOP_PLL_STATUS_0, 0x00},
+	{WSA885X_ANA_TOP_PLL_STATUS_1, 0x00},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00},
+	{WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4},
+	{WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9},
+	{WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00},
+	{WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00},
+	{WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00},
+	{WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00},
+	{WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00},
+	{WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05},
+	{WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00},
+	{WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45},
+	{WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45},
+	{WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f},
+	{WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00},
+	{WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01},
+	{WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01},
+	{WSA885X_DIG_CTRL1_I2S_CTL0, 0x06},
+	{WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00},
+	{WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08},
+	{WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89},
+	{WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64},
+	{WSA885X_CDC_RX0_RX_PATH_CTL, 0x24},
+	{WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01},
+	{WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89},
+	{WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64},
+	{WSA885X_CDC_RX1_RX_PATH_CTL, 0x04},
+	{WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01},
+	{WSA885X_CDC_COMPANDER0_CTL0, 0x01},
+	{WSA885X_CDC_COMPANDER0_CTL7, 0x2a},
+	{WSA885X_CDC_COMPANDER1_CTL0, 0x01},
+	{WSA885X_CDC_COMPANDER1_CTL7, 0x2a},
+	{WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c},
+	{WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d},
+	{WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03},
+	{WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05},
+};
+
+static void wsa885x_multi_update_bits(struct regmap *regmap,
+				      const struct wsa885x_reg_update *updates,
+				      size_t num_updates)
+{
+	size_t i;
+
+	for (i = 0; i < num_updates; i++)
+		regmap_update_bits(regmap, updates[i].reg,
+				   updates[i].mask, updates[i].val);
+}
+
+static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x,
+				   unsigned int reg, unsigned int mask)
+{
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+	regmap_update_bits(wsa885x->regmap, reg, mask, mask);
+}
+
+static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x,
+				  unsigned int reg, unsigned int mask)
+{
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+	regmap_update_bits(wsa885x->regmap, reg, mask, mask);
+	regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+}
+
+static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val)
+{
+	if (!slot_num_val)
+		return -EINVAL;
+
+	switch (slots) {
+	case 2:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2;
+		return 0;
+	case 4:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4;
+		return 0;
+	case 8:
+		*slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 },
+	};
+	unsigned int slot_num_val;
+	int ret;
+
+	if (!regmap)
+		return -EINVAL;
+
+	ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val);
+	if (ret)
+		return ret;
+
+	regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs));
+
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0,
+			   WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK,
+			   slot_num_val);
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0,
+			   WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK,
+			   WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK);
+	regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+		     WSA885X_I2S_TDM_CH_TX_CH0_EN);
+	regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+			   WSA885X_I2S_TDM_CH_TX_CH1_EN,
+			   WSA885X_I2S_TDM_CH_TX_CH1_EN);
+
+	return 0;
+}
+
+static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x)
+{
+	unsigned int status = 0;
+	int cnt = 0, ret = 0;
+
+	do {
+		usleep_range(1000, 1100);
+		ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status);
+		if (ret) {
+			dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret);
+			return ret;
+		}
+
+		if (status & WSA885X_PLL_LOCK_BIT)
+			return 0;
+	} while (++cnt < 20);
+
+	dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status);
+	return -ETIMEDOUT;
+}
+
+static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 },
+		{ WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d },
+		{ WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d },
+		{ WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 },
+		{ WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA },
+	};
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static const struct reg_sequence wsa885x_reg_init[] = {
+	{ WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 },
+	{ WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 },
+	{ WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 },
+	{ WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 },
+	{ WSA885X_CDC_COMPANDER0_CTL0, 0x01 },
+	{ WSA885X_CDC_COMPANDER1_CTL0, 0x01 },
+	{ WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 },
+	{ WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 },
+	{ WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 },
+	{ WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 },
+	{ WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f },
+	{ WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f },
+	{ WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 },
+	{ WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 },
+	{ WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 },
+	{ WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 },
+	{ WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 },
+	{ WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 },
+	{ WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 },
+	{ WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 },
+	{ WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 },
+	{ WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c },
+	{ WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e },
+	{ WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c },
+	{ WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 },
+	{ WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 },
+	{ WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 },
+	{ WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 },
+	{ WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 },
+	{ WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 },
+	{ WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 },
+	{ WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 },
+	{ WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 },
+	{ WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f },
+	{ WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 },
+	{ WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c },
+	{ WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d },
+	{ WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 },
+	{ WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 },
+	{ WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 },
+	{ WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 },
+	{ WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 },
+	{ WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 },
+	{ WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 },
+	{ WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 },
+	{ WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 },
+	{ WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 },
+	{ WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 },
+	{ WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 },
+	{ WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 },
+	{ WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 },
+	{ WSA885X_CDC_COMPANDER0_CTL7, 0x34 },
+	{ WSA885X_CDC_COMPANDER1_CTL7, 0x34 },
+	{ WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 },
+};
+
+static int wsa885x_hw_init(struct wsa885x_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e },
+		{ WSA885X_DIG_CTRL1_INTR_MODE, 0x01 },
+		{ WSA885X_DIG_CTRL1_PIN_CT, 0x04 },
+	};
+	int ret;
+
+	ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init,
+				     ARRAY_SIZE(wsa885x_reg_init));
+	if (ret)
+		return ret;
+
+	if (wsa885x->batt_conf == WSA885X_BATT_2S) {
+		ret = wsa885x_2s_conf(wsa885x);
+		if (ret)
+			return ret;
+	}
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_INTR_MASK0, 0x00 },
+		{ WSA885X_INTR_MASK0 + 1, 0x00 },
+		{ WSA885X_INTR_MASK0 + 2, 0xf8 },
+	};
+
+	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps)
+{
+	unsigned int act_ps = 0, clock_valid = 0;
+	int rc = 0, cnt = 0;
+
+	if (ps < 0 || ps > 3)
+		return -EINVAL;
+
+	do {
+		usleep_range(1000, 1500);
+		rc = regmap_read(wsa885x->regmap,
+				 WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS,
+				 &act_ps);
+		if (rc) {
+			dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc);
+			return rc;
+		}
+		if (act_ps == ps)
+			return 0;
+	} while (++cnt < 5);
+
+	if (regmap_read(wsa885x->regmap,
+			WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID,
+			&clock_valid))
+		dev_err(wsa885x->dev,
+			"PDE power state %d request failed, actual_ps %d, clock_valid read failed\n",
+			ps, act_ps);
+	else
+		dev_err(wsa885x->dev,
+			"PDE power state %d request failed, actual_ps %d, clock_valid:%d\n",
+			ps, act_ps, clock_valid);
+
+	return -ETIMEDOUT;
+}
+
+static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x,
+					  int stereo_vol_db, bool commit)
+{
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
+		     (u8)(s8)stereo_vol_db);
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00);
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
+		     (u8)(s8)stereo_vol_db);
+	regmap_write(wsa885x->regmap,
+		     WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00);
+
+	if (commit)
+		regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01);
+}
+
+static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct wsa885x_priv *wsa885x;
+	u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+
+	switch (params_rate(params)) {
+	case 8000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ;
+		break;
+	case 16000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ;
+		break;
+	case 32000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ;
+		break;
+	case 44100:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ;
+		break;
+	case 48000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ;
+		break;
+	case 88200:
+	case 96000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ;
+		break;
+	case 176400:
+	case 192000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ;
+		break;
+	case 352800:
+	case 384000:
+		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ;
+		cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ;
+		cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ;
+		break;
+	default:
+		dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0,
+			   WSA885X_I2S_CTL0_PCM_RATE_MASK |
+			   WSA885X_I2S_CTL0_ENABLE_MASK,
+			   WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) |
+			   WSA885X_I2S_CTL0_ENABLE_MASK);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX,
+		     cs21_sample_rate_idx);
+	regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX,
+		     cs24_sample_rate_idx);
+
+	mutex_lock(&wsa885x->state_lock);
+	wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false);
+	mutex_unlock(&wsa885x->state_lock);
+
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01);
+
+	return 0;
+}
+
+static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai,
+				      unsigned int tx_slot_mask,
+				      unsigned int rx_slot_mask, int slots,
+				      int slot_width)
+{
+	static const struct wsa885x_reg_update stereo_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK,
+		  WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK,
+		  WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) },
+	};
+	static const struct wsa885x_reg_update mono_left_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) },
+	};
+	static const struct wsa885x_reg_update mono_right_updates[] = {
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK,
+		  WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) },
+	};
+	struct wsa885x_priv *wsa885x;
+	unsigned int slot_num_val;
+	int ret;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+
+	ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val);
+	if (ret) {
+		dev_err(wsa885x->dev, "%s: unsupported slot count %d\n",
+			__func__, slots);
+		return ret;
+	}
+
+	if (rx_slot_mask) {
+		if (!wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) {
+			dev_err(wsa885x->dev,
+				"%s: unsupported rx_slot_mask 0x%x\n",
+				__func__, rx_slot_mask);
+			return -EINVAL;
+		}
+		wsa885x->rx_slot_mask = rx_slot_mask;
+	} else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) {
+		wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK);
+
+	if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) {
+		wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates,
+					  ARRAY_SIZE(stereo_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+		regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+				   WSA885X_I2S_TDM_CH_TX_CH2_EN,
+				   WSA885X_I2S_TDM_CH_TX_CH2_EN);
+		regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX,
+				   WSA885X_I2S_TDM_CH_TX_CH3_EN,
+				   WSA885X_I2S_TDM_CH_TX_CH3_EN);
+	} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) {
+		wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates,
+					  ARRAY_SIZE(mono_left_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+	} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) {
+		wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates,
+					  ARRAY_SIZE(mono_right_updates));
+		ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots);
+		if (ret)
+			return ret;
+	}
+
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0,
+			   WSA885X_I2S_CTL0_ENABLE_MASK,
+			   WSA885X_I2S_CTL0_ENABLE_MASK);
+	regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL,
+			   WSA885X_I2S_RESET_CTL_RESET_MASK, 0);
+
+	return 0;
+}
+
+static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+				    unsigned int freq, int dir)
+{
+	static const struct reg_sequence pll_prep[] = {
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 },
+		{ WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 },
+		{ WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 },
+		{ WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 },
+	};
+	static const struct reg_sequence pll_cleanup[] = {
+		{ WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 },
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 },
+	};
+	struct wsa885x_priv *wsa885x;
+	u32 pll_div;
+	int ret = 0;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+
+	if (!freq)
+		return -EINVAL;
+	if (WSA885X_CLK_RATE_FIXED % freq)
+		return -EINVAL;
+	pll_div = WSA885X_CLK_RATE_FIXED / freq;
+	if (pll_div > 0xff)
+		return -EINVAL;
+
+	regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep));
+	regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02);
+
+	ret = wsa885x_wait_for_pll_lock(wsa885x);
+	if (ret) {
+		dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n");
+		regmap_multi_reg_write(wsa885x->regmap, pll_cleanup,
+				       ARRAY_SIZE(pll_cleanup));
+		return ret;
+	}
+
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00);
+	regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01);
+	regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00);
+
+	return 0;
+}
+
+static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	static const struct reg_sequence mute_regs[] = {
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 },
+	};
+	static const struct reg_sequence mute_commit_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 },
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+	};
+	static const struct reg_sequence unmute_prep_head_regs[] = {
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+	};
+	static const struct reg_sequence unmute_prep_tail_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 },
+	};
+	static const struct reg_sequence unmute_volume_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 },
+	};
+	static const struct reg_sequence unmute_commit_regs[] = {
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 },
+	};
+	static const struct reg_sequence unmute_finish_regs[] = {
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 },
+		{ WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 },
+		{ WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 },
+	};
+	struct wsa885x_priv *wsa885x;
+	int ret = 0, ps0 = 0, ps3 = 3;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+
+	if (stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	mutex_lock(&wsa885x->state_lock);
+
+	if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) {
+		ret = -EINVAL;
+		goto exit_unlock;
+	}
+
+	if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask))
+		wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+
+	if (mute) {
+		regmap_multi_reg_write(wsa885x->regmap, mute_regs,
+				       ARRAY_SIZE(mute_regs));
+		ret = wsa885x_wait_for_pde_state(wsa885x, ps3);
+		if (ret) {
+			dev_err(wsa885x->dev,
+				"PS3 transition failed: %d\n", ret);
+		} else {
+			regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs,
+					       ARRAY_SIZE(mute_commit_regs));
+		}
+	} else {
+		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs,
+				       ARRAY_SIZE(unmute_prep_head_regs));
+		regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE,
+			     wsa885x->usage_mode);
+		regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs,
+				       ARRAY_SIZE(unmute_prep_tail_regs));
+		wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false);
+		regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs,
+				       ARRAY_SIZE(unmute_volume_regs));
+		regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs,
+				       ARRAY_SIZE(unmute_commit_regs));
+		ret = wsa885x_wait_for_pde_state(wsa885x, ps0);
+		if (ret)
+			goto exit_unlock;
+
+		if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03);
+		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01);
+		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) {
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02);
+			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x01);
+		}
+
+		regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs,
+				       ARRAY_SIZE(unmute_finish_regs));
+	}
+
+exit_unlock:
+	mutex_unlock(&wsa885x->state_lock);
+
+	return ret;
+}
+
+static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	static const struct reg_sequence regs[] = {
+		{ WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 },
+		{ WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 },
+		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 },
+		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
+		{ WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00 },
+		{ WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 },
+		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 },
+		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 },
+	};
+	struct wsa885x_priv *wsa885x;
+
+	wsa885x = snd_soc_component_get_drvdata(dai->component);
+
+	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	mutex_lock(&wsa885x->state_lock);
+	regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+	mutex_unlock(&wsa885x->state_lock);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops wsa885x_dai_ops = {
+	.hw_params = wsa885x_codec_hw_params,
+	.set_tdm_slot = wsa885x_codec_set_tdm_slot,
+	.set_sysclk = wsa885x_codec_set_sysclk,
+	.mute_stream = wsa885x_codec_mute_stream,
+	.hw_free = wsa885x_codec_hw_free,
+};
+
+static struct snd_soc_dai_driver wsa885x_dai[] = {
+	{
+		.name = "wsa885x_dai_drv",
+		.playback = {
+			.stream_name = "WSA885X TDM Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+				.rates = SNDRV_PCM_RATE_8000_192000 |
+					 SNDRV_PCM_RATE_352800 |
+					 SNDRV_PCM_RATE_384000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+					   SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &wsa885x_dai_ops,
+	},
+};
+
+static void wsa885x_gpio_powerdown(void *data)
+{
+	struct wsa885x_priv *wsa885x = data;
+
+	gpiod_set_value_cansleep(wsa885x->sd_n, true);
+}
+
+static bool wsa885x_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case WSA885X_ANA_TOP_PLL_STATUS_0:
+	case WSA885X_ANA_TOP_PLL_STATUS_1:
+	case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS:
+	case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID:
+	case WSA885X_INTR_STATUS0:
+	case WSA885X_INTR_STATUS0 + 1:
+	case WSA885X_INTR_STATUS0 + 2:
+	case WSA885X_INTR_CLEAR0:
+	case WSA885X_INTR_CLEAR0 + 1:
+	case WSA885X_INTR_CLEAR0 + 2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool wsa885x_readable_register(struct device *dev, unsigned int reg)
+{
+	if (reg == WSA885X_INTR_CLEAR0 ||
+	    reg == WSA885X_INTR_CLEAR0 + 1 ||
+	    reg == WSA885X_INTR_CLEAR0 + 2)
+		return false;
+	return reg <= 0x88ff;
+}
+
+static bool wsa885x_writeable_register(struct device *dev, unsigned int reg)
+{
+	if (reg > 0x88ff)
+		return false;
+
+	switch (reg) {
+	case WSA885X_ANA_TOP_PLL_STATUS_0:
+	case WSA885X_ANA_TOP_PLL_STATUS_1:
+	case WSA885X_INTR_STATUS0:
+	case WSA885X_INTR_STATUS0 + 1:
+	case WSA885X_INTR_STATUS0 + 2:
+	case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS:
+	case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config wsa885x_regmap_cfg = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x88FF,
+	.ranges = wsa885x_regmap_ranges,
+	.num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges),
+	.reg_defaults = wsa885x_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults),
+	.volatile_reg = wsa885x_volatile_register,
+	.writeable_reg = wsa885x_writeable_register,
+	.readable_reg = wsa885x_readable_register,
+	.cache_type = REGCACHE_MAPLE,
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static int wsa885x_component_probe(struct snd_soc_component *component)
+{
+	struct wsa885x_priv *wsa885x =
+		snd_soc_component_get_drvdata(component);
+	int ret;
+
+	wsa885x->component = component;
+	snd_soc_component_init_regmap(component, wsa885x->regmap);
+
+	ret = wsa885x_hw_init(wsa885x);
+	if (ret)
+		return ret;
+
+	return wsa885x_unmask_interrupts(wsa885x);
+}
+
+static void wsa885x_component_remove(struct snd_soc_component *component)
+{
+	if (!component)
+		return;
+
+	snd_soc_component_exit_regmap(component);
+}
+
+static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+	int val;
+
+	mutex_lock(&wsa885x->state_lock);
+	val = wsa885x->stereo_vol_db + 84;
+	mutex_unlock(&wsa885x->state_lock);
+	if (val < 0 || val > WSA885X_FU21_VOL_STEPS)
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+	long val;
+	int stereo_vol_db;
+
+	val = ucontrol->value.integer.value[0];
+
+	if (val < 0 || val > WSA885X_FU21_VOL_STEPS) {
+		dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val);
+		return -EINVAL;
+	}
+
+	stereo_vol_db = (int)val - 84;
+
+	mutex_lock(&wsa885x->state_lock);
+	if (wsa885x->stereo_vol_db == stereo_vol_db) {
+		mutex_unlock(&wsa885x->state_lock);
+		return 0;
+	}
+
+	wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true);
+	wsa885x->stereo_vol_db = stereo_vol_db;
+	mutex_unlock(&wsa885x->state_lock);
+
+	return 1;
+}
+
+static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+
+	if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX)
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = wsa885x->usage_mode;
+
+	return 0;
+}
+
+static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+	u32 val = ucontrol->value.integer.value[0];
+
+	if (val > WSA885X_USAGE_MODE_MAX)
+		return -EINVAL;
+
+	if (wsa885x->usage_mode == val)
+		return 0;
+
+	wsa885x->usage_mode = val;
+
+	return 1;
+}
+
+static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+	u32 mask;
+
+	mask = wsa885x->rx_slot_mask;
+	if (!wsa885x_is_valid_rx_slot_mask(mask))
+		return -ERANGE;
+
+	ucontrol->value.integer.value[0] = mask;
+
+	return 0;
+}
+
+static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
+	u32 mask = ucontrol->value.integer.value[0];
+
+	if (!wsa885x_is_valid_rx_slot_mask(mask))
+		return -EINVAL;
+
+	if (wsa885x->rx_slot_mask == mask)
+		return 0;
+
+	wsa885x->rx_slot_mask = mask;
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new wsa885x_snd_controls[] = {
+	SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0,
+		       wsa885x_usage_modes_get,
+		       wsa885x_usage_modes_put),
+
+	SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM,
+			   0, WSA885X_FU21_VOL_STEPS, 0,
+			   wsa885x_stereo_gain_offset_get,
+			   wsa885x_stereo_gain_offset_put,
+			   wsa885x_fu21_digital_gain),
+
+	SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0,
+		       wsa885x_rx_slot_mask_get,
+		       wsa885x_rx_slot_mask_put),
+};
+
+static const struct snd_soc_component_driver wsa885x_component = {
+	.name = "wsa885x",
+	.probe = wsa885x_component_probe,
+	.remove = wsa885x_component_remove,
+	.controls = wsa885x_snd_controls,
+	.num_controls = ARRAY_SIZE(wsa885x_snd_controls),
+};
+
+static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data)
+{
+	struct wsa885x_priv *wsa885x = data;
+
+	if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX)
+		return IRQ_NONE;
+
+	switch (irq_idx) {
+	case WSA885X_IRQ_INT_SAF2WAR:
+	case WSA885X_IRQ_INT_WAR2SAF:
+	case WSA885X_IRQ_INT_DISABLE:
+	case WSA885X_IRQ_INT_INTR_GPIO1_PIN:
+	case WSA885X_IRQ_INT_INTR_GPIO2_PIN:
+	case WSA885X_IRQ_INT_PA0_OCP:
+	case WSA885X_IRQ_INT_PA1_OCP:
+	case WSA885X_IRQ_INT_CLIP0:
+	case WSA885X_IRQ_INT_CLIP1:
+	case WSA885X_IRQ_INT_CLK_WD:
+	case WSA885X_IRQ_INT_BOP:
+	case WSA885X_IRQ_INT_UVLO:
+	case WSA885X_IRQ_INT_PCM_DATA0_DC:
+	case WSA885X_IRQ_INT_PCM_DATA1_DC:
+	case WSA885X_IRQ_INT_PLL_UNLOCKED:
+	case WSA885X_IRQ_INT_PROT_MODE_CHANGE:
+	case WSA885X_IRQ_INT_PB_CLOCK_VALID:
+	case WSA885X_IRQ_INT_SENSE_CLOCK_VALID:
+		break;
+	case WSA885X_IRQ_INT_PCM_DATA0_WD:
+	case WSA885X_IRQ_INT_PCM_DATA1_WD:
+		if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD)
+			wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1,
+					       WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK);
+		else
+			wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1,
+					       WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK);
+		break;
+	case WSA885X_IRQ_INT_PA0_FSM_ERR:
+	case WSA885X_IRQ_INT_PA1_FSM_ERR:
+	case WSA885X_IRQ_INT_MAIN_FSM_ERR:
+		if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0,
+					      WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK);
+		} else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0,
+					      WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK);
+		} else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) {
+			wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0,
+					      WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t wsa885x_interrupt_handler(int irq, void *data)
+{
+	static const unsigned int status_reg[WSA885X_NUM_REGS] = {
+		WSA885X_INTR_STATUS0,
+		WSA885X_INTR_STATUS0 + 1,
+		WSA885X_INTR_STATUS0 + 2,
+	};
+	static const unsigned int clear_reg[WSA885X_NUM_REGS] = {
+		WSA885X_INTR_CLEAR0,
+		WSA885X_INTR_CLEAR0 + 1,
+		WSA885X_INTR_CLEAR0 + 2,
+	};
+	unsigned int status[WSA885X_NUM_REGS] = { 0 };
+	struct wsa885x_priv *wsa885x = data;
+	irqreturn_t handled = IRQ_NONE;
+	irqreturn_t irq_ret;
+	int i, bit, ret, irq_num;
+
+	for (i = 0; i < WSA885X_NUM_REGS; i++) {
+		ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]);
+		if (ret) {
+			dev_err(wsa885x->dev,
+				"Failed to read status_reg[%d] (0x%x): %d\n",
+				i, status_reg[i], ret);
+			status[i] = 0;
+			continue;
+		}
+	}
+
+	for (i = 0; i < WSA885X_NUM_REGS; i++) {
+		for (bit = 0; bit < 8; bit++) {
+			if (status[i] & BIT(bit)) {
+				irq_num = i * 8 + bit;
+				regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit));
+				regmap_write(wsa885x->regmap, clear_reg[i], 0);
+				if (irq_num >= WSA885X_IRQ_MAX) {
+					dev_warn_ratelimited(wsa885x->dev,
+							     "Unexpected IRQ bit %d (reg %d)\n",
+							     bit, i);
+					handled = IRQ_HANDLED;
+					continue;
+				}
+				irq_ret = wsa885x_handle_irq(irq_num, wsa885x);
+				if (irq_ret == IRQ_HANDLED)
+					handled = IRQ_HANDLED;
+			}
+		}
+	}
+	return handled;
+}
+
+static int wsa885x_register_irq(struct wsa885x_priv *wsa885x)
+{
+	if (!wsa885x->client->irq)
+		return dev_err_probe(wsa885x->dev, -EINVAL,
+				     "IRQ is not configured\n");
+
+	return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL,
+					wsa885x_interrupt_handler,
+					IRQF_ONESHOT,
+					dev_name(wsa885x->dev), wsa885x);
+}
+
+static int wsa885x_probe(struct i2c_client *client)
+{
+	struct wsa885x_priv *wsa885x;
+	const struct snd_soc_component_driver *component_driver = &wsa885x_component;
+	const char *battery_config;
+	unsigned int i;
+	int ret;
+	struct device *dev = &client->dev;
+
+	wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL);
+	if (!wsa885x)
+		return -ENOMEM;
+
+	wsa885x->client = client;
+	wsa885x->dev = dev;
+	wsa885x->stereo_vol_db = -84;
+	wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO;
+	mutex_init(&wsa885x->state_lock);
+	wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg);
+
+	if (IS_ERR(wsa885x->regmap))
+		return PTR_ERR(wsa885x->regmap);
+
+	ret = device_property_read_string(dev, "qcom,battery-config",
+					  &battery_config);
+	if (ret) {
+		wsa885x->batt_conf = WSA885X_BATT_1S;
+	} else if (!strcmp(battery_config, "1s")) {
+		wsa885x->batt_conf = WSA885X_BATT_1S;
+	} else if (!strcmp(battery_config, "2s")) {
+		wsa885x->batt_conf = WSA885X_BATT_2S;
+	} else {
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid battery config %s (expected 1s or 2s)\n",
+				     battery_config);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) {
+		ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to enable regulator %s\n",
+					     wsa885x_supply_name[i]);
+	}
+
+	wsa885x->sd_n = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_HIGH);
+	if (IS_ERR(wsa885x->sd_n))
+		return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n),
+							 "Shutdown Control GPIO not found\n");
+
+	gpiod_set_value_cansleep(wsa885x->sd_n, false);
+
+	ret = devm_add_action_or_reset(dev, wsa885x_gpio_powerdown, wsa885x);
+	if (ret)
+		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
+
+	i2c_set_clientdata(client, wsa885x);
+
+	ret = devm_snd_soc_register_component(dev, component_driver,
+					      wsa885x_dai,
+					      ARRAY_SIZE(wsa885x_dai));
+	if (ret)
+		return dev_err_probe(dev, ret, "Codec component registration failed\n");
+
+	ret = wsa885x_register_irq(wsa885x);
+	if (ret)
+		return dev_err_probe(dev, ret, "wsa885x irq registration failed\n");
+
+	return 0;
+}
+
+static const struct of_device_id wsa885x_dt_match[] = {
+	{
+		.compatible = "qcom,wsa8855",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, wsa885x_dt_match);
+
+static const struct i2c_device_id wsa885x_id[] = {
+	{
+		.name = "wsa885x",
+		.driver_data = 0,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, wsa885x_id);
+
+static struct i2c_driver wsa885x_driver = {
+	.driver = {
+		.name = "wsa885x",
+		.of_match_table = wsa885x_dt_match,
+	},
+	.probe = wsa885x_probe,
+	.id_table = wsa885x_id,
+};
+
+module_i2c_driver(wsa885x_driver);
+
+MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1


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

* Re: [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver
  2026-07-01 13:59 ` [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver Prasad Kumpatla
@ 2026-07-01 14:14   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-01 14:14 UTC (permalink / raw)
  To: Prasad Kumpatla; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] The `hw_free` callback destructively resets persistent DAI configurations, breaking subsequent playback.
- [High] Calling `snd_soc_component_exit_regmap` on a devm-managed regmap causes a double free and Use-After-Free.
- [Medium] Missing state_lock acquisition in ALSA kcontrol put handlers causes data races with DAI callbacks.
- [Medium] Incomplete hardware state restoration in `mute_stream` leaves the receiver path latched in mono configuration.
- [Medium] Misleading DAI capabilities advertise unsupported sample rates.
- [Medium] Speculative missing spin-up delay after powerdown GPIO deassertion.
--

commit e15bd0da567eddb8e96d21ded36e450db91c562c
Author: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>

ASoC: codecs: add Qualcomm WSA885X codec driver
    
Add an ASoC codec driver for the Qualcomm WSA885X stereo smart speaker
amplifier controlled over I2C.

> diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c
> new file mode 100644
> index 0000000000000..7a3f6373f5d95
> --- /dev/null
> +++ b/sound/soc/codecs/wsa885x.c
> @@ -0,0 +1,1490 @@

[ ... ]

> +static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream,
> +				   struct snd_pcm_hw_params *params,
> +				   struct snd_soc_dai *dai)
> +{
> +	struct wsa885x_priv *wsa885x;
> +	u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx;
> +
> +	wsa885x = snd_soc_component_get_drvdata(dai->component);
> +
> +	switch (params_rate(params)) {
> +	case 8000:

[ ... ]

> +	case 352800:
> +	case 384000:
> +		pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ;
> +		cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ;
> +		cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ;
> +		break;
> +	default:
> +		dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params));
> +		return -EINVAL;
> +	}

[ ... ]

> +static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
> +{

[ ... ]

> +		if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) {
> +			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03);
> +		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) {
> +			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01);
> +		} else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) {
> +			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02);
> +			regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x01);
> +		}

[Severity: Medium]
Does this leave the hardware latched in a mono right configuration?

If the active rx_slot_mask is WSA885X_CHANNEL_MONO_RIGHT,
WSA885X_DIG_CTRL1_I2S_TDM_CH_RX is explicitly set to 0x01. If the mask is
later changed to STEREO or MONO_LEFT via kcontrol, the subsequent un-mute
operation evaluates the other branches but doesn't restore
WSA885X_DIG_CTRL1_I2S_TDM_CH_RX back to its default (0x08).

[ ... ]

> +static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream,
> +				 struct snd_soc_dai *dai)
> +{
> +	static const struct reg_sequence regs[] = {
> +		{ WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 },
> +		{ WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 },
> +		{ WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 },
> +		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 },
> +		{ WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 },
> +		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 },
> +		{ WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 },
> +		{ WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 },
> +		{ WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 },
> +		{ WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00 },
> +		{ WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 },
> +		{ WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 },
> +		{ WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 },
> +	};

[Severity: High]
Does clearing these registers break subsequent audio playback?

These register sequences clear persistent TDM and clock configurations like
WSA885X_DIG_CTRL0_SYS_CLK_SEL and WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE. 

Since machine drivers often invoke set_sysclk and set_tdm_slot only once
during machine initialization or startup, won't hw_free leaving the codec
without a clock source cause the next stream to fail?

[ ... ]

> +static struct snd_soc_dai_driver wsa885x_dai[] = {
> +	{
> +		.name = "wsa885x_dai_drv",
> +		.playback = {
> +			.stream_name = "WSA885X TDM Playback",
> +			.channels_min = 1,
> +			.channels_max = 2,
> +				.rates = SNDRV_PCM_RATE_8000_192000 |
> +					 SNDRV_PCM_RATE_352800 |
> +					 SNDRV_PCM_RATE_384000,

[Severity: Medium]
Does this inadvertently advertise unsupported intermediate sample rates?

SNDRV_PCM_RATE_8000_192000 includes intermediate rates like 11025Hz, 22050Hz,
and 64000Hz, but wsa885x_codec_hw_params() falls through to the default
case and returns -EINVAL for them. This can cause userspace applications to
fail abruptly when they try to negotiate formats.

[ ... ]

> +static void wsa885x_component_remove(struct snd_soc_component *component)
> +{
> +	if (!component)
> +		return;
> +
> +	snd_soc_component_exit_regmap(component);
> +}

[Severity: High]
Can this lead to a double free and use-after-free?

wsa885x->regmap is allocated via devm_regmap_init_i2c(), so devres will
automatically call regmap_exit() on device unbind. Calling
snd_soc_component_exit_regmap() here explicitly calls regmap_exit() early. 

If the sound card unbinds the ASoC component first, the regmap is freed, but
the I2C device is still bound and its IRQ handler remains active. If an
interrupt triggers, wsa885x_interrupt_handler() will access the freed regmap
resulting in a use-after-free. Later, devres will double-free the regmap
when the I2C device is actually removed.

[ ... ]

> +static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
> +				    struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
> +	struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component);
> +	u32 mask = ucontrol->value.integer.value[0];
> +
> +	if (!wsa885x_is_valid_rx_slot_mask(mask))
> +		return -EINVAL;
> +
> +	if (wsa885x->rx_slot_mask == mask)
> +		return 0;
> +
> +	wsa885x->rx_slot_mask = mask;

[Severity: Medium]
Should this modification be protected by wsa885x->state_lock?

wsa885x_codec_mute_stream() evaluates rx_slot_mask multiple times in an
if-else if chain. If this kcontrol is written concurrently, mute_stream()
could see different values for rx_slot_mask during the same invocation,
potentially skipping all conditional branches and leaving
WSA885X_DIG_CTRL0_PA_FSM_CTL unconfigured.

[ ... ]

> +static int wsa885x_probe(struct i2c_client *client)
> +{

[ ... ]

> +	wsa885x->sd_n = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_HIGH);
> +	if (IS_ERR(wsa885x->sd_n))
> +		return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n),
> +							 "Shutdown Control GPIO not found\n");
> +
> +	gpiod_set_value_cansleep(wsa885x->sd_n, false);
> +
> +	ret = devm_add_action_or_reset(dev, wsa885x_gpio_powerdown, wsa885x);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
> +
> +	i2c_set_clientdata(client, wsa885x);
> +
> +	ret = devm_snd_soc_register_component(dev, component_driver,
> +					      wsa885x_dai,
> +					      ARRAY_SIZE(wsa885x_dai));

[Severity: Medium]
Does the codec require a stabilization delay after deasserting the shutdown
GPIO?

Hardware audio codecs typically need a short delay (e.g. 5-20ms) after
coming out of hard reset before their internal digital logic can acknowledge
I2C traffic. Since wsa885x_component_probe() will issue I2C commands
immediately upon component registration via wsa885x_hw_init(), could this
lead to I2C NACKs or timeouts on some platforms?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260701135913.1641328-1-prasad.kumpatla@oss.qualcomm.com?part=2

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

* Re: [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings
  2026-07-01 13:59 ` [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings Prasad Kumpatla
@ 2026-07-01 16:39   ` Rob Herring (Arm)
  0 siblings, 0 replies; 5+ messages in thread
From: Rob Herring (Arm) @ 2026-07-01 16:39 UTC (permalink / raw)
  To: Prasad Kumpatla
  Cc: linux-kernel, Takashi Iwai, Srinivas Kandagatla,
	Krzysztof Kozlowski, Srinivas Kandagatla, devicetree,
	linux-arm-msm, Mark Brown, linux-sound, Jaroslav Kysela,
	Liam Girdwood, Conor Dooley


On Wed, 01 Jul 2026 19:29:12 +0530, Prasad Kumpatla wrote:
> Document the Qualcomm WSA8855 stereo smart speaker amplifier.
> 
> The device uses a PCM audio interface and is controlled over I2C. Add the
> required supplies, powerdown GPIO, interrupt line and optional battery
> topology property.
> 
> Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
> ---
>  .../bindings/sound/qcom,wsa8855.yaml          | 83 +++++++++++++++++++
>  1 file changed, 83 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/sound/qcom,wsa8855.example.dtb: speaker@c (qcom,wsa8855): compatible: 'oneOf' conditional failed, one must be fixed:
	'qcom,wsa8855' does not match '^qcom,(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm|x1[ep])[0-9]+(pro)?-.*$'
	'qcom,wsa8855' does not match '^qcom,sar[0-9]+[a-z]?-.*$'
	'qcom,wsa8855' does not match '^qcom,(sa|sc)8[0-9]+[a-z][a-z]?-.*$'
	'qcom,wsa8855' does not match '^qcom,(glymur|milos)-.*$'
	'qcom,wsa8855' does not match '^qcom,[ak]pss-wdt-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm)[0-9]+.*$'
	'qcom,wsa8855' does not match '^qcom,gcc-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm)[0-9]+.*$'
	'qcom,wsa8855' does not match '^qcom,mmcc-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm)[0-9]+.*$'
	'qcom,wsa8855' does not match '^qcom,pcie-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm|x1[ep])[0-9]+.*$'
	'qcom,wsa8855' does not match '^qcom,rpm-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm)[0-9]+.*$'
	'qcom,wsa8855' does not match '^qcom,scm-(apq|ipq|mdm|msm|qcm|qcs|q[dr]u|sa|sc|sd[amx]|sm|x1[ep])[0-9]+.*$'
	'qcom,wsa8855' is not one of ['qcom,dsi-ctrl-6g-qcm2290', 'qcom,gpucc-sdm630', 'qcom,gpucc-sdm660', 'qcom,lcc-apq8064', 'qcom,lcc-ipq8064', 'qcom,lcc-mdm9615', 'qcom,lcc-msm8960', 'qcom,lpass-cpu-apq8016', 'qcom,usb-ss-ipq4019-phy', 'qcom,usb-hs-ipq4019-phy', 'qcom,vqmmc-ipq4019-regulator']
	'qcom,wsa8855' is not one of ['qcom,ipq806x-gmac', 'qcom,ipq806x-nand', 'qcom,ipq806x-sata-phy', 'qcom,ipq806x-usb-phy-ss', 'qcom,ipq806x-usb-phy-hs']
	from schema $id: http://devicetree.org/schemas/arm/qcom-soc.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260701135913.1641328-2-prasad.kumpatla@oss.qualcomm.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

end of thread, other threads:[~2026-07-01 16:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-01 13:59 [PATCH v2 0/2] ASoC: add Qualcomm WSA885X codec support Prasad Kumpatla
2026-07-01 13:59 ` [PATCH v2 1/2] dt-bindings: sound: add WSA8855 bindings Prasad Kumpatla
2026-07-01 16:39   ` Rob Herring (Arm)
2026-07-01 13:59 ` [PATCH v2 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver Prasad Kumpatla
2026-07-01 14:14   ` sashiko-bot

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