| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd | |
| */ | |
| #include <asm/io.h> | |
| #include <command.h> | |
| #include <display_options.h> | |
| #include <dm.h> | |
| #include <linux/bitops.h> | |
| #include <linux/delay.h> | |
| #include <linux/iopoll.h> | |
| #include <malloc.h> | |
| #include <misc.h> | |
| /* 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) | |
| #define RV1126_OTP_NVM_CEB 0x00 | |
| #define RV1126_OTP_NVM_RSTB 0x04 | |
| #define RV1126_OTP_NVM_ST 0x18 | |
| #define RV1126_OTP_NVM_RADDR 0x1C | |
| #define RV1126_OTP_NVM_RSTART 0x20 | |
| #define RV1126_OTP_NVM_RDATA 0x24 | |
| #define RV1126_OTP_READ_ST 0x30 | |
| struct rockchip_otp_plat { | |
| void __iomem *base; | |
| }; | |
| struct rockchip_otp_data { | |
| int (*init)(struct udevice *dev); | |
| 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_rv1126_otp_init(struct udevice *dev) | |
| { | |
| struct rockchip_otp_plat *otp = dev_get_plat(dev); | |
| int ret; | |
| writel(0x0, otp->base + RV1126_OTP_NVM_CEB); | |
| ret = rockchip_otp_poll_timeout(otp, 0x1, RV1126_OTP_NVM_ST); | |
| if (ret) | |
| return ret; | |
| writel(0x1, otp->base + RV1126_OTP_NVM_RSTB); | |
| ret = rockchip_otp_poll_timeout(otp, 0x4, RV1126_OTP_NVM_ST); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static int rockchip_rv1126_otp_read(struct udevice *dev, int offset, void *buf, | |
| int size) | |
| { | |
| struct rockchip_otp_plat *otp = dev_get_plat(dev); | |
| u32 status = 0; | |
| u8 *buffer = buf; | |
| int ret = 0; | |
| while (size--) { | |
| writel(offset++, otp->base + RV1126_OTP_NVM_RADDR); | |
| writel(0x1, otp->base + RV1126_OTP_NVM_RSTART); | |
| ret = readl_poll_timeout(otp->base + RV1126_OTP_READ_ST, | |
| status, !status, OTPC_TIMEOUT); | |
| if (ret) | |
| return ret; | |
| *buffer++ = (u8)(readl(otp->base + RV1126_OTP_NVM_RDATA) & 0xFF); | |
| } | |
| 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 int rockchip_otp_probe(struct udevice *dev) | |
| { | |
| struct rockchip_otp_data *data; | |
| data = (struct rockchip_otp_data *)dev_get_driver_data(dev); | |
| if (!data) | |
| return -EINVAL; | |
| if (data->init) | |
| return data->init(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 rockchip_otp_data rv1126_data = { | |
| .init = rockchip_rv1126_otp_init, | |
| .read = rockchip_rv1126_otp_read, | |
| .size = 0x40, | |
| }; | |
| 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, | |
| }, | |
| { | |
| .compatible = "rockchip,rv1126-otp", | |
| .data = (ulong)&rv1126_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, | |
| .probe = rockchip_otp_probe, | |
| }; |