From: Lukas Funke <lukas.fu...@weidmueller.com>
Add driver to access ZynqMP efuses. This is a u-boot port of [1].
[1]
https://lore.kernel.org/all/20240224114516.86365-8-srinivas.kandaga...@linaro.org/
Signed-off-by: Lukas Funke <lukas.fu...@weidmueller.com>
---
drivers/misc/Kconfig | 8 ++
drivers/misc/Makefile | 1 +
drivers/misc/zynqmp_efuse.c | 213 ++++++++++++++++++++++++++++++++++++
3 files changed, 222 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..0cfc42a4f39
--- /dev/null
+++ b/drivers/misc/zynqmp_efuse.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2014 - 2015 Xilinx, Inc.
+ * Michal Simek <michal.si...@amd.com>
+ *
+ * (C) Copyright 2024 Weidmueller Interface GmbH
+ * Lukas Funke <lukas.fu...@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 0x5FFF0000
+#define P_USER_127_LOWER_4_BIT_MASK 0xF
+#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)
+#define EFUSE_READ (0)
+#define EFUSE_WRITE (1)
+
+/**
+ * struct xilinx_efuse - the basic structure
+ * @src: address of the buffer to store the data to be write/read
+ * @size: no of words to be read/write
+ * @offset: offset to be read/write`
+ * @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;
+ u32 flag;
+ u32 pufuserfuse;
+};
+
+static int zynqmp_efuse_access(struct udevice *dev, unsigned int offset,
+ void *val, size_t bytes, unsigned int flag,
+ unsigned int pufflag)
+{
+ size_t words = bytes / WORD_INBYTES;
+ ulong dma_addr, dma_buf;
+ struct xilinx_efuse *efuse;
+ char *data;
+ int ret, value;
+
+ 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) {
+ dma_free_coherent(efuse);
+ return -ENOMEM;
+ }
+
+ 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));