// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd */ #include #include #include #include #include #include #include #include #include #include /* OTP Register Offsets */ #define OTPC_SBPI_CTRL 0x0020 #define OTPC_SBPI_CMD_VALID_PRE 0x0024 #define OTPC_SBPI_CS_VALID_PRE 0x0028 #define OTPC_SBPI_STATUS 0x002C #define OTPC_USER_CTRL 0x0100 #define OTPC_USER_ADDR 0x0104 #define OTPC_USER_ENABLE 0x0108 #define OTPC_USER_QP 0x0120 #define OTPC_USER_Q 0x0124 #define OTPC_INT_STATUS 0x0304 #define OTPC_SBPI_CMD0_OFFSET 0x1000 #define OTPC_SBPI_CMD1_OFFSET 0x1004 /* OTP Register bits and masks */ #define OTPC_USER_ADDR_MASK GENMASK(31, 16) #define OTPC_USE_USER BIT(0) #define OTPC_USE_USER_MASK GENMASK(16, 16) #define OTPC_USER_FSM_ENABLE BIT(0) #define OTPC_USER_FSM_ENABLE_MASK GENMASK(16, 16) #define OTPC_SBPI_DONE BIT(1) #define OTPC_USER_DONE BIT(2) #define SBPI_DAP_ADDR 0x02 #define SBPI_DAP_ADDR_SHIFT 8 #define SBPI_DAP_ADDR_MASK GENMASK(31, 24) #define SBPI_CMD_VALID_MASK GENMASK(31, 16) #define SBPI_DAP_CMD_WRF 0xC0 #define SBPI_DAP_REG_ECC 0x3A #define SBPI_ECC_ENABLE 0x00 #define SBPI_ECC_DISABLE 0x09 #define SBPI_ENABLE BIT(0) #define SBPI_ENABLE_MASK GENMASK(16, 16) #define OTPC_TIMEOUT 10000 #define RK3588_OTPC_AUTO_CTRL 0x0004 #define RK3588_ADDR_SHIFT 16 #define RK3588_ADDR(n) ((n) << RK3588_ADDR_SHIFT) #define RK3588_BURST_SHIFT 8 #define RK3588_BURST(n) ((n) << RK3588_BURST_SHIFT) #define RK3588_OTPC_AUTO_EN 0x0008 #define RK3588_AUTO_EN BIT(0) #define RK3588_OTPC_DOUT0 0x0020 #define RK3588_OTPC_INT_ST 0x0084 #define RK3588_RD_DONE BIT(1) struct rockchip_otp_plat { void __iomem *base; }; struct rockchip_otp_data { int (*read)(struct udevice *dev, int offset, void *buf, int size); int offset; int size; int block_size; }; #if defined(DEBUG) static int dump_otp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { struct udevice *dev; u8 data[4]; int ret, i; ret = uclass_get_device_by_driver(UCLASS_MISC, DM_DRIVER_GET(rockchip_otp), &dev); if (ret) { printf("%s: no misc-device found\n", __func__); return 0; } for (i = 0; true; i += sizeof(data)) { ret = misc_read(dev, i, &data, sizeof(data)); if (ret <= 0) return 0; print_buffer(i, data, 1, sizeof(data), sizeof(data)); } return 0; } U_BOOT_CMD( dump_otp, 1, 1, dump_otp, "Dump the content of the otp", "" ); #endif static int rockchip_otp_poll_timeout(struct rockchip_otp_plat *otp, u32 flag, u32 reg) { u32 status; int ret; ret = readl_poll_sleep_timeout(otp->base + reg, status, (status & flag), 1, OTPC_TIMEOUT); if (ret) return ret; /* Clear int flag */ writel(flag, otp->base + reg); return 0; } static int rockchip_otp_ecc_enable(struct rockchip_otp_plat *otp, bool enable) { writel(SBPI_DAP_ADDR_MASK | (SBPI_DAP_ADDR << SBPI_DAP_ADDR_SHIFT), otp->base + OTPC_SBPI_CTRL); writel(SBPI_CMD_VALID_MASK | 0x1, otp->base + OTPC_SBPI_CMD_VALID_PRE); writel(SBPI_DAP_CMD_WRF | SBPI_DAP_REG_ECC, otp->base + OTPC_SBPI_CMD0_OFFSET); if (enable) writel(SBPI_ECC_ENABLE, otp->base + OTPC_SBPI_CMD1_OFFSET); else writel(SBPI_ECC_DISABLE, otp->base + OTPC_SBPI_CMD1_OFFSET); writel(SBPI_ENABLE_MASK | SBPI_ENABLE, otp->base + OTPC_SBPI_CTRL); return rockchip_otp_poll_timeout(otp, OTPC_SBPI_DONE, OTPC_INT_STATUS); } static int rockchip_px30_otp_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_otp_plat *otp = dev_get_plat(dev); u8 *buffer = buf; int ret; ret = rockchip_otp_ecc_enable(otp, false); if (ret) return ret; writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); udelay(5); while (size--) { writel(offset++ | OTPC_USER_ADDR_MASK, otp->base + OTPC_USER_ADDR); writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK, otp->base + OTPC_USER_ENABLE); ret = rockchip_otp_poll_timeout(otp, OTPC_USER_DONE, OTPC_INT_STATUS); if (ret) goto read_end; *buffer++ = (u8)(readl(otp->base + OTPC_USER_Q) & 0xFF); } read_end: writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); return ret; } static int rockchip_rk3568_otp_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_otp_plat *otp = dev_get_plat(dev); u16 *buffer = buf; int ret; ret = rockchip_otp_ecc_enable(otp, false); if (ret) return ret; writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); udelay(5); while (size--) { writel(offset++ | OTPC_USER_ADDR_MASK, otp->base + OTPC_USER_ADDR); writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK, otp->base + OTPC_USER_ENABLE); ret = rockchip_otp_poll_timeout(otp, OTPC_USER_DONE, OTPC_INT_STATUS); if (ret) goto read_end; *buffer++ = (u16)(readl(otp->base + OTPC_USER_Q) & 0xFFFF); } read_end: writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); return ret; } static int rockchip_rk3588_otp_read(struct udevice *dev, int offset, void *buf, int size) { struct rockchip_otp_plat *otp = dev_get_plat(dev); u32 *buffer = buf; int ret; while (size--) { writel(RK3588_ADDR(offset++) | RK3588_BURST(1), otp->base + RK3588_OTPC_AUTO_CTRL); writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN); ret = rockchip_otp_poll_timeout(otp, RK3588_RD_DONE, RK3588_OTPC_INT_ST); if (ret) return ret; *buffer++ = readl(otp->base + RK3588_OTPC_DOUT0); } return 0; } static int rockchip_otp_read(struct udevice *dev, int offset, void *buf, int size) { const struct rockchip_otp_data *data = (void *)dev_get_driver_data(dev); u32 block_start, block_end, block_offset, blocks; u8 *buffer; int ret; if (offset < 0 || !buf || size <= 0 || offset + size > data->size) return -EINVAL; if (!data->read) return -ENOSYS; offset += data->offset; if (data->block_size <= 1) { ret = data->read(dev, offset, buf, size); goto done; } block_start = offset / data->block_size; block_offset = offset % data->block_size; block_end = DIV_ROUND_UP(offset + size, data->block_size); blocks = block_end - block_start; buffer = calloc(blocks, data->block_size); if (!buffer) return -ENOMEM; ret = data->read(dev, block_start, buffer, blocks); if (!ret) memcpy(buf, buffer + block_offset, size); free(buffer); done: return ret < 0 ? ret : size; } static const struct misc_ops rockchip_otp_ops = { .read = rockchip_otp_read, }; static int rockchip_otp_of_to_plat(struct udevice *dev) { struct rockchip_otp_plat *plat = dev_get_plat(dev); plat->base = dev_read_addr_ptr(dev); return 0; } static const struct rockchip_otp_data px30_data = { .read = rockchip_px30_otp_read, .size = 0x40, }; static const struct rockchip_otp_data rk3568_data = { .read = rockchip_rk3568_otp_read, .size = 0x80, .block_size = 2, }; static const struct rockchip_otp_data rk3588_data = { .read = rockchip_rk3588_otp_read, .offset = 0xC00, .size = 0x400, .block_size = 4, }; static const struct udevice_id rockchip_otp_ids[] = { { .compatible = "rockchip,px30-otp", .data = (ulong)&px30_data, }, { .compatible = "rockchip,rk3308-otp", .data = (ulong)&px30_data, }, { .compatible = "rockchip,rk3568-otp", .data = (ulong)&rk3568_data, }, { .compatible = "rockchip,rk3588-otp", .data = (ulong)&rk3588_data, }, {} }; U_BOOT_DRIVER(rockchip_otp) = { .name = "rockchip_otp", .id = UCLASS_MISC, .of_match = rockchip_otp_ids, .of_to_plat = rockchip_otp_of_to_plat, .plat_auto = sizeof(struct rockchip_otp_plat), .ops = &rockchip_otp_ops, };