Message ID | 20240604142741.425307-8-lukas.funke-oss@weidmueller.com |
---|---|
State | Changes Requested |
Delegated to: | Michal Simek |
Headers | show |
Series | Add eFuse access for ZynqMP | expand |
On 6/4/24 16:27, lukas.funke-oss@weidmueller.com wrote: > From: Lukas Funke <lukas.funke@weidmueller.com> > > Add driver to access ZynqMP efuses. This is a u-boot port of [1]. > > Note: Accessing eFuses requires eFuse access to be enabled in the > underlying PMU firmware. > > [1] https://lore.kernel.org/all/20240224114516.86365-8-srinivas.kandagatla@linaro.org/ > > Signed-off-by: Lukas Funke <lukas.funke@weidmueller.com> > --- > > Changes in v3: > - Align ZynqMP eFuse driver with Linux kernel > - Adapt versal, versal-net and zynqmp to use common chip-id function > - Enable CMD_FUSE and ZYNQMP_EFUSE for zynqmp_virt and zynqmp_kria defconfig > - Use 'dev_err' instead 'log_msg_ret' if possible > > Changes in v2: > - Drop vendor specific fuse cmd, use existing fuse cmd > - Minor code refactoring (reverse x-mas tree) > > drivers/misc/Kconfig | 8 + > drivers/misc/Makefile | 1 + > drivers/misc/zynqmp_efuse.c | 360 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 369 insertions(+) > create mode 100644 drivers/misc/zynqmp_efuse.c > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index 6009d55f400..c07f50c9a76 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -298,6 +298,14 @@ config FSL_SEC_MON > Security Monitor can be transitioned on any security failures, > like software violations or hardware security violations. > > +config ZYNQMP_EFUSE > + bool "Enable ZynqMP eFUSE Driver" > + depends on ZYNQMP_FIRMWARE > + help > + Enable access to Zynq UltraScale (ZynqMP) eFUSEs thought PMU firmware > + interface. ZnyqMP has 256 eFUSEs where some of them are security related > + and cannot be read back (i.e. AES key). > + > choice > prompt "Security monitor interaction endianess" > depends on FSL_SEC_MON > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index e53d52c47b3..68ba5648eab 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -92,3 +92,4 @@ obj-$(CONFIG_ESM_K3) += k3_esm.o > obj-$(CONFIG_ESM_PMIC) += esm_pmic.o > obj-$(CONFIG_SL28CPLD) += sl28cpld.o > obj-$(CONFIG_SPL_SOCFPGA_DT_REG) += socfpga_dtreg.o > +obj-$(CONFIG_ZYNQMP_EFUSE) += zynqmp_efuse.o > diff --git a/drivers/misc/zynqmp_efuse.c b/drivers/misc/zynqmp_efuse.c > new file mode 100644 > index 00000000000..d12de7494d2 > --- /dev/null > +++ b/drivers/misc/zynqmp_efuse.c > @@ -0,0 +1,360 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * (C) Copyright 2014 - 2015 Xilinx, Inc. > + * Michal Simek <michal.simek@amd.com> Not sure which version you used as source but please sync up with kernel version you used. I expect there will be much newer dates because efuse were added recently. > + * > + * (C) Copyright 2024 Weidmueller Interface GmbH > + * Lukas Funke <lukas.funke@weidmueller.com> > + */ > + > +#include <compiler.h> > +#include <linux/types.h> > +#include <linux/errno.h> > +#include <zynqmp_firmware.h> > +#include <asm/dma-mapping.h> > +#include <dm.h> > +#include <dm/device_compat.h> > +#include <misc.h> > + > +#define SILICON_REVISION_MASK 0xF > +#define P_USER_0_64_UPPER_MASK GENMASK(31, 16) > +#define P_USER_127_LOWER_4_BIT_MASK GENMASK(3, 0) > +#define WORD_INBYTES 4 > +#define SOC_VER_SIZE 0x4 > +#define EFUSE_MEMORY_SIZE 0x177 > +#define UNUSED_SPACE 0x8 > +#define ZYNQMP_NVMEM_SIZE (SOC_VER_SIZE + UNUSED_SPACE + \ > + EFUSE_MEMORY_SIZE) > +#define SOC_VERSION_OFFSET 0x0 > +#define EFUSE_START_OFFSET 0xC > +#define EFUSE_END_OFFSET 0xFC > +#define EFUSE_PUF_START_OFFSET 0x100 > +#define EFUSE_PUF_MID_OFFSET 0x140 > +#define EFUSE_PUF_END_OFFSET 0x17F > +#define EFUSE_NOT_ENABLED 29 > + > +/* > + * efuse access type > + */ > +enum efuse_access { > + EFUSE_READ = 0, > + EFUSE_WRITE > +}; > + > +/** > + * struct xilinx_efuse - the basic structure > + * @src: address of the buffer to store the data to be write/read > + * @size: read/write word count > + * @offset: read/write offset > + * @flag: 0 - represents efuse read and 1- represents efuse write > + * @pufuserfuse:0 - represents non-puf efuses, offset is used for read/write > + * 1 - represents puf user fuse row number. > + * > + * this structure stores all the required details to > + * read/write efuse memory. > + */ > +struct xilinx_efuse { > + u64 src; > + u32 size; > + u32 offset; > + enum efuse_access flag; > + u32 pufuserfuse; > +}; > + > +/** > + * struct efuse_map_entry - offset and length of zynqmp fuses > + * @offset: offset of efuse to be read/write > + * @length: length of efuse > + */ > +struct efuse_map_entry { > + u32 offset; > + u32 length; > +}; > + > +struct efuse_map_entry zynqmp_efuse_table[] = { > + {0x000, 0x04}, /* soc revision */ > + {0x00c, 0x0c}, /* SoC DNA */ > + {0x020, 0x04}, /* efuse-usr0 */ > + {0x024, 0x04}, /* efuse-usr1 */ > + {0x028, 0x04}, /* efuse-usr2 */ > + {0x02c, 0x04}, /* efuse-usr3 */ > + {0x030, 0x04}, /* efuse-usr4 */ > + {0x034, 0x04}, /* efuse-usr5 */ > + {0x038, 0x04}, /* efuse-usr6 */ > + {0x03c, 0x04}, /* efuse-usr7 */ > + {0x040, 0x04}, /* efuse-miscusr */ > + {0x050, 0x04}, /* efuse-chash */ > + {0x054, 0x04}, /* efuse-pufmisc */ > + {0x058, 0x04}, /* efuse-sec */ > + {0x05c, 0x04}, /* efuse-spkid */ > + {0x060, 0x30}, /* efuse-aeskey */ > + {0x0a0, 0x30}, /* ppk0-hash */ > + {0x0d0, 0x30}, /* ppk1-hash */ > + {0x100, 0x7f}, /* pufuser */ > +}; > + > +static int zynqmp_efuse_get_length(u32 offset, u32 *base_offset, u32 *len) > +{ > + struct efuse_map_entry *fuse; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(zynqmp_efuse_table); ++i) { > + fuse = &zynqmp_efuse_table[i]; > + if (offset >= fuse->offset && > + offset < fuse->offset + fuse->length) { > + *base_offset = fuse->offset; > + *len = fuse->length; > + return 0; > + } nit: wrong indentation one less tab for } > + } > + > + return -ENOENT; > +} > + > +static int zynqmp_efuse_access(void *context, unsigned int offset, > + void *val, size_t bytes, enum efuse_access flag, > + unsigned int pufflag) > +{ > + struct udevice *dev = context; > + struct xilinx_efuse *efuse; > + ulong dma_addr; > + ulong dma_buf; > + size_t words = bytes / WORD_INBYTES; > + int ret; > + int value; > + char *data; > + > + if (bytes % WORD_INBYTES != 0) { > + dev_err(dev, "Bytes requested should be word aligned\n"); > + return -EOPNOTSUPP; > + } > + > + if (pufflag == 0 && offset % WORD_INBYTES) { > + dev_err(dev, "Offset requested should be word aligned\n"); > + return -EOPNOTSUPP; > + } > + > + if (pufflag == 1 && flag == EFUSE_WRITE) { > + memcpy(&value, val, bytes); > + if ((offset == EFUSE_PUF_START_OFFSET || > + offset == EFUSE_PUF_MID_OFFSET) && > + value & P_USER_0_64_UPPER_MASK) { > + dev_err(dev, "Only lower 4 bytes are allowed to be programmed in P_USER_0 & P_USER_64\n"); > + return -EOPNOTSUPP; > + } > + > + if (offset == EFUSE_PUF_END_OFFSET && > + (value & P_USER_127_LOWER_4_BIT_MASK)) { > + dev_err(dev, "Only MSB 28 bits are allowed to be programmed for P_USER_127\n"); > + return -EOPNOTSUPP; > + } > + } > + > + efuse = dma_alloc_coherent(sizeof(struct xilinx_efuse), &dma_addr); > + if (!efuse) > + return -ENOMEM; > + > + data = dma_alloc_coherent(bytes, &dma_buf); > + if (!data) { > + ret = -ENOMEM; > + goto efuse_data_fail; > + } > + > + if (flag == EFUSE_WRITE) { > + memcpy(data, val, bytes); > + efuse->flag = EFUSE_WRITE; > + } else { > + efuse->flag = EFUSE_READ; > + } > + > + efuse->src = dma_buf; > + efuse->size = words; > + efuse->offset = offset; > + efuse->pufuserfuse = pufflag; > + > + flush_dcache_range((ulong)efuse, (ulong)efuse + > + roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN)); > + flush_dcache_range((ulong)data, (ulong)data + > + roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN)); > + > + zynqmp_pm_efuse_access(dma_addr, (u32 *)&ret); > + if (ret != 0) { > + if (ret == EFUSE_NOT_ENABLED) { > + dev_err(dev, "efuse access is not enabled\n"); > + ret = -EOPNOTSUPP; > + } else { > + dev_err(dev, "Error in efuse read %x\n", ret); > + ret = -EPERM; > + } > + goto efuse_access_err; > + } > + > + if (flag == EFUSE_READ) > + memcpy(val, data, bytes); > +efuse_access_err: > + dma_free_coherent(data); > +efuse_data_fail: > + dma_free_coherent(efuse); > + > + return ret; > +} > + > +static int zynqmp_nvmem_read(void *context, unsigned int offset, void *val, > + size_t bytes) > +{ > + struct udevice *dev = context; > + int ret; > + int pufflag = 0; > + int idcode; > + int version; > + > + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) > + pufflag = 1; > + > + switch (offset) { > + /* Soc version offset is zero */ > + case SOC_VERSION_OFFSET: > + if (bytes != SOC_VER_SIZE) > + return -EOPNOTSUPP; > + > + ret = zynqmp_pm_get_chipid((u32 *)&idcode, (u32 *)&version); > + if (ret < 0) > + return ret; > + > + dev_dbg(dev, "Read chipid val %x %x\n", idcode, version); > + *(int *)val = version & SILICON_REVISION_MASK; > + break; > + /* Efuse offset starts from 0xc */ > + case EFUSE_START_OFFSET ... EFUSE_END_OFFSET: > + case EFUSE_PUF_START_OFFSET ... EFUSE_PUF_END_OFFSET: > + ret = zynqmp_efuse_access(context, offset, val, > + bytes, EFUSE_READ, pufflag); > + break; > + default: > + *(u32 *)val = 0xDEADBEEF; > + ret = 0; > + break; > + } > + > + return ret; > +} > + > +static int zynqmp_nvmem_write(void *context, > + unsigned int offset, void *val, size_t bytes) > +{ > + int pufflag = 0; > + > + if (offset < EFUSE_START_OFFSET || offset > EFUSE_PUF_END_OFFSET) > + return -EOPNOTSUPP; > + > + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) > + pufflag = 1; > + > + return zynqmp_efuse_access(context, offset, > + val, bytes, EFUSE_WRITE, pufflag); > +} > + > +int fuse_read(u32 bank, u32 word, u32 *val) > +{ > + u32 base_offset, offset, len; > + struct udevice *dev; > + u8 buf[128]; /* maximal size of efuse is 0x7f (pufuse) */ > + int ret; > + > + ret = uclass_get_device_by_driver(UCLASS_MISC, > + DM_DRIVER_GET(zynqmp_efuse), &dev); > + if (ret) > + return log_msg_ret("uclass_drv", ret); > + > + ret = zynqmp_efuse_get_length(word, &base_offset, &len); > + if (ret) { > + dev_err(dev, "Invalid eFuse word offset\n"); > + return ret; > + } > + > + if (len > sizeof(buf)) > + return -EINVAL; > + > + memset(buf, 0, sizeof(buf)); > + > + ret = misc_read(dev, base_offset, (void *)buf, len); > + if (ret) { > + dev_err(dev, "Cannot read eFuse @%u: %d", word, ret); > + return ret; > + } > + > + offset = word - base_offset; > + *val = ((u32 *)buf)[offset]; > + > + return 0; > +} > + > +int fuse_prog(u32 bank, u32 word, u32 val) > +{ > + u32 base_offset, len; > + struct udevice *dev; > + int ret; > + > + ret = uclass_get_device_by_driver(UCLASS_MISC, > + DM_DRIVER_GET(zynqmp_efuse), &dev); > + if (ret) > + return log_msg_ret("uclass_drv", ret); > + > + ret = zynqmp_efuse_get_length(word, &base_offset, &len); > + if (ret) { > + dev_err(dev, "Invalid eFuse word offset\n"); > + return ret; > + } > + > + if (word != base_offset || len != sizeof(val)) > + return -EINVAL; > + > + ret = misc_write(dev, word, (void *)&val, sizeof(val)); > + if (ret) { > + dev_err(dev, "Cannot write eFuse @%u: %d", word, ret); > + return ret; > + } > + > + return 0; > +} > + > +int fuse_sense(u32 bank, u32 word, u32 *val) > +{ > + /* sense is not supported */ > + return -ENOSYS; > +} > + > +int fuse_override(u32 bank, u32 word, u32 val) > +{ > + /* overriding is not supported */ > + return -ENOSYS; > +} > + > +static int zynqmp_nvmem_read_compat(struct udevice *dev, int offset, > + void *val, int bytes) > +{ > + return zynqmp_nvmem_read((void *)dev, offset, val, bytes); > +} > + > +static int zynqmp_nvmem_write_compat(struct udevice *dev, int offset, > + const void *val, int bytes) > +{ > + return zynqmp_nvmem_write((void *)dev, offset, (void *)val, bytes); > +} > + > +static const struct udevice_id zynqmp_efuse_match[] = { > + { .compatible = "xlnx,zynqmp-nvmem-fw", }, > + { /* sentinel */ }, > +}; > + > +static const struct misc_ops zynqmp_efuse_ops = { > + .read = zynqmp_nvmem_read_compat, > + .write = zynqmp_nvmem_write_compat, > +}; > + > +U_BOOT_DRIVER(zynqmp_efuse) = { > + .name = "zynqmp_efuse", > + .id = UCLASS_MISC, > + .of_match = zynqmp_efuse_match, > + .ops = &zynqmp_efuse_ops, > +}; The rest looks good. When that small things are fixed I will merge it. Thanks, Michal
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6009d55f400..c07f50c9a76 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -298,6 +298,14 @@ config FSL_SEC_MON Security Monitor can be transitioned on any security failures, like software violations or hardware security violations. +config ZYNQMP_EFUSE + bool "Enable ZynqMP eFUSE Driver" + depends on ZYNQMP_FIRMWARE + help + Enable access to Zynq UltraScale (ZynqMP) eFUSEs thought PMU firmware + interface. ZnyqMP has 256 eFUSEs where some of them are security related + and cannot be read back (i.e. AES key). + choice prompt "Security monitor interaction endianess" depends on FSL_SEC_MON diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e53d52c47b3..68ba5648eab 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -92,3 +92,4 @@ obj-$(CONFIG_ESM_K3) += k3_esm.o obj-$(CONFIG_ESM_PMIC) += esm_pmic.o obj-$(CONFIG_SL28CPLD) += sl28cpld.o obj-$(CONFIG_SPL_SOCFPGA_DT_REG) += socfpga_dtreg.o +obj-$(CONFIG_ZYNQMP_EFUSE) += zynqmp_efuse.o diff --git a/drivers/misc/zynqmp_efuse.c b/drivers/misc/zynqmp_efuse.c new file mode 100644 index 00000000000..d12de7494d2 --- /dev/null +++ b/drivers/misc/zynqmp_efuse.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2014 - 2015 Xilinx, Inc. + * Michal Simek <michal.simek@amd.com> + * + * (C) Copyright 2024 Weidmueller Interface GmbH + * Lukas Funke <lukas.funke@weidmueller.com> + */ + +#include <compiler.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <zynqmp_firmware.h> +#include <asm/dma-mapping.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <misc.h> + +#define SILICON_REVISION_MASK 0xF +#define P_USER_0_64_UPPER_MASK GENMASK(31, 16) +#define P_USER_127_LOWER_4_BIT_MASK GENMASK(3, 0) +#define WORD_INBYTES 4 +#define SOC_VER_SIZE 0x4 +#define EFUSE_MEMORY_SIZE 0x177 +#define UNUSED_SPACE 0x8 +#define ZYNQMP_NVMEM_SIZE (SOC_VER_SIZE + UNUSED_SPACE + \ + EFUSE_MEMORY_SIZE) +#define SOC_VERSION_OFFSET 0x0 +#define EFUSE_START_OFFSET 0xC +#define EFUSE_END_OFFSET 0xFC +#define EFUSE_PUF_START_OFFSET 0x100 +#define EFUSE_PUF_MID_OFFSET 0x140 +#define EFUSE_PUF_END_OFFSET 0x17F +#define EFUSE_NOT_ENABLED 29 + +/* + * efuse access type + */ +enum efuse_access { + EFUSE_READ = 0, + EFUSE_WRITE +}; + +/** + * struct xilinx_efuse - the basic structure + * @src: address of the buffer to store the data to be write/read + * @size: read/write word count + * @offset: read/write offset + * @flag: 0 - represents efuse read and 1- represents efuse write + * @pufuserfuse:0 - represents non-puf efuses, offset is used for read/write + * 1 - represents puf user fuse row number. + * + * this structure stores all the required details to + * read/write efuse memory. + */ +struct xilinx_efuse { + u64 src; + u32 size; + u32 offset; + enum efuse_access flag; + u32 pufuserfuse; +}; + +/** + * struct efuse_map_entry - offset and length of zynqmp fuses + * @offset: offset of efuse to be read/write + * @length: length of efuse + */ +struct efuse_map_entry { + u32 offset; + u32 length; +}; + +struct efuse_map_entry zynqmp_efuse_table[] = { + {0x000, 0x04}, /* soc revision */ + {0x00c, 0x0c}, /* SoC DNA */ + {0x020, 0x04}, /* efuse-usr0 */ + {0x024, 0x04}, /* efuse-usr1 */ + {0x028, 0x04}, /* efuse-usr2 */ + {0x02c, 0x04}, /* efuse-usr3 */ + {0x030, 0x04}, /* efuse-usr4 */ + {0x034, 0x04}, /* efuse-usr5 */ + {0x038, 0x04}, /* efuse-usr6 */ + {0x03c, 0x04}, /* efuse-usr7 */ + {0x040, 0x04}, /* efuse-miscusr */ + {0x050, 0x04}, /* efuse-chash */ + {0x054, 0x04}, /* efuse-pufmisc */ + {0x058, 0x04}, /* efuse-sec */ + {0x05c, 0x04}, /* efuse-spkid */ + {0x060, 0x30}, /* efuse-aeskey */ + {0x0a0, 0x30}, /* ppk0-hash */ + {0x0d0, 0x30}, /* ppk1-hash */ + {0x100, 0x7f}, /* pufuser */ +}; + +static int zynqmp_efuse_get_length(u32 offset, u32 *base_offset, u32 *len) +{ + struct efuse_map_entry *fuse; + int i; + + for (i = 0; i < ARRAY_SIZE(zynqmp_efuse_table); ++i) { + fuse = &zynqmp_efuse_table[i]; + if (offset >= fuse->offset && + offset < fuse->offset + fuse->length) { + *base_offset = fuse->offset; + *len = fuse->length; + return 0; + } + } + + return -ENOENT; +} + +static int zynqmp_efuse_access(void *context, unsigned int offset, + void *val, size_t bytes, enum efuse_access flag, + unsigned int pufflag) +{ + struct udevice *dev = context; + struct xilinx_efuse *efuse; + ulong dma_addr; + ulong dma_buf; + size_t words = bytes / WORD_INBYTES; + int ret; + int value; + char *data; + + if (bytes % WORD_INBYTES != 0) { + dev_err(dev, "Bytes requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 0 && offset % WORD_INBYTES) { + dev_err(dev, "Offset requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 1 && flag == EFUSE_WRITE) { + memcpy(&value, val, bytes); + if ((offset == EFUSE_PUF_START_OFFSET || + offset == EFUSE_PUF_MID_OFFSET) && + value & P_USER_0_64_UPPER_MASK) { + dev_err(dev, "Only lower 4 bytes are allowed to be programmed in P_USER_0 & P_USER_64\n"); + return -EOPNOTSUPP; + } + + if (offset == EFUSE_PUF_END_OFFSET && + (value & P_USER_127_LOWER_4_BIT_MASK)) { + dev_err(dev, "Only MSB 28 bits are allowed to be programmed for P_USER_127\n"); + return -EOPNOTSUPP; + } + } + + efuse = dma_alloc_coherent(sizeof(struct xilinx_efuse), &dma_addr); + if (!efuse) + return -ENOMEM; + + data = dma_alloc_coherent(bytes, &dma_buf); + if (!data) { + ret = -ENOMEM; + goto efuse_data_fail; + } + + if (flag == EFUSE_WRITE) { + memcpy(data, val, bytes); + efuse->flag = EFUSE_WRITE; + } else { + efuse->flag = EFUSE_READ; + } + + efuse->src = dma_buf; + efuse->size = words; + efuse->offset = offset; + efuse->pufuserfuse = pufflag; + + flush_dcache_range((ulong)efuse, (ulong)efuse + + roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN)); + flush_dcache_range((ulong)data, (ulong)data + + roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN)); + + zynqmp_pm_efuse_access(dma_addr, (u32 *)&ret); + if (ret != 0) { + if (ret == EFUSE_NOT_ENABLED) { + dev_err(dev, "efuse access is not enabled\n"); + ret = -EOPNOTSUPP; + } else { + dev_err(dev, "Error in efuse read %x\n", ret); + ret = -EPERM; + } + goto efuse_access_err; + } + + if (flag == EFUSE_READ) + memcpy(val, data, bytes); +efuse_access_err: + dma_free_coherent(data); +efuse_data_fail: + dma_free_coherent(efuse); + + return ret; +} + +static int zynqmp_nvmem_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct udevice *dev = context; + int ret; + int pufflag = 0; + int idcode; + int version; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + switch (offset) { + /* Soc version offset is zero */ + case SOC_VERSION_OFFSET: + if (bytes != SOC_VER_SIZE) + return -EOPNOTSUPP; + + ret = zynqmp_pm_get_chipid((u32 *)&idcode, (u32 *)&version); + if (ret < 0) + return ret; + + dev_dbg(dev, "Read chipid val %x %x\n", idcode, version); + *(int *)val = version & SILICON_REVISION_MASK; + break; + /* Efuse offset starts from 0xc */ + case EFUSE_START_OFFSET ... EFUSE_END_OFFSET: + case EFUSE_PUF_START_OFFSET ... EFUSE_PUF_END_OFFSET: + ret = zynqmp_efuse_access(context, offset, val, + bytes, EFUSE_READ, pufflag); + break; + default: + *(u32 *)val = 0xDEADBEEF; + ret = 0; + break; + } + + return ret; +} + +static int zynqmp_nvmem_write(void *context, + unsigned int offset, void *val, size_t bytes) +{ + int pufflag = 0; + + if (offset < EFUSE_START_OFFSET || offset > EFUSE_PUF_END_OFFSET) + return -EOPNOTSUPP; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + return zynqmp_efuse_access(context, offset, + val, bytes, EFUSE_WRITE, pufflag); +} + +int fuse_read(u32 bank, u32 word, u32 *val) +{ + u32 base_offset, offset, len; + struct udevice *dev; + u8 buf[128]; /* maximal size of efuse is 0x7f (pufuse) */ + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MISC, + DM_DRIVER_GET(zynqmp_efuse), &dev); + if (ret) + return log_msg_ret("uclass_drv", ret); + + ret = zynqmp_efuse_get_length(word, &base_offset, &len); + if (ret) { + dev_err(dev, "Invalid eFuse word offset\n"); + return ret; + } + + if (len > sizeof(buf)) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + + ret = misc_read(dev, base_offset, (void *)buf, len); + if (ret) { + dev_err(dev, "Cannot read eFuse @%u: %d", word, ret); + return ret; + } + + offset = word - base_offset; + *val = ((u32 *)buf)[offset]; + + return 0; +} + +int fuse_prog(u32 bank, u32 word, u32 val) +{ + u32 base_offset, len; + struct udevice *dev; + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MISC, + DM_DRIVER_GET(zynqmp_efuse), &dev); + if (ret) + return log_msg_ret("uclass_drv", ret); + + ret = zynqmp_efuse_get_length(word, &base_offset, &len); + if (ret) { + dev_err(dev, "Invalid eFuse word offset\n"); + return ret; + } + + if (word != base_offset || len != sizeof(val)) + return -EINVAL; + + ret = misc_write(dev, word, (void *)&val, sizeof(val)); + if (ret) { + dev_err(dev, "Cannot write eFuse @%u: %d", word, ret); + return ret; + } + + return 0; +} + +int fuse_sense(u32 bank, u32 word, u32 *val) +{ + /* sense is not supported */ + return -ENOSYS; +} + +int fuse_override(u32 bank, u32 word, u32 val) +{ + /* overriding is not supported */ + return -ENOSYS; +} + +static int zynqmp_nvmem_read_compat(struct udevice *dev, int offset, + void *val, int bytes) +{ + return zynqmp_nvmem_read((void *)dev, offset, val, bytes); +} + +static int zynqmp_nvmem_write_compat(struct udevice *dev, int offset, + const void *val, int bytes) +{ + return zynqmp_nvmem_write((void *)dev, offset, (void *)val, bytes); +} + +static const struct udevice_id zynqmp_efuse_match[] = { + { .compatible = "xlnx,zynqmp-nvmem-fw", }, + { /* sentinel */ }, +}; + +static const struct misc_ops zynqmp_efuse_ops = { + .read = zynqmp_nvmem_read_compat, + .write = zynqmp_nvmem_write_compat, +}; + +U_BOOT_DRIVER(zynqmp_efuse) = { + .name = "zynqmp_efuse", + .id = UCLASS_MISC, + .of_match = zynqmp_efuse_match, + .ops = &zynqmp_efuse_ops, +};