[PATCH v2 7/9] i2c: qcom-geni: Vote for SE clock rate using OPP
From: Praveen Talari
Date: Thu Aug 27 2026 - 14:06:59 EST
The I2C driver currently assumes a fixed source clock selection and always
programs SE_GENI_CLK_SEL to 0, which is not sufficient when the controller
needs to switch between different source clocks.
Add the source clock index and frequency to the I2C clock map entries and
use them while configuring the controller. Request the required
source clock rate through dev_pm_opp_set_rate() and program SE_GENI_CLK_SEL
with the matching source index.
Signed-off-by: Praveen Talari <praveen.talari@xxxxxxxxxxxxxxxx>
---
drivers/i2c/busses/i2c-qcom-geni.c | 35 +++++++++++++++++++++++++++--------
1 file changed, 27 insertions(+), 8 deletions(-)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 658636c1ee0e..a3b7609e3cf9 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/soc/qcom/geni-se.h>
#include <linux/spinlock.h>
@@ -165,8 +166,13 @@ struct geni_i2c_clk_fld {
u8 t_high_cnt;
u8 t_low_cnt;
u8 t_cycle_cnt;
+ u8 src_indx;
+ u32 src_clk_freq;
};
+#define CLK_19P2MHZ (19200 * HZ_PER_KHZ)
+#define CLK_32MHZ (32 * HZ_PER_MHZ)
+
/*
* Hardware uses the underlying formula to calculate time periods of
* SCL clock cycle. Firmware uses some additional cycles excluded from the
@@ -180,17 +186,17 @@ struct geni_i2c_clk_fld {
* source_clock = 19.2 MHz
*/
static const struct geni_i2c_clk_fld geni_i2c_clk_map_19p2mhz[] = {
- { I2C_MAX_STANDARD_MODE_FREQ, 7, 10, 12, 26 },
- { I2C_MAX_FAST_MODE_FREQ, 2, 5, 11, 22 },
- { I2C_MAX_FAST_MODE_PLUS_FREQ, 1, 2, 8, 18 },
+ { I2C_MAX_STANDARD_MODE_FREQ, 7, 10, 12, 26, 0, CLK_19P2MHZ },
+ { I2C_MAX_FAST_MODE_FREQ, 2, 5, 11, 22, 0, CLK_19P2MHZ },
+ { I2C_MAX_FAST_MODE_PLUS_FREQ, 1, 2, 8, 18, 0, CLK_19P2MHZ },
{}
};
/* source_clock = 32 MHz */
static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = {
- { I2C_MAX_STANDARD_MODE_FREQ, 8, 14, 18, 38 },
- { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19 },
- { I2C_MAX_FAST_MODE_PLUS_FREQ, 2, 3, 5, 15 },
+ { I2C_MAX_STANDARD_MODE_FREQ, 8, 14, 18, 38, 1, CLK_32MHZ },
+ { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19, 1, CLK_32MHZ },
+ { I2C_MAX_FAST_MODE_PLUS_FREQ, 2, 3, 5, 15, 1, CLK_32MHZ },
{}
};
@@ -198,7 +204,7 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
{
const struct geni_i2c_clk_fld *itr;
- if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ)
+ if (clk_get_rate(gi2c->se.clk) == CLK_32MHZ)
itr = geni_i2c_clk_map_32mhz;
else
itr = geni_i2c_clk_map_19p2mhz;
@@ -218,8 +224,13 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
const struct geni_i2c_clk_fld *itr = gi2c->clk_fld;
u32 val;
+ int ret;
+
+ ret = dev_pm_opp_set_rate(se->dev, itr->src_clk_freq);
+ if (ret)
+ return ret;
- writel_relaxed(0, gi2c->se.base + SE_GENI_CLK_SEL);
+ writel_relaxed(itr->src_indx, gi2c->se.base + SE_GENI_CLK_SEL);
val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN;
writel_relaxed(val, gi2c->se.base + GENI_SER_M_CLK_CFG);
@@ -1228,6 +1239,8 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
}
}
+ dev_pm_opp_set_rate(dev, 0);
+
return 0;
}
@@ -1236,6 +1249,12 @@ static int __maybe_unused geni_i2c_runtime_resume(struct device *dev)
int ret = 0;
struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
+ if (gi2c->clk_fld && gi2c->clk_fld->src_clk_freq) {
+ ret = dev_pm_opp_set_rate(dev, gi2c->clk_fld->src_clk_freq);
+ if (ret)
+ return ret;
+ }
+
if (gi2c->dev_data->power_on) {
ret = gi2c->dev_data->power_on(&gi2c->se);
if (ret)
--
2.34.1