All of lore.kernel.org
 help / color / mirror / Atom feed
From: Charles Keepax <ckeepax@opensource.cirrus.com>
To: <broonie@kernel.org>
Cc: patches@opensource.cirrus.com, alsa-devel@alsa-project.org,
	david.rhodes@cirrus.com, lgirdwood@gmail.com, tiwai@suse.com
Subject: [PATCH 8/8] ASoC: cs35l41: Add support for hibernate memory retention mode
Date: Wed, 5 Jan 2022 11:30:26 +0000	[thread overview]
Message-ID: <20220105113026.18955-9-ckeepax@opensource.cirrus.com> (raw)
In-Reply-To: <20220105113026.18955-1-ckeepax@opensource.cirrus.com>

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>
---
 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     | 200 ++++++++++++++++++++++++++++++++++++++++-
 sound/soc/codecs/cs35l41.h     |   4 +
 6 files changed, 213 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 4037831a2ca98..215f6e36260bc 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..d4ebf3465efa4 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,177 @@ 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)
+};
+
 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,
 };
-- 
2.11.0


  parent reply	other threads:[~2022-01-05 11:34 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-01-05 11:30 [PATCH 0/8] Add low power hibernation support to cs35l41 Charles Keepax
2022-01-05 11:30 ` [PATCH 1/8] ASoC: cs35l41: Add cs35l51/53 IDs Charles Keepax
2022-01-05 11:30 ` [PATCH 2/8] ASoC: cs35l41: Remove incorrect comment Charles Keepax
2022-01-05 11:30 ` [PATCH 3/8] ASoC: cs35l41: Correct DSP power down Charles Keepax
2022-01-05 11:30 ` [PATCH 4/8] ASoC: cs35l41: Correct handling of some registers in the cache Charles Keepax
2022-01-05 11:30 ` [PATCH 5/8] firmware: cs_dsp: Clear core reset for cache Charles Keepax
2022-01-05 11:30 ` [PATCH 6/8] ASoC: wm_adsp: Add support for "toggle" preloaders Charles Keepax
2022-01-05 11:30 ` [PATCH 7/8] ASoC: cs35l41: Update handling of test key registers Charles Keepax
2022-01-05 11:30 ` Charles Keepax [this message]
2022-01-05 12:03 ` [PATCH 0/8] Add low power hibernation support to cs35l41 Takashi Iwai
2022-01-05 13:05   ` Charles Keepax
2022-01-05 14:07     ` Takashi Iwai
2022-01-05 14:23       ` Charles Keepax
2022-01-05 14:30 ` Mark Brown
2022-01-05 14:44   ` Charles Keepax
2022-01-05 16:12 ` Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220105113026.18955-9-ckeepax@opensource.cirrus.com \
    --to=ckeepax@opensource.cirrus.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=broonie@kernel.org \
    --cc=david.rhodes@cirrus.com \
    --cc=lgirdwood@gmail.com \
    --cc=patches@opensource.cirrus.com \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.