[PATCH v4 5/6] media: qcom: camss: csid: Scale the clocks with the CSI-2 link rate
From: Nihal Kumar Gupta
Date: Fri Nov 06 2026 - 01:52:11 EST
From: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
Only the gen1 "csiN" clocks are scaled with the link rate of the sensor,
the CSID core and CSIPHY receive clocks of all the other SoCs are set to
the first rate of the resource table. A SoC can therefore only be
described for one sensor bandwidth: the lowest rate first breaks fast
sensors, the highest one burns power with the slow ones.
Compute the minimum rate from the link frequency for all generations.
The CSIPHY passes each D-PHY lane to the CSID over an 8 bit wide
interface clocked by the receive clock, so that clock has to run at the
per lane bit rate divided by 8, which is link_freq / 4. The core clock
must not be slower, hence the same minimum for it and the cap of the
receive clock at the core rate. The clocks are matched by suffix and
voted for with clk_set_rate_range(), as the CSIDs of the recent SoCs
share them.
Up to 1.8 Gbps/lane the selected rates are unchanged on every supported
SoC.
Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
Signed-off-by: Nihal Kumar Gupta <nihal.gupta@xxxxxxxxxxxxxxxx>
---
drivers/media/platform/qcom/camss/camss-csid.c | 208 ++++++++++++++++++++-----
1 file changed, 169 insertions(+), 39 deletions(-)
diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c
index a00791b00feb79bcdea7dccf6d1d120f01a8c485..26d754d72b66747abfa02fbbe3f2b2bd662b6d40 100644
--- a/drivers/media/platform/qcom/camss/camss-csid.c
+++ b/drivers/media/platform/qcom/camss/camss-csid.c
@@ -16,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
+#include <linux/string.h>
#include <media/media-entity.h>
#include <media/mipi-csi2.h>
#include <media/v4l2-device.h>
@@ -532,72 +533,197 @@ const struct csid_format_info *csid_get_fmt_entry(const struct csid_format_info
return &formats[0];
}
+/* Match the core clock of the CSID */
+static bool csid_match_core_clock_name(struct camss_clock *clock)
+{
+ /* "csid", "vfe0_csid", "vfe_lite_csid", ... */
+ if (strends(clock->name, "csid"))
+ return true;
+
+ /* Named after the CSI port on the SoCs with a gen1 CSID */
+ return !strcmp(clock->name, "csi0") || !strcmp(clock->name, "csi1") ||
+ !strcmp(clock->name, "csi2") || !strcmp(clock->name, "csi3");
+}
+
+/* Match the clock of the CSIPHY to CSID interface */
+static bool csid_match_phy_rx_clock_name(struct camss_clock *clock)
+{
+ /* "csiphy_rx", "csid_csiphy_rx", "vfe0_cphy_rx", ... */
+ return strends(clock->name, "phy_rx");
+}
+
+/* Match a clock whose rate depends on the CSI-2 link rate */
+static bool csid_match_scaled_clock_name(struct camss_clock *clock)
+{
+ return csid_match_core_clock_name(clock) ||
+ csid_match_phy_rx_clock_name(clock);
+}
+
/*
- * csid_set_clock_rates - Calculate and set clock rates on CSID module
- * @csiphy: CSID device
+ * csid_get_min_clock_rate - Get the rate the CSI-2 link requires
+ * @csid: CSID device
+ *
+ * The CSIPHY deserialises every D-PHY data lane into bytes and passes them to
+ * the CSID over an interface which is, as per the CSID HPG, 8 bits wide per
+ * lane and clocked by the CSIPHY receive clock. That clock has to run at the
+ * per lane bit rate divided by 8 at the very least. The link frequency is the
+ * DDR clock of the link, i.e. half the per lane bit rate, which makes the
+ * minimum rate link_freq / 4.
+ *
+ * Qualcomm recommends clocking the CSID core at least as fast as the CSIPHY
+ * receive clock feeding it, so the same minimum applies to the core clock.
+ *
+ * C-PHY maps 16 bits onto 7 symbols and needs a different ratio, but CAMSS
+ * does not support it so far.
+ *
+ * Return the minimum rate in Hz, clock margin included, or 0 if the link
+ * frequency of the sensor is not known
*/
-static int csid_set_clock_rates(struct csid_device *csid)
+static u64 csid_get_min_clock_rate(struct csid_device *csid)
{
- struct device *dev = csid->camss->dev;
const struct csid_format_info *fmt;
s64 link_freq;
- int i, j;
- int ret;
+ u64 min_rate;
fmt = csid_get_fmt_entry(csid->res->formats->formats, csid->res->formats->nformats,
- csid->fmt[MSM_CSIPHY_PAD_SINK].code);
+ csid->fmt[MSM_CSID_PAD_SINK].code);
link_freq = camss_get_link_freq(&csid->subdev.entity, fmt->bpp,
csid->phy.lane_cnt);
if (link_freq < 0)
- link_freq = 0;
+ return 0;
+
+ min_rate = link_freq / 4;
+ camss_add_clock_margin(&min_rate);
+
+ return min_rate;
+}
+
+/* Get the lowest level of a clock above min_rate, or -EINVAL if there is none */
+static int csid_find_clock_level(struct camss_clock *clock, u64 min_rate)
+{
+ int i;
+
+ /* Play safe with the highest level if the link rate is not known */
+ if (!min_rate)
+ return clock->nfreqs - 1;
+
+ for (i = 0; i < clock->nfreqs; i++)
+ if (clock->freq[i] > min_rate)
+ return i;
+
+ return -EINVAL;
+}
+
+/* Lower a level down to max_rate, but never below what min_rate needs */
+static int csid_cap_clock_level(struct camss_clock *clock, int level,
+ u64 min_rate, u64 max_rate)
+{
+ while (level > 0 && clock->freq[level] > max_rate &&
+ clock->freq[level - 1] > min_rate)
+ level--;
+
+ return level;
+}
+
+/*
+ * csid_set_clock_rates - Calculate and set clock rates on CSID module
+ * @csid: CSID device
+ */
+static int csid_set_clock_rates(struct csid_device *csid)
+{
+ struct device *dev = csid->camss->dev;
+ u64 min_rate = csid_get_min_clock_rate(csid);
+ u64 core_rate = 0;
+ int i, j;
+ int ret;
+
+ /*
+ * Settle the rate of the core clock first, as the receive clock must
+ * not outrun it and the clocks are not listed in a fixed order.
+ */
for (i = 0; i < csid->nclocks; i++) {
struct camss_clock *clock = &csid->clock[i];
- if (!strcmp(clock->name, "csi0") ||
- !strcmp(clock->name, "csi1") ||
- !strcmp(clock->name, "csi2") ||
- !strcmp(clock->name, "csi3")) {
- u64 min_rate = link_freq / 4;
- long rate;
+ if (!clock->nfreqs || !csid_match_core_clock_name(clock))
+ continue;
- camss_add_clock_margin(&min_rate);
+ j = csid_find_clock_level(clock, min_rate);
+ if (j < 0) {
+ dev_err(dev, "Link rate is too high for the %s clock\n",
+ clock->name);
+ return j;
+ }
- for (j = 0; j < clock->nfreqs; j++)
- if (min_rate < clock->freq[j])
- break;
+ core_rate = max_t(u64, core_rate, clock->freq[j]);
+ }
- if (j == clock->nfreqs) {
- dev_err(dev,
- "Pixel clock is too high for CSID\n");
- return -EINVAL;
- }
+ for (i = 0; i < csid->nclocks; i++) {
+ struct camss_clock *clock = &csid->clock[i];
- /* if sensor pixel clock is not available */
- /* set highest possible CSID clock rate */
- if (min_rate == 0)
- j = clock->nfreqs - 1;
+ if (!clock->nfreqs)
+ continue;
- rate = clk_round_rate(clock->clk, clock->freq[j]);
- if (rate < 0) {
- dev_err(dev, "clk round rate failed: %ld\n",
- rate);
- return -EINVAL;
- }
+ if (!csid_match_scaled_clock_name(clock)) {
+ /* The remaining clocks run at their lowest level */
- ret = clk_set_rate(clock->clk, rate);
- if (ret < 0) {
- dev_err(dev, "clk set rate failed: %d\n", ret);
- return ret;
- }
- } else if (clock->nfreqs) {
clk_set_rate(clock->clk, clock->freq[0]);
+ continue;
+ }
+
+ j = csid_find_clock_level(clock, min_rate);
+ if (j < 0) {
+ dev_err(dev, "Link rate is too high for the %s clock\n",
+ clock->name);
+ return j;
+ }
+
+ /*
+ * There is nothing to gain from running the receive clock
+ * faster than the core clock, as long as the link rate is
+ * still covered.
+ */
+ if (core_rate && csid_match_phy_rx_clock_name(clock))
+ j = csid_cap_clock_level(clock, j, min_rate, core_rate);
+
+ /*
+ * Vote for the rate as a minimum, as the clock may be shared
+ * with another CSID which needs it to run faster.
+ */
+ ret = clk_set_rate_range(clock->clk, clock->freq[j], ULONG_MAX);
+ if (ret < 0) {
+ dev_err(dev, "clk set rate range failed: %d\n", ret);
+ return ret;
}
}
return 0;
}
+/*
+ * Drop the minimum rates the CSID votes for and take its clocks back to their
+ * lowest level, or to the lowest level the other users of a shared clock need.
+ */
+static void csid_release_clock_rates(struct csid_device *csid)
+{
+ int i;
+
+ for (i = 0; i < csid->nclocks; i++) {
+ struct camss_clock *clock = &csid->clock[i];
+ long rate;
+
+ if (!clock->nfreqs || !csid_match_scaled_clock_name(clock))
+ continue;
+
+ clk_drop_range(clock->clk);
+
+ /* Round first, so that the rate respects the other votes */
+ rate = clk_round_rate(clock->clk, clock->freq[0]);
+ if (rate > 0)
+ clk_set_rate(clock->clk, rate);
+ }
+}
+
/*
* csid_hw_version - CSID hardware version query
* @csid: CSID device
@@ -707,6 +833,7 @@ static int csid_set_power(struct v4l2_subdev *sd, int on)
ret = csid_set_clock_rates(csid);
if (ret < 0) {
+ csid_release_clock_rates(csid);
regulator_bulk_disable(csid->num_supplies,
csid->supplies);
pm_runtime_put_sync(dev);
@@ -715,6 +842,7 @@ static int csid_set_power(struct v4l2_subdev *sd, int on)
ret = camss_enable_clocks(csid->nclocks, csid->clock, dev);
if (ret < 0) {
+ csid_release_clock_rates(csid);
regulator_bulk_disable(csid->num_supplies,
csid->supplies);
pm_runtime_put_sync(dev);
@@ -729,6 +857,7 @@ static int csid_set_power(struct v4l2_subdev *sd, int on)
if (ret < 0) {
disable_irq(csid->irq);
camss_disable_clocks(csid->nclocks, csid->clock);
+ csid_release_clock_rates(csid);
regulator_bulk_disable(csid->num_supplies,
csid->supplies);
pm_runtime_put_sync(dev);
@@ -739,6 +868,7 @@ static int csid_set_power(struct v4l2_subdev *sd, int on)
} else {
disable_irq(csid->irq);
camss_disable_clocks(csid->nclocks, csid->clock);
+ csid_release_clock_rates(csid);
regulator_bulk_disable(csid->num_supplies,
csid->supplies);
pm_runtime_put_sync(dev);
--
2.34.1