/* * eFuse driver for Rockchip devices * * Copyright 2017, Theobroma Systems Design und Consulting GmbH * Written by Philipp Tomsich * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include #include #include #define T_CSB_P_S 0 #define T_PGENB_P_S 0 #define T_LOAD_P_S 0 #define T_ADDR_P_S 0 #define T_STROBE_P_S (0 + 110) /* 1.1us */ #define T_CSB_P_L (0 + 110 + 1000 + 20) /* 200ns */ #define T_PGENB_P_L (0 + 110 + 1000 + 20) #define T_LOAD_P_L (0 + 110 + 1000 + 20) #define T_ADDR_P_L (0 + 110 + 1000 + 20) #define T_STROBE_P_L (0 + 110 + 1000) /* 10us */ #define T_CSB_R_S 0 #define T_PGENB_R_S 0 #define T_LOAD_R_S 0 #define T_ADDR_R_S 2 #define T_STROBE_R_S (2 + 3) #define T_CSB_R_L (2 + 3 + 3 + 3) #define T_PGENB_R_L (2 + 3 + 3 + 3) #define T_LOAD_R_L (2 + 3 + 3 + 3) #define T_ADDR_R_L (2 + 3 + 3 + 2) #define T_STROBE_R_L (2 + 3 + 3) #define T_CSB_P 0x28 #define T_PGENB_P 0x2c #define T_LOAD_P 0x30 #define T_ADDR_P 0x34 #define T_STROBE_P 0x38 #define T_CSB_R 0x3c #define T_PGENB_R 0x40 #define T_LOAD_R 0x44 #define T_ADDR_R 0x48 #define T_STROBE_R 0x4c #define RK1808_USER_MODE BIT(0) #define RK1808_INT_FINISH BIT(0) #define RK1808_AUTO_ENB BIT(0) #define RK1808_AUTO_RD BIT(1) #define RK1808_A_SHIFT 16 #define RK1808_A_MASK 0x3ff #define RK1808_NBYTES 4 #define RK3399_A_SHIFT 16 #define RK3399_A_MASK 0x3ff #define RK3399_NFUSES 32 #define RK3399_BYTES_PER_FUSE 4 #define RK3399_STROBSFTSEL BIT(9) #define RK3399_RSB BIT(7) #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 RK3288_A_SHIFT 6 #define RK3288_A_MASK 0x3ff #define RK3288_NFUSES 32 #define RK3288_BYTES_PER_FUSE 1 #define RK3288_PGENB BIT(3) #define RK3288_LOAD BIT(2) #define RK3288_STROBE BIT(1) #define RK3288_CSB BIT(0) #define RK3328_INT_STATUS 0x0018 #define RK3328_DOUT 0x0020 #define RK3328_AUTO_CTRL 0x0024 #define RK3328_INT_FINISH BIT(0) #define RK3328_AUTO_ENB BIT(0) #define RK3328_AUTO_RD BIT(1) typedef int (*EFUSE_READ)(struct udevice *dev, int offset, void *buf, int size); struct rockchip_efuse_regs { u32 ctrl; /* 0x00 efuse control register */ u32 dout; /* 0x04 efuse data out register */ u32 rf; /* 0x08 efuse redundancy bit used register */ u32 _rsvd0; u32 jtag_pass; /* 0x10 JTAG password */ u32 strobe_finish_ctrl; /* 0x14 efuse strobe finish control register */ u32 int_status;/* 0x18 */ u32 reserved; /* 0x1c */ u32 dout2; /* 0x20 */ u32 auto_ctrl; /* 0x24 */ }; struct rockchip_efuse_platdata { void __iomem *base; struct clk *clk; }; static void rk1808_efuse_timing_init(void __iomem *base) { static bool init; if (init) return; /* enable auto mode */ writel(readl(base) & (~RK1808_USER_MODE), base); /* setup efuse timing */ writel((T_CSB_P_S << 16) | T_CSB_P_L, base + T_CSB_P); writel((T_PGENB_P_S << 16) | T_PGENB_P_L, base + T_PGENB_P); writel((T_LOAD_P_S << 16) | T_LOAD_P_L, base + T_LOAD_P); writel((T_ADDR_P_S << 16) | T_ADDR_P_L, base + T_ADDR_P); writel((T_STROBE_P_S << 16) | T_STROBE_P_L, base + T_STROBE_P); writel((T_CSB_R_S << 16) | T_CSB_R_L, base + T_CSB_R); writel((T_PGENB_R_S << 16) | T_PGENB_R_L, base + T_PGENB_R); writel((T_LOAD_R_S << 16) | T_LOAD_R_L, base + T_LOAD_R); writel((T_ADDR_R_S << 16) | T_ADDR_R_L, base + T_ADDR_R); writel((T_STROBE_R_S << 16) | T_STROBE_R_L, base + T_STROBE_R); init = true; } static int rockchip_rk1808_efuse_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; unsigned int addr_start, addr_end, addr_offset, addr_len; u32 out_value, status; u8 *buffer; int ret = 0, i = 0; rk1808_efuse_timing_init(plat->base); addr_start = rounddown(offset, RK1808_NBYTES) / RK1808_NBYTES; addr_end = roundup(offset + size, RK1808_NBYTES) / RK1808_NBYTES; addr_offset = offset % RK1808_NBYTES; addr_len = addr_end - addr_start; buffer = calloc(1, sizeof(*buffer) * addr_len * RK1808_NBYTES); if (!buffer) return -ENOMEM; while (addr_len--) { writel(RK1808_AUTO_RD | RK1808_AUTO_ENB | ((addr_start++ & RK1808_A_MASK) << RK1808_A_SHIFT), &efuse->auto_ctrl); udelay(2); status = readl(&efuse->int_status); if (!(status & RK1808_INT_FINISH)) { ret = -EIO; goto err; } out_value = readl(&efuse->dout2); writel(RK1808_INT_FINISH, &efuse->int_status); memcpy(&buffer[i], &out_value, RK1808_NBYTES); i += RK1808_NBYTES; } memcpy(buf, buffer + addr_offset, size); err: kfree(buffer); return ret; } #ifndef CONFIG_SPL_BUILD static int rockchip_rk3368_efuse_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; u8 *buffer = buf; struct arm_smccc_res res; /* Switch to read mode */ sip_smc_secure_reg_write((ulong)&efuse->ctrl, RK3288_LOAD | RK3288_PGENB); udelay(1); while (size--) { res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 & (~(RK3288_A_MASK << RK3288_A_SHIFT))); /* set addr */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 | ((offset++ & RK3288_A_MASK) << RK3288_A_SHIFT)); udelay(1); /* strobe low to high */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 | RK3288_STROBE); ndelay(60); /* read data */ res = sip_smc_secure_reg_read((ulong)&efuse->dout); *buffer++ = res.a1; /* reset strobe to low */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 & (~RK3288_STROBE)); udelay(1); } /* Switch to standby mode */ sip_smc_secure_reg_write((ulong)&efuse->ctrl, RK3288_PGENB | RK3288_CSB); return 0; } #endif static int rockchip_rk3399_efuse_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; unsigned int addr_start, addr_end, addr_offset; u32 out_value; u8 bytes[RK3399_NFUSES * RK3399_BYTES_PER_FUSE]; int i = 0; u32 addr; addr_start = offset / RK3399_BYTES_PER_FUSE; addr_offset = offset % RK3399_BYTES_PER_FUSE; addr_end = DIV_ROUND_UP(offset + size, RK3399_BYTES_PER_FUSE); /* cap to the size of the efuse block */ if (addr_end > RK3399_NFUSES) addr_end = RK3399_NFUSES; writel(RK3399_LOAD | RK3399_PGENB | RK3399_STROBSFTSEL | RK3399_RSB, &efuse->ctrl); udelay(1); for (addr = addr_start; addr < addr_end; addr++) { setbits_le32(&efuse->ctrl, RK3399_STROBE | (addr << RK3399_A_SHIFT)); udelay(1); out_value = readl(&efuse->dout); clrbits_le32(&efuse->ctrl, RK3399_STROBE); udelay(1); memcpy(&bytes[i], &out_value, RK3399_BYTES_PER_FUSE); i += RK3399_BYTES_PER_FUSE; } /* Switch to standby mode */ writel(RK3399_PD | RK3399_CSB, &efuse->ctrl); memcpy(buf, bytes + addr_offset, size); return 0; } static int rockchip_rk3288_efuse_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; u8 *buffer = buf; int max_size = RK3288_NFUSES * RK3288_BYTES_PER_FUSE; if (size > (max_size - offset)) size = max_size - offset; /* Switch to read mode */ writel(RK3288_LOAD | RK3288_PGENB, &efuse->ctrl); udelay(1); while (size--) { writel(readl(&efuse->ctrl) & (~(RK3288_A_MASK << RK3288_A_SHIFT)), &efuse->ctrl); /* set addr */ writel(readl(&efuse->ctrl) | ((offset++ & RK3288_A_MASK) << RK3288_A_SHIFT), &efuse->ctrl); udelay(1); /* strobe low to high */ writel(readl(&efuse->ctrl) | RK3288_STROBE, &efuse->ctrl); ndelay(60); /* read data */ *buffer++ = readl(&efuse->dout); /* reset strobe to low */ writel(readl(&efuse->ctrl) & (~RK3288_STROBE), &efuse->ctrl); udelay(1); } /* Switch to standby mode */ writel(RK3288_PGENB | RK3288_CSB, &efuse->ctrl); return 0; } #ifndef CONFIG_SPL_BUILD static int rockchip_rk3288_efuse_secure_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; u8 *buffer = buf; int max_size = RK3288_NFUSES * RK3288_BYTES_PER_FUSE; struct arm_smccc_res res; if (size > (max_size - offset)) size = max_size - offset; /* Switch to read mode */ sip_smc_secure_reg_write((ulong)&efuse->ctrl, RK3288_LOAD | RK3288_PGENB); udelay(1); while (size--) { res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 & (~(RK3288_A_MASK << RK3288_A_SHIFT))); /* set addr */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 | ((offset++ & RK3288_A_MASK) << RK3288_A_SHIFT)); udelay(1); /* strobe low to high */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 | RK3288_STROBE); ndelay(60); /* read data */ res = sip_smc_secure_reg_read((ulong)&efuse->dout); *buffer++ = res.a1; /* reset strobe to low */ res = sip_smc_secure_reg_read((ulong)&efuse->ctrl); sip_smc_secure_reg_write((ulong)&efuse->ctrl, res.a1 & (~RK3288_STROBE)); udelay(1); } /* Switch to standby mode */ sip_smc_secure_reg_write((ulong)&efuse->ctrl, RK3288_PGENB | RK3288_CSB); return 0; } #endif static int rockchip_rk3328_efuse_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); struct rockchip_efuse_regs *efuse = (struct rockchip_efuse_regs *)plat->base; unsigned int addr_start, addr_end, addr_offset, addr_len; u32 out_value, status; u8 *buffer; int ret = 0, i = 0, j = 0; /* Max non-secure Byte */ if (size > 32) size = 32; /* 128 Byte efuse, 96 Byte for secure, 32 Byte for non-secure */ offset += 96; addr_start = rounddown(offset, RK3399_BYTES_PER_FUSE) / RK3399_BYTES_PER_FUSE; addr_end = roundup(offset + size, RK3399_BYTES_PER_FUSE) / RK3399_BYTES_PER_FUSE; addr_offset = offset % RK3399_BYTES_PER_FUSE; addr_len = addr_end - addr_start; buffer = calloc(1, sizeof(*buffer) * addr_len * RK3399_BYTES_PER_FUSE); if (!buffer) return -ENOMEM; for (j = 0; j < addr_len; j++) { writel(RK3328_AUTO_RD | RK3328_AUTO_ENB | ((addr_start++ & RK3399_A_MASK) << RK3399_A_SHIFT), &efuse->auto_ctrl); udelay(5); status = readl(&efuse->int_status); if (!(status & RK3328_INT_FINISH)) { ret = -EIO; goto err; } out_value = readl(&efuse->dout2); writel(RK3328_INT_FINISH, &efuse->int_status); memcpy(&buffer[i], &out_value, RK3399_BYTES_PER_FUSE); i += RK3399_BYTES_PER_FUSE; } memcpy(buf, buffer + addr_offset, size); err: free(buffer); return ret; } static int rockchip_efuse_read(struct udevice *dev, int offset, void *buf, int size) { EFUSE_READ efuse_read = NULL; efuse_read = (EFUSE_READ)dev_get_driver_data(dev); if (!efuse_read) return -ENOSYS; return (*efuse_read)(dev, offset, buf, size); } static int rockchip_efuse_capatiblity(struct udevice *dev, u32 *buf) { *buf = device_is_compatible(dev, "rockchip,rk3288-secure-efuse") ? OTP_S : OTP_NS; return 0; } static int rockchip_efuse_ioctl(struct udevice *dev, unsigned long request, void *buf) { int ret = -EINVAL; switch (request) { case IOCTL_REQ_CAPABILITY: ret = rockchip_efuse_capatiblity(dev, buf); break; } return ret; } static const struct misc_ops rockchip_efuse_ops = { .read = rockchip_efuse_read, .ioctl = rockchip_efuse_ioctl, }; static int rockchip_efuse_ofdata_to_platdata(struct udevice *dev) { struct rockchip_efuse_platdata *plat = dev_get_platdata(dev); plat->base = dev_read_addr_ptr(dev); return 0; } static const struct udevice_id rockchip_efuse_ids[] = { { .compatible = "rockchip,rk1808-efuse", .data = (ulong)&rockchip_rk1808_efuse_read, }, #ifndef CONFIG_SPL_BUILD { .compatible = "rockchip,rk3288-secure-efuse", .data = (ulong)&rockchip_rk3288_efuse_secure_read, }, #endif { .compatible = "rockchip,rk3066a-efuse", .data = (ulong)&rockchip_rk3288_efuse_read, }, { .compatible = "rockchip,rk3188-efuse", .data = (ulong)&rockchip_rk3288_efuse_read, }, { .compatible = "rockchip,rk322x-efuse", .data = (ulong)&rockchip_rk3288_efuse_read, }, { .compatible = "rockchip,rk3328-efuse", .data = (ulong)&rockchip_rk3328_efuse_read, }, #ifndef CONFIG_SPL_BUILD { .compatible = "rockchip,rk3368-efuse", .data = (ulong)&rockchip_rk3368_efuse_read, }, #endif { .compatible = "rockchip,rk3399-efuse", .data = (ulong)&rockchip_rk3399_efuse_read, }, {} }; U_BOOT_DRIVER(rockchip_efuse) = { .name = "rockchip_efuse", .id = UCLASS_MISC, .of_match = rockchip_efuse_ids, .ofdata_to_platdata = rockchip_efuse_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct rockchip_efuse_platdata), .ops = &rockchip_efuse_ops, };