Hi , OTP write documentation is not available in RK3588’s both TRMs. I wanted to program non - secure OTP. Could you help me with documentation ?
Secure Boot on Rock 5B
Bit offset 0xC00 / Word offset 0x300 is where NS OTPs are located. Bits programmed there can be read from Non-Secure freely. You can also program them directly from NS using the OTP_NS base address with my code (the one in my secure boot repo). Using OTP_NS, the offsets start from 0.
I would however be very careful with writing to the NS bank, as non-open BL31/TF-A blobs will use some regions there. Opensource versions will use less.
thank you, will try and let you know
Thank you for your reply! Can I program it from user space using your code ? or should I add write function in kernel’s driver ?
#define OTP_S_PROG_DATA_ADDR (OTP_S_BASE + 0x0010)
#define OTP_S_PROG_DATA ((volatileuint32_t)OTP_S_PROG_DATA_ADDR)
are all these similar offsets for NS ?
The registers are exactly the same whether _S or _NS. you can only OTP_NS from kernel space. If you look at my rk3588-svd repo, it will generate headers for OTP_NS, so you don’t have to manually do it yourself.
thanks a lot, will let you know
RK3588 Data sheet says: Support 32Kbit space and higher 4k address space is non-secure part.
But according to dtb:
otp: otp@fecc0000 {
compatible = “rockchip,rk3588-otp”;
reg = <0x0 0xfecc0000 0x0 0x400>;
its size is only 0x400 bytes , right?
and we start read/write from NO_SECURE_OFFSET i.e 0x300
does this mean there are 0x400 otps each of 4 bytes ?
static int rk3588_otp_write(void *context, unsigned int offset,
void *val, size_t bytes)
{
struct rockchip_otp *otp = context;
unsigned int addr_start, addr_end, addr_offset, addr_len;
int ret = 0;// i = 0;
u32 in_value;
u8 *buf;
OTP_NS_t ns_otp=(OTP_NS_t)otp->base;
dev_info(otp->dev, “inside rk3588_otp_write()\n”);
if (offset >= otp->data->size)
return -ENOMEM;
if (offset + bytes > otp->data->size)
bytes = otp->data->size - offset;
addr_start = rounddown(offset, RK3588_NBYTES) / RK3588_NBYTES;
addr_end = roundup(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
addr_offset = offset % RK3588_NBYTES;
addr_len = addr_end - addr_start;
addr_start += RK3588_NO_SECURE_OFFSET;
printk(KERN_INFO "rk3588_otp_write(): addr_start: %d, addr_end: %d, addr_len: %d, addr_offset: %d\n",addr_start,addr_end,addr_len,addr_offset);
buf = kzalloc(array3_size(addr_len, RK3588_NBYTES, sizeof(*buf)), GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = clk_bulk_prepare_enable(otp->num_clks, otp->clks);
if (ret < 0) {
dev_err(otp->dev, "failed to enable clocks\n");
return -1;
}
//dtOtpWriteNonSecure(ns_otp,val,addr_start,addr_len);
//Copy input to buffer, considering offset inside the first word
memcpy(buf + addr_offset, val, bytes);
while (addr_len--) {
memcpy(&in_value, &buf[i], RK3588_NBYTES);
// Write the data to be programmed
writel(in_value, otp->base + OTP_NS_PROG_DATA_ADDR);
// Set AUTO_CTRL with address and write command
writel((addr_start << RK3588_ADDR_SHIFT) |
(RK3588_BURST_NUM << RK3588_BURST_SHIFT) |
RK3588_CMD_WRITE,
otp->base + RK3588_OTPC_AUTO_CTRL);
// Enable the command
writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);
// Wait for write to complete
// ret = rk3588_otp_wait_status(otp, RK3588_WR_DONE);
// if (ret < 0) {
// dev_err(otp->dev, "timeout during write operation\n");
// goto write_end;
// }
udelay(10000);
udelay(10000);
printk(KERN_INFO "for i = %d ,addr_start = %d \n",i,addr_start);
addr_start++;
i += RK3588_NBYTES;
}
write_end:
clk_bulk_disable_unprepare(otp->num_clks, otp->clks);
out:
kfree(buf);
return ret;
}
is this the right code ? I just used writel() instead of your volatile objects
The block at 0xFECC0000 is the OTP_NS one, so the offset starts at 0. The 0x400 you see is not the size of the OTP bits, but the size of the register window. The general OTP bits are not memory mapped, you have to use the registers to program a read command.
The OTP_NS region contains 0x100 words -> 0x100 * 32bits == 0x400 bytes (just coincidence!).
writel() is equivalent to my volatile. My code uses volatile because it runs in a baremetal environment == no OS or kernel.
The 32KBits is correct. 32kbits is 4096 bytes. The OTP bank is a total of 0x400 words = 4096 bytes = 32768 bits. The first 0x300 words are accessible only from OTP_S (kernel can’t go there), then next 0x100 words are accessible by both S and NS.
my macros:
#define RK3588_OTPC_AUTO_CTRL 0x04
#define RK3588_OTPC_AUTO_EN 0x08
#define RK3588_OTPC_INT_ST 0x84
#define RK3588_OTPC_DOUT0 0x20
#define RK3588_NO_SECURE_OFFSET 0x300
#define RK3588_NBYTES 4
#define RK3588_BURST_NUM 1
#define RK3588_BURST_SHIFT 8
#define RK3588_ADDR_SHIFT 16
#define OTP_NS_PROG_DATA_ADDR 0x0010
#define RK3588_AUTO_EN BIT(0)
#define RK3588_RD_DONE BIT(1)
#define RK3588_CMD_WRITE 0x2
#define RK3588_WR_DONE BIT(3)
my_function
static int rk3588_otp_write(void* context, unsigned int offset,
void val, size_t bytes)
{
struct rockchip_otp otp=(struct rockchip_otp*)context;
unsigned int addr_start, addr_end, addr_offset, addr_len;
unsigned int in_value;
int ret = 0, i = 0;
u8 *buf;
if (offset >= otp->data->size)
return -EINVAL;
if (offset + bytes > otp->data->size)
bytes = otp->data->size - offset;
addr_start = rounddown(offset, RK3588_NBYTES) / RK3588_NBYTES;
addr_end = roundup(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
addr_offset = offset % RK3588_NBYTES;
addr_len = addr_end - addr_start;
addr_start += RK3588_NO_SECURE_OFFSET;
buf = kzalloc(addr_len * RK3588_NBYTES, GFP_KERNEL);
if (!buf)
return -ENOMEM;
// Copying user data into buffer with offset
memcpy(buf + addr_offset, val, bytes);
ret = clk_bulk_prepare_enable(otp->num_clks, otp->clks);
if (ret < 0) {
dev_err(otp->dev, "Failed to enable clocks\n");
goto out_free;
}
// Word-by-word programming
for (i = 0; i < addr_len * RK3588_NBYTES; i += RK3588_NBYTES) {
memcpy(&in_value, &buf[i], RK3588_NBYTES);
writel(in_value, otp->base + OTP_NS_PROG_DATA_ADDR);
writel(((addr_start << RK3588_ADDR_SHIFT) |
(RK3588_BURST_NUM << RK3588_BURST_SHIFT) |
RK3588_CMD_WRITE),
otp->base + RK3588_OTPC_AUTO_CTRL);
writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);
udelay(10000); // 10ms delay for write
ret = rk3588_otp_wait_status(otp, RK3588_WR_DONE);
if (ret < 0) {
dev_err(otp->dev, "timeout during read setup\n");
goto out_free;
}
dev_info(otp->dev, "Wrote word: 0x%08x at word %u\n", in_value, addr_start);
addr_start++;
}
clk_bulk_disable_unprepare(otp->num_clks, otp->clks);
out_free:
kfree(buf);
return ret;
}
here is my function
You have to pay attention. I mentioned at few times that when using OTP_NS, the offset for NS bits is 0, not 0x300.
yes I know it, sorry to bother you ,but the file had predefined
#define RK3588_NO_SECURE_OFFSET 0x300
as per your advice I made OFFSET to 0x0
then both read and write fail.
Also i’m doing this in linux, not baremetal
Can you go through code once and let me know ?
Hope you’re doing good. I request you to look at my query . I have a few boards and I’m trying very best for it. I sincerely need your help. Sorry for the trouble.