| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * CS42L43 core driver | |
| * | |
| * Copyright (C) 2022-2023 Cirrus Logic, Inc. and | |
| * Cirrus Logic International Semiconductor Ltd. | |
| */ | |
| #include <linux/array_size.h> | |
| #include <linux/bitops.h> | |
| #include <linux/build_bug.h> | |
| #include <linux/delay.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/firmware.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/mfd/core.h> | |
| #include <linux/mfd/cs42l43.h> | |
| #include <linux/mfd/cs42l43-regs.h> | |
| #include <linux/module.h> | |
| #include <linux/pm.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/regmap.h> | |
| #include <linux/soundwire/sdw.h> | |
| #include <linux/types.h> | |
| #include "cs42l43.h" | |
| #define CS42L43_RESET_DELAY_MS 20 | |
| #define CS42L43_SDW_ATTACH_TIMEOUT_MS 5000 | |
| #define CS42L43_SDW_DETACH_TIMEOUT_MS 100 | |
| #define CS42L43_MCU_BOOT_STAGE1 1 | |
| #define CS42L43_MCU_BOOT_STAGE2 2 | |
| #define CS42L43_MCU_BOOT_STAGE3 3 | |
| #define CS42L43_MCU_BOOT_STAGE4 4 | |
| #define CS42L43_MCU_POLL_US 5000 | |
| #define CS42L43_MCU_CMD_TIMEOUT_US 20000 | |
| #define CS42L43_MCU_UPDATE_FORMAT 3 | |
| #define CS42L43_MCU_UPDATE_OFFSET 0x100000 | |
| #define CS42L43_MCU_UPDATE_TIMEOUT_US 500000 | |
| #define CS42L43_MCU_UPDATE_RETRIES 5 | |
| #define CS42L43_MCU_ROM_REV 0x2001 | |
| #define CS42L43_MCU_ROM_BIOS_REV 0x0000 | |
| #define CS42L43_MCU_SUPPORTED_REV 0x2105 | |
| #define CS42L43_MCU_SHADOW_REGS_REQUIRED_REV 0x2200 | |
| #define CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV 0x1002 | |
| #define CS42L43_MCU_SUPPORTED_BIOS_REV 0x0001 | |
| #define CS42L43_VDDP_DELAY_US 50 | |
| #define CS42L43_VDDD_DELAY_US 1000 | |
| #define CS42L43_AUTOSUSPEND_TIME_MS 250 | |
| struct cs42l43_patch_header { | |
| __le16 version; | |
| __le16 size; | |
| __u8 reserved; | |
| __u8 secure; | |
| __le16 bss_size; | |
| __le32 apply_addr; | |
| __le32 checksum; | |
| __le32 sha; | |
| __le16 swrev; | |
| __le16 patchid; | |
| __le16 ipxid; | |
| __le16 romver; | |
| __le32 load_addr; | |
| } __packed; | |
| static const struct reg_sequence cs42l43_reva_patch[] = { | |
| { 0x4000, 0x00000055 }, | |
| { 0x4000, 0x000000AA }, | |
| { 0x10084, 0x00000000 }, | |
| { 0x1741C, 0x00CD2000 }, | |
| { 0x1718C, 0x00000003 }, | |
| { 0x4000, 0x00000000 }, | |
| { CS42L43_CCM_BLK_CLK_CONTROL, 0x00000002 }, | |
| { CS42L43_HPPATHVOL, 0x011B011B }, | |
| { CS42L43_OSC_DIV_SEL, 0x00000001 }, | |
| { CS42L43_DACCNFG2, 0x00000005 }, | |
| { CS42L43_MIC_DETECT_CONTROL_ANDROID, 0x80790079 }, | |
| { CS42L43_RELID, 0x0000000F }, | |
| }; | |
| const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { | |
| { CS42L43_DRV_CTRL1, 0x000186C0 }, | |
| { CS42L43_DRV_CTRL3, 0x286DB018 }, | |
| { CS42L43_DRV_CTRL4, 0x000006D8 }, | |
| { CS42L43_DRV_CTRL_5, 0x136C00C0 }, | |
| { CS42L43_GPIO_CTRL1, 0x00000707 }, | |
| { CS42L43_GPIO_CTRL2, 0x00000000 }, | |
| { CS42L43_GPIO_FN_SEL, 0x00000004 }, | |
| { CS42L43_MCLK_SRC_SEL, 0x00000000 }, | |
| { CS42L43_SAMPLE_RATE1, 0x00000003 }, | |
| { CS42L43_SAMPLE_RATE2, 0x00000003 }, | |
| { CS42L43_SAMPLE_RATE3, 0x00000003 }, | |
| { CS42L43_SAMPLE_RATE4, 0x00000003 }, | |
| { CS42L43_PLL_CONTROL, 0x00000000 }, | |
| { CS42L43_FS_SELECT1, 0x00000000 }, | |
| { CS42L43_FS_SELECT2, 0x00000000 }, | |
| { CS42L43_FS_SELECT3, 0x00000000 }, | |
| { CS42L43_FS_SELECT4, 0x00000000 }, | |
| { CS42L43_PDM_CONTROL, 0x00000000 }, | |
| { CS42L43_ASP_CLK_CONFIG1, 0x00010001 }, | |
| { CS42L43_ASP_CLK_CONFIG2, 0x00000000 }, | |
| { CS42L43_OSC_DIV_SEL, 0x00000001 }, | |
| { CS42L43_ADC_B_CTRL1, 0x00000000 }, | |
| { CS42L43_ADC_B_CTRL2, 0x00000000 }, | |
| { CS42L43_DECIM_HPF_WNF_CTRL1, 0x00000001 }, | |
| { CS42L43_DECIM_HPF_WNF_CTRL2, 0x00000001 }, | |
| { CS42L43_DECIM_HPF_WNF_CTRL3, 0x00000001 }, | |
| { CS42L43_DECIM_HPF_WNF_CTRL4, 0x00000001 }, | |
| { CS42L43B_DECIM_HPF_WNF_CTRL5, 0x00000001 }, | |
| { CS42L43B_DECIM_HPF_WNF_CTRL6, 0x00000001 }, | |
| { CS42L43_DMIC_PDM_CTRL, 0x00000000 }, | |
| { CS42L43_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, | |
| { CS42L43_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, | |
| { CS42L43B_DECIM_VOL_CTRL_CH1_CH2, 0x20122012 }, | |
| { CS42L43B_DECIM_VOL_CTRL_CH3_CH4, 0x20122012 }, | |
| { CS42L43B_DECIM_VOL_CTRL_CH5_CH6, 0x20122012 }, | |
| { CS42L43_INTP_VOLUME_CTRL1, 0x00000180 }, | |
| { CS42L43_INTP_VOLUME_CTRL2, 0x00000180 }, | |
| { CS42L43_AMP1_2_VOL_RAMP, 0x00000022 }, | |
| { CS42L43_ASP_CTRL, 0x00000004 }, | |
| { CS42L43_ASP_FSYNC_CTRL1, 0x000000FA }, | |
| { CS42L43_ASP_FSYNC_CTRL2, 0x00000001 }, | |
| { CS42L43_ASP_FSYNC_CTRL3, 0x00000000 }, | |
| { CS42L43_ASP_FSYNC_CTRL4, 0x000001F4 }, | |
| { CS42L43_ASP_DATA_CTRL, 0x0000003A }, | |
| { CS42L43_ASP_RX_EN, 0x00000000 }, | |
| { CS42L43_ASP_TX_EN, 0x00000000 }, | |
| { CS42L43_ASP_RX_CH1_CTRL, 0x00170001 }, | |
| { CS42L43_ASP_RX_CH2_CTRL, 0x00170031 }, | |
| { CS42L43_ASP_RX_CH3_CTRL, 0x00170061 }, | |
| { CS42L43_ASP_RX_CH4_CTRL, 0x00170091 }, | |
| { CS42L43_ASP_RX_CH5_CTRL, 0x001700C1 }, | |
| { CS42L43_ASP_RX_CH6_CTRL, 0x001700F1 }, | |
| { CS42L43_ASP_TX_CH1_CTRL, 0x00170001 }, | |
| { CS42L43_ASP_TX_CH2_CTRL, 0x00170031 }, | |
| { CS42L43_ASP_TX_CH3_CTRL, 0x00170061 }, | |
| { CS42L43_ASP_TX_CH4_CTRL, 0x00170091 }, | |
| { CS42L43_ASP_TX_CH5_CTRL, 0x001700C1 }, | |
| { CS42L43_ASP_TX_CH6_CTRL, 0x001700F1 }, | |
| { CS42L43_ASPTX1_INPUT, 0x00000000 }, | |
| { CS42L43_ASPTX2_INPUT, 0x00000000 }, | |
| { CS42L43_ASPTX3_INPUT, 0x00000000 }, | |
| { CS42L43_ASPTX4_INPUT, 0x00000000 }, | |
| { CS42L43_ASPTX5_INPUT, 0x00000000 }, | |
| { CS42L43_ASPTX6_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP1_CH1_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP1_CH2_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP1_CH3_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP1_CH4_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP2_CH1_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP2_CH2_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP3_CH1_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP3_CH2_INPUT, 0x00000000 }, | |
| { CS42L43B_SWIRE_DP3_CH3_INPUT, 0x00000000 }, | |
| { CS42L43B_SWIRE_DP3_CH4_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP4_CH1_INPUT, 0x00000000 }, | |
| { CS42L43_SWIRE_DP4_CH2_INPUT, 0x00000000 }, | |
| { CS42L43B_SWIRE_DP4_CH3_INPUT, 0x00000000 }, | |
| { CS42L43B_SWIRE_DP4_CH4_INPUT, 0x00000000 }, | |
| { CS42L43_ASRC_INT1_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_INT2_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_INT3_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_INT4_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_DEC1_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_DEC2_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_DEC3_INPUT1, 0x00000000 }, | |
| { CS42L43_ASRC_DEC4_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC1INT1_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC1INT2_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC1DEC1_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC1DEC2_INPUT1, 0x00000000 }, | |
| { CS42L43B_ISRC1DEC3_INPUT1, 0x00000000 }, | |
| { CS42L43B_ISRC1DEC4_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC2INT1_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC2INT2_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC2DEC1_INPUT1, 0x00000000 }, | |
| { CS42L43_ISRC2DEC2_INPUT1, 0x00000000 }, | |
| { CS42L43B_ISRC2DEC3_INPUT1, 0x00000000 }, | |
| { CS42L43B_ISRC2DEC4_INPUT1, 0x00000000 }, | |
| { CS42L43_EQ1MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_EQ1MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_EQ1MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_EQ1MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_EQ2MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_EQ2MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_EQ2MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_EQ2MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_SPDIF1_INPUT1, 0x00000000 }, | |
| { CS42L43_SPDIF2_INPUT1, 0x00000000 }, | |
| { CS42L43_AMP1MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_AMP1MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_AMP1MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_AMP1MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_AMP2MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_AMP2MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_AMP2MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_AMP2MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_AMP3MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_AMP3MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_AMP3MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_AMP3MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_AMP4MIX_INPUT1, 0x00800000 }, | |
| { CS42L43_AMP4MIX_INPUT2, 0x00800000 }, | |
| { CS42L43_AMP4MIX_INPUT3, 0x00800000 }, | |
| { CS42L43_AMP4MIX_INPUT4, 0x00800000 }, | |
| { CS42L43_ASRC_INT_ENABLES, 0x00000100 }, | |
| { CS42L43_ASRC_DEC_ENABLES, 0x00000100 }, | |
| { CS42L43_PDNCNTL, 0x00000000 }, | |
| { CS42L43_RINGSENSE_DEB_CTRL, 0x0000001B }, | |
| { CS42L43_TIPSENSE_DEB_CTRL, 0x0000001B }, | |
| { CS42L43_HS2, 0x050106F3 }, | |
| { CS42L43_STEREO_MIC_CTRL, 0x00000000 }, | |
| { CS42L43_STEREO_MIC_CLAMP_CTRL, 0x00000001 }, | |
| { CS42L43_BLOCK_EN2, 0x00000000 }, | |
| { CS42L43_BLOCK_EN3, 0x00000000 }, | |
| { CS42L43_BLOCK_EN4, 0x00000000 }, | |
| { CS42L43_BLOCK_EN5, 0x00000000 }, | |
| { CS42L43_BLOCK_EN6, 0x00000000 }, | |
| { CS42L43_BLOCK_EN7, 0x00000000 }, | |
| { CS42L43_BLOCK_EN8, 0x00000000 }, | |
| { CS42L43_BLOCK_EN9, 0x00000000 }, | |
| { CS42L43_BLOCK_EN10, 0x00000000 }, | |
| { CS42L43_BLOCK_EN11, 0x00000000 }, | |
| { CS42L43_TONE_CH1_CTRL, 0x00000000 }, | |
| { CS42L43_TONE_CH2_CTRL, 0x00000000 }, | |
| { CS42L43_MIC_DETECT_CONTROL_1, 0x00000003 }, | |
| { CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, 0x02000003 }, | |
| { CS42L43_MIC_DETECT_CONTROL_ANDROID, 0x80790079 }, | |
| { CS42L43_ISRC1_CTRL, 0x00000000 }, | |
| { CS42L43_ISRC2_CTRL, 0x00000000 }, | |
| { CS42L43_CTRL_REG, 0x00000006 }, | |
| { CS42L43_FDIV_FRAC, 0x40000000 }, | |
| { CS42L43_CAL_RATIO, 0x00000080 }, | |
| { CS42L43_SPI_CLK_CONFIG1, 0x00000001 }, | |
| { CS42L43_SPI_CONFIG1, 0x00000000 }, | |
| { CS42L43_SPI_CONFIG2, 0x00000000 }, | |
| { CS42L43_SPI_CONFIG3, 0x00000001 }, | |
| { CS42L43_SPI_CONFIG4, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG3, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG4, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG5, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG6, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG7, 0x00000000 }, | |
| { CS42L43_TRAN_CONFIG8, 0x00000000 }, | |
| { CS42L43_DACCNFG1, 0x00000008 }, | |
| { CS42L43_DACCNFG2, 0x00000005 }, | |
| { CS42L43_HPPATHVOL, 0x011B011B }, | |
| { CS42L43_PGAVOL, 0x00003470 }, | |
| { CS42L43_LOADDETENA, 0x00000000 }, | |
| { CS42L43_CTRL, 0x00000037 }, | |
| { CS42L43_COEFF_DATA_IN0, 0x00000000 }, | |
| { CS42L43_COEFF_RD_WR0, 0x00000000 }, | |
| { CS42L43_START_EQZ0, 0x00000000 }, | |
| { CS42L43_MUTE_EQ_IN0, 0x00000000 }, | |
| { CS42L43_DECIM_MASK, 0x0000000F }, | |
| { CS42L43_EQ_MIX_MASK, 0x0000000F }, | |
| { CS42L43_ASP_MASK, 0x000000FF }, | |
| { CS42L43_PLL_MASK, 0x00000003 }, | |
| { CS42L43_SOFT_MASK, 0x0000FFFF }, | |
| { CS42L43_SWIRE_MASK, 0x00007FFF }, | |
| { CS42L43_MSM_MASK, 0x00000FFF }, | |
| { CS42L43_ACC_DET_MASK, 0x00000FFF }, | |
| { CS42L43_I2C_TGT_MASK, 0x00000003 }, | |
| { CS42L43_SPI_MSTR_MASK, 0x00000007 }, | |
| { CS42L43_SW_TO_SPI_BRIDGE_MASK, 0x00000001 }, | |
| { CS42L43_OTP_MASK, 0x00000007 }, | |
| { CS42L43_CLASS_D_AMP_MASK, 0x00003FFF }, | |
| { CS42L43_GPIO_INT_MASK, 0x0000003F }, | |
| { CS42L43_ASRC_MASK, 0x0000000F }, | |
| { CS42L43_HPOUT_MASK, 0x00000003 }, | |
| }; | |
| EXPORT_SYMBOL_NS_GPL(cs42l43_reg_default, "MFD_CS42L43"); | |
| bool cs42l43_readable_register(struct device *dev, unsigned int reg) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| switch (reg) { | |
| case CS42L43_DEVID: | |
| case CS42L43_REVID: | |
| case CS42L43_RELID: | |
| case CS42L43_SFT_RESET: | |
| case CS42L43_DRV_CTRL1: | |
| case CS42L43_DRV_CTRL3: | |
| case CS42L43_DRV_CTRL4: | |
| case CS42L43_DRV_CTRL_5: | |
| case CS42L43_GPIO_CTRL1: | |
| case CS42L43_GPIO_CTRL2: | |
| case CS42L43_GPIO_STS: | |
| case CS42L43_GPIO_FN_SEL: | |
| case CS42L43_MCLK_SRC_SEL: | |
| case CS42L43_SAMPLE_RATE1 ... CS42L43_SAMPLE_RATE4: | |
| case CS42L43_PLL_CONTROL: | |
| case CS42L43_FS_SELECT1 ... CS42L43_FS_SELECT4: | |
| case CS42L43_PDM_CONTROL: | |
| case CS42L43_ASP_CLK_CONFIG1 ... CS42L43_ASP_CLK_CONFIG2: | |
| case CS42L43_OSC_DIV_SEL: | |
| case CS42L43_ADC_B_CTRL1 ... CS42L43_ADC_B_CTRL2: | |
| case CS42L43_DECIM_HPF_WNF_CTRL1 ... CS42L43_DECIM_HPF_WNF_CTRL4: | |
| case CS42L43_DMIC_PDM_CTRL: | |
| case CS42L43_INTP_VOLUME_CTRL1 ... CS42L43_INTP_VOLUME_CTRL2: | |
| case CS42L43_AMP1_2_VOL_RAMP: | |
| case CS42L43_ASP_CTRL: | |
| case CS42L43_ASP_FSYNC_CTRL1 ... CS42L43_ASP_FSYNC_CTRL4: | |
| case CS42L43_ASP_DATA_CTRL: | |
| case CS42L43_ASP_RX_EN ... CS42L43_ASP_TX_EN: | |
| case CS42L43_ASP_RX_CH1_CTRL ... CS42L43_ASP_RX_CH6_CTRL: | |
| case CS42L43_ASP_TX_CH1_CTRL ... CS42L43_ASP_TX_CH6_CTRL: | |
| case CS42L43_OTP_REVISION_ID: | |
| case CS42L43_ASPTX1_INPUT: | |
| case CS42L43_ASPTX2_INPUT: | |
| case CS42L43_ASPTX3_INPUT: | |
| case CS42L43_ASPTX4_INPUT: | |
| case CS42L43_ASPTX5_INPUT: | |
| case CS42L43_ASPTX6_INPUT: | |
| case CS42L43_SWIRE_DP1_CH1_INPUT: | |
| case CS42L43_SWIRE_DP1_CH2_INPUT: | |
| case CS42L43_SWIRE_DP1_CH3_INPUT: | |
| case CS42L43_SWIRE_DP1_CH4_INPUT: | |
| case CS42L43_SWIRE_DP2_CH1_INPUT: | |
| case CS42L43_SWIRE_DP2_CH2_INPUT: | |
| case CS42L43_SWIRE_DP3_CH1_INPUT: | |
| case CS42L43_SWIRE_DP3_CH2_INPUT: | |
| case CS42L43_SWIRE_DP4_CH1_INPUT: | |
| case CS42L43_SWIRE_DP4_CH2_INPUT: | |
| case CS42L43_ASRC_INT1_INPUT1: | |
| case CS42L43_ASRC_INT2_INPUT1: | |
| case CS42L43_ASRC_INT3_INPUT1: | |
| case CS42L43_ASRC_INT4_INPUT1: | |
| case CS42L43_ASRC_DEC1_INPUT1: | |
| case CS42L43_ASRC_DEC2_INPUT1: | |
| case CS42L43_ASRC_DEC3_INPUT1: | |
| case CS42L43_ASRC_DEC4_INPUT1: | |
| case CS42L43_ISRC1INT1_INPUT1: | |
| case CS42L43_ISRC1INT2_INPUT1: | |
| case CS42L43_ISRC1DEC1_INPUT1: | |
| case CS42L43_ISRC1DEC2_INPUT1: | |
| case CS42L43_ISRC2INT1_INPUT1: | |
| case CS42L43_ISRC2INT2_INPUT1: | |
| case CS42L43_ISRC2DEC1_INPUT1: | |
| case CS42L43_ISRC2DEC2_INPUT1: | |
| case CS42L43_EQ1MIX_INPUT1 ... CS42L43_EQ1MIX_INPUT4: | |
| case CS42L43_EQ2MIX_INPUT1 ... CS42L43_EQ2MIX_INPUT4: | |
| case CS42L43_SPDIF1_INPUT1: | |
| case CS42L43_SPDIF2_INPUT1: | |
| case CS42L43_AMP1MIX_INPUT1 ... CS42L43_AMP1MIX_INPUT4: | |
| case CS42L43_AMP2MIX_INPUT1 ... CS42L43_AMP2MIX_INPUT4: | |
| case CS42L43_AMP3MIX_INPUT1 ... CS42L43_AMP3MIX_INPUT4: | |
| case CS42L43_AMP4MIX_INPUT1 ... CS42L43_AMP4MIX_INPUT4: | |
| case CS42L43_ASRC_INT_ENABLES ... CS42L43_ASRC_DEC_ENABLES: | |
| case CS42L43_PDNCNTL: | |
| case CS42L43_RINGSENSE_DEB_CTRL: | |
| case CS42L43_TIPSENSE_DEB_CTRL: | |
| case CS42L43_TIP_RING_SENSE_INTERRUPT_STATUS: | |
| case CS42L43_HS2: | |
| case CS42L43_HS_STAT: | |
| case CS42L43_MCU_SW_INTERRUPT: | |
| case CS42L43_STEREO_MIC_CTRL: | |
| case CS42L43_STEREO_MIC_CLAMP_CTRL: | |
| case CS42L43_BLOCK_EN2 ... CS42L43_BLOCK_EN11: | |
| case CS42L43_TONE_CH1_CTRL ... CS42L43_TONE_CH2_CTRL: | |
| case CS42L43_MIC_DETECT_CONTROL_1: | |
| case CS42L43_DETECT_STATUS_1: | |
| case CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL: | |
| case CS42L43_MIC_DETECT_CONTROL_ANDROID: | |
| case CS42L43_ISRC1_CTRL: | |
| case CS42L43_ISRC2_CTRL: | |
| case CS42L43_CTRL_REG: | |
| case CS42L43_FDIV_FRAC: | |
| case CS42L43_CAL_RATIO: | |
| case CS42L43_SPI_CLK_CONFIG1: | |
| case CS42L43_SPI_CONFIG1 ... CS42L43_SPI_CONFIG4: | |
| case CS42L43_SPI_STATUS1 ... CS42L43_SPI_STATUS2: | |
| case CS42L43_TRAN_CONFIG1 ... CS42L43_TRAN_CONFIG8: | |
| case CS42L43_TRAN_STATUS1 ... CS42L43_TRAN_STATUS3: | |
| case CS42L43_TX_DATA: | |
| case CS42L43_RX_DATA: | |
| case CS42L43_DACCNFG1 ... CS42L43_DACCNFG2: | |
| case CS42L43_HPPATHVOL: | |
| case CS42L43_PGAVOL: | |
| case CS42L43_LOADDETRESULTS: | |
| case CS42L43_LOADDETENA: | |
| case CS42L43_CTRL: | |
| case CS42L43_COEFF_DATA_IN0: | |
| case CS42L43_COEFF_RD_WR0: | |
| case CS42L43_INIT_DONE0: | |
| case CS42L43_START_EQZ0: | |
| case CS42L43_MUTE_EQ_IN0: | |
| case CS42L43_DECIM_INT ... CS42L43_HPOUT_INT: | |
| case CS42L43_DECIM_MASK ... CS42L43_HPOUT_MASK: | |
| case CS42L43_DECIM_INT_SHADOW ... CS42L43_HP_OUT_SHADOW: | |
| case CS42L43_BOOT_CONTROL: | |
| case CS42L43_BLOCK_EN: | |
| case CS42L43_SHUTTER_CONTROL: | |
| case CS42L43B_MCU_SW_REV ... CS42L43B_MCU_RAM_MAX: | |
| return true; // registers present on all variants | |
| case CS42L43_MCU_SW_REV ... CS42L43B_MCU_SW_REV - 1: | |
| case CS42L43B_MCU_RAM_MAX + 1 ... CS42L43_MCU_RAM_MAX: | |
| case CS42L43_DECIM_VOL_CTRL_CH1_CH2 ... CS42L43_DECIM_VOL_CTRL_CH3_CH4: | |
| return cs42l43->variant_id == CS42L43_DEVID_VAL; // regs only in CS42L43 variant | |
| case CS42L43B_DECIM_VOL_CTRL_CH1_CH2 ... CS42L43B_DECIM_HPF_WNF_CTRL6: | |
| case CS42L43B_SWIRE_DP3_CH3_INPUT ... CS42L43B_SWIRE_DP4_CH4_INPUT: | |
| case CS42L43B_ISRC1DEC3_INPUT1 ... CS42L43B_ISRC2DEC4_INPUT1: | |
| return cs42l43->variant_id == CS42L43B_DEVID_VAL; // regs only in CS42L43B variant | |
| default: | |
| return false; | |
| } | |
| } | |
| EXPORT_SYMBOL_NS_GPL(cs42l43_readable_register, "MFD_CS42L43"); | |
| bool cs42l43_precious_register(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case CS42L43_SFT_RESET: | |
| case CS42L43_TX_DATA: | |
| case CS42L43_RX_DATA: | |
| case CS42L43_DECIM_INT ... CS42L43_HPOUT_INT: | |
| case CS42L43_MCU_SW_REV ... CS42L43_MCU_RAM_MAX: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| EXPORT_SYMBOL_NS_GPL(cs42l43_precious_register, "MFD_CS42L43"); | |
| bool cs42l43_volatile_register(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case CS42L43_DEVID: | |
| case CS42L43_REVID: | |
| case CS42L43_RELID: | |
| case CS42L43_GPIO_STS: | |
| case CS42L43_OTP_REVISION_ID: | |
| case CS42L43_TIP_RING_SENSE_INTERRUPT_STATUS: | |
| case CS42L43_HS_STAT: | |
| case CS42L43_MCU_SW_INTERRUPT: | |
| case CS42L43_DETECT_STATUS_1: | |
| case CS42L43_SPI_STATUS1 ... CS42L43_SPI_STATUS2: | |
| case CS42L43_TRAN_CONFIG1 ... CS42L43_TRAN_CONFIG2: | |
| case CS42L43_TRAN_CONFIG8: | |
| case CS42L43_TRAN_STATUS1 ... CS42L43_TRAN_STATUS3: | |
| case CS42L43_LOADDETRESULTS: | |
| case CS42L43_INIT_DONE0: | |
| case CS42L43_DECIM_INT_SHADOW ... CS42L43_HP_OUT_SHADOW: | |
| case CS42L43_BOOT_CONTROL: | |
| case CS42L43_BLOCK_EN: | |
| return true; | |
| default: | |
| return cs42l43_precious_register(dev, reg); | |
| } | |
| } | |
| EXPORT_SYMBOL_NS_GPL(cs42l43_volatile_register, "MFD_CS42L43"); | |
| #define CS42L43_IRQ_OFFSET(reg) ((CS42L43_##reg##_INT) - CS42L43_DECIM_INT) | |
| #define CS42L43_IRQ_REG(name, reg) REGMAP_IRQ_REG(CS42L43_##name, \ | |
| CS42L43_IRQ_OFFSET(reg), \ | |
| CS42L43_##name##_INT_MASK) | |
| static const struct regmap_irq cs42l43_regmap_irqs[] = { | |
| CS42L43_IRQ_REG(PLL_LOST_LOCK, PLL), | |
| CS42L43_IRQ_REG(PLL_READY, PLL), | |
| CS42L43_IRQ_REG(HP_STARTUP_DONE, MSM), | |
| CS42L43_IRQ_REG(HP_SHUTDOWN_DONE, MSM), | |
| CS42L43_IRQ_REG(HSDET_DONE, MSM), | |
| CS42L43_IRQ_REG(TIPSENSE_UNPLUG_DB, MSM), | |
| CS42L43_IRQ_REG(TIPSENSE_PLUG_DB, MSM), | |
| CS42L43_IRQ_REG(RINGSENSE_UNPLUG_DB, MSM), | |
| CS42L43_IRQ_REG(RINGSENSE_PLUG_DB, MSM), | |
| CS42L43_IRQ_REG(TIPSENSE_UNPLUG_PDET, MSM), | |
| CS42L43_IRQ_REG(TIPSENSE_PLUG_PDET, MSM), | |
| CS42L43_IRQ_REG(RINGSENSE_UNPLUG_PDET, MSM), | |
| CS42L43_IRQ_REG(RINGSENSE_PLUG_PDET, MSM), | |
| CS42L43_IRQ_REG(HS2_BIAS_SENSE, ACC_DET), | |
| CS42L43_IRQ_REG(HS1_BIAS_SENSE, ACC_DET), | |
| CS42L43_IRQ_REG(DC_DETECT1_FALSE, ACC_DET), | |
| CS42L43_IRQ_REG(DC_DETECT1_TRUE, ACC_DET), | |
| CS42L43_IRQ_REG(HSBIAS_CLAMPED, ACC_DET), | |
| CS42L43_IRQ_REG(HS3_4_BIAS_SENSE, ACC_DET), | |
| CS42L43_IRQ_REG(AMP2_CLK_STOP_FAULT, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_CLK_STOP_FAULT, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_VDDSPK_FAULT, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_VDDSPK_FAULT, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_SHUTDOWN_DONE, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_SHUTDOWN_DONE, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_STARTUP_DONE, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_STARTUP_DONE, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_THERM_SHDN, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_THERM_SHDN, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_THERM_WARN, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_THERM_WARN, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP2_SCDET, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(AMP1_SCDET, CLASS_D_AMP), | |
| CS42L43_IRQ_REG(GPIO3_FALL, GPIO), | |
| CS42L43_IRQ_REG(GPIO3_RISE, GPIO), | |
| CS42L43_IRQ_REG(GPIO2_FALL, GPIO), | |
| CS42L43_IRQ_REG(GPIO2_RISE, GPIO), | |
| CS42L43_IRQ_REG(GPIO1_FALL, GPIO), | |
| CS42L43_IRQ_REG(GPIO1_RISE, GPIO), | |
| CS42L43_IRQ_REG(HP_ILIMIT, HPOUT), | |
| CS42L43_IRQ_REG(HP_LOADDET_DONE, HPOUT), | |
| }; | |
| static const struct regmap_irq_chip cs42l43_irq_chip = { | |
| .name = "cs42l43", | |
| .status_base = CS42L43_DECIM_INT, | |
| .mask_base = CS42L43_DECIM_MASK, | |
| .num_regs = 16, | |
| .irqs = cs42l43_regmap_irqs, | |
| .num_irqs = ARRAY_SIZE(cs42l43_regmap_irqs), | |
| .runtime_pm = true, | |
| }; | |
| static const char * const cs42l43_core_supplies[] = { | |
| "vdd-a", "vdd-io", "vdd-cp", | |
| }; | |
| static const char * const cs42l43_parent_supplies[] = { "vdd-amp" }; | |
| static const struct mfd_cell cs42l43_devs[] = { | |
| { .name = "cs42l43-pinctrl", }, | |
| { .name = "cs42l43-spi", }, | |
| { | |
| .name = "cs42l43-codec", | |
| .parent_supplies = cs42l43_parent_supplies, | |
| .num_parent_supplies = ARRAY_SIZE(cs42l43_parent_supplies), | |
| }, | |
| }; | |
| /* | |
| * If the device is connected over Soundwire, as well as soft resetting the | |
| * device, this function will also way for the device to detach from the bus | |
| * before returning. | |
| */ | |
| static int cs42l43_soft_reset(struct cs42l43 *cs42l43) | |
| { | |
| static const struct reg_sequence reset[] = { | |
| { CS42L43_SFT_RESET, CS42L43_SFT_RESET_VAL }, | |
| }; | |
| reinit_completion(&cs42l43->device_detach); | |
| /* | |
| * Apply cache only because the soft reset will cause the device to | |
| * detach from the soundwire bus. | |
| */ | |
| regcache_cache_only(cs42l43->regmap, true); | |
| regmap_multi_reg_write_bypassed(cs42l43->regmap, reset, ARRAY_SIZE(reset)); | |
| msleep(CS42L43_RESET_DELAY_MS); | |
| if (cs42l43->sdw) { | |
| unsigned long timeout = msecs_to_jiffies(CS42L43_SDW_DETACH_TIMEOUT_MS); | |
| unsigned long time; | |
| time = wait_for_completion_timeout(&cs42l43->device_detach, timeout); | |
| if (!time) { | |
| dev_err(cs42l43->dev, "Timed out waiting for device detach\n"); | |
| return -ETIMEDOUT; | |
| } | |
| } | |
| return -EAGAIN; | |
| } | |
| /* | |
| * This function is essentially a no-op on I2C, but will wait for the device to | |
| * attach when the device is used on a SoundWire bus. | |
| */ | |
| static int cs42l43_wait_for_attach(struct cs42l43 *cs42l43) | |
| { | |
| if (!cs42l43->attached) { | |
| unsigned long timeout = msecs_to_jiffies(CS42L43_SDW_ATTACH_TIMEOUT_MS); | |
| unsigned long time; | |
| time = wait_for_completion_timeout(&cs42l43->device_attach, timeout); | |
| if (!time) { | |
| dev_err(cs42l43->dev, "Timed out waiting for device re-attach\n"); | |
| return -ETIMEDOUT; | |
| } | |
| } | |
| regcache_cache_only(cs42l43->regmap, false); | |
| /* The hardware requires enabling OSC_DIV before doing any SoundWire reads. */ | |
| if (cs42l43->sdw) | |
| regmap_write(cs42l43->regmap, CS42L43_OSC_DIV_SEL, | |
| CS42L43_OSC_DIV2_EN_MASK); | |
| return 0; | |
| } | |
| /* | |
| * This function will advance the firmware into boot stage 3 from boot stage 2. | |
| * Boot stage 3 is required to send commands to the firmware. This is achieved | |
| * by setting the firmware NEED configuration register to zero, this indicates | |
| * no configuration is required forcing the firmware to advance to boot stage 3. | |
| * | |
| * Later revisions of the firmware require the use of an alternative register | |
| * for this purpose, which is indicated through the shadow flag. | |
| */ | |
| static int cs42l43_mcu_stage_2_3(struct cs42l43 *cs42l43, bool shadow) | |
| { | |
| unsigned int need_reg = CS42L43_NEED_CONFIGS; | |
| unsigned int boot_reg; | |
| unsigned int val; | |
| int ret; | |
| switch (cs42l43->variant_id) { | |
| case CS42L43_DEVID_VAL: | |
| if (shadow) | |
| need_reg = CS42L43_FW_SH_BOOT_CFG_NEED_CONFIGS; | |
| boot_reg = CS42L43_BOOT_STATUS; | |
| break; | |
| case CS42L43B_DEVID_VAL: | |
| need_reg = CS42L43B_NEED_CONFIGS; | |
| boot_reg = CS42L43B_BOOT_STATUS; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| regmap_write(cs42l43->regmap, need_reg, 0); | |
| ret = regmap_read_poll_timeout(cs42l43->regmap, boot_reg, | |
| val, (val == CS42L43_MCU_BOOT_STAGE3), | |
| CS42L43_MCU_POLL_US, CS42L43_MCU_CMD_TIMEOUT_US); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to move to stage 3: %d, 0x%x\n", ret, val); | |
| return ret; | |
| } | |
| return -EAGAIN; | |
| } | |
| /* | |
| * This function will return the firmware to boot stage 2 from boot stage 3. | |
| * Boot stage 2 is required to apply updates to the firmware. This is achieved | |
| * by setting the firmware NEED configuration register to FW_PATCH_NEED_CFG, | |
| * setting the HAVE configuration register to 0, and soft resetting. The | |
| * firmware will see it is missing a patch configuration and will pause in boot | |
| * stage 2. | |
| * | |
| * Note: Unlike cs42l43_mcu_stage_2_3 there is no need to consider the shadow | |
| * register here as the driver will only return to boot stage 2 if the firmware | |
| * requires update which means the revision does not include shadow register | |
| * support. | |
| */ | |
| static int cs42l43_mcu_stage_3_2(struct cs42l43 *cs42l43) | |
| { | |
| regmap_write(cs42l43->regmap, CS42L43_FW_MISSION_CTRL_NEED_CONFIGS, | |
| CS42L43_FW_PATCH_NEED_CFG_MASK); | |
| regmap_write(cs42l43->regmap, CS42L43_FW_MISSION_CTRL_HAVE_CONFIGS, 0); | |
| return cs42l43_soft_reset(cs42l43); | |
| } | |
| /* | |
| * Disable the firmware running on the device such that the driver can access | |
| * the registers without fear of the MCU changing them under it. | |
| */ | |
| static int cs42l43_mcu_disable(struct cs42l43 *cs42l43) | |
| { | |
| unsigned int val, cfg_reg, ctrl_reg; | |
| int ret; | |
| switch (cs42l43->variant_id) { | |
| case CS42L43_DEVID_VAL: | |
| cfg_reg = CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_REG; | |
| ctrl_reg = CS42L43_FW_MISSION_CTRL_MM_CTRL_SELECTION; | |
| break; | |
| case CS42L43B_DEVID_VAL: | |
| cfg_reg = CS42L43B_FW_MISSION_CTRL_MM_MCU_CFG_REG; | |
| ctrl_reg = CS42L43B_FW_MISSION_CTRL_MM_CTRL_SELECTION; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| regmap_write(cs42l43->regmap, cfg_reg, CS42L43_FW_MISSION_CTRL_MM_MCU_CFG_DISABLE_VAL); | |
| regmap_write(cs42l43->regmap, ctrl_reg, CS42L43_FW_MM_CTRL_MCU_SEL_MASK); | |
| regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, CS42L43_CONTROL_IND_MASK); | |
| regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, 0); | |
| ret = regmap_read_poll_timeout(cs42l43->regmap, CS42L43_SOFT_INT_SHADOW, val, | |
| (val & CS42L43_CONTROL_APPLIED_INT_MASK), | |
| CS42L43_MCU_POLL_US, CS42L43_MCU_CMD_TIMEOUT_US); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to disable firmware: %d, 0x%x\n", ret, val); | |
| return ret; | |
| } | |
| /* Soft reset to clear any register state the firmware left behind. */ | |
| return cs42l43_soft_reset(cs42l43); | |
| } | |
| /* | |
| * Callback to load firmware updates. | |
| */ | |
| static void cs42l43_mcu_load_firmware(const struct firmware *firmware, void *context) | |
| { | |
| struct cs42l43 *cs42l43 = context; | |
| const struct cs42l43_patch_header *hdr; | |
| unsigned int loadaddr, val; | |
| int ret; | |
| if (!firmware || firmware->size < sizeof(*hdr)) { | |
| dev_err(cs42l43->dev, "Failed to load firmware\n"); | |
| cs42l43->firmware_error = -ENODEV; | |
| goto err; | |
| } | |
| hdr = (const struct cs42l43_patch_header *)&firmware->data[0]; | |
| loadaddr = le32_to_cpu(hdr->load_addr); | |
| if (le16_to_cpu(hdr->version) != CS42L43_MCU_UPDATE_FORMAT) { | |
| dev_err(cs42l43->dev, "Bad firmware file format: %d\n", hdr->version); | |
| cs42l43->firmware_error = -EINVAL; | |
| goto err_release; | |
| } | |
| regmap_write(cs42l43->regmap, CS42L43_PATCH_START_ADDR, loadaddr); | |
| regmap_bulk_write(cs42l43->regmap, loadaddr + CS42L43_MCU_UPDATE_OFFSET, | |
| &firmware->data[0], firmware->size / sizeof(u32)); | |
| regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, CS42L43_PATCH_IND_MASK); | |
| regmap_write(cs42l43->regmap, CS42L43_MCU_SW_INTERRUPT, 0); | |
| ret = regmap_read_poll_timeout(cs42l43->regmap, CS42L43_SOFT_INT_SHADOW, val, | |
| (val & CS42L43_PATCH_APPLIED_INT_MASK), | |
| CS42L43_MCU_POLL_US, CS42L43_MCU_UPDATE_TIMEOUT_US); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to update firmware: %d, 0x%x\n", ret, val); | |
| cs42l43->firmware_error = ret; | |
| goto err_release; | |
| } | |
| err_release: | |
| release_firmware(firmware); | |
| err: | |
| complete(&cs42l43->firmware_download); | |
| } | |
| static int cs42l43_mcu_is_hw_compatible(struct cs42l43 *cs42l43, | |
| unsigned int mcu_rev, | |
| unsigned int bios_rev) | |
| { | |
| /* | |
| * The firmware has two revision numbers bringing either of them up to a | |
| * supported version will provide the disable the driver requires. | |
| */ | |
| if (mcu_rev < CS42L43_MCU_SUPPORTED_REV && | |
| bios_rev < CS42L43_MCU_SUPPORTED_BIOS_REV) { | |
| dev_err(cs42l43->dev, "Firmware too old to support disable\n"); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * The process of updating the firmware is split into a series of steps, at the | |
| * end of each step a soft reset of the device might be required which will | |
| * require the driver to wait for the device to re-attach on the SoundWire bus, | |
| * if that control bus is being used. | |
| */ | |
| static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) | |
| { | |
| unsigned int mcu_rev, bios_rev, boot_status, secure_cfg; | |
| bool patched, shadow; | |
| int boot_status_reg, mcu_sw_rev_reg; | |
| int ret; | |
| switch (cs42l43->variant_id) { | |
| case CS42L43_DEVID_VAL: | |
| boot_status_reg = CS42L43_BOOT_STATUS; | |
| mcu_sw_rev_reg = CS42L43_MCU_SW_REV; | |
| break; | |
| case CS42L43B_DEVID_VAL: | |
| boot_status_reg = CS42L43B_BOOT_STATUS; | |
| mcu_sw_rev_reg = CS42L43B_MCU_SW_REV; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| /* Clear any stale software interrupt bits. */ | |
| regmap_read(cs42l43->regmap, CS42L43_SOFT_INT, &mcu_rev); | |
| ret = regmap_read(cs42l43->regmap, boot_status_reg, &boot_status); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read boot status: %d\n", ret); | |
| return ret; | |
| } | |
| ret = regmap_read(cs42l43->regmap, mcu_sw_rev_reg, &mcu_rev); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read firmware revision: %d\n", ret); | |
| return ret; | |
| } | |
| bios_rev = (((mcu_rev & CS42L43_BIOS_MAJOR_REV_MASK) << 12) | | |
| ((mcu_rev & CS42L43_BIOS_MINOR_REV_MASK) << 4) | | |
| ((mcu_rev & CS42L43_BIOS_SUBMINOR_REV_MASK) >> 8)) >> | |
| CS42L43_BIOS_MAJOR_REV_SHIFT; | |
| mcu_rev = ((mcu_rev & CS42L43_FW_MAJOR_REV_MASK) << 12) | | |
| ((mcu_rev & CS42L43_FW_MINOR_REV_MASK) << 4) | | |
| ((mcu_rev & CS42L43_FW_SUBMINOR_REV_MASK) >> 8); | |
| /* | |
| * The firmware has two revision numbers both of them being at the ROM | |
| * revision indicates no patch has been applied. | |
| */ | |
| patched = mcu_rev != CS42L43_MCU_ROM_REV || bios_rev != CS42L43_MCU_ROM_BIOS_REV; | |
| /* | |
| * Later versions of the firmwware require the driver to access some | |
| * features through a set of shadow registers. | |
| */ | |
| shadow = (mcu_rev >= CS42L43_MCU_SHADOW_REGS_REQUIRED_REV) || | |
| (bios_rev >= CS42L43_BIOS_SHADOW_REGS_REQUIRED_REV); | |
| ret = regmap_read(cs42l43->regmap, CS42L43_BOOT_CONTROL, &secure_cfg); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read security settings: %d\n", ret); | |
| return ret; | |
| } | |
| cs42l43->hw_lock = secure_cfg & CS42L43_LOCK_HW_STS_MASK; | |
| if (!patched && cs42l43->hw_lock) { | |
| dev_err(cs42l43->dev, "Unpatched secure device\n"); | |
| return -EPERM; | |
| } | |
| dev_dbg(cs42l43->dev, "Firmware(0x%x, 0x%x) in boot stage %d\n", | |
| mcu_rev, bios_rev, boot_status); | |
| switch (boot_status) { | |
| case CS42L43_MCU_BOOT_STAGE2: | |
| if (!patched) { | |
| ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, | |
| "cs42l43.bin", cs42l43->dev, | |
| GFP_KERNEL, cs42l43, | |
| cs42l43_mcu_load_firmware); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to request firmware: %d\n", ret); | |
| return ret; | |
| } | |
| wait_for_completion(&cs42l43->firmware_download); | |
| if (cs42l43->firmware_error) | |
| return cs42l43->firmware_error; | |
| return -EAGAIN; | |
| } else { | |
| return cs42l43_mcu_stage_2_3(cs42l43, shadow); | |
| } | |
| case CS42L43_MCU_BOOT_STAGE3: | |
| if (patched) { | |
| ret = cs42l43_mcu_is_hw_compatible(cs42l43, mcu_rev, bios_rev); | |
| if (ret) | |
| return ret; | |
| return cs42l43_mcu_disable(cs42l43); | |
| } else { | |
| return cs42l43_mcu_stage_3_2(cs42l43); | |
| } | |
| case CS42L43_MCU_BOOT_STAGE4: | |
| return 0; | |
| default: | |
| dev_err(cs42l43->dev, "Invalid boot status: %d\n", boot_status); | |
| return -EINVAL; | |
| } | |
| } | |
| /* | |
| * Update the firmware running on the device. | |
| */ | |
| static int cs42l43_mcu_update(struct cs42l43 *cs42l43) | |
| { | |
| int i, ret; | |
| for (i = 0; i < CS42L43_MCU_UPDATE_RETRIES; i++) { | |
| ret = cs42l43_mcu_update_step(cs42l43); | |
| if (ret != -EAGAIN) | |
| return ret; | |
| ret = cs42l43_wait_for_attach(cs42l43); | |
| if (ret) | |
| return ret; | |
| } | |
| dev_err(cs42l43->dev, "Failed retrying update\n"); | |
| return -ETIMEDOUT; | |
| } | |
| static int cs42l43_irq_config(struct cs42l43 *cs42l43) | |
| { | |
| struct irq_data *irq_data; | |
| unsigned long irq_flags; | |
| int ret; | |
| if (cs42l43->sdw) | |
| cs42l43->irq = cs42l43->sdw->irq; | |
| cs42l43->irq_chip = cs42l43_irq_chip; | |
| cs42l43->irq_chip.irq_drv_data = cs42l43; | |
| irq_data = irq_get_irq_data(cs42l43->irq); | |
| if (!irq_data) { | |
| dev_err(cs42l43->dev, "Invalid IRQ: %d\n", cs42l43->irq); | |
| return -EINVAL; | |
| } | |
| irq_flags = irqd_get_trigger_type(irq_data); | |
| switch (irq_flags) { | |
| case IRQF_TRIGGER_LOW: | |
| case IRQF_TRIGGER_HIGH: | |
| case IRQF_TRIGGER_RISING: | |
| case IRQF_TRIGGER_FALLING: | |
| break; | |
| case IRQ_TYPE_NONE: | |
| default: | |
| irq_flags = IRQF_TRIGGER_LOW; | |
| break; | |
| } | |
| irq_flags |= IRQF_ONESHOT; | |
| ret = devm_regmap_add_irq_chip(cs42l43->dev, cs42l43->regmap, | |
| cs42l43->irq, irq_flags, 0, | |
| &cs42l43->irq_chip, &cs42l43->irq_data); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to add IRQ chip: %d\n", ret); | |
| return ret; | |
| } | |
| dev_dbg(cs42l43->dev, "Configured IRQ %d with flags 0x%lx\n", | |
| cs42l43->irq, irq_flags); | |
| return 0; | |
| } | |
| static void cs42l43_boot_work(struct work_struct *work) | |
| { | |
| struct cs42l43 *cs42l43 = container_of(work, struct cs42l43, boot_work); | |
| unsigned int devid, revid, otp; | |
| int ret; | |
| ret = cs42l43_wait_for_attach(cs42l43); | |
| if (ret) | |
| goto err; | |
| ret = regmap_read(cs42l43->regmap, CS42L43_DEVID, &devid); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read devid: %d\n", ret); | |
| goto err; | |
| } | |
| switch (devid) { | |
| case CS42L43_DEVID_VAL: | |
| case CS42L43B_DEVID_VAL: | |
| if (devid != cs42l43->variant_id) { | |
| dev_err(cs42l43->dev, | |
| "Device ID (0x%06x) does not match variant ID (0x%06lx)\n", | |
| devid, cs42l43->variant_id); | |
| goto err; | |
| } | |
| break; | |
| default: | |
| dev_err(cs42l43->dev, "Unrecognised devid: 0x%06x\n", devid); | |
| goto err; | |
| } | |
| ret = regmap_read(cs42l43->regmap, CS42L43_REVID, &revid); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read rev: %d\n", ret); | |
| goto err; | |
| } | |
| ret = regmap_read(cs42l43->regmap, CS42L43_OTP_REVISION_ID, &otp); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to read otp rev: %d\n", ret); | |
| goto err; | |
| } | |
| dev_info(cs42l43->dev, | |
| "devid: 0x%06x, rev: 0x%02x, otp: 0x%02x\n", devid, revid, otp); | |
| ret = cs42l43_mcu_update(cs42l43); | |
| if (ret) | |
| goto err; | |
| ret = regmap_register_patch(cs42l43->regmap, cs42l43_reva_patch, | |
| ARRAY_SIZE(cs42l43_reva_patch)); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to apply register patch: %d\n", ret); | |
| goto err; | |
| } | |
| ret = cs42l43_irq_config(cs42l43); | |
| if (ret) | |
| goto err; | |
| ret = devm_mfd_add_devices(cs42l43->dev, PLATFORM_DEVID_NONE, | |
| cs42l43_devs, ARRAY_SIZE(cs42l43_devs), | |
| NULL, 0, NULL); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to add subdevices: %d\n", ret); | |
| goto err; | |
| } | |
| pm_runtime_put_autosuspend(cs42l43->dev); | |
| return; | |
| err: | |
| pm_runtime_put_sync(cs42l43->dev); | |
| } | |
| static int cs42l43_power_up(struct cs42l43 *cs42l43) | |
| { | |
| int ret; | |
| ret = regulator_enable(cs42l43->vdd_p); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to enable vdd-p: %d\n", ret); | |
| return ret; | |
| } | |
| /* vdd-p must be on for 50uS before any other supply */ | |
| usleep_range(CS42L43_VDDP_DELAY_US, 2 * CS42L43_VDDP_DELAY_US); | |
| gpiod_set_raw_value_cansleep(cs42l43->reset, 1); | |
| ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to enable core supplies: %d\n", ret); | |
| goto err_reset; | |
| } | |
| ret = regulator_enable(cs42l43->vdd_d); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to enable vdd-d: %d\n", ret); | |
| goto err_core_supplies; | |
| } | |
| usleep_range(CS42L43_VDDD_DELAY_US, 2 * CS42L43_VDDD_DELAY_US); | |
| return 0; | |
| err_core_supplies: | |
| regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); | |
| err_reset: | |
| gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | |
| regulator_disable(cs42l43->vdd_p); | |
| return ret; | |
| } | |
| static int cs42l43_power_down(struct cs42l43 *cs42l43) | |
| { | |
| int ret; | |
| ret = regulator_disable(cs42l43->vdd_d); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to disable vdd-d: %d\n", ret); | |
| return ret; | |
| } | |
| ret = regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to disable core supplies: %d\n", ret); | |
| return ret; | |
| } | |
| gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | |
| ret = regulator_disable(cs42l43->vdd_p); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to disable vdd-p: %d\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static void cs42l43_dev_remove(void *data) | |
| { | |
| struct cs42l43 *cs42l43 = data; | |
| cancel_work_sync(&cs42l43->boot_work); | |
| cs42l43_power_down(cs42l43); | |
| } | |
| int cs42l43_dev_probe(struct cs42l43 *cs42l43) | |
| { | |
| int i, ret; | |
| dev_set_drvdata(cs42l43->dev, cs42l43); | |
| mutex_init(&cs42l43->pll_lock); | |
| init_completion(&cs42l43->device_attach); | |
| init_completion(&cs42l43->device_detach); | |
| init_completion(&cs42l43->firmware_download); | |
| INIT_WORK(&cs42l43->boot_work, cs42l43_boot_work); | |
| regcache_cache_only(cs42l43->regmap, true); | |
| cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_HIGH); | |
| if (IS_ERR(cs42l43->reset)) | |
| return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->reset), | |
| "Failed to get reset\n"); | |
| gpiod_set_raw_value_cansleep(cs42l43->reset, 0); | |
| cs42l43->vdd_p = devm_regulator_get(cs42l43->dev, "vdd-p"); | |
| if (IS_ERR(cs42l43->vdd_p)) | |
| return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->vdd_p), | |
| "Failed to get vdd-p\n"); | |
| cs42l43->vdd_d = devm_regulator_get(cs42l43->dev, "vdd-d"); | |
| if (IS_ERR(cs42l43->vdd_d)) | |
| return dev_err_probe(cs42l43->dev, PTR_ERR(cs42l43->vdd_d), | |
| "Failed to get vdd-d\n"); | |
| BUILD_BUG_ON(ARRAY_SIZE(cs42l43_core_supplies) != CS42L43_N_SUPPLIES); | |
| for (i = 0; i < CS42L43_N_SUPPLIES; i++) | |
| cs42l43->core_supplies[i].supply = cs42l43_core_supplies[i]; | |
| ret = devm_regulator_bulk_get(cs42l43->dev, CS42L43_N_SUPPLIES, | |
| cs42l43->core_supplies); | |
| if (ret) | |
| return dev_err_probe(cs42l43->dev, ret, | |
| "Failed to get core supplies\n"); | |
| ret = cs42l43_power_up(cs42l43); | |
| if (ret) | |
| return ret; | |
| ret = devm_add_action_or_reset(cs42l43->dev, cs42l43_dev_remove, cs42l43); | |
| if (ret) | |
| return ret; | |
| pm_runtime_set_autosuspend_delay(cs42l43->dev, CS42L43_AUTOSUSPEND_TIME_MS); | |
| pm_runtime_use_autosuspend(cs42l43->dev); | |
| pm_runtime_set_active(cs42l43->dev); | |
| /* | |
| * The device is already powered up, but keep it from suspending until | |
| * the boot work runs. | |
| */ | |
| pm_runtime_get_noresume(cs42l43->dev); | |
| ret = devm_pm_runtime_enable(cs42l43->dev); | |
| if (ret) | |
| return ret; | |
| queue_work(system_long_wq, &cs42l43->boot_work); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43"); | |
| static int cs42l43_suspend(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| int ret; | |
| ret = pm_runtime_resume_and_get(dev); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to resume for suspend: %d\n", ret); | |
| return ret; | |
| } | |
| disable_irq(cs42l43->irq); | |
| ret = pm_runtime_force_suspend(dev); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to force suspend: %d\n", ret); | |
| pm_runtime_put_noidle(dev); | |
| return ret; | |
| } | |
| pm_runtime_put_noidle(dev); | |
| ret = cs42l43_power_down(cs42l43); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static int cs42l43_suspend_noirq(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| enable_irq(cs42l43->irq); | |
| return 0; | |
| } | |
| static int cs42l43_resume_noirq(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| disable_irq(cs42l43->irq); | |
| return 0; | |
| } | |
| static int cs42l43_resume(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| int ret; | |
| ret = cs42l43_power_up(cs42l43); | |
| if (ret) | |
| return ret; | |
| ret = pm_runtime_force_resume(dev); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to force resume: %d\n", ret); | |
| return ret; | |
| } | |
| enable_irq(cs42l43->irq); | |
| return 0; | |
| } | |
| static int cs42l43_runtime_suspend(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| /* | |
| * Whilst the driver doesn't power the chip down here, going into runtime | |
| * suspend lets the SoundWire bus power down, which means the driver | |
| * can't communicate with the device any more. | |
| */ | |
| regcache_cache_only(cs42l43->regmap, true); | |
| return 0; | |
| } | |
| static int cs42l43_runtime_resume(struct device *dev) | |
| { | |
| struct cs42l43 *cs42l43 = dev_get_drvdata(dev); | |
| unsigned int reset_canary; | |
| int ret; | |
| ret = cs42l43_wait_for_attach(cs42l43); | |
| if (ret) | |
| return ret; | |
| ret = regmap_read(cs42l43->regmap, CS42L43_RELID, &reset_canary); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to check reset canary: %d\n", ret); | |
| goto err; | |
| } | |
| if (!reset_canary) { | |
| /* | |
| * If the canary has cleared the chip has reset, re-handle the | |
| * MCU and mark the cache as dirty to indicate the chip reset. | |
| */ | |
| ret = cs42l43_mcu_update(cs42l43); | |
| if (ret) | |
| goto err; | |
| regcache_mark_dirty(cs42l43->regmap); | |
| } | |
| ret = regcache_sync(cs42l43->regmap); | |
| if (ret) { | |
| dev_err(cs42l43->dev, "Failed to restore register cache: %d\n", ret); | |
| goto err; | |
| } | |
| return 0; | |
| err: | |
| regcache_cache_only(cs42l43->regmap, true); | |
| return ret; | |
| } | |
| EXPORT_NS_GPL_DEV_PM_OPS(cs42l43_pm_ops, MFD_CS42L43) = { | |
| SYSTEM_SLEEP_PM_OPS(cs42l43_suspend, cs42l43_resume) | |
| NOIRQ_SYSTEM_SLEEP_PM_OPS(cs42l43_suspend_noirq, cs42l43_resume_noirq) | |
| RUNTIME_PM_OPS(cs42l43_runtime_suspend, cs42l43_runtime_resume, NULL) | |
| }; | |
| MODULE_DESCRIPTION("CS42L43 Core Driver"); | |
| MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_FIRMWARE("cs42l43.bin"); |