ASoC: cs35l45: Hibernation support
authorVlad.Karpovich <vkarpovi@opensource.cirrus.com>
Wed, 15 Mar 2023 15:47:22 +0000 (10:47 -0500)
committerMark Brown <broonie@kernel.org>
Tue, 21 Mar 2023 12:46:42 +0000 (12:46 +0000)
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>
sound/soc/codecs/cs35l45-i2c.c
sound/soc/codecs/cs35l45-spi.c
sound/soc/codecs/cs35l45-tables.c
sound/soc/codecs/cs35l45.c
sound/soc/codecs/cs35l45.h

index 33fa6d59e9a4b7c3385ccb60986d3e18c5b41757..562f73df7afa74053b29e64e3911b9acf956c5ad 100644 (file)
@@ -33,6 +33,8 @@ static int cs35l45_i2c_probe(struct i2c_client *client)
 
        cs35l45->dev = dev;
        cs35l45->irq = client->irq;
+       cs35l45->bus_type = CONTROL_BUS_I2C;
+       cs35l45->i2c_addr = client->addr;
 
        return cs35l45_probe(cs35l45);
 }
index 99657325a281df298e35f04fc932a0970799e831..a00b23b4180c5af7c82383e3d76f499d6e5a5f4f 100644 (file)
@@ -36,6 +36,7 @@ static int cs35l45_spi_probe(struct spi_device *spi)
 
        cs35l45->dev = dev;
        cs35l45->irq = spi->irq;
+       cs35l45->bus_type = CONTROL_BUS_SPI;
 
        return cs35l45_probe(cs35l45);
 }
index 434a6473070e9845aff932c16b25754c300510be..46610e64e8188506991c152bb078e32bc3c8b709 100644 (file)
@@ -47,6 +47,8 @@ static const struct reg_default cs35l45_defaults[] = {
        { CS35L45_INTB_GPIO2_MCLK_REF,          0x00000005 },
        { CS35L45_GPIO3,                        0x00000005 },
        { CS35L45_PWRMGT_CTL,                   0x00000000 },
+       { CS35L45_WAKESRC_CTL,                  0x00000008 },
+       { CS35L45_WKI2C_CTL,                    0x00000030 },
        { CS35L45_REFCLK_INPUT,                 0x00000510 },
        { CS35L45_GLOBAL_SAMPLE_RATE,           0x00000003 },
        { CS35L45_ASP_ENABLES1,                 0x00000000 },
@@ -126,6 +128,9 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
        case CS35L45_INTB_GPIO2_MCLK_REF:
        case CS35L45_GPIO3:
        case CS35L45_PWRMGT_CTL:
+       case CS35L45_WAKESRC_CTL:
+       case CS35L45_WKI2C_CTL:
+       case CS35L45_PWRMGT_STS:
        case CS35L45_REFCLK_INPUT:
        case CS35L45_GLOBAL_SAMPLE_RATE:
        case CS35L45_ASP_ENABLES1:
@@ -210,6 +215,7 @@ static bool cs35l45_volatile_reg(struct device *dev, unsigned int reg)
        case CS35L45_GLOBAL_ENABLES:
        case CS35L45_ERROR_RELEASE:
        case CS35L45_AMP_PCM_HPF_TST:   /* not cachable */
+       case CS35L45_PWRMGT_STS:
        case CS35L45_IRQ1_STATUS:
        case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18:
        case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18:
index 97f6819fa2ad9cbf9aac2ac634c1eff93ebca8cb..c31597f6bfae199e9ea7f2d6d3579c89f16ba518 100644 (file)
@@ -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);
index 87032619b3417c2c3084a17423b5803781602cc8..0da28439f628b0d593e43345cc14daf16898029a 100644 (file)
@@ -30,6 +30,9 @@
 #define CS35L45_INTB_GPIO2_MCLK_REF            0x00002434
 #define CS35L45_GPIO3                          0x00002438
 #define CS35L45_PWRMGT_CTL                     0x00002900
+#define CS35L45_WAKESRC_CTL                    0x00002904
+#define CS35L45_WKI2C_CTL                      0x00002908
+#define CS35L45_PWRMGT_STS                     0x0000290C
 #define CS35L45_REFCLK_INPUT                   0x00002C04
 #define CS35L45_GLOBAL_SAMPLE_RATE             0x00002C0C
 #define CS35L45_BOOST_CCM_CFG                  0x00003808
 #define CS35L45_POST_GLOBAL_EN_US              5000
 #define CS35L45_PRE_GLOBAL_DIS_US              3000
 
+/* WAKESRC_CTL */
+#define CS35L45_WKSRC_SYNC_GPIO1               BIT(0)
+#define CS35L45_WKSRC_INT_GPIO2                        BIT(1)
+#define CS35L45_WKSRC_GPIO3                    BIT(2)
+#define CS35L45_WKSRC_SPI                      BIT(3)
+#define CS35L45_WKSRC_I2C                      BIT(4)
+#define CS35L45_UPDT_WKCTL_SHIFT               15
+#define CS35L45_UPDT_WKCTL_MASK                        BIT(15)
+#define CS35L45_WKSRC_EN_SHIFT                 8
+#define CS35L45_WKSRC_EN_MASK                  GENMASK(12, 8)
+#define CS35L45_WKSRC_POL_SHIFT                        0
+#define CS35L45_WKSRC_POL_MASK                 GENMASK(3, 0)
+
+/* WAKEI2C_CTL */
+#define CS35L45_UPDT_WKI2C_SHIFT               15
+#define CS35L45_UPDT_WKI2C_MASK                        BIT(15)
+#define CS35L45_WKI2C_ADDR_SHIFT               0
+#define CS35L45_WKI2C_ADDR_MASK                        GENMASK(6, 0)
+
 #define CS35L45_SPI_MAX_FREQ                   4000000
 
 enum cs35l45_cspl_mboxstate {
@@ -369,6 +391,11 @@ enum cs35l45_cspl_mboxcmd {
        CSPL_MBOX_CMD_INVALID_SEQUENCE = -2,
 };
 
+enum control_bus_type {
+       CONTROL_BUS_I2C = 0,
+       CONTROL_BUS_SPI = 1,
+};
+
 #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
                         SNDRV_PCM_FMTBIT_S24_3LE| \
                         SNDRV_PCM_FMTBIT_S24_LE)
@@ -439,6 +466,8 @@ struct cs35l45_private {
        u8 slot_count;
        int irq_invert;
        int irq;
+       unsigned int i2c_addr;
+       enum control_bus_type bus_type;
        struct regmap_irq_chip_data *irq_data;
 };