diff options
Diffstat (limited to 'drivers/mfd/cs42l43.c')
-rw-r--r-- | drivers/mfd/cs42l43.c | 144 |
1 files changed, 112 insertions, 32 deletions
diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c index a0fb2dc6c3b2..103787f37443 100644 --- a/drivers/mfd/cs42l43.c +++ b/drivers/mfd/cs42l43.c @@ -29,7 +29,7 @@ #define CS42L43_RESET_DELAY_MS 20 -#define CS42L43_SDW_ATTACH_TIMEOUT_MS 500 +#define CS42L43_SDW_ATTACH_TIMEOUT_MS 5000 #define CS42L43_SDW_DETACH_TIMEOUT_MS 100 #define CS42L43_MCU_BOOT_STAGE1 1 @@ -43,8 +43,12 @@ #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 @@ -261,7 +265,7 @@ const struct reg_default cs42l43_reg_default[CS42L43_N_DEFAULTS] = { { CS42L43_ASRC_MASK, 0x0000000F }, { CS42L43_HPOUT_MASK, 0x00000003 }, }; -EXPORT_SYMBOL_NS_GPL(cs42l43_reg_default, MFD_CS42L43); +EXPORT_SYMBOL_NS_GPL(cs42l43_reg_default, "MFD_CS42L43"); bool cs42l43_readable_register(struct device *dev, unsigned int reg) { @@ -389,7 +393,7 @@ bool cs42l43_readable_register(struct device *dev, unsigned int reg) return false; } } -EXPORT_SYMBOL_NS_GPL(cs42l43_readable_register, MFD_CS42L43); +EXPORT_SYMBOL_NS_GPL(cs42l43_readable_register, "MFD_CS42L43"); bool cs42l43_precious_register(struct device *dev, unsigned int reg) { @@ -404,7 +408,7 @@ bool cs42l43_precious_register(struct device *dev, unsigned int reg) return false; } } -EXPORT_SYMBOL_NS_GPL(cs42l43_precious_register, MFD_CS42L43); +EXPORT_SYMBOL_NS_GPL(cs42l43_precious_register, "MFD_CS42L43"); bool cs42l43_volatile_register(struct device *dev, unsigned int reg) { @@ -432,7 +436,7 @@ bool cs42l43_volatile_register(struct device *dev, unsigned int reg) return cs42l43_precious_register(dev, reg); } } -EXPORT_SYMBOL_NS_GPL(cs42l43_volatile_register, MFD_CS42L43); +EXPORT_SYMBOL_NS_GPL(cs42l43_volatile_register, "MFD_CS42L43"); #define CS42L43_IRQ_OFFSET(reg) ((CS42L43_##reg##_INT) - CS42L43_DECIM_INT) @@ -709,6 +713,23 @@ 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 @@ -745,16 +766,16 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) ((mcu_rev & CS42L43_FW_SUBMINOR_REV_MASK) >> 8); /* - * The firmware has two revision numbers bringing either of them up to a - * supported version will provide the features the driver requires. + * 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_SUPPORTED_REV || - bios_rev >= CS42L43_MCU_SUPPORTED_BIOS_REV; + 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; + 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) { @@ -794,10 +815,15 @@ static int cs42l43_mcu_update_step(struct cs42l43 *cs42l43) return cs42l43_mcu_stage_2_3(cs42l43, shadow); } case CS42L43_MCU_BOOT_STAGE3: - if (patched) + if (patched) { + ret = cs42l43_mcu_is_hw_compatible(cs42l43, mcu_rev, bios_rev); + if (ret) + return ret; + return cs42l43_mcu_disable(cs42l43); - else + } else { return cs42l43_mcu_stage_3_2(cs42l43); + } case CS42L43_MCU_BOOT_STAGE4: return 0; default: @@ -943,7 +969,6 @@ static void cs42l43_boot_work(struct work_struct *work) err: pm_runtime_put_sync(cs42l43->dev); - cs42l43_dev_remove(cs42l43); } static int cs42l43_power_up(struct cs42l43 *cs42l43) @@ -959,7 +984,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) /* vdd-p must be on for 50uS before any other supply */ usleep_range(CS42L43_VDDP_DELAY_US, 2 * CS42L43_VDDP_DELAY_US); - gpiod_set_value_cansleep(cs42l43->reset, 1); + gpiod_set_raw_value_cansleep(cs42l43->reset, 1); ret = regulator_bulk_enable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); if (ret) { @@ -980,7 +1005,7 @@ static int cs42l43_power_up(struct cs42l43 *cs42l43) err_core_supplies: regulator_bulk_disable(CS42L43_N_SUPPLIES, cs42l43->core_supplies); err_reset: - gpiod_set_value_cansleep(cs42l43->reset, 0); + gpiod_set_raw_value_cansleep(cs42l43->reset, 0); regulator_disable(cs42l43->vdd_p); return ret; @@ -1002,7 +1027,7 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) return ret; } - gpiod_set_value_cansleep(cs42l43->reset, 0); + gpiod_set_raw_value_cansleep(cs42l43->reset, 0); ret = regulator_disable(cs42l43->vdd_p); if (ret) { @@ -1013,6 +1038,15 @@ static int cs42l43_power_down(struct cs42l43 *cs42l43) 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; @@ -1027,11 +1061,13 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) regcache_cache_only(cs42l43->regmap, true); - cs42l43->reset = devm_gpiod_get_optional(cs42l43->dev, "reset", GPIOD_OUT_LOW); + 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), @@ -1057,6 +1093,10 @@ int cs42l43_dev_probe(struct cs42l43 *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); @@ -1073,27 +1113,44 @@ int cs42l43_dev_probe(struct cs42l43 *cs42l43) return 0; } -EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, MFD_CS42L43); - -void cs42l43_dev_remove(struct cs42l43 *cs42l43) -{ - cs42l43_power_down(cs42l43); -} -EXPORT_SYMBOL_NS_GPL(cs42l43_dev_remove, MFD_CS42L43); +EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43"); static int cs42l43_suspend(struct device *dev) { struct cs42l43 *cs42l43 = dev_get_drvdata(dev); + static const struct reg_sequence mask_all[] = { + { CS42L43_DECIM_MASK, 0xFFFFFFFF, }, + { CS42L43_EQ_MIX_MASK, 0xFFFFFFFF, }, + { CS42L43_ASP_MASK, 0xFFFFFFFF, }, + { CS42L43_PLL_MASK, 0xFFFFFFFF, }, + { CS42L43_SOFT_MASK, 0xFFFFFFFF, }, + { CS42L43_SWIRE_MASK, 0xFFFFFFFF, }, + { CS42L43_MSM_MASK, 0xFFFFFFFF, }, + { CS42L43_ACC_DET_MASK, 0xFFFFFFFF, }, + { CS42L43_I2C_TGT_MASK, 0xFFFFFFFF, }, + { CS42L43_SPI_MSTR_MASK, 0xFFFFFFFF, }, + { CS42L43_SW_TO_SPI_BRIDGE_MASK, 0xFFFFFFFF, }, + { CS42L43_OTP_MASK, 0xFFFFFFFF, }, + { CS42L43_CLASS_D_AMP_MASK, 0xFFFFFFFF, }, + { CS42L43_GPIO_INT_MASK, 0xFFFFFFFF, }, + { CS42L43_ASRC_MASK, 0xFFFFFFFF, }, + { CS42L43_HPOUT_MASK, 0xFFFFFFFF, }, + }; int ret; - /* - * Don't care about being resumed here, but the driver does want - * force_resume to always trigger an actual resume, so that register - * state for the MCU/GPIOs is returned as soon as possible after system - * resume. force_resume will resume if the reference count is resumed on - * suspend hence the get_noresume. - */ - pm_runtime_get_noresume(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(cs42l43->dev, "Failed to resume for suspend: %d\n", ret); + return ret; + } + + /* The IRQs will be re-enabled on resume by the cache sync */ + ret = regmap_multi_reg_write_bypassed(cs42l43->regmap, + mask_all, ARRAY_SIZE(mask_all)); + if (ret) { + dev_err(cs42l43->dev, "Failed to mask IRQs: %d\n", ret); + return ret; + } ret = pm_runtime_force_suspend(dev); if (ret) { @@ -1108,6 +1165,26 @@ static int cs42l43_suspend(struct device *dev) if (ret) return ret; + disable_irq(cs42l43->irq); + + 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; } @@ -1120,6 +1197,8 @@ static int cs42l43_resume(struct device *dev) if (ret) return ret; + enable_irq(cs42l43->irq); + ret = pm_runtime_force_resume(dev); if (ret) { dev_err(cs42l43->dev, "Failed to force resume: %d\n", ret); @@ -1187,6 +1266,7 @@ err: 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) }; |