diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml new file mode 100644 index 000000000000..00a6a05e9443 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wsa8855.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WSA8855 stereo smart speaker amplifier + +maintainers: + - Srinivas Kandagatla + - Prasad Kumpatla + +description: + WSA885X is a Qualcomm Aqstic stereo smart speaker amplifier. It uses a PCM + audio interface, an I2C control interface, and a Class-H amplifier path for + high efficiency, low output noise, and low idle power consumption. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: qcom,wsa8855 + + reg: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + powerdown-gpios: + description: GPIO controlling the SD_N powerdown pin. + maxItems: 1 + + reset-gpios: + description: GPIO controlling the SD_N powerdown pin. + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-1p8-supply: true + + vdd-io-supply: true + + qcom,battery-config: + description: + Battery topology connected to the speaker amplifier. 1S indicates one + battery cell in series, while 2S indicates two battery cells in series. + $ref: /schemas/types.yaml#/definitions/string + default: 1s + enum: + - 1s + - 2s + +required: + - compatible + - reg + - '#sound-dai-cells' + - interrupts + - vdd-1p8-supply + - vdd-io-supply + +oneOf: + - required: + - powerdown-gpios + - required: + - reset-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + speaker@c { + compatible = "qcom,wsa8855"; + reg = <0x0c>; + #sound-dai-cells = <0>; + powerdown-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>; + interrupt-parent = <&tlmm>; + interrupts = <77 IRQ_TYPE_EDGE_FALLING>; + vdd-1p8-supply = <&vreg_l2g_1p8>; + vdd-io-supply = <&vreg_l1g_1p2>; + qcom,battery-config = "1s"; + }; + }; +... diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bfd81ca89110..a678e68b0bd2 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 @@ -2775,6 +2776,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 d2a689006d69..2e006f5a8abd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -427,6 +427,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 @@ -878,6 +879,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 000000000000..1faa8541a872 --- /dev/null +++ b/sound/soc/codecs/wsa885x.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/* WSA885X codec driver */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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_TDM_CH_RX_CH0_EN BIT(0) +#define WSA885X_I2S_TDM_CH_RX_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, WSA885X_I2S_TDM_CH_RX_CH3_EN}, + {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, + WSA885X_I2S_TDM_CH_RX_CH0_EN | + WSA885X_I2S_TDM_CH_RX_CH3_EN); + 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, + WSA885X_I2S_TDM_CH_RX_CH3_EN); + 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, + WSA885X_I2S_TDM_CH_RX_CH0_EN); + 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_DIG_CTRL0_SDCA_COMMIT: + 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 = 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_dai, + ARRAY_SIZE(wsa885x_dai)); + if (ret) + return dev_err_probe(dev, ret, "Codec component 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");