Register optional operation-points-v2 table for ICE device
and aquire its minimum and maximum frequency during ICE
device probe.
Introduce clock scaling API qcom_ice_scale_clk which scale ICE
core clock based on the target frequency provided and if a valid
OPP-table is registered. Use flags (if provided) to decide on
the rounding of the clock freq against OPP-table. Incase no flags
are provided use default behaviour (CEIL incase of scale_up and FLOOR
incase of ~scale_up). Disable clock scaling if OPP-table is not
registered.
When an ICE-device specific OPP table is available, use the PM OPP
framework to manage frequency scaling and maintain proper power-domain
constraints.
Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
drivers/soc/qcom/ice.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++
include/soc/qcom/ice.h | 5 +++
2 files changed, 112 insertions(+)
diff --git a/drivers/soc/qcom/ice.c b/drivers/soc/qcom/ice.c
index b203bc685cadd21d6f96eb1799963a13db4b2b72..90106186c15e644527fdf75a186a2e8adeb299a3 100644
--- a/drivers/soc/qcom/ice.c
+++ b/drivers/soc/qcom/ice.c
@@ -16,6 +16,7 @@
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
#include <linux/firmware/qcom/qcom_scm.h>
@@ -111,6 +112,9 @@ struct qcom_ice {
bool use_hwkm;
bool hwkm_init_complete;
u8 hwkm_version;
+ unsigned long max_freq;
+ unsigned long min_freq;
+ bool has_opp;
};
static bool qcom_ice_check_supported(struct qcom_ice *ice)
@@ -549,10 +553,73 @@ int qcom_ice_import_key(struct qcom_ice *ice,
}
EXPORT_SYMBOL_GPL(qcom_ice_import_key);
+/**
+ * qcom_ice_scale_clk() - Scale ICE clock for DVFS-aware operations
+ * @ice: ICE driver data
+ * @target_freq: requested frequency in Hz
+ * @scale_up: If @flags is 0, choose ceil (true) or floor (false)
+ * @flags: Rounding policy (ICE_CLOCK_ROUND_*); overrides @scale_up
+ *
+ * Clamps @target_freq to the OPP range (min/max), selects an OPP per rounding
+ * policy, then applies it via dev_pm_opp_set_rate() (including voltage/PD
+ * changes).
+ *
+ * Return: 0 on success; -EOPNOTSUPP if no OPP table; or error from
+ * dev_pm_opp_set_rate()/OPP lookup.
+ */
+int qcom_ice_scale_clk(struct qcom_ice *ice, unsigned long target_freq,
+ bool scale_up, unsigned int flags)
+{
+ unsigned long ice_freq = target_freq;
+ struct dev_pm_opp *opp;
+
+ if (!ice->has_opp)
+ return -EOPNOTSUPP;
+
+ /* Clamp the freq to max if target_freq is beyond supported frequencies */
+ if (ice->max_freq && target_freq >= ice->max_freq) {
+ ice_freq = ice->max_freq;
+ goto scale_clock;
+ }
+
+ /* Clamp the freq to min if target_freq is below supported frequencies */
+ if (ice->min_freq && target_freq <= ice->min_freq) {
+ ice_freq = ice->min_freq;
+ goto scale_clock;
+ }
+
+ switch (flags) {
+ case ICE_CLOCK_ROUND_CEIL:
+ opp = dev_pm_opp_find_freq_ceil_indexed(ice->dev, &ice_freq, 0);
+ break;
+ case ICE_CLOCK_ROUND_FLOOR:
+ opp = dev_pm_opp_find_freq_floor_indexed(ice->dev, &ice_freq, 0);
+ break;
+ default:
+ if (scale_up)
+ opp = dev_pm_opp_find_freq_ceil_indexed(ice->dev, &ice_freq, 0);
+ else
+ opp = dev_pm_opp_find_freq_floor_indexed(ice->dev, &ice_freq, 0);
+ break;
+ }
+
+ if (IS_ERR(opp))
+ return -EINVAL;
+ dev_pm_opp_put(opp);
+
+scale_clock:
+
+ return dev_pm_opp_set_rate(ice->dev, ice_freq);
+}
+EXPORT_SYMBOL_GPL(qcom_ice_scale_clk);
+
static struct qcom_ice *qcom_ice_create(struct device *dev,
void __iomem *base)
{
struct qcom_ice *engine;
+ struct dev_pm_opp *opp;
+ int err;
+ unsigned long rate;
if (!qcom_scm_is_available())
return ERR_PTR(-EPROBE_DEFER);
@@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev,
if (IS_ERR(engine->core_clk))
return ERR_CAST(engine->core_clk);
+ /* Register the OPP table only when ICE is described as a standalone
+ * device node. Older platforms place ICE inside the storage controller
+ * node, so they don't need an OPP table here, as they are handled in
+ * storage controller.
+ */
+ if (of_device_is_compatible(dev->of_node, "qcom,inline-crypto-engine")) {
+ /* OPP table is optional */
+ err = devm_pm_opp_of_add_table(dev);
+ if (err && err != -ENODEV) {
+ dev_err(dev, "Invalid OPP table in Device tree\n");
+ return ERR_PTR(err);
+ }
+ engine->has_opp = (err == 0);
+
+ if (!engine->has_opp)
+ dev_info(dev, "ICE OPP table is not registered\n");
+ }
+
+ if (engine->has_opp) {
+ /* Find the ICE core clock min frequency */
+ rate = 0;
+ opp = dev_pm_opp_find_freq_ceil_indexed(dev, &rate, 0);
+ if (IS_ERR(opp)) {
+ dev_warn(dev, "Unable to find ICE core clock min freq\n");
+ } else {
+ engine->min_freq = rate;
+ dev_pm_opp_put(opp);
+ }
+
+ /* Find the ICE core clock max frequency */
+ rate = ULONG_MAX;
+ opp = dev_pm_opp_find_freq_floor_indexed(dev, &rate, 0);
+ if (IS_ERR(opp)) {
+ dev_warn(dev, "Unable to find ICE core clock max freq\n");
+ } else {
+ engine->max_freq = rate;
+ dev_pm_opp_put(opp);
+ }
+ }
+
if (!qcom_ice_check_supported(engine))
return ERR_PTR(-EOPNOTSUPP);
diff --git a/include/soc/qcom/ice.h b/include/soc/qcom/ice.h
index 4bee553f0a59d86ec6ce20f7c7b4bce28a706415..055edf3a704ff25a608a880cf9be35363f8a02d3 100644
--- a/include/soc/qcom/ice.h
+++ b/include/soc/qcom/ice.h
@@ -9,6 +9,9 @@
#include <linux/blk-crypto.h>
#include <linux/types.h>
+#define ICE_CLOCK_ROUND_CEIL BIT(1)
+#define ICE_CLOCK_ROUND_FLOOR BIT(2)
+
struct qcom_ice;
int qcom_ice_enable(struct qcom_ice *ice);
@@ -30,5 +33,7 @@ int qcom_ice_import_key(struct qcom_ice *ice,
const u8 *raw_key, size_t raw_key_size,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
struct qcom_ice *devm_of_qcom_ice_get(struct device *dev);
+int qcom_ice_scale_clk(struct qcom_ice *ice, unsigned long target_freq,
+ bool scale_up, unsigned int flags);
#endif /* __QCOM_ICE_H__ */
--
2.34.1
On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote:
> struct qcom_ice *engine;
> + struct dev_pm_opp *opp;
> + int err;
> + unsigned long rate;
>
> if (!qcom_scm_is_available())
> return ERR_PTR(-EPROBE_DEFER);
> @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev,
> if (IS_ERR(engine->core_clk))
> return ERR_CAST(engine->core_clk);
>
> + /* Register the OPP table only when ICE is described as a standalone
This is not netdev...
> + * device node. Older platforms place ICE inside the storage controller
> + * node, so they don't need an OPP table here, as they are handled in
> + * storage controller.
> + */
> + if (of_device_is_compatible(dev->of_node, "qcom,inline-crypto-engine")) {
Just add additional argument to qcom_ice_create().
> + /* OPP table is optional */
> + err = devm_pm_opp_of_add_table(dev);
> + if (err && err != -ENODEV) {
> + dev_err(dev, "Invalid OPP table in Device tree\n");
> + return ERR_PTR(err);
> + }
> + engine->has_opp = (err == 0);
> +
> + if (!engine->has_opp)
> + dev_info(dev, "ICE OPP table is not registered\n");
> + }
Best regards,
Krzysztof
On Wed, Jan 28, 2026 at 12:04:26PM +0100, Krzysztof Kozlowski wrote:
> On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote:
> > struct qcom_ice *engine;
> > + struct dev_pm_opp *opp;
> > + int err;
> > + unsigned long rate;
> >
> > if (!qcom_scm_is_available())
> > return ERR_PTR(-EPROBE_DEFER);
> > @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev,
> > if (IS_ERR(engine->core_clk))
> > return ERR_CAST(engine->core_clk);
> >
> > + /* Register the OPP table only when ICE is described as a standalone
>
> This is not netdev...
Okay, if I understand it correct, its not conventional to use of_device_is_compatible
outside netdev subsystem. Will update as mentioned below.
>
> > + * device node. Older platforms place ICE inside the storage controller
> > + * node, so they don't need an OPP table here, as they are handled in
> > + * storage controller.
> > + */
> > + if (of_device_is_compatible(dev->of_node, "qcom,inline-crypto-engine")) {
>
> Just add additional argument to qcom_ice_create().
Sure, that makes more sense.
Will update in the next patchset.
On 02/02/2026 07:32, Abhinaba Rakshit wrote: > On Wed, Jan 28, 2026 at 12:04:26PM +0100, Krzysztof Kozlowski wrote: >> On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote: >>> struct qcom_ice *engine; >>> + struct dev_pm_opp *opp; >>> + int err; >>> + unsigned long rate; >>> >>> if (!qcom_scm_is_available()) >>> return ERR_PTR(-EPROBE_DEFER); >>> @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, >>> if (IS_ERR(engine->core_clk)) >>> return ERR_CAST(engine->core_clk); >>> >>> + /* Register the OPP table only when ICE is described as a standalone >> >> This is not netdev... > > Okay, if I understand it correct, its not conventional to use of_device_is_compatible > outside netdev subsystem. Will update as mentioned below. No, please read entire coding style, although the comment was about comment style. Best regards, Krzysztof
On Thu, Feb 05, 2026 at 12:26:25PM +0100, Krzysztof Kozlowski wrote: > On 02/02/2026 07:32, Abhinaba Rakshit wrote: > > On Wed, Jan 28, 2026 at 12:04:26PM +0100, Krzysztof Kozlowski wrote: > >> On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote: > >>> struct qcom_ice *engine; > >>> + struct dev_pm_opp *opp; > >>> + int err; > >>> + unsigned long rate; > >>> > >>> if (!qcom_scm_is_available()) > >>> return ERR_PTR(-EPROBE_DEFER); > >>> @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, > >>> if (IS_ERR(engine->core_clk)) > >>> return ERR_CAST(engine->core_clk); > >>> > >>> + /* Register the OPP table only when ICE is described as a standalone > >> > >> This is not netdev... > > > > Okay, if I understand it correct, its not conventional to use of_device_is_compatible > > outside netdev subsystem. Will update as mentioned below. > > No, please read entire coding style, although the comment was about > comment style. Sure, will ensure to use the correct comment styles. Abhinaba Rakshit
On 2/2/26 7:32 AM, Abhinaba Rakshit wrote: > On Wed, Jan 28, 2026 at 12:04:26PM +0100, Krzysztof Kozlowski wrote: >> On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote: >>> struct qcom_ice *engine; >>> + struct dev_pm_opp *opp; >>> + int err; >>> + unsigned long rate; >>> >>> if (!qcom_scm_is_available()) >>> return ERR_PTR(-EPROBE_DEFER); >>> @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, >>> if (IS_ERR(engine->core_clk)) >>> return ERR_CAST(engine->core_clk); >>> >>> + /* Register the OPP table only when ICE is described as a standalone >> >> This is not netdev... > > Okay, if I understand it correct, its not conventional to use of_device_is_compatible > outside netdev subsystem. Will update as mentioned below. In Linux /* * This style of comments is generally preferred unless /* You're contributing to netdev for weird legacy reasons * that nobody seems to understand Konrad
On Mon, Feb 02, 2026 at 10:23:43AM +0100, Konrad Dybcio wrote: > On 2/2/26 7:32 AM, Abhinaba Rakshit wrote: > > On Wed, Jan 28, 2026 at 12:04:26PM +0100, Krzysztof Kozlowski wrote: > >> On Wed, Jan 28, 2026 at 02:16:41PM +0530, Abhinaba Rakshit wrote: > >>> struct qcom_ice *engine; > >>> + struct dev_pm_opp *opp; > >>> + int err; > >>> + unsigned long rate; > >>> > >>> if (!qcom_scm_is_available()) > >>> return ERR_PTR(-EPROBE_DEFER); > >>> @@ -584,6 +651,46 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, > >>> if (IS_ERR(engine->core_clk)) > >>> return ERR_CAST(engine->core_clk); > >>> > >>> + /* Register the OPP table only when ICE is described as a standalone > >> > >> This is not netdev... > > > > Okay, if I understand it correct, its not conventional to use of_device_is_compatible > > outside netdev subsystem. Will update as mentioned below. > > In Linux > > /* > * This style of comments is generally preferred > > unless > > /* You're contributing to netdev for weird legacy reasons > * that nobody seems to understand I see. Thanks for correcting me. Will keep this in mind while submitting subsequent patches.
© 2016 - 2026 Red Hat, Inc.