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