[PATCH v2 4/6] media: qcom: camss: share VFE clock sources by the highest rate
From: Atanas Filipov
Date: Sat Oct 24 2026 - 05:43:18 EST
Clocks of different VFEs can be fed by one clock source, and each VFE
sets such a source to the rate it needs. A VFE started later at a lower
rate then lowers the source under a VFE that is still running.
For example, on SM8250 the two VFE Lite share one core clock source,
which the VFE sets to 400 or 480 MHz from the pixel rate. With VFE Lite
0 streaming the CSID test pattern at 480 MHz, starting an imx577 stream
on VFE Lite 1 sets the shared source to 400 MHz under the running VFE
Lite 0.
Group the VFE clocks with rate tables by their clock source at probe
time. A source shared by several VFEs follows the highest rate requested
by its running VFEs and drops to the highest remaining one when a VFE is
powered off; with no VFE left it keeps its rate, as it may feed
consumers outside CAMSS. A source used by a single VFE is dropped to its
lowest rate on power off. Clocks whose rate the VFE does not set are
left untouched.
In the example above the shared source stays at 480 MHz until VFE Lite
0 stops, and then drops to the 400 MHz still requested by VFE Lite 1.
Grouping is enabled through the vfe_clock_domains resource flag, so far
only on SM8250. The current parent of an RCG depends on its last rate,
so other SoCs keep setting their VFE clocks directly.
Signed-off-by: Atanas Filipov <atanas.filipov@xxxxxxxxxxxxxxxx>
---
drivers/media/platform/qcom/camss/camss-vfe.c | 11 +-
drivers/media/platform/qcom/camss/camss.c | 175 ++++++++++++++++++
drivers/media/platform/qcom/camss/camss.h | 13 ++
3 files changed, 195 insertions(+), 4 deletions(-)
diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c
index 359b5942df89..614497235eb4 100644
--- a/drivers/media/platform/qcom/camss/camss-vfe.c
+++ b/drivers/media/platform/qcom/camss/camss-vfe.c
@@ -1016,7 +1016,7 @@ static int vfe_set_clock_rates(struct vfe_device *vfe)
return -EINVAL;
}
- ret = clk_set_rate(clock->clk, rate);
+ ret = camss_set_clock_rate(vfe->camss, clock, rate);
if (ret < 0) {
dev_err(dev, "clk set rate failed: %d\n", ret);
return ret;
@@ -1083,12 +1083,12 @@ int vfe_get(struct vfe_device *vfe)
ret = vfe_set_clock_rates(vfe);
if (ret < 0)
- goto error_pm_runtime_get;
+ goto error_release_rates;
ret = camss_enable_clocks(vfe->nclocks, vfe->clock,
vfe->camss->dev);
if (ret < 0)
- goto error_pm_runtime_get;
+ goto error_release_rates;
ret = vfe_reset(vfe);
if (ret < 0)
@@ -1113,7 +1113,9 @@ int vfe_get(struct vfe_device *vfe)
error_reset:
camss_disable_clocks(vfe->nclocks, vfe->clock);
-error_pm_runtime_get:
+error_release_rates:
+ camss_release_clock_rates(vfe->camss, vfe->nclocks, vfe->clock);
+
pm_runtime_put_sync(vfe->camss->dev);
error_domain_off:
vfe->res->hw_ops->pm_domain_off(vfe);
@@ -1141,6 +1143,7 @@ void vfe_put(struct vfe_device *vfe)
vfe->res->hw_ops->vfe_halt(vfe);
}
camss_disable_clocks(vfe->nclocks, vfe->clock);
+ camss_release_clock_rates(vfe->camss, vfe->nclocks, vfe->clock);
pm_runtime_put_sync(vfe->camss->dev);
vfe->res->hw_ops->pm_domain_off(vfe);
}
diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c
index 7fe12049b78a..68b43a720869 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -4509,6 +4509,171 @@ void camss_disable_clocks(int nclocks, struct camss_clock *clock)
clk_disable_unprepare(clock[i].clk);
}
+/*
+ * A clock domain is a clock source shared by clocks of different VFEs. Each
+ * VFE votes for the rate it needs, and the source runs at the highest vote,
+ * so that a VFE started at a lower rate never lowers the rate under a VFE
+ * that is still running.
+ */
+struct camss_clock_domain {
+ struct list_head node;
+ struct clk *parent;
+ struct list_head votes;
+ unsigned int nvotes;
+};
+
+/*
+ * A clock vote is the rate requested by one VFE clock fed by a clock domain.
+ * A rate of 0 means that the VFE is powered off and does not vote.
+ */
+struct camss_clock_vote {
+ struct list_head node;
+ struct camss_clock_domain *domain;
+ struct camss_clock *clock;
+ unsigned long rate;
+};
+
+static int camss_add_clock_vote(struct camss *camss, struct camss_clock *clock)
+{
+ struct camss_clock_domain *domain;
+ struct camss_clock_vote *vote;
+ struct clk *parent;
+
+ if (!clock->nfreqs)
+ return 0;
+
+ parent = clk_get_parent(clock->clk);
+ if (!parent)
+ return 0;
+
+ list_for_each_entry(domain, &camss->clock_domains, node)
+ if (clk_is_match(domain->parent, parent))
+ goto add_vote;
+
+ domain = devm_kzalloc(camss->dev, sizeof(*domain), GFP_KERNEL);
+ if (!domain)
+ return -ENOMEM;
+
+ domain->parent = parent;
+ INIT_LIST_HEAD(&domain->votes);
+ list_add_tail(&domain->node, &camss->clock_domains);
+
+add_vote:
+ vote = devm_kzalloc(camss->dev, sizeof(*vote), GFP_KERNEL);
+ if (!vote)
+ return -ENOMEM;
+
+ vote->domain = domain;
+ vote->clock = clock;
+ list_add_tail(&vote->node, &domain->votes);
+ domain->nvotes++;
+ clock->vote = vote;
+
+ return 0;
+}
+
+/*
+ * camss_init_clock_domains - Group the VFE clocks by clock source
+ * @camss: CAMSS device
+ *
+ * A clock whose source feeds no other VFE clock forms a dedicated domain
+ * with a single vote. Only SoCs whose VFE sets the rates of clocks with a
+ * fixed source group their clocks; the VFE clocks of other SoCs are set
+ * directly.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int camss_init_clock_domains(struct camss *camss)
+{
+ int ret, i, j;
+
+ if (!camss->res->vfe_clock_domains)
+ return 0;
+
+ for (i = 0; i < camss->res->vfe_num; i++)
+ for (j = 0; j < camss->vfe[i].nclocks; j++) {
+ ret = camss_add_clock_vote(camss, &camss->vfe[i].clock[j]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int camss_apply_clock_domain(struct camss_clock_domain *domain)
+{
+ struct camss_clock_vote *vote, *top = NULL;
+
+ list_for_each_entry(vote, &domain->votes, node)
+ if (vote->rate && (!top || vote->rate > top->rate))
+ top = vote;
+
+ if (!top)
+ return 0;
+
+ return clk_set_rate(top->clock->clk, top->rate);
+}
+
+/*
+ * camss_set_clock_rate - Set the rate of a clock
+ * @camss: CAMSS device
+ * @clock: Clock
+ * @rate: Requested rate
+ *
+ * A shared clock is set to the highest rate voted by the running VFEs
+ * of its source.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int camss_set_clock_rate(struct camss *camss, struct camss_clock *clock,
+ unsigned long rate)
+{
+ int ret;
+
+ if (!clock->vote)
+ return clk_set_rate(clock->clk, rate);
+
+ mutex_lock(&camss->clock_lock);
+ clock->vote->rate = rate;
+ ret = camss_apply_clock_domain(clock->vote->domain);
+ mutex_unlock(&camss->clock_lock);
+
+ return ret;
+}
+
+/*
+ * camss_release_clock_rates - Release the rates requested by an engine
+ * @camss: CAMSS device
+ * @nclocks: Number of clocks of the engine
+ * @clock: Clocks of the engine
+ *
+ * A dedicated clock drops to its lowest rate. A shared clock drops to the
+ * highest rate of the VFEs still running, and keeps its rate when none is
+ * left, as its source may feed consumers outside CAMSS.
+ */
+void camss_release_clock_rates(struct camss *camss, int nclocks,
+ struct camss_clock *clock)
+{
+ int i;
+
+ mutex_lock(&camss->clock_lock);
+
+ for (i = 0; i < nclocks; i++) {
+ struct camss_clock_vote *vote = clock[i].vote;
+
+ if (!vote || !vote->rate)
+ continue;
+
+ vote->rate = 0;
+ if (vote->domain->nvotes == 1)
+ clk_set_rate(vote->clock->clk, 0);
+ else
+ camss_apply_clock_domain(vote->domain);
+ }
+
+ mutex_unlock(&camss->clock_lock);
+}
+
/*
* camss_find_sensor_pad - Find the media pad via which the sensor is linked
* @entity: Media entity to start searching from
@@ -5427,6 +5592,11 @@ static int camss_probe(struct platform_device *pdev)
atomic_set(&camss->ref_count, 0);
camss->dev = dev;
+ INIT_LIST_HEAD(&camss->clock_domains);
+
+ ret = devm_mutex_init(dev, &camss->clock_lock);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, camss);
camss->csiphy = devm_kcalloc(dev, camss->res->csiphy_num,
@@ -5474,6 +5644,10 @@ static int camss_probe(struct platform_device *pdev)
if (ret < 0)
goto err_genpd_cleanup;
+ ret = camss_init_clock_domains(camss);
+ if (ret < 0)
+ goto err_genpd_cleanup;
+
ret = dma_set_mask_and_coherent(dev, 0xffffffff);
if (ret)
goto err_genpd_cleanup;
@@ -5736,6 +5910,7 @@ static const struct camss_resources sm8250_resources = {
.csiphy_num = ARRAY_SIZE(csiphy_res_8250),
.csid_num = ARRAY_SIZE(csid_res_8250),
.vfe_num = ARRAY_SIZE(vfe_res_8250),
+ .vfe_clock_domains = true,
};
static const struct camss_resources sc8280xp_resources = {
diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h
index 7dc2fd9f03e0..03d415a5b9db 100644
--- a/drivers/media/platform/qcom/camss/camss.h
+++ b/drivers/media/platform/qcom/camss/camss.h
@@ -11,6 +11,8 @@
#define QC_MSM_CAMSS_H
#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
#include <linux/types.h>
#include <media/v4l2-async.h>
#include <media/v4l2-device.h>
@@ -120,6 +122,7 @@ struct camss_resources {
const unsigned int tpg_num;
const unsigned int csid_num;
const unsigned int vfe_num;
+ const bool vfe_clock_domains;
};
struct camss {
@@ -141,6 +144,9 @@ struct camss {
struct icc_path *icc_path[ICC_SM8250_COUNT];
const struct camss_resources *res;
bool legacy_phy;
+ /* Protects clock_domains and the rates voted by the VFEs */
+ struct mutex clock_lock;
+ struct list_head clock_domains;
};
struct camss_camera_interface {
@@ -153,11 +159,14 @@ struct camss_async_subdev {
struct camss_camera_interface interface;
};
+struct camss_clock_vote;
+
struct camss_clock {
struct clk *clk;
const char *name;
u32 *freq;
u32 nfreqs;
+ struct camss_clock_vote *vote;
};
struct parent_dev_ops {
@@ -170,6 +179,10 @@ void camss_add_clock_margin(u64 *rate);
int camss_enable_clocks(int nclocks, struct camss_clock *clock,
struct device *dev);
void camss_disable_clocks(int nclocks, struct camss_clock *clock);
+int camss_set_clock_rate(struct camss *camss, struct camss_clock *clock,
+ unsigned long rate);
+void camss_release_clock_rates(struct camss *camss, int nclocks,
+ struct camss_clock *clock);
struct media_pad *camss_find_sensor_pad(struct media_entity *entity);
s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp,
unsigned int lanes);
--
2.34.1