Skip to content

Commit f517ba4

Browse files
charleskeepaxdrhodescirrus
authored andcommitted
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 <[email protected]> Signed-off-by: David Rhodes <[email protected]> Signed-off-by: Charles Keepax <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent d92321b commit f517ba4

File tree

6 files changed

+214
-4
lines changed

6 files changed

+214
-4
lines changed

include/sound/cs35l41.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
#define CS35L41_PROTECT_REL_ERR_IGN 0x00002034
4141
#define CS35L41_GPIO_PAD_CONTROL 0x0000242C
4242
#define CS35L41_JTAG_CONTROL 0x00002438
43+
#define CS35L41_PWRMGT_CTL 0x00002900
44+
#define CS35L41_WAKESRC_CTL 0x00002904
45+
#define CS35L41_PWRMGT_STS 0x00002908
4346
#define CS35L41_PLL_CLK_CTRL 0x00002C04
4447
#define CS35L41_DSP_CLK_CTRL 0x00002C08
4548
#define CS35L41_GLOBAL_CLK_CTRL 0x00002C0C
@@ -635,6 +638,8 @@
635638
#define CS35L41_INPUT_DSP_TX1 0x32
636639
#define CS35L41_INPUT_DSP_TX2 0x33
637640

641+
#define CS35L41_WR_PEND_STS_MASK 0x2
642+
638643
#define CS35L41_PLL_CLK_SEL_MASK 0x07
639644
#define CS35L41_PLL_CLK_SEL_SHIFT 0
640645
#define CS35L41_PLL_CLK_EN_MASK 0x10

sound/soc/codecs/cs35l41-i2c.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match);
8686
static struct i2c_driver cs35l41_i2c_driver = {
8787
.driver = {
8888
.name = "cs35l41",
89+
.pm = &cs35l41_pm_ops,
8990
.of_match_table = of_match_ptr(cs35l41_of_match),
9091
.acpi_match_table = ACPI_PTR(cs35l41_acpi_match),
9192
},

sound/soc/codecs/cs35l41-lib.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ static bool cs35l41_readable_reg(struct device *dev, unsigned int reg)
9090
case CS35L41_PROTECT_REL_ERR_IGN:
9191
case CS35L41_GPIO_PAD_CONTROL:
9292
case CS35L41_JTAG_CONTROL:
93+
case CS35L41_PWRMGT_CTL:
94+
case CS35L41_WAKESRC_CTL:
95+
case CS35L41_PWRMGT_STS:
9396
case CS35L41_PLL_CLK_CTRL:
9497
case CS35L41_DSP_CLK_CTRL:
9598
case CS35L41_GLOBAL_CLK_CTRL:
@@ -376,6 +379,9 @@ static bool cs35l41_volatile_reg(struct device *dev, unsigned int reg)
376379
case CS35L41_OTPID:
377380
case CS35L41_TEST_KEY_CTL:
378381
case CS35L41_USER_KEY_CTL:
382+
case CS35L41_PWRMGT_CTL:
383+
case CS35L41_WAKESRC_CTL:
384+
case CS35L41_PWRMGT_STS:
379385
case CS35L41_DTEMP_EN:
380386
case CS35L41_IRQ1_STATUS:
381387
case CS35L41_IRQ1_STATUS1:

sound/soc/codecs/cs35l41-spi.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match);
8484
static struct spi_driver cs35l41_spi_driver = {
8585
.driver = {
8686
.name = "cs35l41",
87+
.pm = &cs35l41_pm_ops,
8788
.of_match_table = of_match_ptr(cs35l41_of_match),
8889
.acpi_match_table = ACPI_PTR(cs35l41_acpi_match),
8990
},

sound/soc/codecs/cs35l41.c

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <linux/module.h>
1414
#include <linux/moduleparam.h>
1515
#include <linux/of_device.h>
16+
#include <linux/pm_runtime.h>
1617
#include <linux/property.h>
1718
#include <sound/initval.h>
1819
#include <sound/pcm.h>
@@ -187,8 +188,14 @@ static int cs35l41_dsp_preload_ev(struct snd_soc_dapm_widget *w,
187188

188189
switch (event) {
189190
case SND_SOC_DAPM_PRE_PMU:
191+
if (cs35l41->dsp.cs_dsp.booted)
192+
return 0;
193+
190194
return wm_adsp_early_event(w, kcontrol, event);
191195
case SND_SOC_DAPM_PRE_PMD:
196+
if (cs35l41->dsp.preloaded)
197+
return 0;
198+
192199
if (cs35l41->dsp.cs_dsp.running) {
193200
ret = wm_adsp_event(w, kcontrol, event);
194201
if (ret)
@@ -209,6 +216,7 @@ static bool cs35l41_check_cspl_mbox_sts(enum cs35l41_cspl_mbox_cmd cmd,
209216
case CSPL_MBOX_CMD_UNKNOWN_CMD:
210217
return true;
211218
case CSPL_MBOX_CMD_PAUSE:
219+
case CSPL_MBOX_CMD_OUT_OF_HIBERNATE:
212220
return (sts == CSPL_MBOX_STS_PAUSED);
213221
case CSPL_MBOX_CMD_RESUME:
214222
return (sts == CSPL_MBOX_STS_RUNNING);
@@ -230,7 +238,8 @@ static int cs35l41_set_cspl_mbox_cmd(struct cs35l41_private *cs35l41,
230238
// Set mailbox cmd
231239
ret = regmap_write(cs35l41->regmap, CS35L41_DSP_VIRT1_MBOX_1, cmd);
232240
if (ret < 0) {
233-
dev_err(cs35l41->dev, "Failed to write MBOX: %d\n", ret);
241+
if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
242+
dev_err(cs35l41->dev, "Failed to write MBOX: %d\n", ret);
234243
return ret;
235244
}
236245

@@ -413,6 +422,8 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
413422
int ret = IRQ_NONE;
414423
unsigned int i;
415424

425+
pm_runtime_get_sync(cs35l41->dev);
426+
416427
for (i = 0; i < ARRAY_SIZE(status); i++) {
417428
regmap_read(cs35l41->regmap,
418429
CS35L41_IRQ1_STATUS1 + (i * CS35L41_REGSTRIDE),
@@ -425,7 +436,7 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
425436
/* Check to see if unmasked bits are active */
426437
if (!(status[0] & ~masks[0]) && !(status[1] & ~masks[1]) &&
427438
!(status[2] & ~masks[2]) && !(status[3] & ~masks[3]))
428-
return IRQ_NONE;
439+
goto done;
429440

430441
if (status[3] & CS35L41_OTP_BOOT_DONE) {
431442
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK4,
@@ -530,6 +541,10 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
530541
ret = IRQ_HANDLED;
531542
}
532543

544+
done:
545+
pm_runtime_mark_last_busy(cs35l41->dev);
546+
pm_runtime_put_autosuspend(cs35l41->dev);
547+
533548
return ret;
534549
}
535550

@@ -1180,6 +1195,7 @@ static int cs35l41_dsp_init(struct cs35l41_private *cs35l41)
11801195
dsp->cs_dsp.type = WMFW_HALO;
11811196
dsp->cs_dsp.rev = 0;
11821197
dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */
1198+
dsp->toggle_preload = true;
11831199
dsp->cs_dsp.dev = cs35l41->dev;
11841200
dsp->cs_dsp.regmap = cs35l41->regmap;
11851201
dsp->cs_dsp.base = CS35L41_DSP1_CTRL_BASE;
@@ -1367,20 +1383,32 @@ int cs35l41_probe(struct cs35l41_private *cs35l41,
13671383
if (ret < 0)
13681384
goto err;
13691385

1386+
pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
1387+
pm_runtime_use_autosuspend(cs35l41->dev);
1388+
pm_runtime_mark_last_busy(cs35l41->dev);
1389+
pm_runtime_set_active(cs35l41->dev);
1390+
pm_runtime_get_noresume(cs35l41->dev);
1391+
pm_runtime_enable(cs35l41->dev);
1392+
13701393
ret = devm_snd_soc_register_component(cs35l41->dev,
13711394
&soc_component_dev_cs35l41,
13721395
cs35l41_dai, ARRAY_SIZE(cs35l41_dai));
13731396
if (ret < 0) {
13741397
dev_err(cs35l41->dev, "Register codec failed: %d\n", ret);
1375-
goto err_dsp;
1398+
goto err_pm;
13761399
}
13771400

1401+
pm_runtime_put_autosuspend(cs35l41->dev);
1402+
13781403
dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n",
13791404
regid, reg_revid);
13801405

13811406
return 0;
13821407

1383-
err_dsp:
1408+
err_pm:
1409+
pm_runtime_disable(cs35l41->dev);
1410+
pm_runtime_put_noidle(cs35l41->dev);
1411+
13841412
wm_adsp2_remove(&cs35l41->dsp);
13851413
err:
13861414
regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies);
@@ -1392,13 +1420,178 @@ EXPORT_SYMBOL_GPL(cs35l41_probe);
13921420

13931421
void cs35l41_remove(struct cs35l41_private *cs35l41)
13941422
{
1423+
pm_runtime_get_sync(cs35l41->dev);
1424+
pm_runtime_disable(cs35l41->dev);
1425+
13951426
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
13961427
wm_adsp2_remove(&cs35l41->dsp);
1428+
1429+
pm_runtime_put_noidle(cs35l41->dev);
1430+
13971431
regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies);
13981432
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
13991433
}
14001434
EXPORT_SYMBOL_GPL(cs35l41_remove);
14011435

1436+
static int __maybe_unused cs35l41_runtime_suspend(struct device *dev)
1437+
{
1438+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1439+
1440+
dev_dbg(cs35l41->dev, "Runtime suspend\n");
1441+
1442+
if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running)
1443+
return 0;
1444+
1445+
dev_dbg(cs35l41->dev, "Enter hibernate\n");
1446+
1447+
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088);
1448+
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188);
1449+
1450+
// Don't wait for ACK since bus activity would wake the device
1451+
regmap_write(cs35l41->regmap, CS35L41_DSP_VIRT1_MBOX_1,
1452+
CSPL_MBOX_CMD_HIBERNATE);
1453+
1454+
regcache_cache_only(cs35l41->regmap, true);
1455+
regcache_mark_dirty(cs35l41->regmap);
1456+
1457+
return 0;
1458+
}
1459+
1460+
static void cs35l41_wait_for_pwrmgt_sts(struct cs35l41_private *cs35l41)
1461+
{
1462+
const int pwrmgt_retries = 10;
1463+
unsigned int sts;
1464+
int i, ret;
1465+
1466+
for (i = 0; i < pwrmgt_retries; i++) {
1467+
ret = regmap_read(cs35l41->regmap, CS35L41_PWRMGT_STS, &sts);
1468+
if (ret)
1469+
dev_err(cs35l41->dev, "Failed to read PWRMGT_STS: %d\n", ret);
1470+
else if (!(sts & CS35L41_WR_PEND_STS_MASK))
1471+
return;
1472+
1473+
udelay(20);
1474+
}
1475+
1476+
dev_err(cs35l41->dev, "Timed out reading PWRMGT_STS\n");
1477+
}
1478+
1479+
static int cs35l41_exit_hibernate(struct cs35l41_private *cs35l41)
1480+
{
1481+
const int wake_retries = 20;
1482+
const int sleep_retries = 5;
1483+
int ret, i, j;
1484+
1485+
for (i = 0; i < sleep_retries; i++) {
1486+
dev_dbg(cs35l41->dev, "Exit hibernate\n");
1487+
1488+
for (j = 0; j < wake_retries; j++) {
1489+
ret = cs35l41_set_cspl_mbox_cmd(cs35l41,
1490+
CSPL_MBOX_CMD_OUT_OF_HIBERNATE);
1491+
if (!ret)
1492+
break;
1493+
1494+
usleep_range(100, 200);
1495+
}
1496+
1497+
if (j < wake_retries) {
1498+
dev_dbg(cs35l41->dev, "Wake success at cycle: %d\n", j);
1499+
return 0;
1500+
}
1501+
1502+
dev_err(cs35l41->dev, "Wake failed, re-enter hibernate: %d\n", ret);
1503+
1504+
cs35l41_wait_for_pwrmgt_sts(cs35l41);
1505+
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088);
1506+
1507+
cs35l41_wait_for_pwrmgt_sts(cs35l41);
1508+
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188);
1509+
1510+
cs35l41_wait_for_pwrmgt_sts(cs35l41);
1511+
regmap_write(cs35l41->regmap, CS35L41_PWRMGT_CTL, 0x3);
1512+
}
1513+
1514+
dev_err(cs35l41->dev, "Timed out waking device\n");
1515+
1516+
return -ETIMEDOUT;
1517+
}
1518+
1519+
static int __maybe_unused cs35l41_runtime_resume(struct device *dev)
1520+
{
1521+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1522+
int ret;
1523+
1524+
dev_dbg(cs35l41->dev, "Runtime resume\n");
1525+
1526+
if (!cs35l41->dsp.preloaded || !cs35l41->dsp.cs_dsp.running)
1527+
return 0;
1528+
1529+
regcache_cache_only(cs35l41->regmap, false);
1530+
1531+
ret = cs35l41_exit_hibernate(cs35l41);
1532+
if (ret)
1533+
return ret;
1534+
1535+
/* Test key needs to be unlocked to allow the OTP settings to re-apply */
1536+
cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap);
1537+
ret = regcache_sync(cs35l41->regmap);
1538+
cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap);
1539+
if (ret) {
1540+
dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret);
1541+
return ret;
1542+
}
1543+
1544+
return 0;
1545+
}
1546+
1547+
static int __maybe_unused cs35l41_sys_suspend(struct device *dev)
1548+
{
1549+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1550+
1551+
dev_dbg(cs35l41->dev, "System suspend, disabling IRQ\n");
1552+
disable_irq(cs35l41->irq);
1553+
1554+
return 0;
1555+
}
1556+
1557+
static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev)
1558+
{
1559+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1560+
1561+
dev_dbg(cs35l41->dev, "Late system suspend, reenabling IRQ\n");
1562+
enable_irq(cs35l41->irq);
1563+
1564+
return 0;
1565+
}
1566+
1567+
static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev)
1568+
{
1569+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1570+
1571+
dev_dbg(cs35l41->dev, "Early system resume, disabling IRQ\n");
1572+
disable_irq(cs35l41->irq);
1573+
1574+
return 0;
1575+
}
1576+
1577+
static int __maybe_unused cs35l41_sys_resume(struct device *dev)
1578+
{
1579+
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
1580+
1581+
dev_dbg(cs35l41->dev, "System resume, reenabling IRQ\n");
1582+
enable_irq(cs35l41->irq);
1583+
1584+
return 0;
1585+
}
1586+
1587+
const struct dev_pm_ops cs35l41_pm_ops = {
1588+
SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
1589+
1590+
SET_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume)
1591+
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq)
1592+
};
1593+
EXPORT_SYMBOL_GPL(cs35l41_pm_ops);
1594+
14021595
MODULE_DESCRIPTION("ASoC CS35L41 driver");
14031596
MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <[email protected]>");
14041597
MODULE_LICENSE("GPL");

sound/soc/codecs/cs35l41.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#define CS35L41_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
2222
#define CS35L41_TX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
2323

24+
extern const struct dev_pm_ops cs35l41_pm_ops;
25+
2426
enum cs35l41_cspl_mbox_status {
2527
CSPL_MBOX_STS_RUNNING = 0,
2628
CSPL_MBOX_STS_PAUSED = 1,
@@ -33,6 +35,8 @@ enum cs35l41_cspl_mbox_cmd {
3335
CSPL_MBOX_CMD_RESUME = 2,
3436
CSPL_MBOX_CMD_REINIT = 3,
3537
CSPL_MBOX_CMD_STOP_PRE_REINIT = 4,
38+
CSPL_MBOX_CMD_HIBERNATE = 5,
39+
CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6,
3640
CSPL_MBOX_CMD_UNKNOWN_CMD = -1,
3741
CSPL_MBOX_CMD_INVALID_SEQUENCE = -2,
3842
};

0 commit comments

Comments
 (0)