[PATCH 3/9] firmware: imx: ele: Add API functions for OCOTP fuse access
From: Frieder Schrempf <hidden>
Date: 2026-06-16 11:54:00
Also in:
imx, linux-devicetree, lkml
Subsystem:
the rest · Maintainer:
Linus Torvalds
From: Frieder Schrempf <redacted> The ELE S400 API provides read and write access to the OCOTP fuse registers. This adds the necessary API functions imx_se_read_fuse() and imx_se_write_fuse() to be used by other drivers such as the OCOTP S400 NVMEM driver. This is ported from the downstream vendor kernel. Signed-off-by: Frieder Schrempf <redacted> --- drivers/firmware/imx/ele_base_msg.c | 122 ++++++++++++++++++++++++++++++++++++ drivers/firmware/imx/ele_base_msg.h | 6 ++ include/linux/firmware/imx/se_api.h | 3 + 3 files changed, 131 insertions(+)
diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
index ec718d322abc..281d223aa144 100644
--- a/drivers/firmware/imx/ele_base_msg.c
+++ b/drivers/firmware/imx/ele_base_msg.c@@ -8,6 +8,7 @@ #include <linux/cleanup.h> #include <linux/completion.h> #include <linux/dma-mapping.h> +#include <linux/firmware/imx/se_api.h> #include <linux/genalloc.h> #include "ele_base_msg.h"
@@ -303,3 +304,124 @@ int ele_debug_dump(struct se_if_priv *priv) return ret; } + +static int ele_read_fuse(struct se_if_priv *priv, uint16_t fuse_id, u32 *value) +{ + struct se_api_msg *tx_msg __free(kfree) = NULL; + struct se_api_msg *rx_msg __free(kfree) = NULL; + int rx_msg_sz = ELE_READ_FUSE_RSP_MSG_SZ; + int ret = 0; + + if (!priv) + return -EINVAL; + + tx_msg = kzalloc(ELE_READ_FUSE_REQ_MSG_SZ, GFP_KERNEL); + if (!tx_msg) + return -ENOMEM; + + rx_msg = kzalloc(rx_msg_sz, GFP_KERNEL); + if (!rx_msg) + return -ENOMEM; + + ret = se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header, + ELE_READ_FUSE_REQ, ELE_READ_FUSE_REQ_MSG_SZ, + true); + if (ret) + return ret; + + tx_msg->data[0] = fuse_id; + + ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, + ELE_READ_FUSE_REQ_MSG_SZ, rx_msg, rx_msg_sz); + if (ret < 0) + return ret; + + ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_READ_FUSE_REQ, + rx_msg_sz, true); + if (ret) + return ret; + + *value = rx_msg->data[1]; + + return 0; +} + +/** + * imx_se_read_fuse() - API to request SE-FW to read the fuse value. + * @se_if_data: refs to data attached to the se interface. + * @fuse_id: fuse identifier to read. + * @value: unsigned integer array to store the fuse values. + * + * Secure enclave like EdgeLock Enclave, manages the fuses. This API + * requests the FW to read the fuses. FW responds with the read values. + * + * Context: + * + * Return value: + * 0, means success. + * < 0, means failure. + */ +int imx_se_read_fuse(void *se_if_data, uint16_t fuse_id, u32 *value) +{ + return ele_read_fuse((struct se_if_priv *)se_if_data, fuse_id, value); +} +EXPORT_SYMBOL_GPL(imx_se_read_fuse); + +static int ele_write_fuse(struct se_if_priv *priv, uint16_t fuse_id, u32 value) +{ + struct se_api_msg *tx_msg __free(kfree) = NULL; + struct se_api_msg *rx_msg __free(kfree) = NULL; + int ret = 0; + + if (!priv) + return -EINVAL; + + tx_msg = kzalloc(ELE_WRITE_FUSE_REQ_MSG_SZ, GFP_KERNEL); + if (!tx_msg) + return -ENOMEM; + + rx_msg = kzalloc(ELE_WRITE_FUSE_RSP_MSG_SZ, GFP_KERNEL); + if (!rx_msg) + return -ENOMEM; + + ret = se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header, + ELE_WRITE_FUSE, ELE_WRITE_FUSE_REQ_MSG_SZ, + true); + if (ret) + return ret; + + tx_msg->data[0] = (32 << 16) | (fuse_id << 5); + tx_msg->data[1] = value; + + ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, + ELE_WRITE_FUSE_REQ_MSG_SZ, rx_msg, + ELE_WRITE_FUSE_RSP_MSG_SZ); + if (ret < 0) + return ret; + + ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_WRITE_FUSE, + ELE_WRITE_FUSE_RSP_MSG_SZ, true); + + return ret; +} + +/** + * imx_se_write_fuse() - API to request SE-FW to write to fuses. + * @se_if_data: refs to data attached to the se interface. + * @fuse_id: fuse identifier to write to. + * @value: unsigned integer value that to be written to the fuse. + * + * Secure enclave like EdgeLock Enclave, manages the fuses. This API + * requests the FW to write the fuse with the given value. + * + * Context: + * + * Return value: + * 0, means success. + * < 0, means failure. + */ +int imx_se_write_fuse(void *se_if_data, uint16_t fuse_id, u32 value) +{ + return ele_write_fuse((struct se_if_priv *)se_if_data, fuse_id, value); +} +EXPORT_SYMBOL_GPL(imx_se_write_fuse);
diff --git a/drivers/firmware/imx/ele_base_msg.h b/drivers/firmware/imx/ele_base_msg.h
index 1c5d6791b323..2af3ada2ad07 100644
--- a/drivers/firmware/imx/ele_base_msg.h
+++ b/drivers/firmware/imx/ele_base_msg.h@@ -36,6 +36,12 @@ #define ELE_GET_INFO_REQ_MSG_SZ 0x10 #define ELE_GET_INFO_RSP_MSG_SZ 0x08 +#define ELE_READ_FUSE_REQ_MSG_SZ (8) +#define ELE_READ_FUSE_RSP_MSG_SZ (12) + +#define ELE_WRITE_FUSE_REQ_MSG_SZ (12) +#define ELE_WRITE_FUSE_RSP_MSG_SZ (12) + #define MAX_UID_SIZE (16) #define DEV_GETINFO_ROM_PATCH_SHA_SZ (32) #define DEV_GETINFO_FW_SHA_SZ (32)
diff --git a/include/linux/firmware/imx/se_api.h b/include/linux/firmware/imx/se_api.h
index b1c4c9115d7b..ed766b1d48f8 100644
--- a/include/linux/firmware/imx/se_api.h
+++ b/include/linux/firmware/imx/se_api.h@@ -11,4 +11,7 @@ #define SOC_ID_OF_IMX8ULP 0x084d #define SOC_ID_OF_IMX93 0x9300 +int imx_se_read_fuse(void *se_if_data, uint16_t fuse_id, u32 *value); +int imx_se_write_fuse(void *se_if_data, uint16_t fuse_id, u32 value); + #endif /* __SE_API_H__ */
--
2.54.0