[PATCH 0/2] Add low power hibernation support to cs35l41

This patch series adds support for the low power hibernation feature on cs35l41. This allows the DSP memory to be retained whilst the device enters a very low power state.
These patches will cause some very minor conflicts with Lucas's currently outstanding work on the HDA version of cs35l41. Whilst things will still build (well now I fixed my silly mistake), this patch adds a test key function his code will now have to call.
It looks like Lucas's patch might get a respin based on the comments from Andy, in which case I guess we can pull the small change into the next version of it. But either way these patches are not urgent so I am happy if they sit around until Lucas's stuff is merged too.
Thanks, Charles
Charles Keepax (2): ASoC: cs35l41: Update handling of test key registers ASoC: cs35l41: Add support for hibernate memory retention mode
include/sound/cs35l41.h | 7 ++ sound/soc/codecs/cs35l41-i2c.c | 1 + sound/soc/codecs/cs35l41-lib.c | 73 +++++++------ sound/soc/codecs/cs35l41-spi.c | 1 + sound/soc/codecs/cs35l41.c | 233 +++++++++++++++++++++++++++++++++++++---- sound/soc/codecs/cs35l41.h | 4 + 6 files changed, 268 insertions(+), 51 deletions(-)

In preparation for the addition of PM runtime support move the test key out of the register patches themselves. This is necessary to allow the test key to be held during cache synchronisation, which is required by the OTP settings which were unpacked from the device and written by the driver.
Also whilst at it, the driver uses a mixture of accessing the test key register by name and by address, consistently use the name.
Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com ---
Changes since v1: - Add missing export symbol for new test_key functions
include/sound/cs35l41.h | 2 ++ sound/soc/codecs/cs35l41-lib.c | 67 +++++++++++++++++++++--------------------- sound/soc/codecs/cs35l41.c | 32 +++++++++++--------- 3 files changed, 54 insertions(+), 47 deletions(-)
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 29a527457b486..56289b67b9a0e 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -762,6 +762,8 @@ struct cs35l41_otp_map_element_t { extern struct regmap_config cs35l41_regmap_i2c; extern struct regmap_config cs35l41_regmap_spi;
+int cs35l41_test_key_unlock(struct device *dev, struct regmap *regmap); +int cs35l41_test_key_lock(struct device *dev, struct regmap *regmap); int cs35l41_otp_unpack(struct device *dev, struct regmap *regmap); int cs35l41_register_errata_patch(struct device *dev, struct regmap *reg, unsigned int reg_revid); int cs35l41_set_channels(struct device *dev, struct regmap *reg, diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index 639dcd25b17e9..ecaf67fd76531 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -623,8 +623,6 @@ static const struct cs35l41_otp_packed_element_t otp_map_2[CS35L41_NUM_OTP_ELEM] };
static const struct reg_sequence cs35l41_reva0_errata_patch[] = { - { 0x00000040, 0x00005555 }, - { 0x00000040, 0x0000AAAA }, { 0x00003854, 0x05180240 }, { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, { 0x00004310, 0x00000000 }, @@ -637,38 +635,28 @@ static const struct reg_sequence cs35l41_reva0_errata_patch[] = { { CS35L41_IRQ2_DB3, 0x00000000 }, { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, - { 0x00000040, 0x0000CCCC }, - { 0x00000040, 0x00003333 }, { CS35L41_PWR_CTRL2, 0x00000000 }, { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, };
static const struct reg_sequence cs35l41_revb0_errata_patch[] = { - { 0x00000040, 0x00005555 }, - { 0x00000040, 0x0000AAAA }, { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, { 0x00004310, 0x00000000 }, { CS35L41_VPVBST_FS_SEL, 0x00000000 }, { CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 }, { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, - { 0x00000040, 0x0000CCCC }, - { 0x00000040, 0x00003333 }, { CS35L41_PWR_CTRL2, 0x00000000 }, { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, };
static const struct reg_sequence cs35l41_revb2_errata_patch[] = { - { 0x00000040, 0x00005555 }, - { 0x00000040, 0x0000AAAA }, { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, { 0x00004310, 0x00000000 }, { CS35L41_VPVBST_FS_SEL, 0x00000000 }, { CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 }, { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, - { 0x00000040, 0x0000CCCC }, - { 0x00000040, 0x00003333 }, { CS35L41_PWR_CTRL2, 0x00000000 }, { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, }; @@ -756,6 +744,39 @@ static const struct cs35l41_otp_map_element_t *cs35l41_find_otp_map(u32 otp_id) return NULL; }
+int cs35l41_test_key_unlock(struct device *dev, struct regmap *regmap) +{ + static const struct reg_sequence unlock[] = { + { CS35L41_TEST_KEY_CTL, 0x00000055 }, + { CS35L41_TEST_KEY_CTL, 0x000000AA }, + }; + int ret; + + ret = regmap_multi_reg_write(regmap, unlock, ARRAY_SIZE(unlock)); + if (ret) + dev_err(dev, "Failed to unlock test key: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(cs35l41_test_key_unlock); + +int cs35l41_test_key_lock(struct device *dev, struct regmap *regmap) +{ + static const struct reg_sequence unlock[] = { + { CS35L41_TEST_KEY_CTL, 0x000000CC }, + { CS35L41_TEST_KEY_CTL, 0x00000033 }, + }; + int ret; + + ret = regmap_multi_reg_write(regmap, unlock, ARRAY_SIZE(unlock)); + if (ret) + dev_err(dev, "Failed to lock test key: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(cs35l41_test_key_lock); + +/* Must be called with the TEST_KEY unlocked */ int cs35l41_otp_unpack(struct device *dev, struct regmap *regmap) { const struct cs35l41_otp_map_element_t *otp_map_match; @@ -794,17 +815,6 @@ int cs35l41_otp_unpack(struct device *dev, struct regmap *regmap) bit_offset = otp_map_match->bit_offset; word_offset = otp_map_match->word_offset;
- ret = regmap_write(regmap, CS35L41_TEST_KEY_CTL, 0x00000055); - if (ret) { - dev_err(dev, "Write Unlock key failed 1/2: %d\n", ret); - goto err_otp_unpack; - } - ret = regmap_write(regmap, CS35L41_TEST_KEY_CTL, 0x000000AA); - if (ret) { - dev_err(dev, "Write Unlock key failed 2/2: %d\n", ret); - goto err_otp_unpack; - } - for (i = 0; i < otp_map_match->num_elements; i++) { dev_dbg(dev, "bitoffset= %d, word_offset=%d, bit_sum mod 32=%d\n", bit_offset, word_offset, bit_sum % 32); @@ -840,16 +850,6 @@ int cs35l41_otp_unpack(struct device *dev, struct regmap *regmap) } }
- ret = regmap_write(regmap, CS35L41_TEST_KEY_CTL, 0x000000CC); - if (ret) { - dev_err(dev, "Write Lock key failed 1/2: %d\n", ret); - goto err_otp_unpack; - } - ret = regmap_write(regmap, CS35L41_TEST_KEY_CTL, 0x00000033); - if (ret) { - dev_err(dev, "Write Lock key failed 2/2: %d\n", ret); - goto err_otp_unpack; - } ret = 0;
err_otp_unpack: @@ -859,6 +859,7 @@ int cs35l41_otp_unpack(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(cs35l41_otp_unpack);
+/* Must be called with the TEST_KEY unlocked */ int cs35l41_register_errata_patch(struct device *dev, struct regmap *reg, unsigned int reg_revid) { char *rev; diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index 05839fabf97bc..e1b9fd8ee9966 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -534,19 +534,19 @@ static irqreturn_t cs35l41_irq(int irq, void *data) }
static const struct reg_sequence cs35l41_pup_patch[] = { - { 0x00000040, 0x00000055 }, - { 0x00000040, 0x000000AA }, + { CS35L41_TEST_KEY_CTL, 0x00000055 }, + { CS35L41_TEST_KEY_CTL, 0x000000AA }, { 0x00002084, 0x002F1AA0 }, - { 0x00000040, 0x000000CC }, - { 0x00000040, 0x00000033 }, + { CS35L41_TEST_KEY_CTL, 0x000000CC }, + { CS35L41_TEST_KEY_CTL, 0x00000033 }, };
static const struct reg_sequence cs35l41_pdn_patch[] = { - { 0x00000040, 0x00000055 }, - { 0x00000040, 0x000000AA }, + { CS35L41_TEST_KEY_CTL, 0x00000055 }, + { CS35L41_TEST_KEY_CTL, 0x000000AA }, { 0x00002084, 0x002F1AA3 }, - { 0x00000040, 0x000000CC }, - { 0x00000040, 0x00000033 }, + { CS35L41_TEST_KEY_CTL, 0x000000CC }, + { CS35L41_TEST_KEY_CTL, 0x00000033 }, };
static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, @@ -1329,10 +1329,20 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, goto err; }
+ cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + ret = cs35l41_register_errata_patch(cs35l41->dev, cs35l41->regmap, reg_revid); if (ret) goto err;
+ ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); + if (ret < 0) { + dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); + goto err; + } + + cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + irq_pol = cs35l41_irq_gpio_config(cs35l41);
/* Set interrupt masks for critical errors */ @@ -1347,12 +1357,6 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, goto err; }
- ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); - if (ret < 0) { - dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); - goto err; - } - ret = cs35l41_set_pdata(cs35l41); if (ret < 0) { dev_err(cs35l41->dev, "Set pdata failed: %d\n", ret);

The cs35l41 supports a low power DSP memory retention mode. Add support for entering this mode when then device is not in use.
Co-authored-by: David Rhodes david.rhodes@cirrus.com Signed-off-by: David Rhodes david.rhodes@cirrus.com Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com ---
Changes since v1: - Add missing export symbol for cs35l41_pm_ops
include/sound/cs35l41.h | 5 + sound/soc/codecs/cs35l41-i2c.c | 1 + sound/soc/codecs/cs35l41-lib.c | 6 ++ sound/soc/codecs/cs35l41-spi.c | 1 + sound/soc/codecs/cs35l41.c | 201 ++++++++++++++++++++++++++++++++++++++++- sound/soc/codecs/cs35l41.h | 4 + 6 files changed, 214 insertions(+), 4 deletions(-)
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 56289b67b9a0e..bf7f9a9aeba04 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -40,6 +40,9 @@ #define CS35L41_PROTECT_REL_ERR_IGN 0x00002034 #define CS35L41_GPIO_PAD_CONTROL 0x0000242C #define CS35L41_JTAG_CONTROL 0x00002438 +#define CS35L41_PWRMGT_CTL 0x00002900 +#define CS35L41_WAKESRC_CTL 0x00002904 +#define CS35L41_PWRMGT_STS 0x00002908 #define CS35L41_PLL_CLK_CTRL 0x00002C04 #define CS35L41_DSP_CLK_CTRL 0x00002C08 #define CS35L41_GLOBAL_CLK_CTRL 0x00002C0C @@ -635,6 +638,8 @@ #define CS35L41_INPUT_DSP_TX1 0x32 #define CS35L41_INPUT_DSP_TX2 0x33
+#define CS35L41_WR_PEND_STS_MASK 0x2 + #define CS35L41_PLL_CLK_SEL_MASK 0x07 #define CS35L41_PLL_CLK_SEL_SHIFT 0 #define CS35L41_PLL_CLK_EN_MASK 0x10 diff --git a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c index eb8dfb6d9c950..faad5c638cb82 100644 --- a/sound/soc/codecs/cs35l41-i2c.c +++ b/sound/soc/codecs/cs35l41-i2c.c @@ -86,6 +86,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); static struct i2c_driver cs35l41_i2c_driver = { .driver = { .name = "cs35l41", + .pm = &cs35l41_pm_ops, .of_match_table = of_match_ptr(cs35l41_of_match), .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), }, diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index ecaf67fd76531..e5a56bcbb223d 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -90,6 +90,9 @@ static bool cs35l41_readable_reg(struct device *dev, unsigned int reg) case CS35L41_PROTECT_REL_ERR_IGN: case CS35L41_GPIO_PAD_CONTROL: case CS35L41_JTAG_CONTROL: + case CS35L41_PWRMGT_CTL: + case CS35L41_WAKESRC_CTL: + case CS35L41_PWRMGT_STS: case CS35L41_PLL_CLK_CTRL: case CS35L41_DSP_CLK_CTRL: case CS35L41_GLOBAL_CLK_CTRL: @@ -376,6 +379,9 @@ static bool cs35l41_volatile_reg(struct device *dev, unsigned int reg) case CS35L41_OTPID: case CS35L41_TEST_KEY_CTL: case CS35L41_USER_KEY_CTL: + case CS35L41_PWRMGT_CTL: + case CS35L41_WAKESRC_CTL: + case CS35L41_PWRMGT_STS: case CS35L41_DTEMP_EN: case CS35L41_IRQ1_STATUS: case CS35L41_IRQ1_STATUS1: diff --git a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c index 86bbe2fba956e..6dfd5459aa207 100644 --- a/sound/soc/codecs/cs35l41-spi.c +++ b/sound/soc/codecs/cs35l41-spi.c @@ -84,6 +84,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); static struct spi_driver cs35l41_spi_driver = { .driver = { .name = "cs35l41", + .pm = &cs35l41_pm_ops, .of_match_table = of_match_ptr(cs35l41_of_match), .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), }, diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index e1b9fd8ee9966..77a0176946459 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/of_device.h> +#include <linux/pm_runtime.h> #include <linux/property.h> #include <sound/initval.h> #include <sound/pcm.h> @@ -187,8 +188,14 @@ static int cs35l41_dsp_preload_ev(struct snd_soc_dapm_widget *w,
switch (event) { case SND_SOC_DAPM_PRE_PMU: + if (cs35l41->dsp.cs_dsp.booted) + return 0; + return wm_adsp_early_event(w, kcontrol, event); case SND_SOC_DAPM_PRE_PMD: + if (cs35l41->dsp.preloaded) + return 0; + if (cs35l41->dsp.cs_dsp.running) { ret = wm_adsp_event(w, kcontrol, event); if (ret) @@ -209,6 +216,7 @@ static bool cs35l41_check_cspl_mbox_sts(enum cs35l41_cspl_mbox_cmd cmd, case CSPL_MBOX_CMD_UNKNOWN_CMD: return true; case CSPL_MBOX_CMD_PAUSE: + case CSPL_MBOX_CMD_OUT_OF_HIBERNATE: return (sts == CSPL_MBOX_STS_PAUSED); case CSPL_MBOX_CMD_RESUME: return (sts == CSPL_MBOX_STS_RUNNING); @@ -230,7 +238,8 @@ static int cs35l41_set_cspl_mbox_cmd(struct cs35l41_private *cs35l41, // Set mailbox cmd ret = regmap_write(cs35l41->regmap, CS35L41_DSP_VIRT1_MBOX_1, cmd); if (ret < 0) { - dev_err(cs35l41->dev, "Failed to write MBOX: %d\n", ret); + if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE) + dev_err(cs35l41->dev, "Failed to write MBOX: %d\n", ret); return ret; }
@@ -413,6 +422,8 @@ static irqreturn_t cs35l41_irq(int irq, void *data) int ret = IRQ_NONE; unsigned int i;
+ pm_runtime_get_sync(cs35l41->dev); + for (i = 0; i < ARRAY_SIZE(status); i++) { regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS1 + (i * CS35L41_REGSTRIDE), @@ -425,7 +436,7 @@ static irqreturn_t cs35l41_irq(int irq, void *data) /* Check to see if unmasked bits are active */ if (!(status[0] & ~masks[0]) && !(status[1] & ~masks[1]) && !(status[2] & ~masks[2]) && !(status[3] & ~masks[3])) - return IRQ_NONE; + goto done;
if (status[3] & CS35L41_OTP_BOOT_DONE) { regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK4, @@ -530,6 +541,10 @@ static irqreturn_t cs35l41_irq(int irq, void *data) ret = IRQ_HANDLED; }
+done: + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_put_autosuspend(cs35l41->dev); + return ret; }
@@ -1180,6 +1195,7 @@ static int cs35l41_dsp_init(struct cs35l41_private *cs35l41) dsp->cs_dsp.type = WMFW_HALO; dsp->cs_dsp.rev = 0; dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */ + dsp->toggle_preload = true; dsp->cs_dsp.dev = cs35l41->dev; dsp->cs_dsp.regmap = cs35l41->regmap; dsp->cs_dsp.base = CS35L41_DSP1_CTRL_BASE; @@ -1367,20 +1383,32 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, if (ret < 0) goto err;
+ pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000); + pm_runtime_use_autosuspend(cs35l41->dev); + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_set_active(cs35l41->dev); + pm_runtime_get_noresume(cs35l41->dev); + pm_runtime_enable(cs35l41->dev); + ret = devm_snd_soc_register_component(cs35l41->dev, &soc_component_dev_cs35l41, cs35l41_dai, ARRAY_SIZE(cs35l41_dai)); if (ret < 0) { dev_err(cs35l41->dev, "Register codec failed: %d\n", ret); - goto err_dsp; + goto err_pm; }
+ pm_runtime_put_autosuspend(cs35l41->dev); + dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid);
return 0;
-err_dsp: +err_pm: + pm_runtime_disable(cs35l41->dev); + pm_runtime_put_noidle(cs35l41->dev); + wm_adsp2_remove(&cs35l41->dsp); err: regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); @@ -1392,13 +1420,178 @@ EXPORT_SYMBOL_GPL(cs35l41_probe);
void cs35l41_remove(struct cs35l41_private *cs35l41) { + pm_runtime_get_sync(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF); wm_adsp2_remove(&cs35l41->dsp); + + pm_runtime_put_noidle(cs35l41->dev); + regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); } EXPORT_SYMBOL_GPL(cs35l41_remove);
+static int __maybe_unused cs35l41_runtime_suspend(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "Runtime suspend\n"); + + if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) + return 0; + + dev_dbg(cs35l41->dev, "Enter hibernate\n"); + + regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088); + regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188); + + // Don't wait for ACK since bus activity would wake the device + regmap_write(cs35l41->regmap, CS35L41_DSP_VIRT1_MBOX_1, + CSPL_MBOX_CMD_HIBERNATE); + + regcache_cache_only(cs35l41->regmap, true); + regcache_mark_dirty(cs35l41->regmap); + + return 0; +} + +static void cs35l41_wait_for_pwrmgt_sts(struct cs35l41_private *cs35l41) +{ + const int pwrmgt_retries = 10; + unsigned int sts; + int i, ret; + + for (i = 0; i < pwrmgt_retries; i++) { + ret = regmap_read(cs35l41->regmap, CS35L41_PWRMGT_STS, &sts); + if (ret) + dev_err(cs35l41->dev, "Failed to read PWRMGT_STS: %d\n", ret); + else if (!(sts & CS35L41_WR_PEND_STS_MASK)) + return; + + udelay(20); + } + + dev_err(cs35l41->dev, "Timed out reading PWRMGT_STS\n"); +} + +static int cs35l41_exit_hibernate(struct cs35l41_private *cs35l41) +{ + const int wake_retries = 20; + const int sleep_retries = 5; + int ret, i, j; + + for (i = 0; i < sleep_retries; i++) { + dev_dbg(cs35l41->dev, "Exit hibernate\n"); + + for (j = 0; j < wake_retries; j++) { + ret = cs35l41_set_cspl_mbox_cmd(cs35l41, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE); + if (!ret) + break; + + usleep_range(100, 200); + } + + if (j < wake_retries) { + dev_dbg(cs35l41->dev, "Wake success at cycle: %d\n", j); + return 0; + } + + dev_err(cs35l41->dev, "Wake failed, re-enter hibernate: %d\n", ret); + + cs35l41_wait_for_pwrmgt_sts(cs35l41); + regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088); + + cs35l41_wait_for_pwrmgt_sts(cs35l41); + regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188); + + cs35l41_wait_for_pwrmgt_sts(cs35l41); + regmap_write(cs35l41->regmap, CS35L41_PWRMGT_CTL, 0x3); + } + + dev_err(cs35l41->dev, "Timed out waking device\n"); + + return -ETIMEDOUT; +} + +static int __maybe_unused cs35l41_runtime_resume(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + int ret; + + dev_dbg(cs35l41->dev, "Runtime resume\n"); + + if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running) + return 0; + + regcache_cache_only(cs35l41->regmap, false); + + ret = cs35l41_exit_hibernate(cs35l41); + if (ret) + return ret; + + /* Test key needs to be unlocked to allow the OTP settings to re-apply */ + cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + ret = regcache_sync(cs35l41->regmap); + cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); + return ret; + } + + return 0; +} + +static int __maybe_unused cs35l41_sys_suspend(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "System suspend, disabling IRQ\n"); + disable_irq(cs35l41->irq); + + return 0; +} + +static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "Late system suspend, reenabling IRQ\n"); + enable_irq(cs35l41->irq); + + return 0; +} + +static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "Early system resume, disabling IRQ\n"); + disable_irq(cs35l41->irq); + + return 0; +} + +static int __maybe_unused cs35l41_sys_resume(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "System resume, reenabling IRQ\n"); + enable_irq(cs35l41->irq); + + return 0; +} + +const struct dev_pm_ops cs35l41_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) + + SET_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq) +}; +EXPORT_SYMBOL_GPL(cs35l41_pm_ops); + MODULE_DESCRIPTION("ASoC CS35L41 driver"); MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, david.rhodes@cirrus.com"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l41.h b/sound/soc/codecs/cs35l41.h index 26a08d58a8c34..88a3d6e3434fb 100644 --- a/sound/soc/codecs/cs35l41.h +++ b/sound/soc/codecs/cs35l41.h @@ -21,6 +21,8 @@ #define CS35L41_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) #define CS35L41_TX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+extern const struct dev_pm_ops cs35l41_pm_ops; + enum cs35l41_cspl_mbox_status { CSPL_MBOX_STS_RUNNING = 0, CSPL_MBOX_STS_PAUSED = 1, @@ -33,6 +35,8 @@ enum cs35l41_cspl_mbox_cmd { CSPL_MBOX_CMD_RESUME = 2, CSPL_MBOX_CMD_REINIT = 3, CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, + CSPL_MBOX_CMD_HIBERNATE = 5, + CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6, CSPL_MBOX_CMD_UNKNOWN_CMD = -1, CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, };

On Fri, Jan 07, 2022 at 04:06:34PM +0000, Charles Keepax wrote:
These patches will cause some very minor conflicts with Lucas's currently outstanding work on the HDA version of cs35l41. Whilst things will still build (well now I fixed my silly mistake), this patch adds a test key function his code will now have to call.
Which patches are these? It looks like everything I was aware of is merged.

On Fri, 07 Jan 2022 17:06:34 +0100, Charles Keepax wrote:
This patch series adds support for the low power hibernation feature on cs35l41. This allows the DSP memory to be retained whilst the device enters a very low power state.
These patches will cause some very minor conflicts with Lucas's currently outstanding work on the HDA version of cs35l41. Whilst things will still build (well now I fixed my silly mistake), this patch adds a test key function his code will now have to call.
It looks like Lucas's patch might get a respin based on the comments from Andy, in which case I guess we can pull the small change into the next version of it. But either way these patches are not urgent so I am happy if they sit around until Lucas's stuff is merged too.
Well, I've already merged HD-audio part, so any changes will be incremental over it.
CS35L41 HD-audio stuff still won't work as of now with my tree alone because of the lack of the ACPI patch. So it's fine to merge those changes to Mark's tree and break the stuff now, supposing it'll be synced again before 5.17-rc1 pull request. After it's merged to my tree, you can submit the HD-audio fix, to be merged before rc1.
thanks,
Takashi

On Fri, Jan 07, 2022 at 05:15:52PM +0100, Takashi Iwai wrote:
On Fri, 07 Jan 2022 17:06:34 +0100, Charles Keepax wrote:
This patch series adds support for the low power hibernation feature on cs35l41. This allows the DSP memory to be retained whilst the device enters a very low power state.
These patches will cause some very minor conflicts with Lucas's currently outstanding work on the HDA version of cs35l41. Whilst things will still build (well now I fixed my silly mistake), this patch adds a test key function his code will now have to call.
It looks like Lucas's patch might get a respin based on the comments from Andy, in which case I guess we can pull the small change into the next version of it. But either way these patches are not urgent so I am happy if they sit around until Lucas's stuff is merged too.
Well, I've already merged HD-audio part, so any changes will be incremental over it.
CS35L41 HD-audio stuff still won't work as of now with my tree alone because of the lack of the ACPI patch. So it's fine to merge those changes to Mark's tree and break the stuff now, supposing it'll be synced again before 5.17-rc1 pull request. After it's merged to my tree, you can submit the HD-audio fix, to be merged before rc1.
Apologies I didn't realise those were already merged. I will do a patch to update them to use the new functions.
Thanks, Charles

On Fri, 7 Jan 2022 16:06:34 +0000, Charles Keepax wrote:
This patch series adds support for the low power hibernation feature on cs35l41. This allows the DSP memory to be retained whilst the device enters a very low power state.
These patches will cause some very minor conflicts with Lucas's currently outstanding work on the HDA version of cs35l41. Whilst things will still build (well now I fixed my silly mistake), this patch adds a test key function his code will now have to call.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/2] ASoC: cs35l41: Update handling of test key registers commit: d92321bbe46b0ecae0941461379d39599610d869 [2/2] ASoC: cs35l41: Add support for hibernate memory retention mode commit: f517ba4924ad026f2583553db02f3c8bc69de88b
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (3)
-
Charles Keepax
-
Mark Brown
-
Takashi Iwai