* [PATCH v1 0/2] ASoC: add Qualcomm WSA885X I2C codec support
@ 2026-06-10 15:57 Prasad Kumpatla
2026-06-10 15:57 ` [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c Prasad Kumpatla
2026-06-10 15:57 ` [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver Prasad Kumpatla
0 siblings, 2 replies; 5+ messages in thread
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
linux-kernel, linux-gpio
Add support for the Qualcomm WSA885X smart speaker amplifier accessed
over I2C.
The series first documents the qcom,wsa885x-i2c devicetree binding,
including supplies, GPIOs, battery configuration, and the init-table
data used during codec initialization.
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.
Prasad Kumpatla (2):
dt-bindings: sound: add qcom,wsa885x-i2c
ASoC: codecs: add Qualcomm WSA885X I2C codec driver
.../bindings/sound/qcom,wsa885x-i2c.yaml | 89 +
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wsa885x-i2c.c | 1643 +++++++++++++++++
4 files changed, 1745 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
create mode 100644 sound/soc/codecs/wsa885x-i2c.c
base-commit: 49e02880ec0a8c378e811bc9d85da188d7c6204c
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c
2026-06-10 15:57 [PATCH v1 0/2] ASoC: add Qualcomm WSA885X I2C codec support Prasad Kumpatla
@ 2026-06-10 15:57 ` Prasad Kumpatla
2026-06-10 16:08 ` sashiko-bot
2026-06-10 15:57 ` [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver Prasad Kumpatla
1 sibling, 1 reply; 5+ messages in thread
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
linux-kernel, linux-gpio
Document the Qualcomm WSA885X I2C smart amplifier binding.
Describe the required supplies, powerdown and interrupt GPIOs, the
optional battery configuration, and the optional init-table property
used to program the device during codec initialization.
This matches the driver programming model and documents the DT data
needed to use the codec on platforms with Audio IF playback.
Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
---
.../bindings/sound/qcom,wsa885x-i2c.yaml | 89 +++++++++++++++++++
1 file changed, 89 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
new file mode 100644
index 000000000..1069f470d
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/qcom,wsa885x-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WSA885x I2C smart speaker amplifier
+
+maintainers:
+ - Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+ - Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
+
+description: |
+ WSA885x is a Qualcomm Aqstic smart speaker amplifier with an I2C control
+ interface and a digital audio interface exposed through ASoC DAI callbacks.
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: qcom,wsa885x-i2c
+
+ reg:
+ maxItems: 1
+
+ '#sound-dai-cells':
+ const: 0
+
+ powerdown-gpios:
+ description: GPIO controlling the SD_N powerdown pin.
+ maxItems: 1
+
+ interrupt-gpios:
+ description: GPIO used for the codec interrupt output.
+ maxItems: 1
+
+ vdd-1p8-supply: true
+
+ vdd-io-supply: true
+
+ qcom,battery-config:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Speaker battery configuration, 1 for 1S and 2 for 2S.
+ default: 1
+ enum: [1, 2]
+
+ qcom,wsa885x-init-table:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 2
+ maxItems: 256
+ description: |
+ Sequence of register/value pairs applied during codec hardware
+ initialization. Entries are encoded as alternating register address and
+ register value cells. The number of entries must be even (register/value
+ pairs); maxItems is 256 (128 pairs).
+
+required:
+ - compatible
+ - reg
+ - '#sound-dai-cells'
+ - powerdown-gpios
+ - interrupt-gpios
+ - vdd-1p8-supply
+ - vdd-io-supply
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ speaker@c {
+ compatible = "qcom,wsa885x-i2c";
+ reg = <0x0c>;
+ #sound-dai-cells = <0>;
+ powerdown-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>;
+ interrupt-gpios = <&tlmm 77 GPIO_ACTIVE_LOW>;
+ vdd-1p8-supply = <&vreg_l2g_1p8>;
+ vdd-io-supply = <&vreg_l1g_1p2>;
+ qcom,battery-config = <1>;
+ qcom,wsa885x-init-table = <0x8606 0x24 0x8626 0x24>;
+ };
+ };
+...
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
2026-06-10 15:57 [PATCH v1 0/2] ASoC: add Qualcomm WSA885X I2C codec support Prasad Kumpatla
2026-06-10 15:57 ` [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c Prasad Kumpatla
@ 2026-06-10 15:57 ` Prasad Kumpatla
2026-06-10 16:08 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Prasad Kumpatla @ 2026-06-10 15:57 UTC (permalink / raw)
To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Linus Walleij, Bartosz Golaszewski, Prasad Kumpatla
Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
linux-kernel, linux-gpio
Add an ASoC codec driver for the Qualcomm WSA885X smart speaker
amplifier accessed over I2C.
The driver provides the control-side support needed for playback
bring-up, including register programming, serial interface setup, clock
handling, mute and gain control, reset handling and interrupt support.
Program the init table during codec initialization and reapply it only
after an explicit device reset so the static device configuration is
not rewritten on every playback start. 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 the stream-time power-state sequencing in the DAI callbacks and
use normal regmap access 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-i2c.c | 1643 ++++++++++++++++++++++++++++++++
3 files changed, 1656 insertions(+)
create mode 100644 sound/soc/codecs/wsa885x-i2c.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 252f683be..7116b54f3 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_I2C
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_I2C
+ tristate "WSA885X I2C Codec"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This enables support for Qualcomm WSA885X Smart Speaker
+ Amplifiers connected over an I2C control bus. 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..18cfff959 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-i2c-y := wsa885x-i2c.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_I2C) += snd-soc-wsa885x-i2c.o
obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o
# Amp
diff --git a/sound/soc/codecs/wsa885x-i2c.c b/sound/soc/codecs/wsa885x-i2c.c
new file mode 100644
index 000000000..a7d8f8d48
--- /dev/null
+++ b/sound/soc/codecs/wsa885x-i2c.c
@@ -0,0 +1,1643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+/* WSA885X I2C codec driver */
+
+#include <linux/gpio/consumer.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/interrupt.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_SUPPLIES_NUM 2
+#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
+#define WSA885X_INIT_TABLE_MAX_ITEMS 256
+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_i2c_priv {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct snd_soc_component *component;
+ struct regulator_bulk_data supplies[WSA885X_SUPPLIES_NUM];
+ struct gpio_desc *sd_n;
+ u32 sample_rate;
+ u32 *init_table;
+ u32 init_table_size;
+ u32 usage_mode;
+ u32 rx_slot_mask;
+ struct gpio_desc *intr_pin;
+ u32 batt_conf;
+ int stereo_vol_db;
+};
+
+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_gpio_set(struct wsa885x_i2c_priv *wsa885x, bool val)
+{
+ if (!wsa885x || !wsa885x->sd_n)
+ return;
+
+ gpiod_set_value_cansleep(wsa885x->sd_n, val);
+}
+
+static void wsa885x_multi_update_bits(struct regmap *regmap,
+ const struct wsa885x_reg_update *updates,
+ size_t num_updates)
+{
+ size_t i;
+
+ if (!regmap || !updates)
+ return;
+
+ 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_i2c_priv *wsa885x,
+ unsigned int reg, unsigned int mask)
+{
+ if (!wsa885x || !wsa885x->regmap)
+ return;
+
+ regmap_update_bits(wsa885x->regmap, reg, mask, 0);
+ regmap_update_bits(wsa885x->regmap, reg, mask, mask);
+}
+
+static void wsa885x_pulse_irq_bit(struct wsa885x_i2c_priv *wsa885x,
+ unsigned int reg, unsigned int mask)
+{
+ if (!wsa885x || !wsa885x->regmap)
+ return;
+
+ 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_i2c_priv *wsa885x)
+{
+ unsigned int status = 0;
+ int cnt = 0;
+ int ret = 0;
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ 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_i2c_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 int wsa885x_apply_init_table(struct wsa885x_i2c_priv *wsa885x)
+{
+ int i;
+ int ret;
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ if (!wsa885x->init_table_size)
+ return 0;
+
+ if (!wsa885x->init_table)
+ return -EINVAL;
+
+ for (i = 0; i < wsa885x->init_table_size / 2; i++) {
+ u32 reg = wsa885x->init_table[2 * i];
+ u32 val = wsa885x->init_table[2 * i + 1];
+
+ if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH1_CTRL11)
+ continue;
+
+ if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH2_CTRL11)
+ continue;
+
+ ret = regmap_write(wsa885x->regmap, reg, val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wsa885x_hw_init(struct wsa885x_i2c_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;
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ ret = wsa885x_apply_init_table(wsa885x);
+ 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_i2c_priv *wsa885x)
+{
+ static const struct reg_sequence regs[] = {
+ { WSA885X_INTR_MASK0, 0x00 },
+ { WSA885X_INTR_MASK0 + 1, 0x00 },
+ { WSA885X_INTR_MASK0 + 2, 0xf8 },
+ };
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int wsa885x_wait_for_pde_state(struct wsa885x_i2c_priv *wsa885x, int ps)
+{
+ int act_ps = -1, cnt = 0, clock_valid = -1;
+ int rc = 0;
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ 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 int wsa885x_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct wsa885x_i2c_priv *wsa885x;
+ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx;
+
+ (void)substream;
+
+ if (!params || !dai || !dai->component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(dai->component);
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ wsa885x->sample_rate = params_rate(params);
+
+ switch (wsa885x->sample_rate) {
+ 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;
+ /* The VI sensing path has no 32 kHz rate index; use the
+ * nearest supported rate (48 kHz) for the CS24 clock.
+ */
+ 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);
+ regmap_write(wsa885x->regmap,
+ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
+ (s8)wsa885x->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,
+ (s8)wsa885x->stereo_vol_db);
+ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00);
+ 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_i2c_priv *wsa885x;
+ unsigned int slot_num_val;
+ int ret;
+
+ (void)tx_slot_mask;
+ (void)slot_width;
+
+ if (!dai || !dai->component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(dai->component);
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ 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_i2c_priv *wsa885x;
+ u32 pll_div;
+ int ret = 0;
+
+ /*
+ * This device has a single fixed PLL clock source; clk_id and dir
+ * are not used. The PLL divisor is derived solely from freq.
+ */
+ (void)clk_id;
+ (void)dir;
+
+ if (!dai || !dai->component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(dai->component);
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ 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_i2c_priv *wsa885x;
+ int ret = 0, ps0 = 0, ps3 = 3;
+
+ if (!dai || !dai->component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(dai->component);
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ if (stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return 0;
+
+ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX)
+ return -EINVAL;
+
+ 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));
+ regmap_write(wsa885x->regmap,
+ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
+ (s8)wsa885x->stereo_vol_db);
+ regmap_write(wsa885x->regmap,
+ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
+ (s8)wsa885x->stereo_vol_db);
+ 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;
+
+ 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:
+ 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_i2c_priv *wsa885x;
+
+ if (!substream || !dai || !dai->component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(dai->component);
+ if (!wsa885x || !wsa885x->regmap)
+ return -EINVAL;
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return 0;
+
+ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops wsa885x_i2c_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_i2c_dai[] = {
+ {
+ .name = "wsa885x_dai_drv",
+ .playback = {
+ .stream_name = "WSA885X I2C 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_i2c_dai_ops,
+ },
+};
+
+static void wsa885x_gpio_powerdown(void *data)
+{
+ struct wsa885x_i2c_priv *wsa885x = data;
+
+ if (!wsa885x)
+ return;
+
+ wsa885x_gpio_set(wsa885x, 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_i2c_priv *wsa885x =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (!wsa885x || !wsa885x->regmap)
+ return -ENODEV;
+
+ wsa885x->component = component;
+ snd_soc_component_init_regmap(component, wsa885x->regmap);
+
+ ret = wsa885x_hw_init(wsa885x);
+ if (ret)
+ return ret;
+
+ /* Restore interrupt masks after reg_defaults programming. */
+ 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 void wsa885x_regulator_disable(void *data)
+{
+ struct wsa885x_i2c_priv *wsa885x = data;
+
+ if (!wsa885x)
+ return;
+
+ regulator_bulk_disable(WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+}
+
+static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct wsa885x_i2c_priv *wsa885x;
+ int val;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(component);
+ if (!wsa885x)
+ return -EINVAL;
+
+ val = wsa885x->stereo_vol_db + 84;
+ 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;
+ struct wsa885x_i2c_priv *wsa885x;
+ long val;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x = snd_soc_component_get_drvdata(component);
+ if (!wsa885x)
+ return -EINVAL;
+
+ 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;
+ }
+ wsa885x->stereo_vol_db = (int)val - 84;
+ return 0;
+}
+
+static int wsa885x_i2c_usage_modes_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct wsa885x_i2c_priv *wsa885x_i2c;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x_i2c = snd_soc_component_get_drvdata(component);
+ if (!wsa885x_i2c)
+ return -EINVAL;
+
+ if (wsa885x_i2c->usage_mode > WSA885X_USAGE_MODE_MAX)
+ return -ERANGE;
+
+ ucontrol->value.integer.value[0] = wsa885x_i2c->usage_mode;
+
+ return 0;
+}
+
+static int wsa885x_i2c_usage_modes_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct wsa885x_i2c_priv *wsa885x_i2c;
+ long val;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x_i2c = snd_soc_component_get_drvdata(component);
+ if (!wsa885x_i2c)
+ return -EINVAL;
+
+ val = ucontrol->value.integer.value[0];
+
+ if (val < 0 || val > WSA885X_USAGE_MODE_MAX)
+ return -EINVAL;
+
+ wsa885x_i2c->usage_mode = val;
+
+ return 0;
+}
+
+static int wsa885x_i2c_rx_slot_mask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct wsa885x_i2c_priv *wsa885x_i2c;
+ u32 mask;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x_i2c = snd_soc_component_get_drvdata(component);
+ if (!wsa885x_i2c)
+ return -EINVAL;
+
+ mask = wsa885x_i2c->rx_slot_mask;
+ if (!wsa885x_is_valid_rx_slot_mask(mask))
+ return -ERANGE;
+
+ ucontrol->value.integer.value[0] = mask;
+
+ return 0;
+}
+
+static int wsa885x_i2c_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct wsa885x_i2c_priv *wsa885x_i2c;
+ long mask;
+
+ if (!kcontrol || !ucontrol)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -EINVAL;
+
+ wsa885x_i2c = snd_soc_component_get_drvdata(component);
+ if (!wsa885x_i2c)
+ return -EINVAL;
+
+ mask = ucontrol->value.integer.value[0];
+
+ if (!wsa885x_is_valid_rx_slot_mask(mask))
+ return -EINVAL;
+
+ wsa885x_i2c->rx_slot_mask = mask;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new wsa885x_snd_controls[] = {
+ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0,
+ wsa885x_i2c_usage_modes_get,
+ wsa885x_i2c_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_i2c_rx_slot_mask_get,
+ wsa885x_i2c_rx_slot_mask_put),
+};
+
+static const struct snd_soc_component_driver wsa885x_i2c_component = {
+ .name = "wsa885x-i2c",
+ .probe = wsa885x_component_probe,
+ .remove = wsa885x_component_remove,
+ .controls = wsa885x_snd_controls,
+ .num_controls = ARRAY_SIZE(wsa885x_snd_controls),
+};
+
+static irqreturn_t wsa885x_handle_i2c_irq(int irq_idx, void *data)
+{
+ struct wsa885x_i2c_priv *wsa885x = data;
+
+ if (!wsa885x)
+ return IRQ_NONE;
+
+ 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_i2c_priv *wsa885x = data;
+ irqreturn_t handled = IRQ_NONE;
+ irqreturn_t irq_ret;
+ int i, bit, ret;
+ int irq_num;
+
+ (void)irq;
+ if (!wsa885x || !wsa885x->regmap)
+ return IRQ_NONE;
+
+ 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;
+ /* INTR_CLEAR registers are write-only; use regmap_write
+ * instead of regmap_update_bits to avoid the read-modify-write
+ * that regmap_update_bits performs on non-readable registers.
+ */
+ 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_i2c_irq(irq_num, wsa885x);
+ if (irq_ret == IRQ_HANDLED)
+ handled = IRQ_HANDLED;
+ }
+ }
+ }
+ return handled;
+}
+
+static int wsa885x_register_irq(struct wsa885x_i2c_priv *wsa885x)
+{
+ int irq_number;
+
+ irq_number = gpiod_to_irq(wsa885x->intr_pin);
+ if (irq_number < 0)
+ return dev_err_probe(wsa885x->dev, irq_number,
+ "Failed to get interrupt IRQ\n");
+
+ return devm_request_threaded_irq(wsa885x->dev, irq_number, NULL,
+ wsa885x_interrupt_handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+ dev_name(wsa885x->dev), wsa885x);
+}
+
+static int wsa885x_i2c_probe(struct i2c_client *client)
+{
+ struct wsa885x_i2c_priv *wsa885x;
+ const struct snd_soc_component_driver *component_driver = &wsa885x_i2c_component;
+ const char *init_table_prop = "qcom,wsa885x-init-table";
+ int ret, i, count;
+ 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;
+ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg);
+
+ if (IS_ERR(wsa885x->regmap))
+ return PTR_ERR(wsa885x->regmap);
+
+ /*
+ * Use a signed int for the count check: device_property_count_u32()
+ * returns a negative errno on error. Storing it directly into the
+ * uint32_t field before checking causes the negative value to wrap to
+ * a huge positive number, bypassing the <= 0 guard and triggering a
+ * multi-GB kmalloc that fails with -ENOMEM.
+ */
+ count = device_property_count_u32(dev, init_table_prop);
+
+ if (count > 0) {
+ if (count % 2) {
+ dev_err(dev, "%s: Invalid number of elements in %s (%d)\n",
+ __func__, init_table_prop, count);
+ return -EINVAL;
+ }
+ if (count > WSA885X_INIT_TABLE_MAX_ITEMS) {
+ dev_err(dev, "%s: %s has too many elements (%d > %u)\n",
+ __func__, init_table_prop, count,
+ WSA885X_INIT_TABLE_MAX_ITEMS);
+ return -EINVAL;
+ }
+ wsa885x->init_table_size = count;
+
+ wsa885x->init_table = devm_kcalloc(dev, wsa885x->init_table_size,
+ sizeof(*wsa885x->init_table), GFP_KERNEL);
+ if (!wsa885x->init_table)
+ return -ENOMEM;
+
+ if (device_property_read_u32_array(dev, init_table_prop,
+ wsa885x->init_table,
+ wsa885x->init_table_size)) {
+ dev_err(dev, "%s: Failed to read %s\n",
+ __func__, init_table_prop);
+ return -EINVAL;
+ }
+ }
+
+ ret = device_property_read_u32(dev, "qcom,battery-config",
+ &wsa885x->batt_conf);
+ if (ret) {
+ wsa885x->batt_conf = WSA885X_BATT_1S;
+ } else if (wsa885x->batt_conf != WSA885X_BATT_1S &&
+ wsa885x->batt_conf != WSA885X_BATT_2S) {
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid battery config %u (expected 1S or 2S)\n",
+ wsa885x->batt_conf);
+ }
+
+ for (i = 0; i < WSA885X_SUPPLIES_NUM; i++)
+ wsa885x->supplies[i].supply = wsa885x_supply_name[i];
+
+ ret = devm_regulator_bulk_get(dev, WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ ret = regulator_bulk_enable(WSA885X_SUPPLIES_NUM, wsa885x->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulators\n");
+
+ ret = devm_add_action_or_reset(dev, wsa885x_regulator_disable, wsa885x);
+ if (ret)
+ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
+
+ 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");
+
+ wsa885x_gpio_set(wsa885x, 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);
+
+ wsa885x->intr_pin = devm_gpiod_get(dev, "interrupt", GPIOD_IN);
+ if (IS_ERR(wsa885x->intr_pin))
+ return dev_err_probe(dev, PTR_ERR(wsa885x->intr_pin),
+ "Interrupt GPIO not found\n");
+
+ ret = wsa885x_register_irq(wsa885x);
+ if (ret)
+ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n");
+
+ ret = devm_snd_soc_register_component(dev, component_driver,
+ wsa885x_i2c_dai,
+ ARRAY_SIZE(wsa885x_i2c_dai));
+ if (ret)
+ return dev_err_probe(dev, ret, "Codec component registration failed\n");
+
+ return 0;
+}
+
+static const struct of_device_id wsa885x_i2c_dt_match[] = {
+ {
+ .compatible = "qcom,wsa885x-i2c",
+ },
+ {}
+};
+
+static const struct i2c_device_id wsa885x_id_i2c[] = {
+ {"wsa885x_i2c", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, wsa885x_id_i2c);
+MODULE_DEVICE_TABLE(of, wsa885x_i2c_dt_match);
+
+static struct i2c_driver wsa885x_i2c_driver = {
+ .driver = {
+ .name = "wsa885x_i2c",
+ .of_match_table = wsa885x_i2c_dt_match,
+ },
+ .probe = wsa885x_i2c_probe,
+ .id_table = wsa885x_id_i2c,
+};
+
+module_i2c_driver(wsa885x_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC WSA885X I2C Smart PA Codec Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c
2026-06-10 15:57 ` [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c Prasad Kumpatla
@ 2026-06-10 16:08 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-06-10 16:08 UTC (permalink / raw)
To: Prasad Kumpatla; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] The schema incorrectly uses `additionalProperties: false` while including a `$ref` to `dai-common.yaml#`, which will erroneously reject valid standard DAI properties.
- [Medium] The binding uses a custom GPIO property (`interrupt-gpios`) to model a hardware interrupt line instead of the standard `interrupts` property.
- [Medium] The `qcom,wsa885x-init-table` property inappropriately encodes raw hardware register scripts into the Device Tree.
- [Low] Wildcard ('x') used in the compatible string instead of specific hardware part numbers.
--
commit 535bce1da75b7ff8bde3534f15f04d5dbbcc99a2
Author: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
dt-bindings: sound: add qcom,wsa885x-i2c
This commit documents the Qualcomm WSA885X I2C smart amplifier binding. It
describes the required supplies, powerdown and interrupt GPIOs, battery
configuration, and an initialization table for programming the device.
> diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
> new file mode 100644
> index 0000000000000..1069f470dea5d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/qcom,wsa885x-i2c.yaml
> @@ -0,0 +1,89 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/qcom,wsa885x-i2c.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm WSA885x I2C smart speaker amplifier
[ ... ]
> +allOf:
> + - $ref: dai-common.yaml#
> +
> +properties:
> + compatible:
> + const: qcom,wsa885x-i2c
[Severity: Low]
Is it standard to use a wildcard (x) in the compatible string here?
Devicetree typically requires specific hardware part numbers, such as
qcom,wsa8850-i2c, to ensure future variant quirks or different tuning
requirements can be handled properly without ambiguity.
> +
> + reg:
> + maxItems: 1
> +
> + '#sound-dai-cells':
> + const: 0
> +
> + powerdown-gpios:
> + description: GPIO controlling the SD_N powerdown pin.
> + maxItems: 1
> +
> + interrupt-gpios:
> + description: GPIO used for the codec interrupt output.
> + maxItems: 1
[Severity: Medium]
Does this hardware interrupt line need to be modeled as a raw GPIO property
rather than using the standard interrupts and interrupt-parent properties?
Modeling it as a standard interrupt allows proper subsystem integration and
routing, whereas interrupt-gpios bypasses the standard IRQ hierarchy.
> +
> + vdd-1p8-supply: true
> +
> + vdd-io-supply: true
> +
> + qcom,battery-config:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Speaker battery configuration, 1 for 1S and 2 for 2S.
> + default: 1
> + enum: [1, 2]
> +
> + qcom,wsa885x-init-table:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + minItems: 2
> + maxItems: 256
> + description: |
> + Sequence of register/value pairs applied during codec hardware
> + initialization. Entries are encoded as alternating register address and
> + register value cells. The number of entries must be even (register/value
> + pairs); maxItems is 256 (128 pairs).
[Severity: Medium]
Does this property encode raw hardware register scripts directly into the
devicetree?
The devicetree is generally meant to describe static hardware topology rather
than act as a repository for magic register initialization sequences.
Typically, this sort of initialization logic resides within the driver code
itself, keyed off specific compatible strings.
> +
> +required:
> + - compatible
> + - reg
> + - '#sound-dai-cells'
> + - powerdown-gpios
> + - interrupt-gpios
> + - vdd-1p8-supply
> + - vdd-io-supply
> +
> +additionalProperties: false
[Severity: Medium]
Since this binding includes a reference to dai-common.yaml# in the allOf
block above, will using additionalProperties: false cause standard DAI
properties to be erroneously rejected?
The additionalProperties keyword only accounts for properties explicitly
defined in the local schema. The standard approach in this scenario is to
use unevaluatedProperties: false so that properties from the referenced
schema are correctly validated.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
2026-06-10 15:57 ` [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver Prasad Kumpatla
@ 2026-06-10 16:08 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-06-10 16:08 UTC (permalink / raw)
To: Prasad Kumpatla; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Manual snd_soc_component_exit_regmap() call on a devm-managed regmap causes a double free and potential Use-After-Free.
- [High] Volume control changes do not take effect dynamically during active playback.
- [Medium] ALSA Kcontrol .put callbacks unconditionally return 0 instead of 1 when values are successfully changed.
- [Medium] Shared configuration variables are modified by ALSA kcontrols and read by DAI callbacks without locking.
- [Low] Passing 'int *' to regmap_read() causes incompatible pointer type compiler warnings.
--
commit e03e869e67ca57141b83b27db8520ee9f2d72031
Author: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
Date: Wed Jun 10 15:57:08 2026 +0000
ASoC: codecs: add Qualcomm WSA885X I2C codec driver
Add an ASoC codec driver for the Qualcomm WSA885X smart speaker
amplifier accessed over I2C. The driver provides the control-side
support needed for playback bring-up, including register programming,
serial interface setup, clock handling, mute and gain control, reset
handling and interrupt support.
> diff --git a/sound/soc/codecs/wsa885x-i2c.c b/sound/soc/codecs/wsa885x-i2c.c
> new file mode 100644
> index 0000000000000..a7d8f8d48a21c
> --- /dev/null
> +++ b/sound/soc/codecs/wsa885x-i2c.c
[ ... ]
> +static int wsa885x_wait_for_pde_state(struct wsa885x_i2c_priv *wsa885x, int ps)
> +{
> + int act_ps = -1, cnt = 0, clock_valid = -1;
> + int rc = 0;
> +
> + if (!wsa885x || !wsa885x->regmap)
> + return -EINVAL;
> +
> + 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);
[Severity: Low]
Does this trigger a compiler warning? regmap_read() expects an unsigned
int * for its third argument, but act_ps is declared as an int. Should
act_ps and clock_valid be declared as unsigned int to match the API
signature?
[ ... ]
> +static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
> +{
[ ... ]
> + } 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));
> + regmap_write(wsa885x->regmap,
> + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB,
> + (s8)wsa885x->stereo_vol_db);
> + regmap_write(wsa885x->regmap,
> + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB,
> + (s8)wsa885x->stereo_vol_db);
[Severity: Medium]
Can this lead to a race condition where mismatched channel volumes are
applied? The stereo_vol_db is read twice sequentially here without lock
protection.
If a concurrent call to wsa885x_stereo_gain_offset_put() occurs, could it
update stereo_vol_db exactly between these two regmap writes?
[ ... ]
> +static void wsa885x_component_remove(struct snd_soc_component *component)
> +{
> + if (!component)
> + return;
> +
> + snd_soc_component_exit_regmap(component);
> +}
[Severity: Critical]
Is it safe to manually exit the regmap here? Since the regmap was allocated
using devm_regmap_init_i2c(), devres will attempt to free it automatically.
Calling snd_soc_component_exit_regmap() here causes an early free, which
can result in a double free.
Additionally, because the interrupt handler is registered via
devm_request_threaded_irq() before the component is registered, the IRQ
remains active after this component removal. If the interrupt fires in this
window, will wsa885x_interrupt_handler() trigger a use-after-free when
accessing wsa885x->regmap?
[ ... ]
> +static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_component *component;
> + struct wsa885x_i2c_priv *wsa885x;
> + long val;
> +
> + if (!kcontrol || !ucontrol)
> + return -EINVAL;
> +
> + component = snd_kcontrol_chip(kcontrol);
> + if (!component)
> + return -EINVAL;
> +
> + wsa885x = snd_soc_component_get_drvdata(component);
> + if (!wsa885x)
> + return -EINVAL;
> +
> + 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;
> + }
> + wsa885x->stereo_vol_db = (int)val - 84;
> + return 0;
> +}
[Severity: High]
Will this update take effect immediately during playback? The cached
volume is updated, but it doesn't look like the new value is written to the
hardware registers here. Are users expected to pause and restart the stream
for the volume change to apply?
[Severity: Medium]
Should this return 1 instead of 0 when the value actually changes?
ALSA kcontrol .put callbacks are expected to return 1 if the control was
modified so the core can generate a state change event for userspace.
[ ... ]
> +static int wsa885x_i2c_usage_modes_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
[ ... ]
> + val = ucontrol->value.integer.value[0];
> +
> + if (val < 0 || val > WSA885X_USAGE_MODE_MAX)
> + return -EINVAL;
> +
> + wsa885x_i2c->usage_mode = val;
> +
> + return 0;
> +}
[Severity: Medium]
Does this need to return 1 when the usage_mode changes so ALSA generates
a state change event, similar to the volume control above?
[ ... ]
> +static int wsa885x_i2c_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
[ ... ]
> + mask = ucontrol->value.integer.value[0];
> +
> + if (!wsa885x_is_valid_rx_slot_mask(mask))
> + return -EINVAL;
> +
> + wsa885x_i2c->rx_slot_mask = mask;
> +
> + return 0;
> +}
[Severity: Medium]
Does this also need to return 1 when the mask changes?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610155708.151067-1-prasad.kumpatla@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-10 16:08 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-10 15:57 [PATCH v1 0/2] ASoC: add Qualcomm WSA885X I2C codec support Prasad Kumpatla
2026-06-10 15:57 ` [PATCH v1 1/2] dt-bindings: sound: add qcom,wsa885x-i2c Prasad Kumpatla
2026-06-10 16:08 ` sashiko-bot
2026-06-10 15:57 ` [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver Prasad Kumpatla
2026-06-10 16:08 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox