[PATCH] nvmem: rockchip-otp: convert to runtime PM
Sascha Hauer
s.hauer at pengutronix.de
Wed Aug 19 05:33:45 PDT 2026
From: Stefan Kerkmann <s.kerkmann at pengutronix.de>
The driver enables the OTP clocks inside rockchip_otp_read() and drops
them again before returning, so the controller is only alive for the
duration of a nvmem read issued by Linux.
On RK3588 that is not sufficient. Part of the OTP array is readable only
from the secure world, and OP-TEE reads it through this same controller.
Its clocks are in the normal world's CRU and are plain gates, so an SMC
into OP-TEE hits a clock-gated controller unless Linux turns them on
first. OP-TEE cannot do that without a CRU driver of its own, which
would put both worlds on the same gate registers.
Move the clock handling into runtime PM callbacks. A consumer can then
take a DL_FLAG_PM_RUNTIME device link on the OTP and hold a reference
for as long as it needs the controller, without knowing anything about
its clock list. DEFINE_RUNTIME_DEV_PM_OPS() supplies the system sleep
callbacks too, so holding one does not keep the clocks on over suspend.
Enabling the clocks is now the callbacks' job alone, hence the dependency
on PM: with CONFIG_PM=n they never run and a read would go out to a gated
controller. Reads are otherwise unchanged.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Stefan Kerkmann <s.kerkmann at pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
The driver enables the OTP clocks inside rockchip_otp_read() and drops
them again before returning, so the controller is only alive for the
duration of a nvmem read issued by Linux.
On RK3588 that is not sufficient. Part of the OTP array is readable only
from the secure world, and OP-TEE reads it through this same controller.
Its clocks are in the normal world's CRU and are plain gates, so an SMC
into OP-TEE hits a clock-gated controller unless Linux turns them on
first. OP-TEE cannot do that without a CRU driver of its own, which
would put both worlds on the same gate registers.
Move the clock handling into runtime PM callbacks. A consumer can then
take a DL_FLAG_PM_RUNTIME device link on the OTP and hold a reference
for as long as it needs the controller, without knowing anything about
its clock list. DEFINE_RUNTIME_DEV_PM_OPS() supplies the system sleep
callbacks too, so holding one does not keep the clocks on over suspend.
Enabling the clocks is now the callbacks' job alone, hence the dependency
on PM: with CONFIG_PM=n they never run and a read would go out to a gated
controller. Reads are otherwise unchanged.
---
drivers/nvmem/Kconfig | 1 +
drivers/nvmem/rockchip-otp.c | 39 ++++++++++++++++++++++++++++++++++++---
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 74ddbd0f79b0e..3e1a66530fdea 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -337,6 +337,7 @@ config NVMEM_ROCKCHIP_OTP
tristate "Rockchip OTP controller support"
depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on HAS_IOMEM
+ depends on PM
help
This is a simple driver to dump specified values of Rockchip SoC
from OTP, such as cpu-leakage.
diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c
index 0ec78b5e19e7d..bf1ffb46550c2 100644
--- a/drivers/nvmem/rockchip-otp.c
+++ b/drivers/nvmem/rockchip-otp.c
@@ -18,6 +18,7 @@
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
/* OTP Register Offsets */
#define OTPC_SBPI_CTRL 0x0020
@@ -272,9 +273,9 @@ static int rockchip_otp_read(void *context, unsigned int offset,
if (!otp->data || !otp->data->reg_read)
return -EINVAL;
- ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks);
+ ret = pm_runtime_resume_and_get(otp->dev);
if (ret < 0) {
- dev_err(otp->dev, "failed to prepare/enable clks\n");
+ dev_err(otp->dev, "failed to resume OTP: %d\n", ret);
return ret;
}
@@ -306,7 +307,7 @@ static int rockchip_otp_read(void *context, unsigned int offset,
}
err:
- clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks);
+ pm_runtime_put(otp->dev);
return ret;
}
@@ -457,18 +458,50 @@ static int rockchip_otp_probe(struct platform_device *pdev)
otp_config.priv = otp;
otp_config.dev = dev;
+ platform_set_drvdata(pdev, otp);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable runtime PM\n");
+
nvmem = devm_nvmem_register(dev, &otp_config);
if (IS_ERR(nvmem))
return dev_err_probe(dev, PTR_ERR(nvmem),
"failed to register nvmem device\n");
+
+ return 0;
+}
+
+static int rockchip_otp_runtime_suspend(struct device *dev)
+{
+ struct rockchip_otp *otp = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks);
+
return 0;
}
+static int rockchip_otp_runtime_resume(struct device *dev)
+{
+ struct rockchip_otp *otp = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks);
+ if (ret)
+ dev_err(dev, "failed to prepare/enable clks\n");
+
+ return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(rockchip_otp_pm_ops,
+ rockchip_otp_runtime_suspend,
+ rockchip_otp_runtime_resume, NULL);
+
static struct platform_driver rockchip_otp_driver = {
.probe = rockchip_otp_probe,
.driver = {
.name = "rockchip-otp",
.of_match_table = rockchip_otp_match,
+ .pm = pm_ptr(&rockchip_otp_pm_ops),
},
};
---
base-commit: bd5f485f3f026225b86573e559af0b7254ef4184
change-id: 20260819-rockchip-nvmem-pmruntime-82bd0437a216
Best regards,
--
Sascha Hauer <s.hauer at pengutronix.de>
More information about the Linux-rockchip
mailing list