ASoC: cs35l41: Add support for hibernate memory retention mode
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> Link: https://lore.kernel.org/r/20220107160636.6555-3-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
d92321bbe4
commit
f517ba4924
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue