Add optional PM clock support to the CAMSS driver using the PM clock
framework. This allows CAMSS clocks to be registered once and
automatically managed during runtime suspend and resume.

This is especially useful for global CAMSS clocks that are shared across
multiple CAMSS subnodes. Now that CAMSS is modeled as a simple-bus,
these clocks are automatically enabled whenever a child node becomes
active.

This avoids the need for each subdevice to reference and manage the
shared clocks individually. A typical example is the set of clocks in
the top_group, which may be used by CSID, PHY, CCI, OPE, and other
CAMSS blocks.

Introduce a small PM clock descriptor table in the CAMSS resources
structure to describe clocks and their optional rates. Initialize
these clocks at probe time and delegate clock ownership to the PM
core.

Hook PM clock handling into the runtime PM callbacks to ensure clocks
are properly suspended and resumed alongside power domains and ICC
paths.

Signed-off-by: Loic Poulain <[email protected]>
---
 drivers/media/platform/qcom/camss/camss.c | 54 ++++++++++++++++++++++++++++++-
 drivers/media/platform/qcom/camss/camss.h |  6 ++++
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/qcom/camss/camss.c 
b/drivers/media/platform/qcom/camss/camss.c
index 
8f2b1d3cd9f289895aa439443d2a18bb036fccde..ca68ad7fc9ff30eae23d3baf34cf1ca642acf9d7
 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -19,6 +19,7 @@
 #include <linux/of_platform.h>
 #include <linux/pm_runtime.h>
 #include <linux/pm_domain.h>
+#include <linux/pm_clock.h>
 #include <linux/slab.h>
 #include <linux/videodev2.h>
 
@@ -4593,6 +4594,49 @@ static void camss_genpd_cleanup(struct camss *camss)
        dev_pm_domain_detach(camss->genpd, true);
 }
 
+static int camss_init_pm_clks(struct camss *camss)
+{
+       struct device *dev = camss->dev;
+       unsigned int i;
+       int ret;
+
+       if (!camss->res->pm_clks[0].name)
+               return 0;
+
+       ret = devm_pm_clk_create(dev);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < CAMSS_RES_MAX && camss->res->pm_clks[i].name; i++) {
+               const struct camss_pm_clk *entry = &camss->res->pm_clks[i];
+               struct clk *clk;
+
+               clk = clk_get(dev, entry->name);
+               if (IS_ERR(clk)) {
+                       dev_warn(dev, "failed to get pm_clk %s: %pe\n",
+                                entry->name, clk);
+                       continue;
+               }
+
+               if (entry->rate) {
+                       ret = clk_set_rate(clk, entry->rate);
+                       if (ret)
+                               dev_warn(dev, "failed to set rate for pm_clk 
%s: %d\n",
+                                        entry->name, ret);
+               }
+
+               /* PM takes ownership of the clock, no explicit clk_put() is 
required. */
+               ret = pm_clk_add_clk(dev, clk);
+               if (ret) {
+                       dev_warn(dev, "failed to add pm_clk %s: %d\n",
+                                entry->name, ret);
+                       clk_put(clk);
+               }
+       }
+
+       return 0;
+}
+
 /*
  * camss_probe - Probe CAMSS platform device
  * @pdev: Pointer to CAMSS platform device
@@ -4677,6 +4721,10 @@ static int camss_probe(struct platform_device *pdev)
 
        pm_runtime_enable(dev);
 
+       ret = camss_init_pm_clks(camss);
+       if (ret)
+               goto err_v4l2_device_unregister;
+
        ret = camss_of_parse_ports(camss);
        if (ret < 0)
                goto err_v4l2_device_unregister;
@@ -4984,7 +5032,7 @@ static int __maybe_unused camss_runtime_suspend(struct 
device *dev)
                        return ret;
        }
 
-       return 0;
+       return pm_clk_suspend(dev);
 }
 
 static int __maybe_unused camss_runtime_resume(struct device *dev)
@@ -4994,6 +5042,10 @@ static int __maybe_unused camss_runtime_resume(struct 
device *dev)
        int i;
        int ret;
 
+       ret = pm_clk_resume(dev);
+       if (ret)
+               return ret;
+
        for (i = 0; i < camss->res->icc_path_num; i++) {
                ret = icc_set_bw(camss->icc_path[i],
                                 icc_res[i].icc_bw_tbl.avg,
diff --git a/drivers/media/platform/qcom/camss/camss.h 
b/drivers/media/platform/qcom/camss/camss.h
index 
9d9a62640e25dce0e8d45af9df01bbfd64b9bb4b..bd5e572f0a0a7daa1668831b7d2fc60e0498200d
 100644
--- a/drivers/media/platform/qcom/camss/camss.h
+++ b/drivers/media/platform/qcom/camss/camss.h
@@ -100,9 +100,15 @@ enum icc_count {
        ICC_SM8250_COUNT = 4,
 };
 
+struct camss_pm_clk {
+       const char *name;
+       unsigned long rate;     /* 0 = do not set rate */
+};
+
 struct camss_resources {
        enum camss_version version;
        const char *pd_name;
+       struct camss_pm_clk pm_clks[CAMSS_RES_MAX];
        const struct camss_subdev_resources *csiphy_res;
        const struct camss_subdev_resources *csid_res;
        const struct camss_subdev_resources *ispif_res;

-- 
2.34.1


Reply via email to