The Qualcomm automotive SA8255p SoC relies on firmware to configure
platform resources, including clocks, interconnects and TLMM.
The driver requests resources operations over SCMI using power
and performance protocols.
The SCMI power protocol enables or disables resources like clocks,
interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs,
such as resume/suspend, to control power on/off.
The SCMI performance protocol manages I2C frequency, with each
frequency rate represented by a performance level. The driver uses
geni_se_set_perf_opp() API to request the desired frequency rate..
As part of geni_se_set_perf_opp(), the OPP for the requested frequency
is obtained using dev_pm_opp_find_freq_floor() and the performance
level is set using dev_pm_opp_set_opp().
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
V1->v2:
From kernel test robot:
- Initialized ret to "0" in resume/suspend callbacks.
Bjorn:
- Used seperate APIs for the resouces enable/disable.
---
drivers/i2c/busses/i2c-qcom-geni.c | 53 ++++++++++++++++++++++--------
1 file changed, 40 insertions(+), 13 deletions(-)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 1c9356e13b97..72457b98f155 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -82,6 +82,10 @@ struct geni_i2c_desc {
char *icc_ddr;
bool no_dma_support;
unsigned int tx_fifo_depth;
+ int (*resources_init)(struct geni_se *se);
+ int (*set_rate)(struct geni_se *se, unsigned long freq);
+ int (*power_on)(struct geni_se *se);
+ int (*power_off)(struct geni_se *se);
};
#define QCOM_I2C_MIN_NUM_OF_MSGS_MULTI_DESC 2
@@ -203,8 +207,9 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
return -EINVAL;
}
-static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c)
+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;
@@ -217,6 +222,7 @@ static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c)
val |= itr->t_low_cnt << LOW_COUNTER_SHFT;
val |= itr->t_cycle_cnt;
writel_relaxed(val, gi2c->se.base + SE_I2C_SCL_COUNTERS);
+ return 0;
}
static void geni_i2c_err_misc(struct geni_i2c_dev *gi2c)
@@ -908,7 +914,9 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
return ret;
}
- qcom_geni_i2c_conf(gi2c);
+ ret = gi2c->dev_data->set_rate(&gi2c->se, gi2c->clk_freq_out);
+ if (ret)
+ return ret;
if (gi2c->gpi_mode)
ret = geni_i2c_gpi_xfer(gi2c, msgs, num);
@@ -1041,8 +1049,9 @@ static int geni_i2c_init(struct geni_i2c_dev *gi2c)
return ret;
}
-static int geni_i2c_resources_init(struct geni_i2c_dev *gi2c)
+static int geni_i2c_resources_init(struct geni_se *se)
{
+ struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
int ret;
ret = geni_se_resources_init(&gi2c->se);
@@ -1095,7 +1104,7 @@ static int geni_i2c_probe(struct platform_device *pdev)
spin_lock_init(&gi2c->lock);
platform_set_drvdata(pdev, gi2c);
- ret = geni_i2c_resources_init(gi2c);
+ ret = gi2c->dev_data->resources_init(&gi2c->se);
if (ret)
return ret;
@@ -1154,15 +1163,17 @@ static void geni_i2c_shutdown(struct platform_device *pdev)
static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
{
- int ret;
+ int ret = 0;
struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
disable_irq(gi2c->irq);
- ret = geni_se_resources_deactivate(&gi2c->se);
- if (ret) {
- enable_irq(gi2c->irq);
- return ret;
+ if (gi2c->dev_data->power_off) {
+ ret = gi2c->dev_data->power_off(&gi2c->se);
+ if (ret) {
+ enable_irq(gi2c->irq);
+ return ret;
+ }
}
gi2c->suspended = 1;
@@ -1171,12 +1182,14 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
static int __maybe_unused geni_i2c_runtime_resume(struct device *dev)
{
- int ret;
+ int ret = 0;
struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
- ret = geni_se_resources_activate(&gi2c->se);
- if (ret)
- return ret;
+ if (gi2c->dev_data->power_on) {
+ ret = gi2c->dev_data->power_on(&gi2c->se);
+ if (ret)
+ return ret;
+ }
enable_irq(gi2c->irq);
gi2c->suspended = 0;
@@ -1215,6 +1228,10 @@ static const struct dev_pm_ops geni_i2c_pm_ops = {
static const struct geni_i2c_desc geni_i2c = {
.icc_ddr = "qup-memory",
+ .resources_init = geni_i2c_resources_init,
+ .set_rate = qcom_geni_i2c_conf,
+ .power_on = geni_se_resources_activate,
+ .power_off = geni_se_resources_deactivate,
};
static const struct geni_i2c_desc i2c_master_hub = {
@@ -1222,11 +1239,21 @@ static const struct geni_i2c_desc i2c_master_hub = {
.icc_ddr = NULL,
.no_dma_support = true,
.tx_fifo_depth = 16,
+ .resources_init = geni_i2c_resources_init,
+ .set_rate = qcom_geni_i2c_conf,
+ .power_on = geni_se_resources_activate,
+ .power_off = geni_se_resources_deactivate,
+};
+
+static const struct geni_i2c_desc sa8255p_geni_i2c = {
+ .resources_init = geni_se_domain_attach,
+ .set_rate = geni_se_set_perf_opp,
};
static const struct of_device_id geni_i2c_dt_match[] = {
{ .compatible = "qcom,geni-i2c", .data = &geni_i2c },
{ .compatible = "qcom,geni-i2c-master-hub", .data = &i2c_master_hub },
+ { .compatible = "qcom,sa8255p-geni-i2c", .data = &sa8255p_geni_i2c },
{}
};
MODULE_DEVICE_TABLE(of, geni_i2c_dt_match);
--
2.34.1
On 1/12/26 11:47 AM, Praveen Talari wrote:
> The Qualcomm automotive SA8255p SoC relies on firmware to configure
> platform resources, including clocks, interconnects and TLMM.
> The driver requests resources operations over SCMI using power
> and performance protocols.
>
> The SCMI power protocol enables or disables resources like clocks,
> interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs,
> such as resume/suspend, to control power on/off.
>
> The SCMI performance protocol manages I2C frequency, with each
> frequency rate represented by a performance level. The driver uses
> geni_se_set_perf_opp() API to request the desired frequency rate..
>
> As part of geni_se_set_perf_opp(), the OPP for the requested frequency
> is obtained using dev_pm_opp_find_freq_floor() and the performance
> level is set using dev_pm_opp_set_opp().
>
> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
> ---
[...]
> +static const struct geni_i2c_desc sa8255p_geni_i2c = {
> + .resources_init = geni_se_domain_attach,
> + .set_rate = geni_se_set_perf_opp,
This means, on SCMI devices you won't don't the vote on the POWER
domain (or PERF for that matter) and switch the GPIOs to a _suspend
state - is that by design?
Konrad
Hi Konrad,
On 1/30/2026 6:04 PM, Konrad Dybcio wrote:
>> +static const struct geni_i2c_desc sa8255p_geni_i2c = {
>> + .resources_init = geni_se_domain_attach,
>> + .set_rate = geni_se_set_perf_opp,
> This means, on SCMI devices you won't don't the vote on the POWER
> domain (or PERF for that matter) and switch the GPIOs to a _suspend
> state - is that by design?
With PD_FLAG_DEV_LINK_ON enabled, every pm_runtime_get_sync() or
pm_runtime_put_sync() on the device triggers a corresponding genpd
on/off transition. These transitions are translated into SCMI
power‑domain commands, allowing the firmware (GearVM) to perform the
actual enable/disable sequencing.
Thanks,
Praveen.
On 1/30/26 5:44 PM, Praveen Talari wrote:
> Hi Konrad,
>
> On 1/30/2026 6:04 PM, Konrad Dybcio wrote:
>>> +static const struct geni_i2c_desc sa8255p_geni_i2c = {
>>> + .resources_init = geni_se_domain_attach,
>>> + .set_rate = geni_se_set_perf_opp,
>> This means, on SCMI devices you won't don't the vote on the POWER
>> domain (or PERF for that matter) and switch the GPIOs to a _suspend
>> state - is that by design?
>
> With PD_FLAG_DEV_LINK_ON enabled, every pm_runtime_get_sync() or pm_runtime_put_sync() on the device triggers a corresponding genpd on/off transition. These transitions are translated into SCMI power‑domain commands, allowing the firmware (GearVM) to perform the actual enable/disable sequencing.
Does that handle the >1 pd case too? If so, then all good
Konrad
Hi Konrad,
On 2/2/2026 8:23 PM, Konrad Dybcio wrote:
> On 1/30/26 5:44 PM, Praveen Talari wrote:
>> Hi Konrad,
>>
>> On 1/30/2026 6:04 PM, Konrad Dybcio wrote:
>>>> +static const struct geni_i2c_desc sa8255p_geni_i2c = {
>>>> + .resources_init = geni_se_domain_attach,
>>>> + .set_rate = geni_se_set_perf_opp,
>>> This means, on SCMI devices you won't don't the vote on the POWER
>>> domain (or PERF for that matter) and switch the GPIOs to a _suspend
>>> state - is that by design?
>>
>> With PD_FLAG_DEV_LINK_ON enabled, every pm_runtime_get_sync() or pm_runtime_put_sync() on the device triggers a corresponding genpd on/off transition. These transitions are translated into SCMI power‑domain commands, allowing the firmware (GearVM) to perform the actual enable/disable sequencing.
>
> Does that handle the >1 pd case too? If so, then all good
Yes,
Thanks,
Praveen Talari
>
> Konrad
Acked-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
On 1/12/2026 4:17 PM, Praveen Talari wrote:
> The Qualcomm automotive SA8255p SoC relies on firmware to configure
> platform resources, including clocks, interconnects and TLMM.
> The driver requests resources operations over SCMI using power
> and performance protocols.
>
> The SCMI power protocol enables or disables resources like clocks,
> interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs,
> such as resume/suspend, to control power on/off.
>
> The SCMI performance protocol manages I2C frequency, with each
> frequency rate represented by a performance level. The driver uses
> geni_se_set_perf_opp() API to request the desired frequency rate..
>
> As part of geni_se_set_perf_opp(), the OPP for the requested frequency
> is obtained using dev_pm_opp_find_freq_floor() and the performance
> level is set using dev_pm_opp_set_opp().
>
> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
> ---
> V1->v2:
> From kernel test robot:
> - Initialized ret to "0" in resume/suspend callbacks.
>
> Bjorn:
> - Used seperate APIs for the resouces enable/disable.
> ---
> drivers/i2c/busses/i2c-qcom-geni.c | 53 ++++++++++++++++++++++--------
> 1 file changed, 40 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
> index 1c9356e13b97..72457b98f155 100644
> --- a/drivers/i2c/busses/i2c-qcom-geni.c
> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
> @@ -82,6 +82,10 @@ struct geni_i2c_desc {
> char *icc_ddr;
> bool no_dma_support;
> unsigned int tx_fifo_depth;
> + int (*resources_init)(struct geni_se *se);
> + int (*set_rate)(struct geni_se *se, unsigned long freq);
> + int (*power_on)(struct geni_se *se);
> + int (*power_off)(struct geni_se *se);
> };
>
> #define QCOM_I2C_MIN_NUM_OF_MSGS_MULTI_DESC 2
> @@ -203,8 +207,9 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
> return -EINVAL;
> }
>
> -static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c)
> +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;
>
> @@ -217,6 +222,7 @@ static void qcom_geni_i2c_conf(struct geni_i2c_dev *gi2c)
> val |= itr->t_low_cnt << LOW_COUNTER_SHFT;
> val |= itr->t_cycle_cnt;
> writel_relaxed(val, gi2c->se.base + SE_I2C_SCL_COUNTERS);
> + return 0;
> }
>
> static void geni_i2c_err_misc(struct geni_i2c_dev *gi2c)
> @@ -908,7 +914,9 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
> return ret;
> }
>
> - qcom_geni_i2c_conf(gi2c);
> + ret = gi2c->dev_data->set_rate(&gi2c->se, gi2c->clk_freq_out);
> + if (ret)
> + return ret;
>
> if (gi2c->gpi_mode)
> ret = geni_i2c_gpi_xfer(gi2c, msgs, num);
> @@ -1041,8 +1049,9 @@ static int geni_i2c_init(struct geni_i2c_dev *gi2c)
> return ret;
> }
>
> -static int geni_i2c_resources_init(struct geni_i2c_dev *gi2c)
> +static int geni_i2c_resources_init(struct geni_se *se)
> {
> + struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
> int ret;
>
> ret = geni_se_resources_init(&gi2c->se);
> @@ -1095,7 +1104,7 @@ static int geni_i2c_probe(struct platform_device *pdev)
> spin_lock_init(&gi2c->lock);
> platform_set_drvdata(pdev, gi2c);
>
> - ret = geni_i2c_resources_init(gi2c);
> + ret = gi2c->dev_data->resources_init(&gi2c->se);
> if (ret)
> return ret;
>
> @@ -1154,15 +1163,17 @@ static void geni_i2c_shutdown(struct platform_device *pdev)
>
> static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
> {
> - int ret;
> + int ret = 0;
> struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
>
> disable_irq(gi2c->irq);
>
> - ret = geni_se_resources_deactivate(&gi2c->se);
> - if (ret) {
> - enable_irq(gi2c->irq);
> - return ret;
> + if (gi2c->dev_data->power_off) {
> + ret = gi2c->dev_data->power_off(&gi2c->se);
> + if (ret) {
> + enable_irq(gi2c->irq);
> + return ret;
> + }
> }
>
> gi2c->suspended = 1;
> @@ -1171,12 +1182,14 @@ static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
>
> static int __maybe_unused geni_i2c_runtime_resume(struct device *dev)
> {
> - int ret;
> + int ret = 0;
> struct geni_i2c_dev *gi2c = dev_get_drvdata(dev);
>
> - ret = geni_se_resources_activate(&gi2c->se);
> - if (ret)
> - return ret;
> + if (gi2c->dev_data->power_on) {
> + ret = gi2c->dev_data->power_on(&gi2c->se);
> + if (ret)
> + return ret;
> + }
>
> enable_irq(gi2c->irq);
> gi2c->suspended = 0;
> @@ -1215,6 +1228,10 @@ static const struct dev_pm_ops geni_i2c_pm_ops = {
>
> static const struct geni_i2c_desc geni_i2c = {
> .icc_ddr = "qup-memory",
> + .resources_init = geni_i2c_resources_init,
> + .set_rate = qcom_geni_i2c_conf,
> + .power_on = geni_se_resources_activate,
> + .power_off = geni_se_resources_deactivate,
> };
>
> static const struct geni_i2c_desc i2c_master_hub = {
> @@ -1222,11 +1239,21 @@ static const struct geni_i2c_desc i2c_master_hub = {
> .icc_ddr = NULL,
> .no_dma_support = true,
> .tx_fifo_depth = 16,
> + .resources_init = geni_i2c_resources_init,
> + .set_rate = qcom_geni_i2c_conf,
> + .power_on = geni_se_resources_activate,
> + .power_off = geni_se_resources_deactivate,
> +};
> +
> +static const struct geni_i2c_desc sa8255p_geni_i2c = {
> + .resources_init = geni_se_domain_attach,
> + .set_rate = geni_se_set_perf_opp,
> };
>
> static const struct of_device_id geni_i2c_dt_match[] = {
> { .compatible = "qcom,geni-i2c", .data = &geni_i2c },
> { .compatible = "qcom,geni-i2c-master-hub", .data = &i2c_master_hub },
> + { .compatible = "qcom,sa8255p-geni-i2c", .data = &sa8255p_geni_i2c },
> {}
> };
> MODULE_DEVICE_TABLE(of, geni_i2c_dt_match);
© 2016 - 2026 Red Hat, Inc.