| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * eFuse driver for Rockchip devices | |
| * | |
| * Copyright 2017, Theobroma Systems Design und Consulting GmbH | |
| * Written by Philipp Tomsich <philipp.tomsich@theobroma-systems.com> | |
| */ | |
| #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> | |
| #define EFUSE_CTRL 0x0000 | |
| #define RK3036_A_SHIFT 8 | |
| #define RK3036_A_MASK GENMASK(15, 8) | |
| #define RK3036_ADDR(n) ((n) << RK3036_A_SHIFT) | |
| #define RK3128_A_SHIFT 7 | |
| #define RK3128_A_MASK GENMASK(15, 7) | |
| #define RK3128_ADDR(n) ((n) << RK3128_A_SHIFT) | |
| #define RK3288_A_SHIFT 6 | |
| #define RK3288_A_MASK GENMASK(15, 6) | |
| #define RK3288_ADDR(n) ((n) << RK3288_A_SHIFT) | |
| #define RK3399_A_SHIFT 16 | |
| #define RK3399_A_MASK GENMASK(25, 16) | |
| #define RK3399_ADDR(n) ((n) << RK3399_A_SHIFT) | |
| #define RK3399_STROBSFTSEL BIT(9) | |
| #define RK3399_RSB BIT(7) | |
| #define RK3399_PD BIT(5) | |
| #define EFUSE_PGENB BIT(3) | |
| #define EFUSE_LOAD BIT(2) | |
| #define EFUSE_STROBE BIT(1) | |
| #define EFUSE_CSB BIT(0) | |
| #define EFUSE_DOUT 0x0004 | |
| #define RK3328_INT_STATUS 0x0018 | |
| #define RK3328_INT_FINISH BIT(0) | |
| #define RK3328_DOUT 0x0020 | |
| #define RK3328_AUTO_CTRL 0x0024 | |
| #define RK3328_AUTO_RD BIT(1) | |
| #define RK3328_AUTO_ENB BIT(0) | |
| struct rockchip_efuse_plat { | |
| void __iomem *base; | |
| }; | |
| struct rockchip_efuse_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_efuse(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_efuse), &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_efuse, 1, 1, dump_efuse, | |
| "Dump the content of the efuse", | |
| "" | |
| ); | |
| #endif | |
| static int rockchip_rk3036_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| struct rockchip_efuse_plat *efuse = dev_get_plat(dev); | |
| u8 *buffer = buf; | |
| /* Switch to read mode */ | |
| writel(EFUSE_LOAD, efuse->base + EFUSE_CTRL); | |
| udelay(2); | |
| while (size--) { | |
| clrsetbits_le32(efuse->base + EFUSE_CTRL, RK3036_A_MASK, | |
| RK3036_ADDR(offset++)); | |
| udelay(2); | |
| setbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| *buffer++ = (u8)(readl(efuse->base + EFUSE_DOUT) & 0xFF); | |
| clrbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| } | |
| /* Switch to inactive mode */ | |
| writel(0x0, efuse->base + EFUSE_CTRL); | |
| return 0; | |
| } | |
| static int rockchip_rk3128_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| struct rockchip_efuse_plat *efuse = dev_get_plat(dev); | |
| u8 *buffer = buf; | |
| /* Switch to read mode */ | |
| writel(EFUSE_LOAD, efuse->base + EFUSE_CTRL); | |
| udelay(2); | |
| while (size--) { | |
| clrsetbits_le32(efuse->base + EFUSE_CTRL, RK3128_A_MASK, | |
| RK3128_ADDR(offset++)); | |
| udelay(2); | |
| setbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| *buffer++ = (u8)(readl(efuse->base + EFUSE_DOUT) & 0xFF); | |
| clrbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| } | |
| /* Switch to inactive mode */ | |
| writel(0x0, efuse->base + EFUSE_CTRL); | |
| return 0; | |
| } | |
| static int rockchip_rk3288_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| struct rockchip_efuse_plat *efuse = dev_get_plat(dev); | |
| u8 *buffer = buf; | |
| /* Switch to read mode */ | |
| writel(EFUSE_CSB, efuse->base + EFUSE_CTRL); | |
| writel(EFUSE_LOAD | EFUSE_PGENB, efuse->base + EFUSE_CTRL); | |
| udelay(2); | |
| while (size--) { | |
| clrsetbits_le32(efuse->base + EFUSE_CTRL, RK3288_A_MASK, | |
| RK3288_ADDR(offset++)); | |
| udelay(2); | |
| setbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| *buffer++ = (u8)(readl(efuse->base + EFUSE_DOUT) & 0xFF); | |
| clrbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(2); | |
| } | |
| /* Switch to standby mode */ | |
| writel(EFUSE_CSB | EFUSE_PGENB, efuse->base + EFUSE_CTRL); | |
| return 0; | |
| } | |
| static int rockchip_rk3328_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| struct rockchip_efuse_plat *efuse = dev_get_plat(dev); | |
| u32 status, *buffer = buf; | |
| int ret; | |
| while (size--) { | |
| writel(RK3328_AUTO_RD | RK3328_AUTO_ENB | RK3399_ADDR(offset++), | |
| efuse->base + RK3328_AUTO_CTRL); | |
| udelay(1); | |
| ret = readl_poll_sleep_timeout(efuse->base + RK3328_INT_STATUS, | |
| status, (status & RK3328_INT_FINISH), 1, 50); | |
| if (ret) | |
| return ret; | |
| *buffer++ = readl(efuse->base + RK3328_DOUT); | |
| writel(RK3328_INT_FINISH, efuse->base + RK3328_INT_STATUS); | |
| } | |
| return 0; | |
| } | |
| static int rockchip_rk3399_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| struct rockchip_efuse_plat *efuse = dev_get_plat(dev); | |
| u32 *buffer = buf; | |
| /* Switch to array read mode */ | |
| writel(EFUSE_LOAD | EFUSE_PGENB | RK3399_STROBSFTSEL | RK3399_RSB, | |
| efuse->base + EFUSE_CTRL); | |
| udelay(1); | |
| while (size--) { | |
| setbits_le32(efuse->base + EFUSE_CTRL, | |
| EFUSE_STROBE | RK3399_ADDR(offset++)); | |
| udelay(1); | |
| *buffer++ = readl(efuse->base + EFUSE_DOUT); | |
| clrbits_le32(efuse->base + EFUSE_CTRL, EFUSE_STROBE); | |
| udelay(1); | |
| } | |
| /* Switch to power-down mode */ | |
| writel(RK3399_PD | EFUSE_CSB, efuse->base + EFUSE_CTRL); | |
| return 0; | |
| } | |
| static int rockchip_efuse_read(struct udevice *dev, int offset, | |
| void *buf, int size) | |
| { | |
| const struct rockchip_efuse_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_efuse_ops = { | |
| .read = rockchip_efuse_read, | |
| }; | |
| static int rockchip_efuse_of_to_plat(struct udevice *dev) | |
| { | |
| struct rockchip_efuse_plat *plat = dev_get_plat(dev); | |
| plat->base = dev_read_addr_ptr(dev); | |
| return 0; | |
| } | |
| static const struct rockchip_efuse_data rk3036_data = { | |
| .read = rockchip_rk3036_efuse_read, | |
| .size = 0x20, | |
| }; | |
| static const struct rockchip_efuse_data rk3128_data = { | |
| .read = rockchip_rk3128_efuse_read, | |
| .size = 0x40, | |
| }; | |
| static const struct rockchip_efuse_data rk3288_data = { | |
| .read = rockchip_rk3288_efuse_read, | |
| .size = 0x20, | |
| }; | |
| static const struct rockchip_efuse_data rk3328_data = { | |
| .read = rockchip_rk3328_efuse_read, | |
| .offset = 0x60, | |
| .size = 0x20, | |
| .block_size = 4, | |
| }; | |
| static const struct rockchip_efuse_data rk3399_data = { | |
| .read = rockchip_rk3399_efuse_read, | |
| .size = 0x80, | |
| .block_size = 4, | |
| }; | |
| static const struct udevice_id rockchip_efuse_ids[] = { | |
| { | |
| .compatible = "rockchip,rk3036-efuse", | |
| .data = (ulong)&rk3036_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3066a-efuse", | |
| .data = (ulong)&rk3288_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3128-efuse", | |
| .data = (ulong)&rk3128_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3188-efuse", | |
| .data = (ulong)&rk3288_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3228-efuse", | |
| .data = (ulong)&rk3288_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3288-efuse", | |
| .data = (ulong)&rk3288_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3328-efuse", | |
| .data = (ulong)&rk3328_data, | |
| }, | |
| { | |
| .compatible = "rockchip,rk3399-efuse", | |
| .data = (ulong)&rk3399_data, | |
| }, | |
| {} | |
| }; | |
| U_BOOT_DRIVER(rockchip_efuse) = { | |
| .name = "rockchip_efuse", | |
| .id = UCLASS_MISC, | |
| .of_match = rockchip_efuse_ids, | |
| .of_to_plat = rockchip_efuse_of_to_plat, | |
| .plat_auto = sizeof(struct rockchip_efuse_plat), | |
| .ops = &rockchip_efuse_ops, | |
| }; |