#include <linux/clk.h>
 #include <linux/io.h>
 #include <linux/module.h>
+#include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <linux/spinlock.h>
 
        }
 }
 
+static bool mchp_spdifrx_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case SPDIFRX_IMR:
+       case SPDIFRX_ISR:
+       case SPDIFRX_RSR:
+       case SPDIFRX_CHSR(0, 0):
+       case SPDIFRX_CHSR(0, 1):
+       case SPDIFRX_CHSR(0, 2):
+       case SPDIFRX_CHSR(0, 3):
+       case SPDIFRX_CHSR(0, 4):
+       case SPDIFRX_CHSR(0, 5):
+       case SPDIFRX_CHUD(0, 0):
+       case SPDIFRX_CHUD(0, 1):
+       case SPDIFRX_CHUD(0, 2):
+       case SPDIFRX_CHUD(0, 3):
+       case SPDIFRX_CHUD(0, 4):
+       case SPDIFRX_CHUD(0, 5):
+       case SPDIFRX_CHSR(1, 0):
+       case SPDIFRX_CHSR(1, 1):
+       case SPDIFRX_CHSR(1, 2):
+       case SPDIFRX_CHSR(1, 3):
+       case SPDIFRX_CHSR(1, 4):
+       case SPDIFRX_CHSR(1, 5):
+       case SPDIFRX_CHUD(1, 0):
+       case SPDIFRX_CHUD(1, 1):
+       case SPDIFRX_CHUD(1, 2):
+       case SPDIFRX_CHUD(1, 3):
+       case SPDIFRX_CHUD(1, 4):
+       case SPDIFRX_CHUD(1, 5):
+       case SPDIFRX_VERSION:
+               return true;
+       default:
+               return false;
+       }
+}
+
 static const struct regmap_config mchp_spdifrx_regmap_config = {
        .reg_bits = 32,
        .reg_stride = 4,
        .readable_reg = mchp_spdifrx_readable_reg,
        .writeable_reg = mchp_spdifrx_writeable_reg,
        .precious_reg = mchp_spdifrx_precious_reg,
+       .volatile_reg = mchp_spdifrx_volatile_reg,
+       .cache_type = REGCACHE_FLAT,
 };
 
 #define SPDIFRX_GCLK_RATIO_MIN (12 * 64)
        struct clk                              *pclk;
        struct clk                              *gclk;
        unsigned int                            trigger_enabled;
-       unsigned int                            gclk_enabled:1;
 };
 
 static void mchp_spdifrx_channel_status_read(struct mchp_spdifrx_dev *dev,
                goto unlock;
        }
 
-       if (dev->gclk_enabled) {
-               clk_disable_unprepare(dev->gclk);
-               dev->gclk_enabled = 0;
-       }
+       /* GCLK is enabled by runtime PM. */
+       clk_disable_unprepare(dev->gclk);
+
        ret = clk_set_min_rate(dev->gclk, params_rate(params) *
                                          SPDIFRX_GCLK_RATIO_MIN + 1);
        if (ret) {
                dev_err(dev->dev,
                        "unable to set gclk min rate: rate %u * ratio %u + 1\n",
                        params_rate(params), SPDIFRX_GCLK_RATIO_MIN);
+               /* Restore runtime PM state. */
+               clk_prepare_enable(dev->gclk);
                goto unlock;
        }
        ret = clk_prepare_enable(dev->gclk);
                dev_err(dev->dev, "unable to enable gclk: %d\n", ret);
                goto unlock;
        }
-       dev->gclk_enabled = 1;
 
        dev_dbg(dev->dev, "GCLK range min set to %d\n",
                params_rate(params) * SPDIFRX_GCLK_RATIO_MIN + 1);
        return ret;
 }
 
-static int mchp_spdifrx_hw_free(struct snd_pcm_substream *substream,
-                               struct snd_soc_dai *dai)
-{
-       struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
-
-       mutex_lock(&dev->mlock);
-       if (dev->gclk_enabled) {
-               clk_disable_unprepare(dev->gclk);
-               dev->gclk_enabled = 0;
-       }
-       mutex_unlock(&dev->mlock);
-       return 0;
-}
-
 static const struct snd_soc_dai_ops mchp_spdifrx_dai_ops = {
        .trigger        = mchp_spdifrx_trigger,
        .hw_params      = mchp_spdifrx_hw_params,
-       .hw_free        = mchp_spdifrx_hw_free,
 };
 
 #define MCHP_SPDIF_RATES       SNDRV_PCM_RATE_8000_192000
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * We may reach this point with both clocks enabled but the receiver
         * still disabled. To void waiting for completion and return with
                                channel);
                        regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_CSC(channel));
                        ret = ret ? : -ETIMEDOUT;
-                       goto unlock;
+                       goto pm_runtime_put;
                } else {
                        ret = 0;
                }
        memcpy(uvalue->value.iec958.status, ch_stat->data,
               sizeof(ch_stat->data));
 
+pm_runtime_put:
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
 unlock:
        mutex_unlock(&dev->mlock);
        return ret;
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * We may reach this point with both clocks enabled but the receiver
         * still disabled. To void waiting for completion to just timeout we
                                channel);
                        regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_BLOCKEND);
                        ret = ret ? : -ETIMEDOUT;
-                       goto unlock;
+                       goto pm_runtime_put;
                } else {
                        ret = 0;
                }
        memcpy(uvalue->value.iec958.subcode, user_data->data,
               sizeof(user_data->data));
 
+pm_runtime_put:
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
 unlock:
        mutex_unlock(&dev->mlock);
        return ret;
        struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
        struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
        u32 val;
+       int ret;
        bool ulock_old = ctrl->ulock;
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
         * and the receiver is disabled. Thus we take into account the
 
        uvalue->value.integer.value[0] = ctrl->ulock;
 
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
+unlock:
        mutex_unlock(&dev->mlock);
 
        return ulock_old != ctrl->ulock;
        struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
        struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
        u32 val;
+       int ret;
        bool badf_old = ctrl->badf;
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
         * and the receiver is disabled. Thus we take into account the
                ctrl->badf = 0;
        }
 
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
+unlock:
        mutex_unlock(&dev->mlock);
 
        uvalue->value.integer.value[0] = ctrl->badf;
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * To get the signal we need to have receiver enabled. This
         * could be enabled also from trigger() function thus we need to
         * take care of not disabling the receiver when it runs.
         */
        if (!dev->trigger_enabled) {
-               ret = clk_prepare_enable(dev->gclk);
-               if (ret)
-                       goto unlock;
-
                regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
                                   SPDIFRX_MR_RXEN_ENABLE);
 
 
                regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
                                   SPDIFRX_MR_RXEN_DISABLE);
-
-               clk_disable_unprepare(dev->gclk);
        } else {
                regmap_read(dev->regmap, SPDIFRX_RSR, &val);
        }
 
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
+
 unlock:
        mutex_unlock(&dev->mlock);
 
        struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
        unsigned long rate;
        u32 val;
+       int ret;
 
        mutex_lock(&dev->mlock);
 
+       ret = pm_runtime_resume_and_get(dev->dev);
+       if (ret < 0)
+               goto unlock;
+
        /*
         * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
         * and the receiver is disabled. Thus we take into account the
                /* If the receiver is not locked, ISF data is invalid. */
                if (val & SPDIFRX_RSR_ULOCK || !(val & SPDIFRX_RSR_IFS_MASK)) {
                        ucontrol->value.integer.value[0] = 0;
-                       goto unlock;
+                       goto pm_runtime_put;
                }
        } else {
                /* Reveicer is not locked, IFS data is invalid. */
                ucontrol->value.integer.value[0] = 0;
-               goto unlock;
+               goto pm_runtime_put;
        }
 
        rate = clk_get_rate(dev->gclk);
 
        ucontrol->value.integer.value[0] = rate / (32 * SPDIFRX_RSR_IFS(val));
 
+pm_runtime_put:
+       pm_runtime_mark_last_busy(dev->dev);
+       pm_runtime_put_autosuspend(dev->dev);
 unlock:
        mutex_unlock(&dev->mlock);
-       return 0;
+       return ret;
 }
 
 static struct snd_kcontrol_new mchp_spdifrx_ctrls[] = {
        struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
        struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
        int ch;
-       int err;
-
-       err = clk_prepare_enable(dev->pclk);
-       if (err) {
-               dev_err(dev->dev,
-                       "failed to enable the peripheral clock: %d\n", err);
-               return err;
-       }
 
        snd_soc_dai_init_dma_data(dai, NULL, &dev->capture);
 
        /* Disable interrupts */
        regmap_write(dev->regmap, SPDIFRX_IDR, GENMASK(14, 0));
 
-       clk_disable_unprepare(dev->pclk);
-
        return 0;
 }
 
 };
 MODULE_DEVICE_TABLE(of, mchp_spdifrx_dt_ids);
 
+static int mchp_spdifrx_runtime_suspend(struct device *dev)
+{
+       struct mchp_spdifrx_dev *spdifrx = dev_get_drvdata(dev);
+
+       regcache_cache_only(spdifrx->regmap, true);
+       clk_disable_unprepare(spdifrx->gclk);
+       clk_disable_unprepare(spdifrx->pclk);
+
+       return 0;
+}
+
+static int mchp_spdifrx_runtime_resume(struct device *dev)
+{
+       struct mchp_spdifrx_dev *spdifrx = dev_get_drvdata(dev);
+       int ret;
+
+       ret = clk_prepare_enable(spdifrx->pclk);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(spdifrx->gclk);
+       if (ret)
+               goto disable_pclk;
+
+       regcache_cache_only(spdifrx->regmap, false);
+       regcache_mark_dirty(spdifrx->regmap);
+       ret = regcache_sync(spdifrx->regmap);
+       if (ret) {
+               regcache_cache_only(spdifrx->regmap, true);
+               clk_disable_unprepare(spdifrx->gclk);
+disable_pclk:
+               clk_disable_unprepare(spdifrx->pclk);
+       }
+
+       return ret;
+}
+
+static const struct dev_pm_ops mchp_spdifrx_pm_ops = {
+       RUNTIME_PM_OPS(mchp_spdifrx_runtime_suspend, mchp_spdifrx_runtime_resume,
+                      NULL)
+};
+
 static int mchp_spdifrx_probe(struct platform_device *pdev)
 {
        struct mchp_spdifrx_dev *dev;
        dev->regmap = regmap;
        platform_set_drvdata(pdev, dev);
 
+       pm_runtime_enable(dev->dev);
+       if (!pm_runtime_enabled(dev->dev)) {
+               err = mchp_spdifrx_runtime_resume(dev->dev);
+               if (err)
+                       goto pm_runtime_disable;
+       }
+
        dev->capture.addr       = (dma_addr_t)mem->start + SPDIFRX_RHR;
        dev->capture.maxburst   = 1;
 
        err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
        if (err) {
                dev_err(&pdev->dev, "failed to register PCM: %d\n", err);
-               return err;
+               goto pm_runtime_suspend;
        }
 
        err = devm_snd_soc_register_component(&pdev->dev,
                                              &mchp_spdifrx_dai, 1);
        if (err) {
                dev_err(&pdev->dev, "fail to register dai\n");
-               return err;
+               goto pm_runtime_suspend;
        }
 
        regmap_read(regmap, SPDIFRX_VERSION, &vers);
        dev_info(&pdev->dev, "hw version: %#lx\n", vers & SPDIFRX_VERSION_MASK);
 
        return 0;
+
+pm_runtime_suspend:
+       if (!pm_runtime_status_suspended(dev->dev))
+               mchp_spdifrx_runtime_suspend(dev->dev);
+pm_runtime_disable:
+       pm_runtime_disable(dev->dev);
+       return err;
+}
+
+static int mchp_spdifrx_remove(struct platform_device *pdev)
+{
+       struct mchp_spdifrx_dev *dev = platform_get_drvdata(pdev);
+
+       pm_runtime_disable(dev->dev);
+       if (!pm_runtime_status_suspended(dev->dev))
+               mchp_spdifrx_runtime_suspend(dev->dev);
+
+       return 0;
 }
 
 static struct platform_driver mchp_spdifrx_driver = {
        .probe  = mchp_spdifrx_probe,
+       .remove = mchp_spdifrx_remove,
        .driver = {
                .name   = "mchp_spdifrx",
                .of_match_table = of_match_ptr(mchp_spdifrx_dt_ids),
+               .pm     = pm_ptr(&mchp_spdifrx_pm_ops),
        },
 };