* [PATCH 0/2] nvmem: rockchip-efuse: add RK3399 OTP write support
@ 2026-07-15 11:01 Hrushiraj Gandhi
2026-07-15 11:01 ` [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property Hrushiraj Gandhi
2026-07-15 11:01 ` [PATCH 2/2] nvmem: rockchip-efuse: add write support for RK3399 Hrushiraj Gandhi
0 siblings, 2 replies; 4+ messages in thread
From: Hrushiraj Gandhi @ 2026-07-15 11:01 UTC (permalink / raw)
To: srini
Cc: robh, krzk+dt, conor+dt, heiko, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Hrushiraj Gandhi
This series adds write (OTP programming) support for the RK3399 eFuse
controller, gated behind an explicit devicetree opt-in property.
eFuse bits are one-time-programmable (OTP): once set, they cannot be
cleared. Enabling write access unconditionally for any board using the
rockchip,rk3399-efuse compatible would be a significant safety risk.
This series therefore:
1. Adds a new boolean DT property, rockchip,efuse-write-enable, which
must be explicitly declared in the board's device tree to enable
OTP programming from userspace.
2. Implements rockchip_rk3399_efuse_write() using the Array Program
Mode (A_PGM) as described in the RK3399 TRM section 21. Each bit
is programmed individually using a software-controlled STROBE
pulse of 15 us. The SoC-specific callbacks are managed via a new
struct rockchip_efuse_soc_data, and nvmem_config is moved to the
probe stack so read_only can be set per-device.
Hardware precondition: The VQPS programming supply (1.8V to 1.98V per
RK3399 TRM) must be present and correctly sequenced during writes. The
driver assumes the board power design guarantees this when the DT
property is present. Future work may add an optional regulator reference
to enforce this in software.
Tested on RK3399 board with rockchip,efuse-write-enable set
in the device tree. Read-back after programming confirmed correct bit
patterns.
Hrushiraj Gandhi (2):
dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable
property
nvmem: rockchip-efuse: add write support for RK3399
.../bindings/nvmem/rockchip-efuse.yaml | 11 ++
drivers/nvmem/rockchip-efuse.c | 152 +++++++++++++++---
2 files changed, 138 insertions(+), 25 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property 2026-07-15 11:01 [PATCH 0/2] nvmem: rockchip-efuse: add RK3399 OTP write support Hrushiraj Gandhi @ 2026-07-15 11:01 ` Hrushiraj Gandhi 2026-07-15 11:42 ` Heiko Stübner 2026-07-15 11:01 ` [PATCH 2/2] nvmem: rockchip-efuse: add write support for RK3399 Hrushiraj Gandhi 1 sibling, 1 reply; 4+ messages in thread From: Hrushiraj Gandhi @ 2026-07-15 11:01 UTC (permalink / raw) To: srini Cc: robh, krzk+dt, conor+dt, heiko, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, Hrushiraj Gandhi Add an optional boolean property to explicitly opt in to write (OTP programming) support. eFuse bits are one-time-programmable and permanently set once written; write support must therefore not be enabled by default on arbitrary boards. Boards that intend to use software-initiated eFuse programming (e.g. factory key provisioning) must declare this property and must ensure the required VQPS programming supply (1.8V to 1.98V per RK3399 TRM) is present and correctly sequenced during writes. Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com> --- .../devicetree/bindings/nvmem/rockchip-efuse.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml index b80fd8d1ae5b..8a7195245c84 100644 --- a/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml +++ b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml @@ -46,6 +46,17 @@ properties: this property is defined. $ref: /schemas/types.yaml#/definitions/uint32 + rockchip,efuse-write-enable: + type: boolean + description: + Enable write (programming) support for this eFuse block. eFuse bits + are one-time-programmable; setting a bit is permanent and cannot be + undone. This property must only be set on boards where irreversible + OTP programming from software is an intended use case (e.g. factory + provisioning), and where the required VQPS programming voltage + (1.8V to 1.98V per RK3399 TRM) is guaranteed to be present and + correctly sequenced by the board's power design during writes. + required: - compatible - reg -- 2.47.3 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property 2026-07-15 11:01 ` [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property Hrushiraj Gandhi @ 2026-07-15 11:42 ` Heiko Stübner 0 siblings, 0 replies; 4+ messages in thread From: Heiko Stübner @ 2026-07-15 11:42 UTC (permalink / raw) To: srini, Hrushiraj Gandhi Cc: robh, krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, Hrushiraj Gandhi Am Mittwoch, 15. Juli 2026, 13:01:06 Mitteleuropäische Sommerzeit schrieb Hrushiraj Gandhi: > Add an optional boolean property to explicitly opt in to write (OTP > programming) support. eFuse bits are one-time-programmable and > permanently set once written; write support must therefore not be > enabled by default on arbitrary boards. > > Boards that intend to use software-initiated eFuse programming (e.g. > factory key provisioning) must declare this property and must ensure > the required VQPS programming supply (1.8V to 1.98V per RK3399 TRM) > is present and correctly sequenced during writes. > > Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com> > --- > .../devicetree/bindings/nvmem/rockchip-efuse.yaml | 11 +++++++++++ > 1 file changed, 11 insertions(+) > > diff --git a/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml > index b80fd8d1ae5b..8a7195245c84 100644 > --- a/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml > +++ b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.yaml > @@ -46,6 +46,17 @@ properties: > this property is defined. > $ref: /schemas/types.yaml#/definitions/uint32 > > + rockchip,efuse-write-enable: > + type: boolean > + description: > + Enable write (programming) support for this eFuse block. eFuse bits > + are one-time-programmable; setting a bit is permanent and cannot be > + undone. This property must only be set on boards where irreversible > + OTP programming from software is an intended use case (e.g. factory > + provisioning), and where the required VQPS programming voltage > + (1.8V to 1.98V per RK3399 TRM) is guaranteed to be present and > + correctly sequenced by the board's power design during writes. Devicetree is not a configuration space, and I think this really does count as configuration - as the efuse will be writeable on every board. You mention the VQPS voltage. If I'm reading schematics and application notes correctly, this is a separate input used solely for writing efuses and _needs_ to be 0V (off?) during reads. You mention "needs to be present and correctly sequenced", who is supposed to turn on/off that regulator? So you very likely need to define that regulator and can even use its absence as an indicator to disable writes. Heiko ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] nvmem: rockchip-efuse: add write support for RK3399 2026-07-15 11:01 [PATCH 0/2] nvmem: rockchip-efuse: add RK3399 OTP write support Hrushiraj Gandhi 2026-07-15 11:01 ` [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property Hrushiraj Gandhi @ 2026-07-15 11:01 ` Hrushiraj Gandhi 1 sibling, 0 replies; 4+ messages in thread From: Hrushiraj Gandhi @ 2026-07-15 11:01 UTC (permalink / raw) To: srini Cc: robh, krzk+dt, conor+dt, heiko, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, Hrushiraj Gandhi Implement rockchip_rk3399_efuse_write() using the Array Program Mode (A_PGM) as described in the RK3399 TRM. Each bit is programmed individually by asserting STROBE for 15us with the target bit address set in the EFUSE_CTRL register. Introduce struct rockchip_efuse_soc_data to hold both reg_read and reg_write callbacks per SoC, replacing the bare function pointer previously stored in of_device_id.data. Move nvmem_config to the probe stack so read_only can be set per-device based on whether a write callback is provided. Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com> --- drivers/nvmem/rockchip-efuse.c | 152 +++++++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 25 deletions(-) diff --git a/drivers/nvmem/rockchip-efuse.c b/drivers/nvmem/rockchip-efuse.c index 013e67136f3b..19eb61380ea4 100644 --- a/drivers/nvmem/rockchip-efuse.c +++ b/drivers/nvmem/rockchip-efuse.c @@ -37,12 +37,16 @@ #define RK3399_NBYTES 4 #define RK3399_STROBSFTSEL BIT(9) #define RK3399_RSB BIT(7) +#define RK3399_PS BIT(4) #define RK3399_PD BIT(5) #define RK3399_PGENB BIT(3) #define RK3399_LOAD BIT(2) #define RK3399_STROBE BIT(1) #define RK3399_CSB BIT(0) +#define RK3399_PGM_TIME_US 15 + #define REG_EFUSE_CTRL 0x0000 #define REG_EFUSE_DOUT 0x0004 @@ -52,6 +56,16 @@ struct rockchip_efuse_chip { struct clk *clk; }; +/** + * struct rockchip_efuse_soc_data - per-SoC efuse callbacks + * @reg_read: mandatory read callback + * @reg_write: optional write callback; NULL means read-only + */ +struct rockchip_efuse_soc_data { + int (*reg_read)(void *ctx, unsigned int offset, void *val, size_t bytes); + int (*reg_write)(void *ctx, unsigned int offset, void *val, size_t bytes); +}; + static int rockchip_rk3288_efuse_read(void *context, unsigned int offset, void *val, size_t bytes) { @@ -177,6 +191,7 @@ static int rockchip_rk3399_efuse_read(void *context, unsigned int offset, writel(RK3399_LOAD | RK3399_PGENB | RK3399_STROBSFTSEL | RK3399_RSB, efuse->base + REG_EFUSE_CTRL); udelay(1); + while (addr_len--) { writel(readl(efuse->base + REG_EFUSE_CTRL) | RK3399_STROBE | ((addr_start++ & RK3399_A_MASK) << RK3399_A_SHIFT), @@ -203,48 +218,120 @@ static int rockchip_rk3399_efuse_read(void *context, unsigned int offset, return 0; } -static struct nvmem_config econfig = { - .name = "rockchip-efuse", - .add_legacy_fixed_of_cells = true, - .type = NVMEM_TYPE_OTP, - .stride = 1, - .word_size = 1, - .read_only = true, +/** + * rockchip_rk3399_efuse_write - program fuse bits on RK3399 (A_PGM mode) + * @context: pointer to rockchip_efuse_chip + * @offset: byte offset in the efuse space + * @val: data to write (only set bits are programmed; OTP cannot clear) + * @bytes: number of bytes + * + * Each bit is programmed individually using a hardware-timed STROBE pulse. + * The caller must ensure VQPS (1.8V~1.98V) is present during the write. + * Per TRM Section 21.6, only one bit is programmed per STROBE cycle. + */ +static int rockchip_rk3399_efuse_write(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_efuse_chip *efuse = context; + u8 *buf = val; + int ret; + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) { + dev_err(efuse->dev, "failed to prepare/enable efuse clk\n"); + return ret; + } + + while (bytes--) { + u8 byte = *buf++; + int bit; + + for (bit = 0; bit < 8; bit++) { + u32 addr, ctrl; + + if (!(byte & BIT(bit))) + continue; + + addr = offset * 8 + bit; + + /* + * A_PGM mode (TRM table 23-3): + * CSB=L, PGENB=L, PS=H, PD=L, LOAD=L, RSB=L, + * STROBSFTSEL=H (software controls STROBE) + * STROBE is asserted separately below. + */ + ctrl = RK3399_STROBSFTSEL | RK3399_PS | + ((addr & RK3399_A_MASK) << RK3399_A_SHIFT); + + writel(ctrl, efuse->base + REG_EFUSE_CTRL); + udelay(1); + + writel(ctrl | RK3399_STROBE, efuse->base + REG_EFUSE_CTRL); + udelay(RK3399_PGM_TIME_US); + + writel(ctrl, efuse->base + REG_EFUSE_CTRL); + udelay(1); + } + + offset++; + } + + /* Return to standby mode: PD=H, CSB=H */ + writel(RK3399_PD | RK3399_CSB, efuse->base + REG_EFUSE_CTRL); + + clk_disable_unprepare(efuse->clk); + + return 0; +} + +static const struct rockchip_efuse_soc_data rk3288_efuse_data = { + .reg_read = rockchip_rk3288_efuse_read, +}; + +static const struct rockchip_efuse_soc_data rk3328_efuse_data = { + .reg_read = rockchip_rk3328_efuse_read, +}; + +static const struct rockchip_efuse_soc_data rk3399_efuse_data = { + .reg_read = rockchip_rk3399_efuse_read, + .reg_write = rockchip_rk3399_efuse_write, }; static const struct of_device_id rockchip_efuse_match[] = { /* deprecated but kept around for dts binding compatibility */ { .compatible = "rockchip,rockchip-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3066a-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3188-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3228-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3288-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3368-efuse", - .data = (void *)&rockchip_rk3288_efuse_read, + .data = &rk3288_efuse_data, }, { .compatible = "rockchip,rk3328-efuse", - .data = (void *)&rockchip_rk3328_efuse_read, + .data = &rk3328_efuse_data, }, { .compatible = "rockchip,rk3399-efuse", - .data = (void *)&rockchip_rk3399_efuse_read, + .data = &rk3399_efuse_data, }, { /* sentinel */}, }; @@ -252,20 +339,26 @@ MODULE_DEVICE_TABLE(of, rockchip_efuse_match); static int rockchip_efuse_probe(struct platform_device *pdev) { - struct resource *res; - struct nvmem_device *nvmem; + const struct rockchip_efuse_soc_data *soc_data; struct rockchip_efuse_chip *efuse; - const void *data; + struct nvmem_device *nvmem; + struct nvmem_config econfig = { + .name = "rockchip-efuse", + .add_legacy_fixed_of_cells = true, + .type = NVMEM_TYPE_OTP, + .stride = 1, + .word_size = 1, + }; + struct resource *res; struct device *dev = &pdev->dev; - data = of_device_get_match_data(dev); - if (!data) { + soc_data = of_device_get_match_data(dev); + if (!soc_data) { dev_err(dev, "failed to get match data\n"); return -EINVAL; } - efuse = devm_kzalloc(dev, sizeof(struct rockchip_efuse_chip), - GFP_KERNEL); + efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL); if (!efuse) return -ENOMEM; @@ -278,12 +371,20 @@ static int rockchip_efuse_probe(struct platform_device *pdev) return PTR_ERR(efuse->clk); efuse->dev = dev; + if (of_property_read_u32(dev->of_node, "rockchip,efuse-size", &econfig.size)) econfig.size = resource_size(res); - econfig.reg_read = data; - econfig.priv = efuse; - econfig.dev = efuse->dev; + + econfig.reg_read = soc_data->reg_read; + if (soc_data->reg_write && + of_property_read_bool(dev->of_node, "rockchip,efuse-write-enable")) + econfig.reg_write = soc_data->reg_write; + + econfig.read_only = !econfig.reg_write; + econfig.priv = efuse; + econfig.dev = dev; + nvmem = devm_nvmem_register(dev, &econfig); return PTR_ERR_OR_ZERO(nvmem); @@ -300,3 +401,4 @@ static struct platform_driver rockchip_efuse_driver = { module_platform_driver(rockchip_efuse_driver); MODULE_DESCRIPTION("rockchip_efuse driver"); MODULE_LICENSE("GPL v2"); + -- 2.47.3 ^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-15 11:43 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-15 11:01 [PATCH 0/2] nvmem: rockchip-efuse: add RK3399 OTP write support Hrushiraj Gandhi 2026-07-15 11:01 ` [PATCH 1/2] dt-bindings: nvmem: rockchip-efuse: add rockchip,efuse-write-enable property Hrushiraj Gandhi 2026-07-15 11:42 ` Heiko Stübner 2026-07-15 11:01 ` [PATCH 2/2] nvmem: rockchip-efuse: add write support for RK3399 Hrushiraj Gandhi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox