mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-26 01:28:14 +00:00 
			
		
		
		
	As part of bringing the master branch back in to next, we need to allow for all of these changes to exist here. Reported-by: Jonas Karlman <jonas@kwiboo.se> Signed-off-by: Tom Rini <trini@konsulko.com>
		
			
				
	
	
		
			410 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			410 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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,
 | |
| };
 |