#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>
 
        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)
        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);
        // 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;
        }
 
        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),
        /* 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,
                ret = IRQ_HANDLED;
        }
 
+done:
+       pm_runtime_mark_last_busy(cs35l41->dev);
+       pm_runtime_put_autosuspend(cs35l41->dev);
+
        return ret;
 }
 
        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;
        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);
 
 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");