diff mbox series

[v3,7/7] drivers: misc: Add driver to access ZynqMP efuses

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

Commit Message

Lukas Funke June 4, 2024, 2:27 p.m. UTC
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

Comments

Michal Simek June 6, 2024, 2:29 p.m. UTC | #1
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 mbox series

Patch

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,
+};