[PATCH v4 2/4] soc: qcom: ice: Add OPP-based clock scaling support for ICE
From: Abhinaba Rakshit <hidden>
Date: 2026-01-28 08:47:00
Also in:
linux-arm-msm, linux-crypto, linux-scsi, lkml
Subsystem:
arm/qualcomm mailing list, arm/qualcomm support, the rest · Maintainers:
Bjorn Andersson, Konrad Dybcio, Linus Torvalds
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 <redacted> --- 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