The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently
manage performance levels and operating points directly. This resulting
in code duplication across drivers. such as configuring a specific level
or find and apply an OPP based on a clock frequency.
Introduce two new helper APIs, geni_se_set_perf_level() and
geni_se_set_perf_opp(), addresses this issue by providing a streamlined
method for the GENI Serial Engine (SE) drivers to find and set the OPP
based on the desired performance level, thereby eliminating redundancy.
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
drivers/soc/qcom/qcom-geni-se.c | 50 ++++++++++++++++++++++++++++++++
include/linux/soc/qcom/geni-se.h | 4 +++
2 files changed, 54 insertions(+)
diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c
index b8e5066d4881..dc5f5bb52915 100644
--- a/drivers/soc/qcom/qcom-geni-se.c
+++ b/drivers/soc/qcom/qcom-geni-se.c
@@ -282,6 +282,12 @@ struct se_fw_hdr {
#define geni_setbits32(_addr, _v) writel(readl(_addr) | (_v), _addr)
#define geni_clrbits32(_addr, _v) writel(readl(_addr) & ~(_v), _addr)
+enum domain_idx {
+ DOMAIN_IDX_POWER,
+ DOMAIN_IDX_PERF,
+ DOMAIN_IDX_MAX
+};
+
/**
* geni_se_get_qup_hw_version() - Read the QUP wrapper Hardware version
* @se: Pointer to the corresponding serial engine.
@@ -1093,6 +1099,50 @@ int geni_se_resources_activate(struct geni_se *se)
}
EXPORT_SYMBOL_GPL(geni_se_resources_activate);
+/**
+ * geni_se_set_perf_level() - Set performance level for GENI SE.
+ * @se: Pointer to the struct geni_se instance.
+ * @level: The desired performance level.
+ *
+ * Sets the performance level by directly calling dev_pm_opp_set_level
+ * on the performance device associated with the SE.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
+{
+ return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
+}
+EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
+
+/**
+ * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
+ * @se: Pointer to the struct geni_se instance.
+ * @clk_freq: The requested clock frequency.
+ *
+ * Finds the nearest operating performance point (OPP) for the given
+ * clock frequency and applies it to the SE's performance device.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
+{
+ struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
+ struct dev_pm_opp *opp;
+ int ret;
+
+ opp = dev_pm_opp_find_freq_floor(perf_dev, &clk_freq);
+ if (IS_ERR(opp)) {
+ dev_err(se->dev, "failed to find opp for freq %lu\n", clk_freq);
+ return PTR_ERR(opp);
+ }
+
+ ret = dev_pm_opp_set_opp(perf_dev, opp);
+ dev_pm_opp_put(opp);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(geni_se_set_perf_opp);
+
/**
* geni_se_domain_attach() - Attach power domains to a GENI SE device.
* @se: Pointer to the geni_se structure representing the GENI SE device.
diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h
index 5f75159c5531..c5e6ab85df09 100644
--- a/include/linux/soc/qcom/geni-se.h
+++ b/include/linux/soc/qcom/geni-se.h
@@ -550,5 +550,9 @@ int geni_se_resources_deactivate(struct geni_se *se);
int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol);
int geni_se_domain_attach(struct geni_se *se);
+
+int geni_se_set_perf_level(struct geni_se *se, unsigned long level);
+
+int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq);
#endif
#endif
--
2.34.1
On 1/12/26 11:47 AM, Praveen Talari wrote:
> The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently
> manage performance levels and operating points directly. This resulting
> in code duplication across drivers. such as configuring a specific level
> or find and apply an OPP based on a clock frequency.
>
> Introduce two new helper APIs, geni_se_set_perf_level() and
> geni_se_set_perf_opp(), addresses this issue by providing a streamlined
> method for the GENI Serial Engine (SE) drivers to find and set the OPP
> based on the desired performance level, thereby eliminating redundancy.
>
> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
> ---
[...]
> +/**
> + * geni_se_set_perf_level() - Set performance level for GENI SE.
> + * @se: Pointer to the struct geni_se instance.
> + * @level: The desired performance level.
> + *
> + * Sets the performance level by directly calling dev_pm_opp_set_level
> + * on the performance device associated with the SE.
> + *
> + * Return: 0 on success, or a negative error code on failure.
> + */
> +int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
> +{
> + return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
> +}
> +EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
This function is never used
> +
> +/**
> + * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
> + * @se: Pointer to the struct geni_se instance.
> + * @clk_freq: The requested clock frequency.
> + *
> + * Finds the nearest operating performance point (OPP) for the given
> + * clock frequency and applies it to the SE's performance device.
> + *
> + * Return: 0 on success, or a negative error code on failure.
> + */
> +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
I think with the SPI driver in mind (which seems to do a simple rateset
for both backends) we could do:
> +{
> + struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
Then, we can do struct device * perf_dev = se->dev;
if (se->pd_list && se->pd_list->pd_devs[DOMAIN_IDX_PERF])
perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
and reuse it in both cases, completely transparently to the caller
Konrad
Hi Konrad
On 1/30/2026 5:53 PM, Konrad Dybcio wrote:
> On 1/12/26 11:47 AM, Praveen Talari wrote:
>> The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently
>> manage performance levels and operating points directly. This resulting
>> in code duplication across drivers. such as configuring a specific level
>> or find and apply an OPP based on a clock frequency.
>>
>> Introduce two new helper APIs, geni_se_set_perf_level() and
>> geni_se_set_perf_opp(), addresses this issue by providing a streamlined
>> method for the GENI Serial Engine (SE) drivers to find and set the OPP
>> based on the desired performance level, thereby eliminating redundancy.
>>
>> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
>> ---
>
> [...]
>
>> +/**
>> + * geni_se_set_perf_level() - Set performance level for GENI SE.
>> + * @se: Pointer to the struct geni_se instance.
>> + * @level: The desired performance level.
>> + *
>> + * Sets the performance level by directly calling dev_pm_opp_set_level
>> + * on the performance device associated with the SE.
>> + *
>> + * Return: 0 on success, or a negative error code on failure.
>> + */
>> +int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
>> +{
>> + return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
>> +}
>> +EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
>
> This function is never used
it will be used by UART driver, not for I2C/SPI.
>
>> +
>> +/**
>> + * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
>> + * @se: Pointer to the struct geni_se instance.
>> + * @clk_freq: The requested clock frequency.
>> + *
>> + * Finds the nearest operating performance point (OPP) for the given
>> + * clock frequency and applies it to the SE's performance device.
>> + *
>> + * Return: 0 on success, or a negative error code on failure.
>> + */
>> +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
>
> I think with the SPI driver in mind (which seems to do a simple rateset
APIs were added as generic interfaces shared across I²C/SPI which is
specific to firmware control, not Linux control.
> for both backends) we could do:
>
>> +{
>> + struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
>
> Then, we can do struct device * perf_dev = se->dev;
I don't think, it is needed since this is specific to firmware control,
not Linux control.
Thanks,
Praveen Talari
>
> if (se->pd_list && se->pd_list->pd_devs[DOMAIN_IDX_PERF])
> perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
>
> and reuse it in both cases, completely transparently to the caller
>
> Konrad
On 1/30/26 5:54 PM, Praveen Talari wrote:
> Hi Konrad
>
> On 1/30/2026 5:53 PM, Konrad Dybcio wrote:
>> On 1/12/26 11:47 AM, Praveen Talari wrote:
>>> The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently
>>> manage performance levels and operating points directly. This resulting
>>> in code duplication across drivers. such as configuring a specific level
>>> or find and apply an OPP based on a clock frequency.
>>>
>>> Introduce two new helper APIs, geni_se_set_perf_level() and
>>> geni_se_set_perf_opp(), addresses this issue by providing a streamlined
>>> method for the GENI Serial Engine (SE) drivers to find and set the OPP
>>> based on the desired performance level, thereby eliminating redundancy.
>>>
>>> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
>>> ---
>>
>> [...]
>>
>>> +/**
>>> + * geni_se_set_perf_level() - Set performance level for GENI SE.
>>> + * @se: Pointer to the struct geni_se instance.
>>> + * @level: The desired performance level.
>>> + *
>>> + * Sets the performance level by directly calling dev_pm_opp_set_level
>>> + * on the performance device associated with the SE.
>>> + *
>>> + * Return: 0 on success, or a negative error code on failure.
>>> + */
>>> +int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
>>> +{
>>> + return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
>>> +}
>>> +EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
>>
>> This function is never used
>
> it will be used by UART driver, not for I2C/SPI.
Adding unused exported symbols is "eeeh"..
>>
>>> +
>>> +/**
>>> + * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
>>> + * @se: Pointer to the struct geni_se instance.
>>> + * @clk_freq: The requested clock frequency.
>>> + *
>>> + * Finds the nearest operating performance point (OPP) for the given
>>> + * clock frequency and applies it to the SE's performance device.
>>> + *
>>> + * Return: 0 on success, or a negative error code on failure.
>>> + */
>>> +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
>>
>> I think with the SPI driver in mind (which seems to do a simple rateset
>
> APIs were added as generic interfaces shared across I²C/SPI which is specific to firmware control, not Linux control.
>
>> for both backends) we could do:
>>
>>> +{
>>> + struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
>>
>> Then, we can do struct device * perf_dev = se->dev;
> I don't think, it is needed since this is specific to firmware control, not Linux control.
My point is that it doesn't have to be specific to the auto usecase,
further commonizing the code..
Konrad
Hi Konrad,
On 2/3/2026 4:44 PM, Konrad Dybcio wrote:
> On 1/30/26 5:54 PM, Praveen Talari wrote:
>> Hi Konrad
>>
>> On 1/30/2026 5:53 PM, Konrad Dybcio wrote:
>>> On 1/12/26 11:47 AM, Praveen Talari wrote:
>>>> The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently
>>>> manage performance levels and operating points directly. This resulting
>>>> in code duplication across drivers. such as configuring a specific level
>>>> or find and apply an OPP based on a clock frequency.
>>>>
>>>> Introduce two new helper APIs, geni_se_set_perf_level() and
>>>> geni_se_set_perf_opp(), addresses this issue by providing a streamlined
>>>> method for the GENI Serial Engine (SE) drivers to find and set the OPP
>>>> based on the desired performance level, thereby eliminating redundancy.
>>>>
>>>> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
>>>> ---
>>>
>>> [...]
>>>
>>>> +/**
>>>> + * geni_se_set_perf_level() - Set performance level for GENI SE.
>>>> + * @se: Pointer to the struct geni_se instance.
>>>> + * @level: The desired performance level.
>>>> + *
>>>> + * Sets the performance level by directly calling dev_pm_opp_set_level
>>>> + * on the performance device associated with the SE.
>>>> + *
>>>> + * Return: 0 on success, or a negative error code on failure.
>>>> + */
>>>> +int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
>>>> +{
>>>> + return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
>>>
>>> This function is never used
>>
>> it will be used by UART driver, not for I2C/SPI.
>
> Adding unused exported symbols is "eeeh"..
I keep in mind for UART, i have added this API.
>
>>>
>>>> +
>>>> +/**
>>>> + * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
>>>> + * @se: Pointer to the struct geni_se instance.
>>>> + * @clk_freq: The requested clock frequency.
>>>> + *
>>>> + * Finds the nearest operating performance point (OPP) for the given
>>>> + * clock frequency and applies it to the SE's performance device.
>>>> + *
>>>> + * Return: 0 on success, or a negative error code on failure.
>>>> + */
>>>> +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
>>>
>>> I think with the SPI driver in mind (which seems to do a simple rateset
>>
>> APIs were added as generic interfaces shared across I²C/SPI which is specific to firmware control, not Linux control.
>>
>>> for both backends) we could do:
>>>
>>>> +{
>>>> + struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
>>>
>>> Then, we can do struct device * perf_dev = se->dev;
>> I don't think, it is needed since this is specific to firmware control, not Linux control.
>
> My point is that it doesn't have to be specific to the auto usecase,
> further commonizing the code.
This API will not useful for non-auto cases as well.
Lets talk on on V4 version patch-set.
Thanks,
Praveen Talari
>
> Konrad
© 2016 - 2026 Red Hat, Inc.