ASoC: mchp-pdmc: use runtime pm for clock power saving
authorClaudiu Beznea <claudiu.beznea@microchip.com>
Tue, 13 Dec 2022 11:28:49 +0000 (13:28 +0200)
committerMark Brown <broonie@kernel.org>
Sun, 25 Dec 2022 23:33:16 +0000 (23:33 +0000)
Implement clock power saving taking advantage of runtime PM infrastructure.
This simplifies the code and allow using the same infrastructure for
suspend to RAM functionalities.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/20221213112851.89212-3-claudiu.beznea@microchip.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/atmel/mchp-pdmc.c

index 44aefbd..f184404 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 
 #include <sound/core.h>
@@ -115,7 +116,6 @@ struct mchp_pdmc {
        int mic_no;
        int sinc_order;
        bool audio_filter_en;
-       u8 gclk_enabled:1;
 };
 
 static const char *const mchp_pdmc_sinc_filter_order_text[] = {
@@ -454,13 +454,6 @@ static int mchp_pdmc_startup(struct snd_pcm_substream *substream,
                             struct snd_soc_dai *dai)
 {
        struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
-       int ret;
-
-       ret = clk_prepare_enable(dd->pclk);
-       if (ret) {
-               dev_err(dd->dev, "failed to enable the peripheral clock: %d\n", ret);
-               return ret;
-       }
 
        regmap_write(dd->regmap, MCHP_PDMC_CR, MCHP_PDMC_CR_SWRST);
 
@@ -470,14 +463,6 @@ static int mchp_pdmc_startup(struct snd_pcm_substream *substream,
        return 0;
 }
 
-static void mchp_pdmc_shutdown(struct snd_pcm_substream *substream,
-                              struct snd_soc_dai *dai)
-{
-       struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
-
-       clk_disable_unprepare(dd->pclk);
-}
-
 static int mchp_pdmc_dai_probe(struct snd_soc_dai *dai)
 {
        struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
@@ -594,11 +579,6 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
                        cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
        }
 
-       if (dd->gclk_enabled) {
-               clk_disable_unprepare(dd->gclk);
-               dd->gclk_enabled = 0;
-       }
-
        for (osr_start = dd->audio_filter_en ? 64 : 8;
             osr_start <= 256 && best_diff_rate; osr_start *= 2) {
                long round_rate;
@@ -620,8 +600,12 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
                return -EINVAL;
        }
 
+       /* CLK is enabled by runtime PM. */
+       clk_disable_unprepare(dd->gclk);
+
        /* set the rate */
        ret = clk_set_rate(dd->gclk, gclk_rate);
+       clk_prepare_enable(dd->gclk);
        if (ret) {
                dev_err(comp->dev, "unable to set rate %lu to GCLK: %d\n",
                        gclk_rate, ret);
@@ -636,9 +620,6 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
        mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
        dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
 
-       clk_prepare_enable(dd->gclk);
-       dd->gclk_enabled = 1;
-
        snd_soc_component_update_bits(comp, MCHP_PDMC_MR,
                                      MCHP_PDMC_MR_OSR_MASK |
                                      MCHP_PDMC_MR_SINCORDER_MASK |
@@ -650,19 +631,6 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
        return 0;
 }
 
-static int mchp_pdmc_hw_free(struct snd_pcm_substream *substream,
-                            struct snd_soc_dai *dai)
-{
-       struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
-
-       if (dd->gclk_enabled) {
-               clk_disable_unprepare(dd->gclk);
-               dd->gclk_enabled = 0;
-       }
-
-       return 0;
-}
-
 static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
                             int cmd, struct snd_soc_dai *dai)
 {
@@ -711,9 +679,7 @@ static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
 static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
        .set_fmt        = mchp_pdmc_set_fmt,
        .startup        = mchp_pdmc_startup,
-       .shutdown       = mchp_pdmc_shutdown,
        .hw_params      = mchp_pdmc_hw_params,
-       .hw_free        = mchp_pdmc_hw_free,
        .trigger        = mchp_pdmc_trigger,
 };
 
@@ -864,6 +830,7 @@ static const struct regmap_config mchp_pdmc_regmap_config = {
        .readable_reg   = mchp_pdmc_readable_reg,
        .writeable_reg  = mchp_pdmc_writeable_reg,
        .precious_reg   = mchp_pdmc_precious_reg,
+       .cache_type     = REGCACHE_FLAT,
 };
 
 static int mchp_pdmc_dt_init(struct mchp_pdmc *dd)
@@ -970,6 +937,49 @@ static struct snd_dmaengine_pcm_config mchp_pdmc_config = {
        .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
 };
 
+static int mchp_pdmc_runtime_suspend(struct device *dev)
+{
+       struct mchp_pdmc *dd = dev_get_drvdata(dev);
+
+       regcache_cache_only(dd->regmap, true);
+
+       clk_disable_unprepare(dd->gclk);
+       clk_disable_unprepare(dd->pclk);
+
+       return 0;
+}
+
+static int mchp_pdmc_runtime_resume(struct device *dev)
+{
+       struct mchp_pdmc *dd = dev_get_drvdata(dev);
+       int ret;
+
+       ret = clk_prepare_enable(dd->pclk);
+       if (ret) {
+               dev_err(dd->dev,
+                       "failed to enable the peripheral clock: %d\n", ret);
+               return ret;
+       }
+       ret = clk_prepare_enable(dd->gclk);
+       if (ret) {
+               dev_err(dd->dev,
+                       "failed to enable generic clock: %d\n", ret);
+               goto disable_pclk;
+       }
+
+       regcache_cache_only(dd->regmap, false);
+       regcache_mark_dirty(dd->regmap);
+       ret = regcache_sync(dd->regmap);
+       if (ret) {
+               regcache_cache_only(dd->regmap, true);
+               clk_disable_unprepare(dd->gclk);
+disable_pclk:
+               clk_disable_unprepare(dd->pclk);
+       }
+
+       return ret;
+}
+
 static int mchp_pdmc_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -1039,18 +1049,25 @@ static int mchp_pdmc_probe(struct platform_device *pdev)
        dd->addr.addr = (dma_addr_t)res->start + MCHP_PDMC_RHR;
        platform_set_drvdata(pdev, dd);
 
+       pm_runtime_enable(dd->dev);
+       if (!pm_runtime_enabled(dd->dev)) {
+               ret = mchp_pdmc_runtime_resume(dd->dev);
+               if (ret)
+                       return ret;
+       }
+
        /* register platform */
        ret = devm_snd_dmaengine_pcm_register(dev, &mchp_pdmc_config, 0);
        if (ret) {
                dev_err(dev, "could not register platform: %d\n", ret);
-               return ret;
+               goto pm_runtime_suspend;
        }
 
        ret = devm_snd_soc_register_component(dev, &mchp_pdmc_dai_component,
                                              &mchp_pdmc_dai, 1);
        if (ret) {
                dev_err(dev, "could not register CPU DAI: %d\n", ret);
-               return ret;
+               goto pm_runtime_suspend;
        }
 
        /* print IP version */
@@ -1059,6 +1076,25 @@ static int mchp_pdmc_probe(struct platform_device *pdev)
                 version & MCHP_PDMC_VER_VERSION);
 
        return 0;
+
+pm_runtime_suspend:
+       if (!pm_runtime_status_suspended(dd->dev))
+               mchp_pdmc_runtime_suspend(dd->dev);
+       pm_runtime_disable(dd->dev);
+
+       return ret;
+}
+
+static int mchp_pdmc_remove(struct platform_device *pdev)
+{
+       struct mchp_pdmc *dd = platform_get_drvdata(pdev);
+
+       if (!pm_runtime_status_suspended(dd->dev))
+               mchp_pdmc_runtime_suspend(dd->dev);
+
+       pm_runtime_disable(dd->dev);
+
+       return 0;
 }
 
 static const struct of_device_id mchp_pdmc_of_match[] = {
@@ -1070,13 +1106,19 @@ static const struct of_device_id mchp_pdmc_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
 
+static const struct dev_pm_ops mchp_pdmc_pm_ops = {
+       RUNTIME_PM_OPS(mchp_pdmc_runtime_suspend, mchp_pdmc_runtime_resume,
+                      NULL)
+};
+
 static struct platform_driver mchp_pdmc_driver = {
        .driver = {
                .name           = "mchp-pdmc",
                .of_match_table = of_match_ptr(mchp_pdmc_of_match),
-               .pm             = &snd_soc_pm_ops,
+               .pm             = pm_ptr(&mchp_pdmc_pm_ops),
        },
        .probe  = mchp_pdmc_probe,
+       .remove = mchp_pdmc_remove,
 };
 module_platform_driver(mchp_pdmc_driver);