* [PATCH v3 2/2] ASoC: codecs: add Qualcomm WSA885X codec driver
2026-07-09 17:59 [PATCH v3 0/2] Add support for the Qualcomm WSA885X Stereo smart speaker amplifier Prasad Kumpatla
2026-07-09 17:59 ` [PATCH v3 1/2] ASoC: dt-bindings: qcom,wsa8855: add Qualcomm WSA8855 " Prasad Kumpatla
@ 2026-07-09 17:59 ` Prasad Kumpatla
2026-07-09 18:12 ` sashiko-bot
1 sibling, 1 reply; 7+ messages in thread
From: Prasad Kumpatla @ 2026-07-09 17:59 UTC (permalink / raw)
To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
Philipp Zabel, Prasad Kumpatla
Cc: Srinivas Kandagatla, linux-sound, linux-arm-msm, devicetree,
linux-kernel
Add an ASoC codec driver for the Qualcomm WSA885X stereo smart speaker
amplifier.
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.
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 | 1526 ++++++++++++++++++++++++++++++++++++
3 files changed, 1539 insertions(+)
create mode 100644 sound/soc/codecs/wsa885x.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index a41d0f508..0ff336b19 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..d2f4b980a
--- /dev/null
+++ b/sound/soc/codecs/wsa885x.c
@@ -0,0 +1,1526 @@
+// 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/delay.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/reset.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_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000)
+
+#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;
+ struct reset_control *sd_reset;
+ 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;
+ u32 mask;
+ 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 && !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;
+ }
+
+ mutex_lock(&wsa885x->state_lock);
+ if (rx_slot_mask)
+ 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;
+ mask = wsa885x->rx_slot_mask;
+
+ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL,
+ WSA885X_I2S_RESET_CTL_RESET_MASK,
+ WSA885X_I2S_RESET_CTL_RESET_MASK);
+
+ if (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)
+ goto exit_unlock;
+ 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 (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)
+ goto exit_unlock;
+ } else if (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)
+ goto exit_unlock;
+ }
+
+ 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);
+
+ ret = 0;
+
+exit_unlock:
+ mutex_unlock(&wsa885x->state_lock);
+
+ return ret;
+}
+
+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_CTRL1_I2S_TDM_CH_RX, 0x08);
+ 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_CTRL1_I2S_TDM_CH_RX, 0x08);
+ 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_CTRL1_I2S_TDM_CH_RX, 0x01);
+ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02);
+ }
+
+ 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_CTRL0_PA_FSM_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 = WSA885X_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &wsa885x_dai_ops,
+ },
+};
+
+static void wsa885x_reset_assert(void *data)
+{
+ struct wsa885x_priv *wsa885x = data;
+
+ if (wsa885x->sd_reset)
+ reset_control_assert(wsa885x->sd_reset);
+ else
+ gpiod_direction_output(wsa885x->sd_n, 1);
+}
+
+static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x)
+{
+ if (wsa885x->sd_reset)
+ reset_control_deassert(wsa885x->sd_reset);
+ else
+ gpiod_direction_output(wsa885x->sd_n, 0);
+}
+
+static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x)
+{
+ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL);
+ if (IS_ERR(wsa885x->sd_reset))
+ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset),
+ "Failed to get reset\n");
+ else if (wsa885x->sd_reset)
+ return 0;
+
+ wsa885x->sd_n = devm_gpiod_get_optional(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");
+
+ return 0;
+}
+
+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 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);
+
+ mutex_lock(&wsa885x->state_lock);
+ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) {
+ mutex_unlock(&wsa885x->state_lock);
+ return -ERANGE;
+ }
+
+ ucontrol->value.integer.value[0] = wsa885x->usage_mode;
+ mutex_unlock(&wsa885x->state_lock);
+
+ 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;
+
+ mutex_lock(&wsa885x->state_lock);
+ if (wsa885x->usage_mode == val) {
+ mutex_unlock(&wsa885x->state_lock);
+ return 0;
+ }
+
+ wsa885x->usage_mode = val;
+ mutex_unlock(&wsa885x->state_lock);
+
+ 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;
+
+ mutex_lock(&wsa885x->state_lock);
+ mask = wsa885x->rx_slot_mask;
+ mutex_unlock(&wsa885x->state_lock);
+ 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;
+
+ mutex_lock(&wsa885x->state_lock);
+ if (wsa885x->rx_slot_mask == mask) {
+ mutex_unlock(&wsa885x->state_lock);
+ return 0;
+ }
+
+ wsa885x->rx_slot_mask = mask;
+ mutex_unlock(&wsa885x->state_lock);
+
+ 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,
+ .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]);
+ }
+
+ ret = wsa885x_get_reset(dev, wsa885x);
+ if (ret)
+ return ret;
+
+ wsa885x_reset_deassert(wsa885x);
+ usleep_range(5000, 5500);
+
+ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, 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] 7+ messages in thread