summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/cs35l45.c
diff options
context:
space:
mode:
authorVlad.Karpovich <vkarpovi@opensource.cirrus.com>2023-03-15 10:47:22 -0500
committerMark Brown <broonie@kernel.org>2023-03-21 12:46:42 +0000
commit6c07be8fe92c6b0c24ee1c599601dce3506b83c7 (patch)
treefcf50455ab68733a03d3c49201826bf7c1b8337b /sound/soc/codecs/cs35l45.c
parent74b14e2850a34740c121cf2758d4181063d4c77c (diff)
downloadlinux-stable-6c07be8fe92c6b0c24ee1c599601dce3506b83c7.tar.gz
linux-stable-6c07be8fe92c6b0c24ee1c599601dce3506b83c7.tar.bz2
linux-stable-6c07be8fe92c6b0c24ee1c599601dce3506b83c7.zip
ASoC: cs35l45: Hibernation support
Adds support for a low-power Hibernation State. Add support for a low-power hibernation state for the DSP. In this state the DSP RAM contents are maintained, such that firmware does not need to be re-downloaded, but the rest of the chip's register state is lost. Entry to this state is achieved via the register interface (either by an external driver using the control port, or the programmable DSP). Exit from this state is triggered by activity on device GPIO pins, intended SPI transaction, or I2C transaction with intended slave address. Signed-off-by: Vlad Karpovich <vkarpovi@opensource.cirrus.com> Link: https://lore.kernel.org/r/167933511185.26.10641185496218226278@mailman-core.alsa-project.org Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound/soc/codecs/cs35l45.c')
-rw-r--r--sound/soc/codecs/cs35l45.c80
1 files changed, 80 insertions, 0 deletions
diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c
index 97f6819fa2ad..c31597f6bfae 100644
--- a/sound/soc/codecs/cs35l45.c
+++ b/sound/soc/codecs/cs35l45.c
@@ -36,6 +36,8 @@ static bool cs35l45_check_cspl_mbox_sts(const enum cs35l45_cspl_mboxcmd cmd,
return (sts == CSPL_MBOX_STS_RUNNING);
case CSPL_MBOX_CMD_STOP_PRE_REINIT:
return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
+ case CSPL_MBOX_CMD_HIBERNATE:
+ return (sts == CSPL_MBOX_STS_HIBERNATE);
default:
return false;
}
@@ -744,11 +746,81 @@ static const struct snd_soc_component_driver cs35l45_component = {
.endianness = 1,
};
+static void cs35l45_setup_hibernate(struct cs35l45_private *cs35l45)
+{
+ unsigned int wksrc;
+
+ if (cs35l45->bus_type == CONTROL_BUS_I2C)
+ wksrc = CS35L45_WKSRC_I2C;
+ else
+ wksrc = CS35L45_WKSRC_SPI;
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL,
+ CS35L45_WKSRC_EN_MASK,
+ wksrc << CS35L45_WKSRC_EN_SHIFT);
+
+ regmap_set_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL,
+ CS35L45_UPDT_WKCTL_MASK);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_WKI2C_CTL,
+ CS35L45_WKI2C_ADDR_MASK, cs35l45->i2c_addr);
+
+ regmap_set_bits(cs35l45->regmap, CS35L45_WKI2C_CTL,
+ CS35L45_UPDT_WKI2C_MASK);
+}
+
+static int cs35l45_enter_hibernate(struct cs35l45_private *cs35l45)
+{
+ dev_dbg(cs35l45->dev, "Enter hibernate\n");
+
+ cs35l45_setup_hibernate(cs35l45);
+
+ // Don't wait for ACK since bus activity would wake the device
+ regmap_write(cs35l45->regmap, CS35L45_DSP_VIRT1_MBOX_1, CSPL_MBOX_CMD_HIBERNATE);
+
+ return 0;
+}
+
+static int cs35l45_exit_hibernate(struct cs35l45_private *cs35l45)
+{
+ const int wake_retries = 20;
+ const int sleep_retries = 5;
+ int ret, i, j;
+
+ for (i = 0; i < sleep_retries; i++) {
+ dev_dbg(cs35l45->dev, "Exit hibernate\n");
+
+ for (j = 0; j < wake_retries; j++) {
+ ret = cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
+ CSPL_MBOX_CMD_OUT_OF_HIBERNATE);
+ if (!ret) {
+ dev_dbg(cs35l45->dev, "Wake success at cycle: %d\n", j);
+ return 0;
+ }
+ usleep_range(100, 200);
+ }
+
+ dev_err(cs35l45->dev, "Wake failed, re-enter hibernate: %d\n", ret);
+
+ cs35l45_setup_hibernate(cs35l45);
+ }
+
+ dev_err(cs35l45->dev, "Timed out waking device\n");
+
+ return -ETIMEDOUT;
+}
+
static int __maybe_unused cs35l45_runtime_suspend(struct device *dev)
{
struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running)
+ return 0;
+
+ cs35l45_enter_hibernate(cs35l45);
+
regcache_cache_only(cs35l45->regmap, true);
+ regcache_mark_dirty(cs35l45->regmap);
dev_dbg(cs35l45->dev, "Runtime suspended\n");
@@ -760,9 +832,17 @@ static int __maybe_unused cs35l45_runtime_resume(struct device *dev)
struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
int ret;
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running)
+ return 0;
+
dev_dbg(cs35l45->dev, "Runtime resume\n");
regcache_cache_only(cs35l45->regmap, false);
+
+ ret = cs35l45_exit_hibernate(cs35l45);
+ if (ret)
+ return ret;
+
ret = regcache_sync(cs35l45->regmap);
if (ret != 0)
dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret);