[PATCH 3/3] soc: qcom: ice: Add runtime PM support

From: Kuldeep Singh

Date: Thu Oct 22 2026 - 05:57:38 EST


Ice driver currently enables its clocks during probe and keeps them
prepared/enabled even after probe is complete. On targets where ICE
exists as separate DT node and storage consumers reference it through
"qcom,ice", leaves ICE clock voted while the system is idle.

To mitigate this, add runtime PM callbacks to gate the core and iface
clocks. During probe, enable the clocks to read the ICE version/fuse
state, and then disable clocks and mark runtime PM state as suspended.

For legacy binding, keep clock ownership with the storage consumer
dev as it created the ICE instance.

Signed-off-by: Kuldeep Singh <kuldeep.singh@xxxxxxxxxxxxxxxx>
---
drivers/soc/qcom/ice.c | 139 +++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 116 insertions(+), 23 deletions(-)

diff --git a/drivers/soc/qcom/ice.c b/drivers/soc/qcom/ice.c
index e256595b9826..f9939e2d037c 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_runtime.h>
#include <linux/xarray.h>

#include <linux/firmware/qcom/qcom_scm.h>
@@ -113,6 +114,7 @@ struct qcom_ice {
bool use_hwkm;
bool hwkm_init_complete;
u8 hwkm_version;
+ bool use_pm_runtime;
};

static DEFINE_XARRAY(ice_handles);
@@ -303,42 +305,99 @@ static void qcom_ice_hwkm_init(struct qcom_ice *ice)

int qcom_ice_enable(struct qcom_ice *ice)
{
+ bool pm_acquired = false;
+ int err;
+
+ if (ice->use_pm_runtime && pm_runtime_suspended(ice->dev)) {
+ err = pm_runtime_resume_and_get(ice->dev);
+ if (err) {
+ dev_err(ice->dev, "runtime resume failed: %d\n", err);
+ return err;
+ }
+ pm_acquired = true;
+ }
+
qcom_ice_low_power_mode_enable(ice);
qcom_ice_optimization_enable(ice);
qcom_ice_hwkm_init(ice);
- return qcom_ice_wait_bist_status(ice);
+
+ err = qcom_ice_wait_bist_status(ice);
+ if (err && pm_acquired) {
+ dev_err(ice->dev, "BIST failed, ret: %d\n", err);
+ pm_runtime_put_sync_suspend(ice->dev);
+ }
+
+ return err;
}
EXPORT_SYMBOL_GPL(qcom_ice_enable);

-int qcom_ice_resume(struct qcom_ice *ice)
+static int qcom_ice_enable_clocks(struct qcom_ice *ice)
{
- struct device *dev = ice->dev;
int err;

err = clk_prepare_enable(ice->core_clk);
- if (err) {
- dev_err(dev, "Failed to enable core clock: %d\n", err);
+ if (err)
return err;
- }

err = clk_prepare_enable(ice->iface_clk);
if (err) {
- dev_err(dev, "Failed to enable iface clock: %d\n", err);
+ clk_disable_unprepare(ice->core_clk);
return err;
}
- qcom_ice_hwkm_init(ice);
- return qcom_ice_wait_bist_status(ice);
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(qcom_ice_resume);

-int qcom_ice_suspend(struct qcom_ice *ice)
+static void qcom_ice_disable_clocks(struct qcom_ice *ice)
{
clk_disable_unprepare(ice->iface_clk);
clk_disable_unprepare(ice->core_clk);
+}
+
+static int qcom_ice_do_resume(struct qcom_ice *ice)
+{
+ int err = 0;
+
+ err = qcom_ice_enable_clocks(ice);
+ if (err)
+ return err;
+
+ qcom_ice_hwkm_init(ice);
+
+ err = qcom_ice_wait_bist_status(ice);
+ if (err) {
+ dev_err(ice->dev, "Bist failed, ret: %d\n", err);
+ qcom_ice_disable_clocks(ice);
+ ice->hwkm_init_complete = false;
+ }
+
+ return err;
+}
+
+int qcom_ice_resume(struct qcom_ice *ice)
+{
+ if (ice->use_pm_runtime)
+ return pm_runtime_resume_and_get(ice->dev);
+
+ return qcom_ice_do_resume(ice);
+}
+EXPORT_SYMBOL_GPL(qcom_ice_resume);
+
+static int qcom_ice_do_suspend(struct qcom_ice *ice)
+{
ice->hwkm_init_complete = false;
+ qcom_ice_disable_clocks(ice);

return 0;
}
+
+int qcom_ice_suspend(struct qcom_ice *ice)
+{
+ if (ice->use_pm_runtime)
+ return pm_runtime_put_sync_suspend(ice->dev);
+
+ return qcom_ice_do_suspend(ice);
+}
EXPORT_SYMBOL_GPL(qcom_ice_suspend);

static unsigned int translate_hwkm_slot(struct qcom_ice *ice, unsigned int slot)
@@ -560,10 +619,11 @@ int qcom_ice_import_key(struct qcom_ice *ice,
}
EXPORT_SYMBOL_GPL(qcom_ice_import_key);

-static struct qcom_ice *qcom_ice_create(struct device *dev,
- void __iomem *base)
+static struct qcom_ice *qcom_ice_create(struct device *dev, void __iomem *base,
+ bool use_pm_runtime)
{
struct qcom_ice *engine;
+ int err;

if (!qcom_scm_is_available())
return ERR_PTR(-EPROBE_DEFER);
@@ -579,36 +639,54 @@ static struct qcom_ice *qcom_ice_create(struct device *dev,

engine->dev = dev;
engine->base = base;
+ engine->use_pm_runtime = use_pm_runtime;

/*
* Legacy DT binding uses different clk names for each consumer,
* so lets try those first. If none of those are a match, it means
* the we only have one clock and it is part of the dedicated DT node.
- * Also, enable the clock before we check what HW version the driver
- * supports.
*/
- engine->core_clk = devm_clk_get_optional_enabled(dev, "ice_core_clk");
+ engine->core_clk = devm_clk_get_optional(dev, "ice_core_clk");
if (!engine->core_clk)
- engine->core_clk = devm_clk_get_optional_enabled(dev, "ice");
+ engine->core_clk = devm_clk_get_optional(dev, "ice");
if (!engine->core_clk)
- engine->core_clk = devm_clk_get_optional_enabled(dev, "core");
+ engine->core_clk = devm_clk_get_optional(dev, "core");
if (!engine->core_clk)
- engine->core_clk = devm_clk_get_enabled(dev, NULL);
+ engine->core_clk = devm_clk_get(dev, NULL);
if (IS_ERR(engine->core_clk))
return ERR_CAST(engine->core_clk);

- engine->iface_clk = devm_clk_get_optional_enabled(dev, "iface");
+ engine->iface_clk = devm_clk_get_optional(dev, "iface");
if (IS_ERR(engine->iface_clk))
return ERR_CAST(engine->iface_clk);

- if (!qcom_ice_check_supported(engine))
+ err = qcom_ice_enable_clocks(engine);
+ if (err)
+ return ERR_PTR(err);
+
+ if (!qcom_ice_check_supported(engine)) {
+ qcom_ice_disable_clocks(engine);
return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ if (use_pm_runtime)
+ qcom_ice_disable_clocks(engine);

dev_dbg(dev, "Registered Qualcomm Inline Crypto Engine\n");

return engine;
}

+static int qcom_ice_runtime_resume(struct device *dev)
+{
+ return qcom_ice_do_resume(dev_get_drvdata(dev));
+}
+
+static int qcom_ice_runtime_suspend(struct device *dev)
+{
+ return qcom_ice_do_suspend(dev_get_drvdata(dev));
+}
+
/**
* of_qcom_ice_get() - get an ICE instance from a DT node
* @dev: device pointer for the consumer device
@@ -645,7 +723,7 @@ static struct qcom_ice *of_qcom_ice_get(struct device *dev)
return ERR_CAST(base);

/* create ICE instance using consumer dev */
- return qcom_ice_create(&pdev->dev, base);
+ return qcom_ice_create(&pdev->dev, base, false);
}

guard(mutex)(&ice_mutex);
@@ -738,6 +816,7 @@ static int qcom_ice_probe(struct platform_device *pdev)
unsigned long phandle = pdev->dev.of_node->phandle;
struct qcom_ice *engine;
void __iomem *base;
+ int err;

guard(mutex)(&ice_mutex);

@@ -749,13 +828,21 @@ static int qcom_ice_probe(struct platform_device *pdev)
return PTR_ERR(base);
}

- engine = qcom_ice_create(&pdev->dev, base);
+ engine = qcom_ice_create(&pdev->dev, base, true);
if (IS_ERR(engine)) {
/* Store the error pointer for devm_of_qcom_ice_get() */
xa_store(&ice_handles, phandle, engine, GFP_KERNEL);
return PTR_ERR(engine);
}

+ platform_set_drvdata(pdev, engine);
+ pm_runtime_set_suspended(&pdev->dev);
+
+ err = devm_pm_runtime_enable(&pdev->dev);
+ if (err) {
+ xa_store(&ice_handles, phandle, ERR_PTR(err), GFP_KERNEL);
+ return err;
+ }
xa_store(&ice_handles, phandle, engine, GFP_KERNEL);

return 0;
@@ -775,12 +862,18 @@ static const struct of_device_id qcom_ice_of_match_table[] = {
};
MODULE_DEVICE_TABLE(of, qcom_ice_of_match_table);

+static const struct dev_pm_ops qcom_ice_pm_ops = {
+ RUNTIME_PM_OPS(qcom_ice_runtime_suspend, qcom_ice_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
static struct platform_driver qcom_ice_driver = {
.probe = qcom_ice_probe,
.remove = qcom_ice_remove,
.driver = {
.name = "qcom-ice",
.of_match_table = qcom_ice_of_match_table,
+ .pm = pm_ptr(&qcom_ice_pm_ops),
},
};


--
2.34.1