| // 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_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"); |