* [PATCH] SPI: Introduce initial EEPROM driver mode support
@ 2024-12-04 11:38 Loureiro, Joao
2025-02-24 11:43 ` [PATCH] spi: Introduce initial eeprom " Loureiro, Joao
2025-03-05 12:26 ` [PATCH v2] spi: Introduce initial EEPROM " Loureiro, Joao
0 siblings, 2 replies; 18+ messages in thread
From: Loureiro, Joao @ 2024-12-04 11:38 UTC (permalink / raw)
To: u-boot@lists.denx.de
This patch introduces the initial SPI EEPROM driver mode support
analogous to the I2C EEPROM driver mode support. The SPI EEPROM
driver mode support is enabled by default in the sandbox_defconfig.
Signed-off-by: João Loureiro <joao.loureiro@philips.com>
---
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 5 +
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 254 ++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 88 +++++++++++++
6 files changed, 350 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 include/spi_eeprom.h
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 718e4a8283..e03ac16197 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -233,6 +233,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6009d55f40..22e46c92fa 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -578,6 +578,11 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "Enable driver for generic SPI-attached EEPROMs"
+ depends on MISC
+ help
+ Enable a generic driver for EEPROMs attached via SPI.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index dac805e4cd..def79d6ec9 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(XPL_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(XPL_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 0000000000..1638a676bf
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V.
+// Joao Loureiro <joao.loureiro@philips.com>
+
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <spi_eeprom.h>
+
+struct spi_eeprom_drv_data {
+ u32 size; /* size in bytes */
+ u32 pagesize; /* page size in bytes */
+ u32 addr_offset_mask; /* bits in addr used for offset overflow */
+ u32 offset_len; /* size in bytes of offset */
+ u32 start_offset; /* valid start offset inside memory, by default 0 */
+ uint8_t cmd_read_data; /* Command buffer (1 byte for command + 2 bytes for address) */
+ uint8_t cmd_read_status; /* Command buffer (1 byte for command + 2 bytes for address) */
+};
+
+
+int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf, int size) {
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const uint8_t *buf, int size) {
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, int offset, uint8_t *buf, int size,
+ uint8_t* cmd, uint8_t cmd_size) {
+ int ret = 0;
+
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ if (offset + size > priv->size) {
+ log_warning("Read beyond end of EEPROM\n");
+ return -EINVAL;
+ }
+ if (offset < 0) {
+ log_warning("Negative offset. Counting from the end\n");
+ offset = priv->size + offset;
+ }
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret){
+ log_err("Failed to claim SPI bus\n");
+ goto cleanup;
+ }
+
+ // Send the read command (begin the SPI transaction)
+ ret = spi_xfer(slave, 8 * cmd_size, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send read command\n");
+ goto cleanup;
+ }
+
+ // Receive the data (end the SPI transaction)
+ ret = spi_xfer(slave, 8 * size, NULL, buf, SPI_XFER_END);
+ if (ret) {
+ log_err("Failed to receive data\n");
+ goto cleanup;
+ }
+
+cleanup:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+static uint8_t spi_eeprom_read_status(struct udevice *dev) {
+ /*
+ Bit 0: Ready/Busy Status
+ 0 - Device is ready for a new sequence
+ 1 - Device is busy with an internal operation
+
+ Bit 1: Write Enable Latch
+ 0 - Device is not write enabled (Power-up Default)
+ 1 - Device is write enabled
+
+ Bits 2-3: Block Write Protection
+ 00 - No array write protection (Factory Default)
+ 01 - Quarter array write protection
+ 10 - Half array write protection
+ 11 - Entire array write protection
+
+ Bits 4-6: Reserved for Future Use (RFU)
+ Reads as zeros when the device is not in a write cycle
+ Reads as ones when the device is in a write cycle
+
+ Bit 7: Write-Protect Enable
+ 0 - Factory Default
+ 1 - Write protection enabled (refer to Table 6-5 for configuration details)
+ */
+
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ // Make it 0xff initially to check if the SPI reply overwrites it
+ uint8_t status[1] = {0xff};
+ uint8_t cmd[1] = {drv_data->cmd_read_status};
+
+ int ret = spi_eeprom_read_cmd(dev, 0, status, 1, cmd, 1);
+
+ if (ret) {
+ log_err("Failed to read status register\n");
+ return 0xff;
+ }
+
+ return status[0];
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, uint8_t *buf, int size) {
+ // Use the read command from the driver data struct
+ // to read the contents of the EEPROM
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ uint8_t cmd[CMD_SIZE] = {
+ drv_data->cmd_read_data,
+ (offset >> 8) & 0xFF,
+ offset & 0xFF
+ };
+
+ return spi_eeprom_read_cmd(dev, offset, buf, size, cmd, CMD_SIZE);
+}
+
+static int spi_eeprom_std_write(struct udevice *dev, int offset,
+ const uint8_t *buf, int size) {
+ // TODO: Implement the write operation
+ // which is nearly the same as the one from i2c_eeprom_std_write
+ // found in i2c_eeprom.c
+ log_warning("%s: Write operation not implemented. Nothing done...\n", __func__);
+ return 0;
+}
+
+static int spi_eeprom_std_size(struct udevice *dev) {
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .write = spi_eeprom_std_write,
+ .size = spi_eeprom_std_size,
+};
+
+static int spi_eeprom_std_of_to_plat(struct udevice *dev) {
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ struct spi_eeprom_drv_data *data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ u32 pagesize;
+ u32 size;
+
+ // Ensure that the SPI slave is properly configured
+ struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev);
+ spi_slave_of_to_plat(dev, plat);
+
+ // Read pagesize from device tree and save it into priv
+ if (dev_read_u32(dev, "pagesize", &pagesize) == 0)
+ priv->pagesize = pagesize;
+ else
+ // Else take the default value from the driver data
+ /* 6 bit -> page size of up to 2^63 (should be sufficient) */
+ priv->pagesize = data->pagesize;
+
+ if (dev_read_u32(dev, "size", &size) == 0)
+ priv->size = size;
+ else
+ priv->size = data->size;
+
+ return 0;
+}
+
+static int spi_eeprom_std_bind(struct udevice *dev) {
+ debug("%s\n", __func__);
+ return 0;
+}
+
+static int spi_eeprom_std_probe(struct udevice *dev) {
+ // The calls below can be used to get info on the device configuration
+
+ /* Verify the chip's status */
+ uint8_t status = spi_eeprom_read_status(dev);
+ debug("%s: status register: 0x%02x\n", __func__, status);
+
+ if (status == 0xff){
+ log_err("%s: eeprom not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (status) {
+ log_warning("%s: status register not as expected\n", __func__);
+ }
+
+ return 0;
+}
+
+static const struct spi_eeprom_drv_data atmel25_data = {
+ .size = 128,
+ .pagesize = 8,
+ .addr_offset_mask = 0,
+ .offset_len = 1,
+ .cmd_read_data = AT25_CMD_READ_DATA,
+ .cmd_read_status = AT25_CMD_READ_STATUS,
+};
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,at25160bn", (ulong) & atmel25_data },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .bind = spi_eeprom_std_bind,
+ .probe = spi_eeprom_std_probe,
+ .of_to_plat = spi_eeprom_std_of_to_plat,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 270088ad94..e949b6159e 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -133,6 +133,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 0000000000..ce4e5aec6f
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V. */
+/* Joao Loureiro <joao.loureiro@philips.com> */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+
+#define AT25_CMD_READ_DATA 0x03 // AT25 read command
+#define AT25_CMD_READ_STATUS 0x05 // Command to read status register
+#define CMD_SIZE 3
+
+struct udevice;
+
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, uint8_t *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+struct spi_eeprom {
+ /* The EEPROM's page size in byte */
+ unsigned long pagesize;
+ /* The EEPROM's capacity in bytes */
+ unsigned long size;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/*
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf, int size);
+
+// int spi_eeprom_read_status(struct udevice *dev, uint8_t *status);
+
+/*
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+
+/*
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ *
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const uint8_t *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif
--
2.43.0
________________________________
The information contained in this message may be confidential and legally protected under applicable law. The message is intended solely for the addressee(s). If you are not the intended recipient, you are hereby notified that any use, forwarding, dissemination, or reproduction of this message is strictly prohibited and may be unlawful. If you are not the intended recipient, please contact the sender by return e-mail and destroy all copies of the original message.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH] spi: Introduce initial eeprom driver mode support
2024-12-04 11:38 [PATCH] SPI: Introduce initial EEPROM driver mode support Loureiro, Joao
@ 2025-02-24 11:43 ` Loureiro, Joao
2025-03-04 17:47 ` Tom Rini
2025-03-05 12:26 ` [PATCH v2] spi: Introduce initial EEPROM " Loureiro, Joao
1 sibling, 1 reply; 18+ messages in thread
From: Loureiro, Joao @ 2025-02-24 11:43 UTC (permalink / raw)
To: u-boot@lists.denx.de
This patch introduces the initial SPI EEPROM driver mode support
analogous to the I2C EEPROM driver mode support. The SPI EEPROM
driver mode support is enabled by default in the sandbox_defconfig.
Signed-off-by: João Loureiro <joao.loureiro@philips.com>
---
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 5 +
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 254 ++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 88 +++++++++++++
6 files changed, 350 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 include/spi_eeprom.h
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 718e4a8283..e03ac16197 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -233,6 +233,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6009d55f40..22e46c92fa 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -578,6 +578,11 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "Enable driver for generic SPI-attached EEPROMs"
+ depends on MISC
+ help
+ Enable a generic driver for EEPROMs attached via SPI.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index dac805e4cd..def79d6ec9 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(XPL_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(XPL_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 0000000000..1638a676bf
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V.
+// Joao Loureiro <joao.loureiro@philips.com>
+
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <spi_eeprom.h>
+
+struct spi_eeprom_drv_data {
+ u32 size; /* size in bytes */
+ u32 pagesize; /* page size in bytes */
+ u32 addr_offset_mask; /* bits in addr used for offset overflow */
+ u32 offset_len; /* size in bytes of offset */
+ u32 start_offset; /* valid start offset inside memory, by default 0 */
+ uint8_t cmd_read_data; /* Command buffer (1 byte for command + 2 bytes for address) */
+ uint8_t cmd_read_status; /* Command buffer (1 byte for command + 2 bytes for address) */
+};
+
+
+int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf, int size) {
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const uint8_t *buf, int size) {
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, int offset, uint8_t *buf, int size,
+ uint8_t* cmd, uint8_t cmd_size) {
+ int ret = 0;
+
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ if (offset + size > priv->size) {
+ log_warning("Read beyond end of EEPROM\n");
+ return -EINVAL;
+ }
+ if (offset < 0) {
+ log_warning("Negative offset. Counting from the end\n");
+ offset = priv->size + offset;
+ }
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret){
+ log_err("Failed to claim SPI bus\n");
+ goto cleanup;
+ }
+
+ // Send the read command (begin the SPI transaction)
+ ret = spi_xfer(slave, 8 * cmd_size, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send read command\n");
+ goto cleanup;
+ }
+
+ // Receive the data (end the SPI transaction)
+ ret = spi_xfer(slave, 8 * size, NULL, buf, SPI_XFER_END);
+ if (ret) {
+ log_err("Failed to receive data\n");
+ goto cleanup;
+ }
+
+cleanup:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+static uint8_t spi_eeprom_read_status(struct udevice *dev) {
+ /*
+ Bit 0: Ready/Busy Status
+ 0 - Device is ready for a new sequence
+ 1 - Device is busy with an internal operation
+
+ Bit 1: Write Enable Latch
+ 0 - Device is not write enabled (Power-up Default)
+ 1 - Device is write enabled
+
+ Bits 2-3: Block Write Protection
+ 00 - No array write protection (Factory Default)
+ 01 - Quarter array write protection
+ 10 - Half array write protection
+ 11 - Entire array write protection
+
+ Bits 4-6: Reserved for Future Use (RFU)
+ Reads as zeros when the device is not in a write cycle
+ Reads as ones when the device is in a write cycle
+
+ Bit 7: Write-Protect Enable
+ 0 - Factory Default
+ 1 - Write protection enabled (refer to Table 6-5 for configuration details)
+ */
+
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ // Make it 0xff initially to check if the SPI reply overwrites it
+ uint8_t status[1] = {0xff};
+ uint8_t cmd[1] = {drv_data->cmd_read_status};
+
+ int ret = spi_eeprom_read_cmd(dev, 0, status, 1, cmd, 1);
+
+ if (ret) {
+ log_err("Failed to read status register\n");
+ return 0xff;
+ }
+
+ return status[0];
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, uint8_t *buf, int size) {
+ // Use the read command from the driver data struct
+ // to read the contents of the EEPROM
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ uint8_t cmd[CMD_SIZE] = {
+ drv_data->cmd_read_data,
+ (offset >> 8) & 0xFF,
+ offset & 0xFF
+ };
+
+ return spi_eeprom_read_cmd(dev, offset, buf, size, cmd, CMD_SIZE);
+}
+
+static int spi_eeprom_std_write(struct udevice *dev, int offset,
+ const uint8_t *buf, int size) {
+ // TODO: Implement the write operation
+ // which is nearly the same as the one from i2c_eeprom_std_write
+ // found in i2c_eeprom.c
+ log_warning("%s: Write operation not implemented. Nothing done...\n", __func__);
+ return 0;
+}
+
+static int spi_eeprom_std_size(struct udevice *dev) {
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .write = spi_eeprom_std_write,
+ .size = spi_eeprom_std_size,
+};
+
+static int spi_eeprom_std_of_to_plat(struct udevice *dev) {
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ struct spi_eeprom_drv_data *data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ u32 pagesize;
+ u32 size;
+
+ // Ensure that the SPI slave is properly configured
+ struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev);
+ spi_slave_of_to_plat(dev, plat);
+
+ // Read pagesize from device tree and save it into priv
+ if (dev_read_u32(dev, "pagesize", &pagesize) == 0)
+ priv->pagesize = pagesize;
+ else
+ // Else take the default value from the driver data
+ /* 6 bit -> page size of up to 2^63 (should be sufficient) */
+ priv->pagesize = data->pagesize;
+
+ if (dev_read_u32(dev, "size", &size) == 0)
+ priv->size = size;
+ else
+ priv->size = data->size;
+
+ return 0;
+}
+
+static int spi_eeprom_std_bind(struct udevice *dev) {
+ debug("%s\n", __func__);
+ return 0;
+}
+
+static int spi_eeprom_std_probe(struct udevice *dev) {
+ // The calls below can be used to get info on the device configuration
+
+ /* Verify the chip's status */
+ uint8_t status = spi_eeprom_read_status(dev);
+ debug("%s: status register: 0x%02x\n", __func__, status);
+
+ if (status == 0xff){
+ log_err("%s: eeprom not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (status) {
+ log_warning("%s: status register not as expected\n", __func__);
+ }
+
+ return 0;
+}
+
+static const struct spi_eeprom_drv_data atmel25_data = {
+ .size = 128,
+ .pagesize = 8,
+ .addr_offset_mask = 0,
+ .offset_len = 1,
+ .cmd_read_data = AT25_CMD_READ_DATA,
+ .cmd_read_status = AT25_CMD_READ_STATUS,
+};
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,at25160bn", (ulong) & atmel25_data },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .bind = spi_eeprom_std_bind,
+ .probe = spi_eeprom_std_probe,
+ .of_to_plat = spi_eeprom_std_of_to_plat,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 270088ad94..e949b6159e 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -133,6 +133,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 0000000000..ce4e5aec6f
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V. */
+/* Joao Loureiro <joao.loureiro@philips.com> */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+
+#define AT25_CMD_READ_DATA 0x03 // AT25 read command
+#define AT25_CMD_READ_STATUS 0x05 // Command to read status register
+#define CMD_SIZE 3
+
+struct udevice;
+
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, uint8_t *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+struct spi_eeprom {
+ /* The EEPROM's page size in byte */
+ unsigned long pagesize;
+ /* The EEPROM's capacity in bytes */
+ unsigned long size;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/*
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf, int size);
+
+// int spi_eeprom_read_status(struct udevice *dev, uint8_t *status);
+
+/*
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+
+/*
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ *
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const uint8_t *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif
--
2.43.0
________________________________
The information contained in this message may be confidential and legally protected under applicable law. The message is intended solely for the addressee(s). If you are not the intended recipient, you are hereby notified that any use, forwarding, dissemination, or reproduction of this message is strictly prohibited and may be unlawful. If you are not the intended recipient, please contact the sender by return e-mail and destroy all copies of the original message.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH] spi: Introduce initial eeprom driver mode support
2025-02-24 11:43 ` [PATCH] spi: Introduce initial eeprom " Loureiro, Joao
@ 2025-03-04 17:47 ` Tom Rini
0 siblings, 0 replies; 18+ messages in thread
From: Tom Rini @ 2025-03-04 17:47 UTC (permalink / raw)
To: Loureiro, Joao; +Cc: u-boot@lists.denx.de
[-- Attachment #1: Type: text/plain, Size: 903 bytes --]
On Mon, Feb 24, 2025 at 11:43:38AM +0000, Loureiro, Joao wrote:
> This patch introduces the initial SPI EEPROM driver mode support
> analogous to the I2C EEPROM driver mode support. The SPI EEPROM
> driver mode support is enabled by default in the sandbox_defconfig.
>
> Signed-off-by: João Loureiro <joao.loureiro@philips.com>
> ---
> configs/sandbox_defconfig | 1 +
> drivers/misc/Kconfig | 5 +
> drivers/misc/Makefile | 1 +
> drivers/misc/spi_eeprom.c | 254 ++++++++++++++++++++++++++++++++++++++
> include/dm/uclass-id.h | 1 +
> include/spi_eeprom.h | 88 +++++++++++++
> 6 files changed, 350 insertions(+)
> create mode 100644 drivers/misc/spi_eeprom.c
> create mode 100644 include/spi_eeprom.h
In theory, this seems fine. This patch however produces a large number
of checkpatch.pl warnings and errors to resolve first. Thanks!
--
Tom
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 659 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2] spi: Introduce initial EEPROM driver mode support
2024-12-04 11:38 [PATCH] SPI: Introduce initial EEPROM driver mode support Loureiro, Joao
2025-02-24 11:43 ` [PATCH] spi: Introduce initial eeprom " Loureiro, Joao
@ 2025-03-05 12:26 ` Loureiro, Joao
2026-06-10 22:11 ` [PATCH v3 0/2] " João Loureiro
1 sibling, 1 reply; 18+ messages in thread
From: Loureiro, Joao @ 2025-03-05 12:26 UTC (permalink / raw)
To: u-boot@lists.denx.de; +Cc: trini@konsulko.com
This patch introduces the initial SPI EEPROM driver mode support
analogous to the I2C EEPROM driver mode support. The SPI EEPROM
driver mode support is enabled by default in the sandbox_defconfig.
Signed-off-by: João Loureiro <joao.loureiro@philips.com>
---
Changes in v2:
- Fix checkpatch.pl issues
---
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 5 +
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 262 ++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 88 +++++++++++++
6 files changed, 358 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 include/spi_eeprom.h
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 7b35ad8a88f..a26b9286401 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -234,6 +234,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index da84b35e804..0ef5de114ad 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -578,6 +578,11 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "SPI EEPROM support"
+ help
+ Enable support for SPI EEPROM devices.
+ Currently with only read access.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index dac805e4cdd..def79d6ec92 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(XPL_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(XPL_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 00000000000..9a0cc77df7b
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V.
+// Joao Loureiro <joao.loureiro@philips.com>
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <spi_eeprom.h>
+
+struct spi_eeprom_drv_data {
+ u32 size; /* size in bytes */
+ u32 pagesize; /* page size in bytes */
+ u32 addr_offset_mask; /* bits in addr used for offset overflow */
+ u32 offset_len; /* size in bytes of offset */
+ u32 start_offset; /* valid start offset inside memory, by default 0 */
+ u8 cmd_read_data; /* Command buffer (1 byte cmd + 2 bytes addr) */
+ u8 cmd_read_status; /* Command buffer (1 byte cmd + 2 bytes addr) */
+};
+
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, int offset, u8 *buf, int size, u8 *cmd,
+ u8 cmd_size)
+{
+ int ret = 0;
+
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ if (offset + size > priv->size) {
+ log_warning("Read beyond end of EEPROM\n");
+ return -EINVAL;
+ }
+ if (offset < 0) {
+ log_warning("Negative offset. Counting from the end\n");
+ offset = priv->size + offset;
+ }
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret) {
+ log_err("Failed to claim SPI bus\n");
+ goto cleanup;
+ }
+
+ // Send the read command (begin the SPI transaction)
+ ret = spi_xfer(slave, 8 * cmd_size, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send read command\n");
+ goto cleanup;
+ }
+
+ // Receive the data (end the SPI transaction)
+ ret = spi_xfer(slave, 8 * size, NULL, buf, SPI_XFER_END);
+ if (ret) {
+ log_err("Failed to receive data\n");
+ goto cleanup;
+ }
+
+cleanup:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+static u8 spi_eeprom_read_status(struct udevice *dev)
+{
+ /*
+ * Bit 0: Ready/Busy Status
+ * 0 - Device is ready for a new sequence
+ * 1 - Device is busy with an internal operation
+ *
+ * Bit 1: Write Enable Latch
+ * 0 - Device is not write enabled (Power-up Default)
+ * 1 - Device is write enabled
+ *
+ * Bits 2-3: Block Write Protection
+ * 00 - No array write protection (Factory Default)
+ * 01 - Quarter array write protection
+ * 10 - Half array write protection
+ * 11 - Entire array write protection
+ *
+ * Bits 4-6: Reserved for Future Use (RFU)
+ * Reads as zeros when the device is not in a write cycle
+ * Reads as ones when the device is in a write cycle
+ *
+ * Bit 7: Write-Protect Enable
+ * 0 - Factory Default
+ * 1 - Write protection enabled (refer to Table 6-5 for configuration details)
+ */
+
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ // Make it 0xff initially to check if the SPI reply overwrites it
+ u8 status[1] = {0xff};
+ u8 cmd[1] = {drv_data->cmd_read_status};
+
+ int ret = spi_eeprom_read_cmd(dev, 0, status, 1, cmd, 1);
+
+ if (ret) {
+ log_err("Failed to read status register\n");
+ return 0xff;
+ }
+
+ return status[0];
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, u8 *buf, int size)
+{
+ // Use the read command from the driver data struct
+ // to read the contents of the EEPROM
+ struct spi_eeprom_drv_data *drv_data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ u8 cmd[CMD_SIZE] = {
+ drv_data->cmd_read_data,
+ (offset >> 8) & 0xFF,
+ offset & 0xFF
+ };
+
+ return spi_eeprom_read_cmd(dev, offset, buf, size, cmd, CMD_SIZE);
+}
+
+static int spi_eeprom_std_write(struct udevice *dev, int offset,
+ const u8 *buf, int size)
+{
+ // TODO: Implement the write operation
+ // which is nearly the same as the one from i2c_eeprom_std_write
+ // found in i2c_eeprom.c
+ log_warning("%s: Write operation not implemented. Nothing done...\n", __func__);
+ return 0;
+}
+
+static int spi_eeprom_std_size(struct udevice *dev)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .write = spi_eeprom_std_write,
+ .size = spi_eeprom_std_size,
+};
+
+static int spi_eeprom_std_of_to_plat(struct udevice *dev)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ struct spi_eeprom_drv_data *data =
+ (struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+
+ u32 pagesize;
+ u32 size;
+
+ // Ensure that the SPI slave is properly configured
+ struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev);
+
+ spi_slave_of_to_plat(dev, plat);
+
+ // Read pagesize from device tree and save it into priv
+ if (dev_read_u32(dev, "pagesize", &pagesize) == 0)
+ priv->pagesize = pagesize;
+ else
+ // Else take the default value from the driver data
+ /* 6 bit -> page size of up to 2^63 (should be sufficient) */
+ priv->pagesize = data->pagesize;
+
+ if (dev_read_u32(dev, "size", &size) == 0)
+ priv->size = size;
+ else
+ priv->size = data->size;
+
+ return 0;
+}
+
+static int spi_eeprom_std_bind(struct udevice *dev)
+{
+ return 0;
+}
+
+static int spi_eeprom_std_probe(struct udevice *dev)
+{
+ // The calls below can be used to get info on the device configuration
+
+ /* Verify the chip's status */
+ u8 status = spi_eeprom_read_status(dev);
+
+ debug("%s: status register: 0x%02x\n", __func__, status);
+
+ if (status == 0xff) {
+ log_err("%s: eeprom not found\n", __func__);
+ return -ENODEV;
+ }
+
+ if (status)
+ log_warning("%s: status register not as expected\n", __func__);
+
+ return 0;
+}
+
+static const struct spi_eeprom_drv_data atmel25_data = {
+ .size = 128,
+ .pagesize = 8,
+ .addr_offset_mask = 0,
+ .offset_len = 1,
+ .cmd_read_data = AT25_CMD_READ_DATA,
+ .cmd_read_status = AT25_CMD_READ_STATUS,
+};
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,at25160bn", (ulong)&atmel25_data },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .bind = spi_eeprom_std_bind,
+ .probe = spi_eeprom_std_probe,
+ .of_to_plat = spi_eeprom_std_of_to_plat,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 270088ad94f..e949b6159e6 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -133,6 +133,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 00000000000..61236a37775
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright (c) 2024 Koninklijke Philips N.V. */
+/* Joao Loureiro <joao.loureiro@philips.com> */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+
+#define AT25_CMD_READ_DATA 0x03 // AT25 read command
+#define AT25_CMD_READ_STATUS 0x05 // Command to read status register
+#define CMD_SIZE 3
+
+struct udevice;
+
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, uint8_t *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+struct spi_eeprom {
+ /* The EEPROM's page size in byte */
+ unsigned long pagesize;
+ /* The EEPROM's capacity in bytes */
+ unsigned long size;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/*
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf, int size);
+
+// int spi_eeprom_read_status(struct udevice *dev, uint8_t *status);
+
+/*
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const uint8_t *buf,
+ int size);
+
+/*
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ *
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, uint8_t *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const u8 *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif
--
2.43.0
________________________________
The information contained in this message may be confidential and legally protected under applicable law. The message is intended solely for the addressee(s). If you are not the intended recipient, you are hereby notified that any use, forwarding, dissemination, or reproduction of this message is strictly prohibited and may be unlawful. If you are not the intended recipient, please contact the sender by return e-mail and destroy all copies of the original message.
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 0/2] spi: Introduce initial EEPROM driver mode support
2025-03-05 12:26 ` [PATCH v2] spi: Introduce initial EEPROM " Loureiro, Joao
@ 2026-06-10 22:11 ` João Loureiro
2026-06-10 22:11 ` [PATCH v3 1/2] " João Loureiro
` (2 more replies)
0 siblings, 3 replies; 18+ messages in thread
From: João Loureiro @ 2026-06-10 22:11 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
This series introduces an initial SPI EEPROM driver model uclass,
analogous to the existing I2C EEPROM support, currently providing
read-only access.
v2 was deferred mainly because of the large number of checkpatch
issues. This v3 reworks the driver from scratch to be checkpatch
clean, drops dead code and the misleading no-op write stub, fixes the
read bounds checking, and corrects the AT25160 geometry. It is rebased
on current master.
In addition, and to address the "new uclass needs a sandbox driver and
a test" request, patch 2 adds a sandbox SPI EEPROM emulator and a
test/dm test. To make that possible the sandbox SPI controller now lets
a slave select its emulator through a "sandbox,emul" phandle (mirroring
the sandbox I2C bus), while still defaulting to the SPI flash emulator
so existing behaviour is unchanged.
Note: this work was originally submitted from a Philips address which I
no longer have; it is resent from my personal address.
Tested on sandbox: "ut dm spi_eeprom" passes, and the existing
"ut dm spi*" tests still pass.
João Loureiro (2):
spi: Introduce initial EEPROM driver mode support
sandbox: spi: Add SPI EEPROM emulator and DM test
MAINTAINERS | 8 ++
arch/sandbox/dts/test.dts | 13 ++-
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 6 ++
drivers/misc/Makefile | 2 +
drivers/misc/spi_eeprom.c | 185 +++++++++++++++++++++++++++++++++
drivers/misc/spi_eeprom_emul.c | 118 +++++++++++++++++++++
drivers/spi/sandbox_spi.c | 31 +++++-
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 91 ++++++++++++++++
test/dm/Makefile | 1 +
test/dm/spi_eeprom.c | 45 ++++++++
12 files changed, 500 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 include/spi_eeprom.h
create mode 100644 test/dm/spi_eeprom.c
--
2.54.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v3 1/2] spi: Introduce initial EEPROM driver mode support
2026-06-10 22:11 ` [PATCH v3 0/2] " João Loureiro
@ 2026-06-10 22:11 ` João Loureiro
2026-06-10 22:12 ` [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
2 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-06-10 22:11 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
This patch introduces the initial SPI EEPROM driver mode support
analogous to the I2C EEPROM driver mode support. The SPI EEPROM
driver mode support is enabled by default in the sandbox_defconfig.
Signed-off-by: João Loureiro <joaofl@gmail.com>
---
Changes in v3:
- Resolve all checkpatch errors and warnings (the driver was previously
indented with spaces instead of tabs)
- Drop the no-op write stub that silently returned success; writes now
return -ENOSYS via the uclass wrapper
- Remove unused spi_eeprom_drv_data fields and the empty bind hook
- Drop the bogus spi_slave_of_to_plat() call; the SPI uclass already
parses the slave platform data
- Fix the read bounds check (reject negative offset/size and overflow)
- Make probe fail only on a real transfer error instead of treating a
0xff status as "device not found"
- Correct the AT25160 geometry to 2048 bytes / 32-byte page
- Use kernel u8 types and mirror include/i2c_eeprom.h
- Add a MAINTAINERS entry
- Rebase on current master and resend from a personal address (the
original Philips address is no longer reachable)
MAINTAINERS | 6 ++
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 6 ++
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 185 ++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 91 +++++++++++++++++++
7 files changed, 291 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 include/spi_eeprom.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 571af196465..3a99307051c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1772,6 +1772,12 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-spi.git
F: drivers/spi/
F: include/spi*
+SPI EEPROM
+M: João Loureiro <joaofl@gmail.com>
+S: Maintained
+F: drivers/misc/spi_eeprom.c
+F: include/spi_eeprom.h
+
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
M: Michael Trimarchi <michael@amarulasolutions.com>
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index ba800f7d19d..b13f236636f 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -262,6 +262,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ea785793d18..f7336d3c7f0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -567,6 +567,12 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "SPI EEPROM support"
+ depends on MISC
+ help
+ Enable support for SPI EEPROM devices.
+ Currently with only read access.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e2170212e5a..8c9f8eb9cfa 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 00000000000..67685c24771
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <spi_eeprom.h>
+#include <linux/err.h>
+
+struct spi_eeprom_drv_data {
+ u32 size; /* total capacity in bytes */
+ u32 pagesize; /* page size in bytes */
+ u8 cmd_read_data; /* opcode to read the memory array */
+ u8 cmd_read_status; /* opcode to read the status register */
+};
+
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, u8 *buf, int size,
+ const u8 *cmd, int cmd_size)
+{
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ int ret;
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret) {
+ log_err("Failed to claim SPI bus: %d\n", ret);
+ return ret;
+ }
+
+ /* Send the command, keeping the transaction open */
+ ret = spi_xfer(slave, cmd_size * 8, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send command: %d\n", ret);
+ goto release;
+ }
+
+ /* Read the data back and close the transaction */
+ ret = spi_xfer(slave, size * 8, NULL, buf, SPI_XFER_END);
+ if (ret)
+ log_err("Failed to read data: %d\n", ret);
+
+release:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+/*
+ * Read the status register. Returns the register value on success or a
+ * negative error code on failure.
+ *
+ * Status register layout (AT25-style devices):
+ * Bit 0 Ready/Busy: 1 while an internal write cycle is in progress
+ * Bit 1 Write Enable Latch
+ * Bits 2-3 Block write protection
+ * Bits 4-6 Reserved
+ * Bit 7 Write-protect enable
+ */
+static int spi_eeprom_read_status(struct udevice *dev, u8 *status)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ u8 cmd = data->cmd_read_status;
+
+ return spi_eeprom_read_cmd(dev, status, 1, &cmd, 1);
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+ u8 cmd[SPI_EEPROM_CMD_SIZE];
+
+ if (offset < 0 || size < 0 || offset + size > priv->size) {
+ log_err("Read out of bounds (offset %d, size %d, max %lu)\n",
+ offset, size, priv->size);
+ return -EINVAL;
+ }
+
+ cmd[0] = data->cmd_read_data;
+ cmd[1] = (offset >> 8) & 0xff;
+ cmd[2] = offset & 0xff;
+
+ return spi_eeprom_read_cmd(dev, buf, size, cmd, sizeof(cmd));
+}
+
+static int spi_eeprom_std_size(struct udevice *dev)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .size = spi_eeprom_std_size,
+};
+
+static int spi_eeprom_std_of_to_plat(struct udevice *dev)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ priv->size = dev_read_u32_default(dev, "size", data->size);
+ priv->pagesize = dev_read_u32_default(dev, "pagesize", data->pagesize);
+
+ return 0;
+}
+
+static int spi_eeprom_std_probe(struct udevice *dev)
+{
+ u8 status;
+ int ret;
+
+ ret = spi_eeprom_read_status(dev, &status);
+ if (ret) {
+ log_err("Failed to read status register: %d\n", ret);
+ return ret;
+ }
+
+ log_debug("status register: 0x%02x\n", status);
+
+ return 0;
+}
+
+static const struct spi_eeprom_drv_data atmel25_data = {
+ .size = 2048,
+ .pagesize = 32,
+ .cmd_read_data = AT25_CMD_READ_DATA,
+ .cmd_read_status = AT25_CMD_READ_STATUS,
+};
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,at25160bn", .data = (ulong)&atmel25_data },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .probe = spi_eeprom_std_probe,
+ .of_to_plat = spi_eeprom_std_of_to_plat,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 36b5d87c304..d24b4a1a121 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -138,6 +138,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 00000000000..e50daa64634
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+/* AT25-style command set */
+#define AT25_CMD_READ_DATA 0x03 /* Read data from memory array */
+#define AT25_CMD_READ_STATUS 0x05 /* Read status register */
+
+/* 1 command byte followed by a 16-bit address */
+#define SPI_EEPROM_CMD_SIZE 3
+
+struct udevice;
+
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, u8 *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+struct spi_eeprom {
+ /* The EEPROM's page size in bytes */
+ unsigned long pagesize;
+ /* The EEPROM's capacity in bytes */
+ unsigned long size;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/*
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size);
+
+/*
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+
+/*
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ *
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const u8 *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test
2026-06-10 22:11 ` [PATCH v3 0/2] " João Loureiro
2026-06-10 22:11 ` [PATCH v3 1/2] " João Loureiro
@ 2026-06-10 22:12 ` João Loureiro
2026-06-14 12:18 ` Simon Glass
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
2 siblings, 1 reply; 18+ messages in thread
From: João Loureiro @ 2026-06-10 22:12 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
Add a sandbox emulator for an AT25-style SPI EEPROM so the new SPI
EEPROM uclass can be exercised without real hardware.
The sandbox SPI controller previously always bound the SPI flash
emulator for every chip select. Allow a slave node to select its own
emulator through a sandbox,emul phandle (mirroring the sandbox I2C
bus), falling back to the SPI flash emulator when none is given so that
existing behaviour is preserved.
A test EEPROM is wired up on chip select 3 of the sandbox SPI bus and a
test/dm test verifies size reporting, patterned reads, out-of-bounds
rejection and the unimplemented write path.
Signed-off-by: João Loureiro <joaofl@gmail.com>
---
Changes in v3:
- New patch, added to provide sandbox coverage for the new uclass
MAINTAINERS | 2 +
arch/sandbox/dts/test.dts | 13 +++-
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom_emul.c | 118 +++++++++++++++++++++++++++++++++
drivers/spi/sandbox_spi.c | 31 ++++++++-
test/dm/Makefile | 1 +
test/dm/spi_eeprom.c | 45 +++++++++++++
7 files changed, 209 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 test/dm/spi_eeprom.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a99307051c..173f83a9cb8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1776,7 +1776,9 @@ SPI EEPROM
M: João Loureiro <joaofl@gmail.com>
S: Maintained
F: drivers/misc/spi_eeprom.c
+F: drivers/misc/spi_eeprom_emul.c
F: include/spi_eeprom.h
+F: test/dm/spi_eeprom.c
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 0887de4333b..4ccb6984a68 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1602,7 +1602,7 @@
#size-cells = <0>;
reg = <0 1>;
compatible = "sandbox,spi";
- cs-gpios = <0>, <0>, <&gpio_a 0>;
+ cs-gpios = <0>, <0>, <&gpio_a 0>, <0>;
pinctrl-names = "default";
pinctrl-0 = <&pinmux_spi0_pins>;
@@ -1620,6 +1620,17 @@
spi-cpol;
spi-cpha;
};
+ eeprom@3 {
+ reg = <3>;
+ compatible = "microchip,at25160bn";
+ spi-max-frequency = <1000000>;
+ sandbox,emul = <&spi_eeprom_emul>;
+ };
+ };
+
+ spi_eeprom_emul: spi-eeprom-emul {
+ compatible = "sandbox,spi-eeprom";
+ sandbox,size = <2048>;
};
syscon0: syscon@0 {
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 8c9f8eb9cfa..8fa0fe75b53 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
+obj-$(CONFIG_SANDBOX) += spi_eeprom_emul.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom_emul.c b/drivers/misc/spi_eeprom_emul.c
new file mode 100644
index 00000000000..1504c7e1de8
--- /dev/null
+++ b/drivers/misc/spi_eeprom_emul.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sandbox emulation of an AT25-style SPI EEPROM.
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EMUL
+
+#include <dm.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi_eeprom.h>
+#include <linux/err.h>
+
+#define SANDBOX_SPI_EEPROM_SIZE 2048
+
+/**
+ * struct sandbox_spi_eeprom - state of the emulated EEPROM
+ *
+ * @data: Backing store for the memory array
+ * @size: Size of the memory array in bytes
+ * @cmd: Opcode of the command currently being processed
+ * @cmd_len: Number of command/address bytes consumed so far
+ * @addr: Byte address decoded from the command
+ */
+struct sandbox_spi_eeprom {
+ u8 *data;
+ uint size;
+ u8 cmd;
+ uint cmd_len;
+ uint addr;
+};
+
+static int sandbox_spi_eeprom_xfer(struct udevice *dev, uint bitlen,
+ const void *dout, void *din, ulong flags)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ const u8 *tx = dout;
+ u8 *rx = din;
+ uint bytes = bitlen / 8;
+ uint i;
+
+ if (bitlen % 8)
+ return -EINVAL;
+
+ /* A new transaction starts with the command/address phase */
+ if (flags & SPI_XFER_BEGIN) {
+ priv->cmd = 0;
+ priv->cmd_len = 0;
+ priv->addr = 0;
+ }
+
+ for (i = 0; i < bytes; i++) {
+ if (tx) {
+ /* Command phase: first byte is the opcode */
+ if (priv->cmd_len == 0)
+ priv->cmd = tx[i];
+ else if (priv->cmd == AT25_CMD_READ_DATA)
+ priv->addr = (priv->addr << 8) | tx[i];
+ priv->cmd_len++;
+ } else if (rx) {
+ /* Data phase: serve the requested register/array */
+ switch (priv->cmd) {
+ case AT25_CMD_READ_STATUS:
+ /* Ready, write disabled, not protected */
+ rx[i] = 0x00;
+ break;
+ case AT25_CMD_READ_DATA:
+ rx[i] = priv->addr < priv->size ?
+ priv->data[priv->addr] : 0xff;
+ priv->addr++;
+ break;
+ default:
+ rx[i] = 0xff;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sandbox_spi_eeprom_probe(struct udevice *dev)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ uint i;
+
+ priv->size = dev_read_u32_default(dev, "sandbox,size",
+ SANDBOX_SPI_EEPROM_SIZE);
+ priv->data = calloc(1, priv->size);
+ if (!priv->data)
+ return -ENOMEM;
+
+ /* Fill with a known pattern so reads can be verified */
+ for (i = 0; i < priv->size; i++)
+ priv->data[i] = i & 0xff;
+
+ return 0;
+}
+
+static const struct dm_spi_emul_ops sandbox_spi_eeprom_ops = {
+ .xfer = sandbox_spi_eeprom_xfer,
+};
+
+static const struct udevice_id sandbox_spi_eeprom_ids[] = {
+ { .compatible = "sandbox,spi-eeprom" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_spi_eeprom) = {
+ .name = "sandbox_spi_eeprom",
+ .id = UCLASS_SPI_EMUL,
+ .of_match = sandbox_spi_eeprom_ids,
+ .probe = sandbox_spi_eeprom_probe,
+ .priv_auto = sizeof(struct sandbox_spi_eeprom),
+ .ops = &sandbox_spi_eeprom_ops,
+};
diff --git a/drivers/spi/sandbox_spi.c b/drivers/spi/sandbox_spi.c
index 3ee97d67f4a..619368243ed 100644
--- a/drivers/spi/sandbox_spi.c
+++ b/drivers/spi/sandbox_spi.c
@@ -77,6 +77,35 @@ static int sandbox_spi_set_wordlen(struct udevice *dev, unsigned int wordlen)
return 0;
}
+/**
+ * sandbox_spi_emul_get() - find the emulator for a SPI slave
+ *
+ * A slave can name a dedicated emulator through a "sandbox,emul" phandle (for
+ * example an SPI EEPROM). When no such phandle is present we fall back to the
+ * built-in SPI flash emulator, preserving the previous behaviour.
+ */
+static int sandbox_spi_emul_get(struct sandbox_state *state, struct udevice *bus,
+ struct udevice *slave, struct udevice **emulp)
+{
+ struct sandbox_spi_info *info;
+ int ret;
+
+ info = &state->spi[dev_seq(bus)][spi_chip_select(slave)];
+ if (info->emul) {
+ *emulp = info->emul;
+ return 0;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_SPI_EMUL, slave, "sandbox,emul",
+ emulp);
+ if (!ret) {
+ info->emul = *emulp;
+ return 0;
+ }
+
+ return sandbox_spi_get_emul(state, bus, slave, emulp);
+}
+
static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
const void *dout, void *din, unsigned long flags)
{
@@ -106,7 +135,7 @@ static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
busnum, cs);
return -ENOENT;
}
- ret = sandbox_spi_get_emul(state, bus, slave, &emul);
+ ret = sandbox_spi_emul_get(state, bus, slave, &emul);
if (ret) {
printf("%s: busnum=%u, cs=%u: no emulation available (err=%d)\n",
__func__, busnum, cs, ret);
diff --git a/test/dm/Makefile b/test/dm/Makefile
index d69b0e08d66..df8e0c1011e 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -117,6 +117,7 @@ obj-$(CONFIG_SMEM) += smem.o
obj-$(CONFIG_SOC_DEVICE) += soc.o
obj-$(CONFIG_SOUND) += sound.o
obj-$(CONFIG_DM_SPI) += spi.o
+obj-$(CONFIG_SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_SPMI) += spmi.o
obj-y += syscon.o
obj-$(CONFIG_RESET_SYSCON) += syscon-reset.o
diff --git a/test/dm/spi_eeprom.c b/test/dm/spi_eeprom.c
new file mode 100644
index 00000000000..2601b9254b4
--- /dev/null
+++ b/test/dm/spi_eeprom.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for the SPI EEPROM uclass
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#include <dm.h>
+#include <spi_eeprom.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+/* Read from the emulated SPI EEPROM and check the uclass operations */
+static int dm_test_spi_eeprom(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 buf[16];
+ int i;
+
+ ut_assertok(uclass_get_device_by_name(UCLASS_SPI_EEPROM, "eeprom@3",
+ &dev));
+
+ /* The emulator advertises a 2 KiB array */
+ ut_asserteq(2048, spi_eeprom_size(dev));
+
+ /* The backing store is filled with a (addr & 0xff) pattern */
+ ut_assertok(spi_eeprom_read(dev, 0, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq(i & 0xff, buf[i]);
+
+ /* Reads honour the requested offset */
+ ut_assertok(spi_eeprom_read(dev, 0x100, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq((0x100 + i) & 0xff, buf[i]);
+
+ /* Reads past the end of the array are rejected */
+ ut_asserteq(-EINVAL, spi_eeprom_read(dev, 2040, buf, sizeof(buf)));
+
+ /* Writing is not implemented yet */
+ ut_asserteq(-ENOSYS, spi_eeprom_write(dev, 0, buf, 1));
+
+ return 0;
+}
+DM_TEST(dm_test_spi_eeprom, UTF_SCAN_PDATA | UTF_SCAN_FDT);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test
2026-06-10 22:12 ` [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
@ 2026-06-14 12:18 ` Simon Glass
0 siblings, 0 replies; 18+ messages in thread
From: Simon Glass @ 2026-06-14 12:18 UTC (permalink / raw)
To: joaofl; +Cc: u-boot, Tom Rini, Simon Glass
Hi João,
On 2026-06-10T22:11:58, João Loureiro <joaofl@gmail.com> wrote:
> sandbox: spi: Add SPI EEPROM emulator and DM test
>
> Add a sandbox emulator for an AT25-style SPI EEPROM so the new SPI
> EEPROM uclass can be exercised without real hardware.
>
> The sandbox SPI controller previously always bound the SPI flash
> emulator for every chip select. Allow a slave node to select its own
> emulator through a sandbox,emul phandle (mirroring the sandbox I2C
> bus), falling back to the SPI flash emulator when none is given so that
> existing behaviour is preserved.
>
> A test EEPROM is wired up on chip select 3 of the sandbox SPI bus and a
> test/dm test verifies size reporting, patterned reads, out-of-bounds
> rejection and the unimplemented write path.
>
> Signed-off-by: João Loureiro <joaofl@gmail.com>
>
> MAINTAINERS | 2 +
> arch/sandbox/dts/test.dts | 13 ++++-
> drivers/misc/Makefile | 1 +
> drivers/misc/spi_eeprom_emul.c | 118 +++++++++++++++++++++++++++++++++++++++++
> drivers/spi/sandbox_spi.c | 31 ++++++++++-
> test/dm/Makefile | 1 +
> test/dm/spi_eeprom.c | 45 ++++++++++++++++
> 7 files changed, 209 insertions(+), 2 deletions(-)
> diff --git a/drivers/spi/sandbox_spi.c b/drivers/spi/sandbox_spi.c
> @@ -77,6 +77,35 @@ static int sandbox_spi_set_wordlen(struct udevice *dev, unsigned int wordlen)
> +/**
> + * sandbox_spi_emul_get() - find the emulator for a SPI slave
> + *
> + * A slave can name a dedicated emulator through a 'sandbox,emul' phandle (for
> + * example an SPI EEPROM). When no such phandle is present we fall back to the
> + * built-in SPI flash emulator, preserving the previous behaviour.
> + */
> +static int sandbox_spi_emul_get(struct sandbox_state *state, struct udevice *bus,
> + struct udevice *slave, struct udevice **emulp)
Can you please add rull kerneldoc for @state, @bus, @slave, @emulp and
a Return: line. Try to wrap to 80 columns.
> diff --git a/drivers/misc/spi_eeprom_emul.c b/drivers/misc/spi_eeprom_emul.c
> @@ -0,0 +1,118 @@
> + priv->data = calloc(1, priv->size);
> + if (!priv->data)
> + return -ENOMEM;
This is never freed, so each probe/remove cycle in the tests leaks the
backing store. Please can you add a remove method that frees
priv->data, as sandbox_i2c_eeprom_remove() does in i2c_eeprom_emul.c
With those fixed:
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org> # sandbox
Regards,
Simon
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support
2026-06-10 22:11 ` [PATCH v3 0/2] " João Loureiro
2026-06-10 22:11 ` [PATCH v3 1/2] " João Loureiro
2026-06-10 22:12 ` [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
@ 2026-06-15 17:51 ` João Loureiro
2026-06-15 17:51 ` [PATCH v4 1/2] " João Loureiro
` (5 more replies)
2 siblings, 6 replies; 18+ messages in thread
From: João Loureiro @ 2026-06-15 17:51 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
This series introduces an initial SPI EEPROM driver model uclass,
analogous to the existing I2C EEPROM support, currently providing
read-only access. Patch 2 adds a sandbox emulator and a test/dm test.
v4 addresses Simon Glass's review of v3: the emulator now frees its
backing store in a remove() method, and sandbox_spi_emul_get() gets
full kerneldoc. Patch 1 is unchanged.
João Loureiro (2):
spi: Introduce initial EEPROM driver mode support
sandbox: spi: Add SPI EEPROM emulator and DM test
MAINTAINERS | 8 ++
arch/sandbox/dts/test.dts | 13 ++-
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 6 ++
drivers/misc/Makefile | 2 +
drivers/misc/spi_eeprom.c | 185 +++++++++++++++++++++++++++++++++
drivers/misc/spi_eeprom_emul.c | 128 +++++++++++++++++++++++
drivers/spi/sandbox_spi.c | 37 ++++++-
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 91 ++++++++++++++++
test/dm/Makefile | 1 +
test/dm/spi_eeprom.c | 45 ++++++++
12 files changed, 516 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 include/spi_eeprom.h
create mode 100644 test/dm/spi_eeprom.c
--
2.54.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v4 1/2] spi: Introduce initial EEPROM driver mode support
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
@ 2026-06-15 17:51 ` João Loureiro
2026-07-13 13:03 ` Simon Glass
2026-06-15 17:51 ` [PATCH v4 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
` (4 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: João Loureiro @ 2026-06-15 17:51 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
This patch introduces the initial SPI EEPROM driver mode support
analogous to the I2C EEPROM driver mode support. The SPI EEPROM
driver mode support is enabled by default in the sandbox_defconfig.
Signed-off-by: João Loureiro <joaofl@gmail.com>
---
Changes in v4:
- No change
Changes in v3:
- Resolve all checkpatch errors and warnings (the driver was previously
indented with spaces instead of tabs)
- Drop the no-op write stub that silently returned success; writes now
return -ENOSYS via the uclass wrapper
- Remove unused spi_eeprom_drv_data fields and the empty bind hook
- Drop the bogus spi_slave_of_to_plat() call; the SPI uclass already
parses the slave platform data
- Fix the read bounds check (reject negative offset/size and overflow)
- Make probe fail only on a real transfer error instead of treating a
0xff status as "device not found"
- Correct the AT25160 geometry to 2048 bytes / 32-byte page
- Use kernel u8 types and mirror include/i2c_eeprom.h
- Add a MAINTAINERS entry
- Rebase on current master and resend from a personal address (the
original Philips address is no longer reachable)
MAINTAINERS | 6 ++
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 6 ++
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 185 ++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 91 +++++++++++++++++++
7 files changed, 291 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 include/spi_eeprom.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 571af196465..3a99307051c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1772,6 +1772,12 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-spi.git
F: drivers/spi/
F: include/spi*
+SPI EEPROM
+M: João Loureiro <joaofl@gmail.com>
+S: Maintained
+F: drivers/misc/spi_eeprom.c
+F: include/spi_eeprom.h
+
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
M: Michael Trimarchi <michael@amarulasolutions.com>
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index ba800f7d19d..b13f236636f 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -262,6 +262,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ea785793d18..f7336d3c7f0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -567,6 +567,12 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "SPI EEPROM support"
+ depends on MISC
+ help
+ Enable support for SPI EEPROM devices.
+ Currently with only read access.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e2170212e5a..8c9f8eb9cfa 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 00000000000..67685c24771
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <spi_eeprom.h>
+#include <linux/err.h>
+
+struct spi_eeprom_drv_data {
+ u32 size; /* total capacity in bytes */
+ u32 pagesize; /* page size in bytes */
+ u8 cmd_read_data; /* opcode to read the memory array */
+ u8 cmd_read_status; /* opcode to read the status register */
+};
+
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, u8 *buf, int size,
+ const u8 *cmd, int cmd_size)
+{
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ int ret;
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret) {
+ log_err("Failed to claim SPI bus: %d\n", ret);
+ return ret;
+ }
+
+ /* Send the command, keeping the transaction open */
+ ret = spi_xfer(slave, cmd_size * 8, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send command: %d\n", ret);
+ goto release;
+ }
+
+ /* Read the data back and close the transaction */
+ ret = spi_xfer(slave, size * 8, NULL, buf, SPI_XFER_END);
+ if (ret)
+ log_err("Failed to read data: %d\n", ret);
+
+release:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+/*
+ * Read the status register. Returns the register value on success or a
+ * negative error code on failure.
+ *
+ * Status register layout (AT25-style devices):
+ * Bit 0 Ready/Busy: 1 while an internal write cycle is in progress
+ * Bit 1 Write Enable Latch
+ * Bits 2-3 Block write protection
+ * Bits 4-6 Reserved
+ * Bit 7 Write-protect enable
+ */
+static int spi_eeprom_read_status(struct udevice *dev, u8 *status)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ u8 cmd = data->cmd_read_status;
+
+ return spi_eeprom_read_cmd(dev, status, 1, &cmd, 1);
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+ u8 cmd[SPI_EEPROM_CMD_SIZE];
+
+ if (offset < 0 || size < 0 || offset + size > priv->size) {
+ log_err("Read out of bounds (offset %d, size %d, max %lu)\n",
+ offset, size, priv->size);
+ return -EINVAL;
+ }
+
+ cmd[0] = data->cmd_read_data;
+ cmd[1] = (offset >> 8) & 0xff;
+ cmd[2] = offset & 0xff;
+
+ return spi_eeprom_read_cmd(dev, buf, size, cmd, sizeof(cmd));
+}
+
+static int spi_eeprom_std_size(struct udevice *dev)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .size = spi_eeprom_std_size,
+};
+
+static int spi_eeprom_std_of_to_plat(struct udevice *dev)
+{
+ const struct spi_eeprom_drv_data *data =
+ (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ priv->size = dev_read_u32_default(dev, "size", data->size);
+ priv->pagesize = dev_read_u32_default(dev, "pagesize", data->pagesize);
+
+ return 0;
+}
+
+static int spi_eeprom_std_probe(struct udevice *dev)
+{
+ u8 status;
+ int ret;
+
+ ret = spi_eeprom_read_status(dev, &status);
+ if (ret) {
+ log_err("Failed to read status register: %d\n", ret);
+ return ret;
+ }
+
+ log_debug("status register: 0x%02x\n", status);
+
+ return 0;
+}
+
+static const struct spi_eeprom_drv_data atmel25_data = {
+ .size = 2048,
+ .pagesize = 32,
+ .cmd_read_data = AT25_CMD_READ_DATA,
+ .cmd_read_status = AT25_CMD_READ_STATUS,
+};
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,at25160bn", .data = (ulong)&atmel25_data },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .probe = spi_eeprom_std_probe,
+ .of_to_plat = spi_eeprom_std_of_to_plat,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 36b5d87c304..d24b4a1a121 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -138,6 +138,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 00000000000..e50daa64634
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+/* AT25-style command set */
+#define AT25_CMD_READ_DATA 0x03 /* Read data from memory array */
+#define AT25_CMD_READ_STATUS 0x05 /* Read status register */
+
+/* 1 command byte followed by a 16-bit address */
+#define SPI_EEPROM_CMD_SIZE 3
+
+struct udevice;
+
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, u8 *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+struct spi_eeprom {
+ /* The EEPROM's page size in bytes */
+ unsigned long pagesize;
+ /* The EEPROM's capacity in bytes */
+ unsigned long size;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/*
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size);
+
+/*
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ *
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+
+/*
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ *
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const u8 *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v4 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
2026-06-15 17:51 ` [PATCH v4 1/2] " João Loureiro
@ 2026-06-15 17:51 ` João Loureiro
2026-09-05 10:24 ` [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs João Loureiro
` (3 subsequent siblings)
5 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-06-15 17:51 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
Add a sandbox emulator for an AT25-style SPI EEPROM so the new SPI
EEPROM uclass can be exercised without real hardware.
The sandbox SPI controller previously always bound the SPI flash
emulator for every chip select. Allow a slave node to select its own
emulator through a sandbox,emul phandle (mirroring the sandbox I2C
bus), falling back to the SPI flash emulator when none is given so that
existing behaviour is preserved.
A test EEPROM is wired up on chip select 3 of the sandbox SPI bus and a
test/dm test verifies size reporting, patterned reads, out-of-bounds
rejection and the unimplemented write path.
Signed-off-by: João Loureiro <joaofl@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org> # sandbox
---
Changes in v4:
- Free the emulator backing store in a remove() method (Simon Glass)
- Add full kerneldoc for sandbox_spi_emul_get() (Simon Glass)
Changes in v3:
- New patch, added to provide sandbox coverage for the new uclass
MAINTAINERS | 2 +
arch/sandbox/dts/test.dts | 13 +++-
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom_emul.c | 128 +++++++++++++++++++++++++++++++++
drivers/spi/sandbox_spi.c | 37 +++++++++-
test/dm/Makefile | 1 +
test/dm/spi_eeprom.c | 45 ++++++++++++
7 files changed, 225 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 test/dm/spi_eeprom.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a99307051c..173f83a9cb8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1776,7 +1776,9 @@ SPI EEPROM
M: João Loureiro <joaofl@gmail.com>
S: Maintained
F: drivers/misc/spi_eeprom.c
+F: drivers/misc/spi_eeprom_emul.c
F: include/spi_eeprom.h
+F: test/dm/spi_eeprom.c
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 0887de4333b..4ccb6984a68 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1602,7 +1602,7 @@
#size-cells = <0>;
reg = <0 1>;
compatible = "sandbox,spi";
- cs-gpios = <0>, <0>, <&gpio_a 0>;
+ cs-gpios = <0>, <0>, <&gpio_a 0>, <0>;
pinctrl-names = "default";
pinctrl-0 = <&pinmux_spi0_pins>;
@@ -1620,6 +1620,17 @@
spi-cpol;
spi-cpha;
};
+ eeprom@3 {
+ reg = <3>;
+ compatible = "microchip,at25160bn";
+ spi-max-frequency = <1000000>;
+ sandbox,emul = <&spi_eeprom_emul>;
+ };
+ };
+
+ spi_eeprom_emul: spi-eeprom-emul {
+ compatible = "sandbox,spi-eeprom";
+ sandbox,size = <2048>;
};
syscon0: syscon@0 {
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 8c9f8eb9cfa..8fa0fe75b53 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
+obj-$(CONFIG_SANDBOX) += spi_eeprom_emul.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom_emul.c b/drivers/misc/spi_eeprom_emul.c
new file mode 100644
index 00000000000..3ac60f84299
--- /dev/null
+++ b/drivers/misc/spi_eeprom_emul.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sandbox emulation of an AT25-style SPI EEPROM.
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EMUL
+
+#include <dm.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi_eeprom.h>
+#include <linux/err.h>
+
+#define SANDBOX_SPI_EEPROM_SIZE 2048
+
+/**
+ * struct sandbox_spi_eeprom - state of the emulated EEPROM
+ *
+ * @data: Backing store for the memory array
+ * @size: Size of the memory array in bytes
+ * @cmd: Opcode of the command currently being processed
+ * @cmd_len: Number of command/address bytes consumed so far
+ * @addr: Byte address decoded from the command
+ */
+struct sandbox_spi_eeprom {
+ u8 *data;
+ uint size;
+ u8 cmd;
+ uint cmd_len;
+ uint addr;
+};
+
+static int sandbox_spi_eeprom_xfer(struct udevice *dev, uint bitlen,
+ const void *dout, void *din, ulong flags)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ const u8 *tx = dout;
+ u8 *rx = din;
+ uint bytes = bitlen / 8;
+ uint i;
+
+ if (bitlen % 8)
+ return -EINVAL;
+
+ /* A new transaction starts with the command/address phase */
+ if (flags & SPI_XFER_BEGIN) {
+ priv->cmd = 0;
+ priv->cmd_len = 0;
+ priv->addr = 0;
+ }
+
+ for (i = 0; i < bytes; i++) {
+ if (tx) {
+ /* Command phase: first byte is the opcode */
+ if (priv->cmd_len == 0)
+ priv->cmd = tx[i];
+ else if (priv->cmd == AT25_CMD_READ_DATA)
+ priv->addr = (priv->addr << 8) | tx[i];
+ priv->cmd_len++;
+ } else if (rx) {
+ /* Data phase: serve the requested register/array */
+ switch (priv->cmd) {
+ case AT25_CMD_READ_STATUS:
+ /* Ready, write disabled, not protected */
+ rx[i] = 0x00;
+ break;
+ case AT25_CMD_READ_DATA:
+ rx[i] = priv->addr < priv->size ?
+ priv->data[priv->addr] : 0xff;
+ priv->addr++;
+ break;
+ default:
+ rx[i] = 0xff;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sandbox_spi_eeprom_probe(struct udevice *dev)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ uint i;
+
+ priv->size = dev_read_u32_default(dev, "sandbox,size",
+ SANDBOX_SPI_EEPROM_SIZE);
+ priv->data = calloc(1, priv->size);
+ if (!priv->data)
+ return -ENOMEM;
+
+ /* Fill with a known pattern so reads can be verified */
+ for (i = 0; i < priv->size; i++)
+ priv->data[i] = i & 0xff;
+
+ return 0;
+}
+
+static int sandbox_spi_eeprom_remove(struct udevice *dev)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+
+ free(priv->data);
+
+ return 0;
+}
+
+static const struct dm_spi_emul_ops sandbox_spi_eeprom_ops = {
+ .xfer = sandbox_spi_eeprom_xfer,
+};
+
+static const struct udevice_id sandbox_spi_eeprom_ids[] = {
+ { .compatible = "sandbox,spi-eeprom" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_spi_eeprom) = {
+ .name = "sandbox_spi_eeprom",
+ .id = UCLASS_SPI_EMUL,
+ .of_match = sandbox_spi_eeprom_ids,
+ .probe = sandbox_spi_eeprom_probe,
+ .remove = sandbox_spi_eeprom_remove,
+ .priv_auto = sizeof(struct sandbox_spi_eeprom),
+ .ops = &sandbox_spi_eeprom_ops,
+};
diff --git a/drivers/spi/sandbox_spi.c b/drivers/spi/sandbox_spi.c
index 3ee97d67f4a..aa4edcf64b2 100644
--- a/drivers/spi/sandbox_spi.c
+++ b/drivers/spi/sandbox_spi.c
@@ -77,6 +77,41 @@ static int sandbox_spi_set_wordlen(struct udevice *dev, unsigned int wordlen)
return 0;
}
+/**
+ * sandbox_spi_emul_get() - find the emulator for a SPI slave
+ *
+ * A slave can name a dedicated emulator through a "sandbox,emul" phandle (for
+ * example an SPI EEPROM). When no such phandle is present we fall back to the
+ * built-in SPI flash emulator, preserving the previous behaviour.
+ *
+ * @state: Sandbox state, used to cache the bound emulator per chip select
+ * @bus: SPI bus the slave is attached to
+ * @slave: SPI slave to find the emulator for
+ * @emulp: Returns the emulator device on success
+ * Return: 0 if OK, -ve on error
+ */
+static int sandbox_spi_emul_get(struct sandbox_state *state, struct udevice *bus,
+ struct udevice *slave, struct udevice **emulp)
+{
+ struct sandbox_spi_info *info;
+ int ret;
+
+ info = &state->spi[dev_seq(bus)][spi_chip_select(slave)];
+ if (info->emul) {
+ *emulp = info->emul;
+ return 0;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_SPI_EMUL, slave, "sandbox,emul",
+ emulp);
+ if (!ret) {
+ info->emul = *emulp;
+ return 0;
+ }
+
+ return sandbox_spi_get_emul(state, bus, slave, emulp);
+}
+
static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
const void *dout, void *din, unsigned long flags)
{
@@ -106,7 +141,7 @@ static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
busnum, cs);
return -ENOENT;
}
- ret = sandbox_spi_get_emul(state, bus, slave, &emul);
+ ret = sandbox_spi_emul_get(state, bus, slave, &emul);
if (ret) {
printf("%s: busnum=%u, cs=%u: no emulation available (err=%d)\n",
__func__, busnum, cs, ret);
diff --git a/test/dm/Makefile b/test/dm/Makefile
index d69b0e08d66..df8e0c1011e 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -117,6 +117,7 @@ obj-$(CONFIG_SMEM) += smem.o
obj-$(CONFIG_SOC_DEVICE) += soc.o
obj-$(CONFIG_SOUND) += sound.o
obj-$(CONFIG_DM_SPI) += spi.o
+obj-$(CONFIG_SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_SPMI) += spmi.o
obj-y += syscon.o
obj-$(CONFIG_RESET_SYSCON) += syscon-reset.o
diff --git a/test/dm/spi_eeprom.c b/test/dm/spi_eeprom.c
new file mode 100644
index 00000000000..2601b9254b4
--- /dev/null
+++ b/test/dm/spi_eeprom.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for the SPI EEPROM uclass
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ */
+
+#include <dm.h>
+#include <spi_eeprom.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+/* Read from the emulated SPI EEPROM and check the uclass operations */
+static int dm_test_spi_eeprom(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 buf[16];
+ int i;
+
+ ut_assertok(uclass_get_device_by_name(UCLASS_SPI_EEPROM, "eeprom@3",
+ &dev));
+
+ /* The emulator advertises a 2 KiB array */
+ ut_asserteq(2048, spi_eeprom_size(dev));
+
+ /* The backing store is filled with a (addr & 0xff) pattern */
+ ut_assertok(spi_eeprom_read(dev, 0, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq(i & 0xff, buf[i]);
+
+ /* Reads honour the requested offset */
+ ut_assertok(spi_eeprom_read(dev, 0x100, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq((0x100 + i) & 0xff, buf[i]);
+
+ /* Reads past the end of the array are rejected */
+ ut_asserteq(-EINVAL, spi_eeprom_read(dev, 2040, buf, sizeof(buf)));
+
+ /* Writing is not implemented yet */
+ ut_asserteq(-ENOSYS, spi_eeprom_write(dev, 0, buf, 1));
+
+ return 0;
+}
+DM_TEST(dm_test_spi_eeprom, UTF_SCAN_PDATA | UTF_SCAN_FDT);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v4 1/2] spi: Introduce initial EEPROM driver mode support
2026-06-15 17:51 ` [PATCH v4 1/2] " João Loureiro
@ 2026-07-13 13:03 ` Simon Glass
2026-09-05 10:14 ` João Loureiro
0 siblings, 1 reply; 18+ messages in thread
From: Simon Glass @ 2026-07-13 13:03 UTC (permalink / raw)
To: joaofl; +Cc: u-boot, Tom Rini, Simon Glass
Hi João,
On 2026-06-15T17:51:16, João Loureiro <joaofl@gmail.com> wrote:
> spi: Introduce initial EEPROM driver mode support
>
> This patch introduces the initial SPI EEPROM driver mode support
> analogous to the I2C EEPROM driver mode support. The SPI EEPROM
> driver mode support is enabled by default in the sandbox_defconfig.
>
> Signed-off-by: João Loureiro <joaofl@gmail.com>
>
> MAINTAINERS | 6 ++
> configs/sandbox_defconfig | 1 +
> drivers/misc/Kconfig | 6 ++
> drivers/misc/Makefile | 1 +
> drivers/misc/spi_eeprom.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++
> include/dm/uclass-id.h | 1 +
> include/spi_eeprom.h | 91 +++++++++++++++++++++++
> 7 files changed, 291 insertions(+)
Sorry I didn't notice the earlier versions of this patch, but I do
have a few comments.
> This patch introduces the initial SPI EEPROM driver mode support
> analogous to the I2C EEPROM driver mode support. The SPI EEPROM
> driver mode support is enabled by default in the sandbox_defconfig.
'driver model', not 'driver mode' - three times. Please also use the
imperative: 'Introduce initial driver-model support for SPI EEPROMs,
analogous to ...' matches the subject and U-Boot convention.
Please explain the motivation briefly (why now, what will consume it)
before your current commit msg.
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> @@ -567,6 +567,12 @@ config I2C_EEPROM
> +config SPI_EEPROM
> + bool "SPI EEPROM support"
> + depends on MISC
> + help
> + Enable support for SPI EEPROM devices.
> + Currently with only read access.
Please expand the help text - mention that this provides a uclass with
a small AT25-style driver, that only 16-bit-addressed parts are
handled, and that only read is implemented.
Does this really need 'depends on MISC'? The driver does not use the
misc uclass; it sits on its own UCLASS_SPI_EEPROM. i2c_eeprom does not
depend on MISC either.
> diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
> @@ -0,0 +1,185 @@
> +/* 1 command byte followed by a 16-bit address */
> +#define SPI_EEPROM_CMD_SIZE 3
This lives in the public header and hard-codes a 16-bit address, but
the uclass is presented as generic. Anything larger than 64 KiB
(AT25M02, AT25512, most 24-bit-addressed parts) will not work. Please
either:
- move SPI_EEPROM_CMD_SIZE into spi_eeprom.c and derive the address
length from driver-data (an addr_len field), so growing the compatible
list later does not need an ABI change; or
- state explicitly in the commit message and Kconfig help that
initial support is limited to 16-bit-addressed AT25-style parts.
I'd prefer the first.
> + if (offset < 0 || size < 0 || offset + size > priv->size) {
> + log_err("Read out of bounds (offset %d, size %d, max %lu)\n",
> + offset, size, priv->size);
> + return -EINVAL;
> + }
offset + size is an int and can overflow before the comparison against
the unsigned long priv->size. Please check the two ranges
independently:
if (offset < 0 || size < 0 || offset >= priv->size ||
size > priv->size - offset)
> + cmd[0] = data->cmd_read_data;
> + cmd[1] = (offset >> 8) & 0xff;
> + cmd[2] = offset & 0xff;
If you keep the 16-bit assumption, please reject offsets that don't
fit in 16 bits rather than silently truncating. priv->size can be
overridden from DT via the 'size' property with any value.
> +static int spi_eeprom_std_of_to_plat(struct udevice *dev)
> +{
> + const struct spi_eeprom_drv_data *data =
> + (const struct spi_eeprom_drv_data *)dev_get_driver_data(dev);
> + struct spi_eeprom *priv = dev_get_priv(dev);
> +
> + priv->size = dev_read_u32_default(dev, 'size', data->size);
> + priv->pagesize = dev_read_u32_default(dev, 'pagesize', data->pagesize);
Two things:
'size' and 'pagesize' don't seem to be standard bindings for SPI
EEPROM nodes - the size is normally determined by the compatible.
Please either drop these overrides or point at a binding that
documents them.
Also, this writes into priv from of_to_plat(). Per driver-model
convention, of_to_plat() should populate plat data (auto_plat) and
probe() should use plat or can put a few things in priv if it likes.
Since there is no write path using pagesize yet, the simpler fix is to
move this into spi_eeprom_std_probe()
> +static const struct udevice_id spi_eeprom_std_ids[] = {
> + { .compatible = 'microchip,at25160bn', .data = (ulong)&atmel25_data },
> + { }
> +};
Only one compatible for a generic driver feels thin - any reason not
to add the rest of the AT25 family that shares this command set
(at25010b, at25020b, at25040b, at25080b, at2532b, at25640b etc)? Each
just needs a drv_data entry.
> diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
> @@ -0,0 +1,91 @@
> +/* AT25-style command set */
> +#define AT25_CMD_READ_DATA 0x03 /* Read data from memory array */
> +#define AT25_CMD_READ_STATUS 0x05 /* Read status register */
> +
> +/* 1 command byte followed by a 16-bit address */
> +#define SPI_EEPROM_CMD_SIZE 3
These are only used by drivers/misc/spi_eeprom.c and
drivers/misc/spi_eeprom_emul.c - implementation details rather than
uclass API. Please move them into a private header shared by the
driver and emulator, or duplicate the two opcodes locally.
> +#if CONFIG_IS_ENABLED(SPI_EEPROM)
> +/*
> + * spi_eeprom_read() - read bytes from an SPI EEPROM chip
Please switch to kerneldoc (/** rather than /*) - new code should
carry proper kerneldoc even where i2c_eeprom.h has not been updated.
> +#endif /* SPI_EEPROM */
> +
> +#endif
Please add /* __SPI_EEPROM */ after the outer #endif to match the inner one.
Regards,
Simon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Re: [PATCH v4 1/2] spi: Introduce initial EEPROM driver mode support
2026-07-13 13:03 ` Simon Glass
@ 2026-09-05 10:14 ` João Loureiro
0 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-09-05 10:14 UTC (permalink / raw)
To: sjg; +Cc: u-boot, trini
[-- Attachment #1: Type: text/plain, Size: 6663 bytes --]
Hi Simon,
Thank you for the detailed review of v4 — genuinely useful. I'll
address the points in v5. The hard-coded 16-bit address in particular
sent this somewhere better, and chasing your comment on the
non-standard 'size' and 'pagesize' properties turned up the
atmel,at25 binding already in tree, which now drives the whole
geometry.
Thanks,
João
On Mon, Jul 13, 2026 03:04 PM, Simon Glass <sjg@chromium.org> wrote:
> Hi João,
>
> On 2026-06-15T17:51:16, João Loureiro <joaofl@gmail.com> wrote:
> > spi: Introduce initial EEPROM driver mode support
> >
> > This patch introduces the initial SPI EEPROM driver mode support
> > analogous to the I2C EEPROM driver mode support. The SPI EEPROM
> > driver mode support is enabled by default in the sandbox_defconfig.
> >
> > Signed-off-by: João Loureiro <joaofl@gmail.com>
> >
> > MAINTAINERS | 6 ++
> > configs/sandbox_defconfig | 1 +
> > drivers/misc/Kconfig | 6 ++
> > drivers/misc/Makefile | 1 +
> > drivers/misc/spi_eeprom.c | 185 ++++++++++++++++++++++++++++++
> ++++++++++++++++
> > include/dm/uclass-id.h | 1 +
> > include/spi_eeprom.h | 91 +++++++++++++++++++++++
> > 7 files changed, 291 insertions(+)
>
> Sorry I didn't notice the earlier versions of this patch, but I do
> have a few comments.
>
> > This patch introduces the initial SPI EEPROM driver mode support
> > analogous to the I2C EEPROM driver mode support. The SPI EEPROM
> > driver mode support is enabled by default in the sandbox_defconfig.
>
> 'driver model', not 'driver mode' - three times. Please also use the
> imperative: 'Introduce initial driver-model support for SPI EEPROMs,
> analogous to ...' matches the subject and U-Boot convention.
>
> Please explain the motivation briefly (why now, what will consume it)
> before your current commit msg.
>
> > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> > @@ -567,6 +567,12 @@ config I2C_EEPROM
> > +config SPI_EEPROM
> > + bool "SPI EEPROM support"
> > + depends on MISC
> > + help
> > + Enable support for SPI EEPROM devices.
> > + Currently with only read access.
>
> Please expand the help text - mention that this provides a uclass with
> a small AT25-style driver, that only 16-bit-addressed parts are
> handled, and that only read is implemented.
>
> Does this really need 'depends on MISC'? The driver does not use the
> misc uclass; it sits on its own UCLASS_SPI_EEPROM. i2c_eeprom does not
> depend on MISC either.
>
> > diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
> > @@ -0,0 +1,185 @@
> > +/* 1 command byte followed by a 16-bit address */
> > +#define SPI_EEPROM_CMD_SIZE 3
>
> This lives in the public header and hard-codes a 16-bit address, but
> the uclass is presented as generic. Anything larger than 64 KiB
> (AT25M02, AT25512, most 24-bit-addressed parts) will not work. Please
> either:
>
> - move SPI_EEPROM_CMD_SIZE into spi_eeprom.c and derive the address
> length from driver-data (an addr_len field), so growing the compatible
> list later does not need an ABI change; or
> - state explicitly in the commit message and Kconfig help that
> initial support is limited to 16-bit-addressed AT25-style parts.
>
> I'd prefer the first.
>
> > + if (offset < 0 || size < 0 || offset + size > priv->size) {
> > + log_err("Read out of bounds (offset %d, size %d, max
> %lu)\n",
> > + offset, size, priv->size);
> > + return -EINVAL;
> > + }
>
> offset + size is an int and can overflow before the comparison against
> the unsigned long priv->size. Please check the two ranges
> independently:
>
> if (offset < 0 || size < 0 || offset >= priv->size ||
> size > priv->size - offset)
>
> > + cmd[0] = data->cmd_read_data;
> > + cmd[1] = (offset >> 8) & 0xff;
> > + cmd[2] = offset & 0xff;
>
> If you keep the 16-bit assumption, please reject offsets that don't
> fit in 16 bits rather than silently truncating. priv->size can be
> overridden from DT via the 'size' property with any value.
>
> > +static int spi_eeprom_std_of_to_plat(struct udevice *dev)
> > +{
> > + const struct spi_eeprom_drv_data *data =
> > + (const struct spi_eeprom_drv_data
> *)dev_get_driver_data(dev);
> > + struct spi_eeprom *priv = dev_get_priv(dev);
> > +
> > + priv->size = dev_read_u32_default(dev, 'size', data->size);
> > + priv->pagesize = dev_read_u32_default(dev, 'pagesize',
> data->pagesize);
>
> Two things:
>
> 'size' and 'pagesize' don't seem to be standard bindings for SPI
> EEPROM nodes - the size is normally determined by the compatible.
> Please either drop these overrides or point at a binding that
> documents them.
>
> Also, this writes into priv from of_to_plat(). Per driver-model
> convention, of_to_plat() should populate plat data (auto_plat) and
> probe() should use plat or can put a few things in priv if it likes.
> Since there is no write path using pagesize yet, the simpler fix is to
> move this into spi_eeprom_std_probe()
>
> > +static const struct udevice_id spi_eeprom_std_ids[] = {
> > + { .compatible = 'microchip,at25160bn', .data =
> (ulong)&atmel25_data },
> > + { }
> > +};
>
> Only one compatible for a generic driver feels thin - any reason not
> to add the rest of the AT25 family that shares this command set
> (at25010b, at25020b, at25040b, at25080b, at2532b, at25640b etc)? Each
> just needs a drv_data entry.
>
> > diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
> > @@ -0,0 +1,91 @@
> > +/* AT25-style command set */
> > +#define AT25_CMD_READ_DATA 0x03 /* Read data from memory array */
> > +#define AT25_CMD_READ_STATUS 0x05 /* Read status register */
> > +
> > +/* 1 command byte followed by a 16-bit address */
> > +#define SPI_EEPROM_CMD_SIZE 3
>
> These are only used by drivers/misc/spi_eeprom.c and
> drivers/misc/spi_eeprom_emul.c - implementation details rather than
> uclass API. Please move them into a private header shared by the
> driver and emulator, or duplicate the two opcodes locally.
>
> > +#if CONFIG_IS_ENABLED(SPI_EEPROM)
> > +/*
> > + * spi_eeprom_read() - read bytes from an SPI EEPROM chip
>
> Please switch to kerneldoc (/** rather than /*) - new code should
> carry proper kerneldoc even where i2c_eeprom.h has not been updated.
>
> > +#endif /* SPI_EEPROM */
> > +
> > +#endif
>
> Please add /* __SPI_EEPROM */ after the outer #endif to match the inner
> one.
>
> Regards,
> Simon
>
[-- Attachment #2: Type: text/html, Size: 7916 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
2026-06-15 17:51 ` [PATCH v4 1/2] " João Loureiro
2026-06-15 17:51 ` [PATCH v4 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
@ 2026-09-05 10:24 ` João Loureiro
2026-09-10 14:30 ` Simon Glass
2026-09-05 10:24 ` [PATCH v5 1/3] test: dm: sf: Get the emulator attached to the flash slave João Loureiro
` (2 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: João Loureiro @ 2026-09-05 10:24 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
This series adds a UCLASS_SPI_EEPROM uclass, a driver for AT25-style
parts, a sandbox emulator and a DM test.
Simon, thank you for the detailed review of v4 - it was a genuinely
useful read. The point about the hard-coded 16-bit address in
particular sent this somewhere better than where I had it, and chasing
your comment about the non-standard 'size' and 'pagesize' properties is
what turned up the atmel,at25 binding already sitting in the tree,
which now drives the whole geometry.
v5 addresses that review. The main change is that the driver
is no longer hard-wired to a single 16-bit-addressed part: the address
length now comes from driver data and can be overridden from the device
tree, so 8-, 16- and 24-bit addressed devices all work.
It also fixes a crash that v3 and v4 both had, and that I only spotted
while re-testing this round. Adding the EEPROM emulator to test.dts
makes "ut dm" segfault in dm_test_spi_flash: that test picks up its
emulator with uclass_first_device_err(UCLASS_SPI_EMUL), assuming the
sandbox SPI flash emulator is the only one. The flash emulator is bound
lazily on first transfer, so a second emulator described in the device
tree is bound during the devicetree scan and lands first in the uclass.
sandbox_sf_set_block_protect() then casts a struct sandbox_spi_eeprom
to struct sandbox_spi_flash and writes past the end of it, which trips
the dlmalloc heap check. Sorry for shipping that in v3/v4 - my testing
had been running "ut dm spi_eeprom" rather than the whole suite. New
patch 1 fixes the test; with it, "ut dm" on sandbox reports the same 28
failures as an unpatched v2026.10-rc3 tree here (all fixture-related),
plus the new spi_eeprom test passing.
Changes in v5:
- New patch 1: test/dm/sf.c now asks for the emulator attached to its
own slave rather than the first one in UCLASS_SPI_EMUL, fixing the
segfault described above.
- Commit message on patch 2 reworded: imperative mood, "driver model"
rather than "driver mode", and it now explains the motivation - what
these EEPROMs are used for and why the uclass is wanted.
- SPI_EEPROM_CMD_SIZE is gone. Address length is an addr_len field in
driver data, overridable from the device tree, so parts larger than
64 KiB are handled and adding one later does not need an API change.
- The AT25_CMD_* opcodes have moved out of include/spi_eeprom.h into a
new private header, drivers/misc/spi_eeprom_priv.h, shared by the
driver and the emulator.
- The read path no longer computes "offset + size", which could
overflow; the two ranges are checked independently as suggested.
probe() additionally rejects a "size" that does not fit the
configured address width, so an offset can no longer be silently
truncated.
- of_to_plat() dropped; the geometry is read in probe() instead.
- "size" and "pagesize" are kept, and "address-width" added, because
they are documented -- and in fact required for non-FRAM parts -- by
the atmel,at25 binding in dts/upstream/Bindings/eeprom/at25.yaml,
which is already in tree. The commit message and Kconfig help now
point at it.
- Compatible list expanded, taking the strings from that binding rather
than inventing them: microchip,25aa010a, microchip,at25160bn,
atmel,at25256B, st,m95640, st,m95256 and st,m95m02, plus the generic
"atmel,at25" fallback the binding requires every node to carry. A
node matching only the fallback is fully described by its device
tree. The at25010b/020b/040b/080b/640b strings suggested in review
are not part of the binding so I left them out, and microchip,25lc040
is in the binding but uses 9-bit addressing, which this driver does
not implement yet.
- Kconfig: "depends on MISC" dropped -- the misc uclass really is not
used here. (For the record, I2C_EEPROM in tree does still carry it.)
The help text now describes the uclass, the AT25 driver, the
supported address widths and the read-only limitation.
- include/spi_eeprom.h converted to kerneldoc, and the outer #endif is
now commented.
- The sandbox EEPROM node in test.dts follows the binding: it carries
the "atmel,at25" fallback compatible and the required size, pagesize
and address-width properties.
- Added my own copyright line alongside the existing Philips one on the
new files. The first version of this series was posted while I worked
at Philips; the rework since is my own.
Patch 3 (the emulator and test) is otherwise unchanged, and I have kept
Simon's Reviewed-by and Tested-by on it.
Simon - please do re-test rather than let those tags stand. The tree
you tested for v4 segfaulted on a full "ut dm"; only "ut dm spi_eeprom"
on its own passes, which I assume is what we both ran. New patch 1 is
what makes the full suite pass again. Patch 3 has also changed since
v4: its DT node now follows the atmel,at25 binding, and the emulator
includes the new private header instead of the uclass one. Happy to
drop the tags if you would rather re-review from scratch.
Changes in v4:
- Emulator frees its backing store in a remove() method.
- Full kerneldoc for sandbox_spi_emul_get().
Changes in v3:
- Reindented with tabs; the series is now checkpatch-clean.
- Dropped the no-op write stub in favour of returning -ENOSYS.
- Removed dead code, fixed the read bounds check, corrected the
AT25160 geometry, switched to u8 types and added a MAINTAINERS entry.
- Added the sandbox emulator and the DM test (patch 2).
João Loureiro (3):
test: dm: sf: Get the emulator attached to the flash slave
spi: Introduce initial driver-model support for SPI EEPROMs
sandbox: spi: Add SPI EEPROM emulator and DM test
MAINTAINERS | 9 ++
arch/sandbox/dts/test.dts | 16 +-
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 15 ++
drivers/misc/Makefile | 2 +
drivers/misc/spi_eeprom.c | 267 +++++++++++++++++++++++++++++++++
drivers/misc/spi_eeprom_emul.c | 130 ++++++++++++++++
drivers/misc/spi_eeprom_priv.h | 18 +++
drivers/spi/sandbox_spi.c | 37 ++++-
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 95 ++++++++++++
test/dm/Makefile | 1 +
test/dm/sf.c | 9 +-
test/dm/spi_eeprom.c | 46 ++++++
14 files changed, 643 insertions(+), 4 deletions(-)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 drivers/misc/spi_eeprom_priv.h
create mode 100644 include/spi_eeprom.h
create mode 100644 test/dm/spi_eeprom.c
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v5 1/3] test: dm: sf: Get the emulator attached to the flash slave
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
` (2 preceding siblings ...)
2026-09-05 10:24 ` [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs João Loureiro
@ 2026-09-05 10:24 ` João Loureiro
2026-09-05 10:24 ` [PATCH v5 2/3] spi: Introduce initial driver-model support for SPI EEPROMs João Loureiro
2026-09-05 10:24 ` [PATCH v5 3/3] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
5 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-09-05 10:24 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
dm_test_spi_flash() picks up its SPI emulator with
uclass_first_device_err(UCLASS_SPI_EMUL), which assumes the sandbox SPI
flash emulator is the only device in that uclass. That holds today only
because the flash emulator is bound lazily, on the first transfer.
As soon as a second SPI emulator is described in the device tree it is
bound during the devicetree scan and so comes first in the uclass. The
test then hands a foreign device to sandbox_sf_set_block_protect(),
which casts its private data to struct sandbox_spi_flash and writes
past the end of it, corrupting the heap.
Ask for the emulator attached to this particular slave instead, which
is what the test means, and which stays correct however many SPI
emulators exist.
Signed-off-by: João Loureiro <joaofl@gmail.com>
---
test/dm/sf.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/test/dm/sf.c b/test/dm/sf.c
index 3684d021709..0ec02ac694b 100644
--- a/test/dm/sf.c
+++ b/test/dm/sf.c
@@ -50,8 +50,13 @@ static int dm_test_spi_flash(struct unit_test_state *uts)
ut_assertok(spi_flash_read_dm(dev, 0, size, dst));
ut_asserteq_mem(src, dst, size);
- /* Try the write-protect stuff */
- ut_assertok(uclass_first_device_err(UCLASS_SPI_EMUL, &emul));
+ /*
+ * Try the write-protect stuff. Ask for the emulator attached to this
+ * particular slave rather than the first one in the uclass, since
+ * other SPI emulators may be present.
+ */
+ ut_assertok(sandbox_spi_get_emul(state_get_current(), dev->parent, dev,
+ &emul));
ut_asserteq(0, spl_flash_get_sw_write_prot(dev));
sandbox_sf_set_block_protect(emul, 1);
ut_asserteq(1, spl_flash_get_sw_write_prot(dev));
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v5 2/3] spi: Introduce initial driver-model support for SPI EEPROMs
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
` (3 preceding siblings ...)
2026-09-05 10:24 ` [PATCH v5 1/3] test: dm: sf: Get the emulator attached to the flash slave João Loureiro
@ 2026-09-05 10:24 ` João Loureiro
2026-09-05 10:24 ` [PATCH v5 3/3] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
5 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-09-05 10:24 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
U-Boot has driver-model support for EEPROMs on several buses - I2C
through UCLASS_I2C_EEPROM and 1-Wire through UCLASS_W1_EEPROM - but
nothing for SPI, even though SPI EEPROMs are common and are usually the
faster part of the two.
These devices typically hold a board's identity: its serial number, a
unique device ID and the hardware variant. U-Boot needs that early,
both to decide which device tree and overlays to load for the variant
it happens to be running on, and to hand the unique IDs to the system
it is about to boot. Today a board that wants any of this has to
open-code raw SPI transfers in board code, which is neither reusable
nor testable.
Introduce a UCLASS_SPI_EEPROM uclass with read/write/size operations,
analogous to the I2C EEPROM uclass, along with a driver for AT25-style
parts. The chip geometry - total size, page size and address width - is
taken from the device tree as described by the atmel,at25 binding in
dts/upstream/Bindings/eeprom/at25.yaml, which requires those properties,
falling back to per-compatible defaults held in driver data. Devices
using 8-, 16- and 24-bit addressing are therefore all covered, and
adding a part later is a driver-data entry rather than a change to the
uclass API. The 9-bit addressing mode the binding also allows, where the
ninth address bit travels in the opcode, is not implemented.
Only reading is implemented for now; spi_eeprom_write() returns -ENOSYS.
Enable the uclass in sandbox_defconfig so that the emulator and DM test
added by the next patch are built.
Signed-off-by: João Loureiro <joaofl@gmail.com>
---
MAINTAINERS | 7 +
configs/sandbox_defconfig | 1 +
drivers/misc/Kconfig | 15 ++
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom.c | 267 +++++++++++++++++++++++++++++++++
drivers/misc/spi_eeprom_priv.h | 18 +++
include/dm/uclass-id.h | 1 +
include/spi_eeprom.h | 95 ++++++++++++
8 files changed, 405 insertions(+)
create mode 100644 drivers/misc/spi_eeprom.c
create mode 100644 drivers/misc/spi_eeprom_priv.h
create mode 100644 include/spi_eeprom.h
diff --git a/MAINTAINERS b/MAINTAINERS
index eb48eea55c5..893cf421378 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1708,6 +1708,13 @@ T: git https://git.u-boot-project.org/u-boot/custodians/u-boot-spi.git
F: drivers/spi/
F: include/spi*
+SPI EEPROM
+M: João Loureiro <joaofl@gmail.com>
+S: Maintained
+F: drivers/misc/spi_eeprom.c
+F: drivers/misc/spi_eeprom_priv.h
+F: include/spi_eeprom.h
+
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
M: Michael Trimarchi <michael@amarulasolutions.com>
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 79f46317e45..f6373af8217 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -266,6 +266,7 @@ CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SANDBOX=y
CONFIG_SYS_NAND_ONFI_DETECTION=y
CONFIG_SYS_NAND_PAGE_SIZE=0x200
+CONFIG_SPI_EEPROM=y
CONFIG_SPI_FLASH_SANDBOX=y
CONFIG_BOOTDEV_SPI_FLASH=y
CONFIG_SPI_FLASH_ATMEL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 44415f24ae1..8409635ef17 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -567,6 +567,21 @@ config I2C_EEPROM
help
Enable a generic driver for EEPROMs attached via I2C.
+config SPI_EEPROM
+ bool "Enable driver for generic SPI-attached EEPROMs"
+ help
+ Enable a generic driver for EEPROMs attached via SPI.
+
+ This provides the UCLASS_SPI_EEPROM uclass together with a driver
+ for AT25-style parts, i.e. devices described by the "atmel,at25"
+ device tree binding. The chip geometry (size, pagesize and
+ address-width) is read from the device tree, with per-compatible
+ defaults for the parts listed in the driver. Parts using 8-, 16-
+ and 24-bit addressing are supported; 9-bit addressing, where the
+ ninth address bit travels in the opcode, is not.
+
+ Only read access is implemented at present; spi_eeprom_write()
+ returns -ENOSYS.
config SPL_I2C_EEPROM
bool "Enable driver for generic I2C-attached EEPROMs for SPL"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e2170212e5a..8c9f8eb9cfa 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_GDSYS_SOC) += gdsys_soc.o
obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
+obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom.c b/drivers/misc/spi_eeprom.c
new file mode 100644
index 00000000000..3c64c0ef9f5
--- /dev/null
+++ b/drivers/misc/spi_eeprom.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ * Copyright (c) 2026 João Loureiro <joaofl@gmail.com>
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EEPROM
+
+#include <dm.h>
+#include <spi.h>
+#include <spi_eeprom.h>
+#include <linux/err.h>
+
+#include "spi_eeprom_priv.h"
+
+/* Longest address accepted by spi_eeprom_std_read(), in bytes */
+#define SPI_EEPROM_MAX_ADDR_LEN 3
+
+/**
+ * struct at25_chip - geometry defaults for an AT25-style EEPROM
+ *
+ * The atmel,at25 binding (dts/upstream/Bindings/eeprom/at25.yaml) requires
+ * "size", "pagesize" and "address-width" to be present in the device tree.
+ * These values are used when a device tree omits them, and are all zero for
+ * the generic "atmel,at25" compatible, where nothing can be assumed.
+ *
+ * @size: Total capacity in bytes
+ * @pagesize: Write page size in bytes
+ * @addr_len: Number of address bytes sent after the command opcode
+ */
+struct at25_chip {
+ u32 size;
+ u32 pagesize;
+ u8 addr_len;
+};
+
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->read)
+ return -ENOSYS;
+
+ return ops->read(dev, offset, buf, size);
+}
+
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf, int size)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->write)
+ return -ENOSYS;
+
+ return ops->write(dev, offset, buf, size);
+}
+
+int spi_eeprom_size(struct udevice *dev)
+{
+ const struct spi_eeprom_ops *ops = device_get_ops(dev);
+
+ if (!ops->size)
+ return -ENOSYS;
+
+ return ops->size(dev);
+}
+
+static int spi_eeprom_read_cmd(struct udevice *dev, u8 *buf, int size,
+ const u8 *cmd, int cmd_size)
+{
+ struct spi_slave *slave = dev_get_parent_priv(dev);
+ int ret;
+
+ ret = dm_spi_claim_bus(dev);
+ if (ret) {
+ log_err("Failed to claim SPI bus: %d\n", ret);
+ return ret;
+ }
+
+ /* Send the command, keeping the transaction open */
+ ret = spi_xfer(slave, cmd_size * 8, cmd, NULL, SPI_XFER_BEGIN);
+ if (ret) {
+ log_err("Failed to send command: %d\n", ret);
+ goto release;
+ }
+
+ /* Read the data back and close the transaction */
+ ret = spi_xfer(slave, size * 8, NULL, buf, SPI_XFER_END);
+ if (ret)
+ log_err("Failed to read data: %d\n", ret);
+
+release:
+ dm_spi_release_bus(dev);
+ return ret;
+}
+
+/*
+ * Read the status register. Returns the register value on success or a
+ * negative error code on failure.
+ *
+ * Status register layout (AT25-style devices):
+ * Bit 0 Ready/Busy: 1 while an internal write cycle is in progress
+ * Bit 1 Write Enable Latch
+ * Bits 2-3 Block write protection
+ * Bits 4-6 Reserved
+ * Bit 7 Write-protect enable
+ */
+static int spi_eeprom_read_status(struct udevice *dev, u8 *status)
+{
+ u8 cmd = AT25_CMD_READ_STATUS;
+
+ return spi_eeprom_read_cmd(dev, status, 1, &cmd, 1);
+}
+
+static int spi_eeprom_std_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+ u8 cmd[1 + SPI_EEPROM_MAX_ADDR_LEN];
+ int i;
+
+ if (offset < 0 || size < 0 || offset >= priv->size ||
+ size > priv->size - offset) {
+ log_err("Read out of bounds (offset %d, size %d, max %lu)\n",
+ offset, size, priv->size);
+ return -EINVAL;
+ }
+
+ cmd[0] = AT25_CMD_READ_DATA;
+ for (i = 0; i < priv->addr_len; i++)
+ cmd[1 + i] = offset >> (8 * (priv->addr_len - 1 - i));
+
+ return spi_eeprom_read_cmd(dev, buf, size, cmd, 1 + priv->addr_len);
+}
+
+static int spi_eeprom_std_size(struct udevice *dev)
+{
+ struct spi_eeprom *priv = dev_get_priv(dev);
+
+ return priv->size;
+}
+
+static const struct spi_eeprom_ops spi_eeprom_std_ops = {
+ .read = spi_eeprom_std_read,
+ .size = spi_eeprom_std_size,
+};
+
+/* Geometry is described by the device tree for the generic compatible */
+static const struct at25_chip at25_generic = { };
+
+static const struct at25_chip mchp_25aa010a = {
+ .size = 128,
+ .pagesize = 16,
+ .addr_len = 1,
+};
+
+static const struct at25_chip mchp_at25160bn = {
+ .size = 2048,
+ .pagesize = 32,
+ .addr_len = 2,
+};
+
+static const struct at25_chip atmel_at25256b = {
+ .size = 32768,
+ .pagesize = 64,
+ .addr_len = 2,
+};
+
+static const struct at25_chip st_m95640 = {
+ .size = 8192,
+ .pagesize = 32,
+ .addr_len = 2,
+};
+
+static const struct at25_chip st_m95256 = {
+ .size = 32768,
+ .pagesize = 64,
+ .addr_len = 2,
+};
+
+static const struct at25_chip st_m95m02 = {
+ .size = 262144,
+ .pagesize = 256,
+ .addr_len = 3,
+};
+
+static int spi_eeprom_std_probe(struct udevice *dev)
+{
+ const struct at25_chip *chip =
+ (const struct at25_chip *)dev_get_driver_data(dev);
+ struct spi_eeprom *priv = dev_get_priv(dev);
+ u32 addr_bits;
+ u8 status;
+ int ret;
+
+ if (!chip)
+ chip = &at25_generic;
+
+ priv->size = dev_read_u32_default(dev, "size", chip->size);
+ priv->pagesize = dev_read_u32_default(dev, "pagesize", chip->pagesize);
+ addr_bits = dev_read_u32_default(dev, "address-width",
+ chip->addr_len * 8);
+
+ if (!priv->size || !priv->pagesize || !addr_bits) {
+ log_err("Missing 'size'/'pagesize'/'address-width', required by the atmel,at25 binding\n");
+ return -EINVAL;
+ }
+
+ /*
+ * The binding also allows 9-bit addressing, where the ninth address
+ * bit travels in the opcode. That is not implemented here.
+ */
+ switch (addr_bits) {
+ case 8:
+ case 16:
+ case 24:
+ priv->addr_len = addr_bits / 8;
+ break;
+ default:
+ log_err("Unsupported address width %u\n", addr_bits);
+ return -EOPNOTSUPP;
+ }
+
+ if (priv->size > 1UL << addr_bits) {
+ log_err("Size %lu exceeds the %u-bit address range\n",
+ priv->size, addr_bits);
+ return -EINVAL;
+ }
+
+ ret = spi_eeprom_read_status(dev, &status);
+ if (ret) {
+ log_err("Failed to read status register: %d\n", ret);
+ return ret;
+ }
+
+ log_debug("status register: 0x%02x\n", status);
+
+ return 0;
+}
+
+static const struct udevice_id spi_eeprom_std_ids[] = {
+ { .compatible = "microchip,25aa010a",
+ .data = (ulong)&mchp_25aa010a },
+ { .compatible = "microchip,at25160bn",
+ .data = (ulong)&mchp_at25160bn },
+ { .compatible = "atmel,at25256B",
+ .data = (ulong)&atmel_at25256b },
+ { .compatible = "st,m95640", .data = (ulong)&st_m95640 },
+ { .compatible = "st,m95256", .data = (ulong)&st_m95256 },
+ { .compatible = "st,m95m02", .data = (ulong)&st_m95m02 },
+ /* Fallback compatible required by the binding; must be last */
+ { .compatible = "atmel,at25", .data = (ulong)&at25_generic },
+ { }
+};
+
+U_BOOT_DRIVER(spi_eeprom_std) = {
+ .name = "spi_eeprom",
+ .id = UCLASS_SPI_EEPROM,
+ .of_match = spi_eeprom_std_ids,
+ .probe = spi_eeprom_std_probe,
+ .priv_auto = sizeof(struct spi_eeprom),
+ .ops = &spi_eeprom_std_ops,
+};
+
+UCLASS_DRIVER(spi_eeprom) = {
+ .id = UCLASS_SPI_EEPROM,
+ .name = "spi_eeprom",
+};
diff --git a/drivers/misc/spi_eeprom_priv.h b/drivers/misc/spi_eeprom_priv.h
new file mode 100644
index 00000000000..142ffc736c7
--- /dev/null
+++ b/drivers/misc/spi_eeprom_priv.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Command set shared by the AT25-style SPI EEPROM driver and its sandbox
+ * emulator. These opcodes are an implementation detail of the driver, not
+ * part of the UCLASS_SPI_EEPROM API.
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ * Copyright (c) 2026 João Loureiro <joaofl@gmail.com>
+ */
+
+#ifndef __SPI_EEPROM_PRIV_H
+#define __SPI_EEPROM_PRIV_H
+
+/* AT25-style command set */
+#define AT25_CMD_READ_DATA 0x03 /* Read data from memory array */
+#define AT25_CMD_READ_STATUS 0x05 /* Read status register */
+
+#endif /* __SPI_EEPROM_PRIV_H */
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 36b5d87c304..d24b4a1a121 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -138,6 +138,7 @@ enum uclass_id {
UCLASS_SOC, /* SOC Device */
UCLASS_SOUND, /* Playing simple sounds */
UCLASS_SPI, /* SPI bus */
+ UCLASS_SPI_EEPROM, /* SPI EEPROM device */
UCLASS_SPI_FLASH, /* SPI flash */
UCLASS_SPI_GENERIC, /* Generic SPI flash target */
UCLASS_SPMI, /* System Power Management Interface bus */
diff --git a/include/spi_eeprom.h b/include/spi_eeprom.h
new file mode 100644
index 00000000000..64b5d3130be
--- /dev/null
+++ b/include/spi_eeprom.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ * Copyright (c) 2026 João Loureiro <joaofl@gmail.com>
+ */
+
+#ifndef __SPI_EEPROM
+#define __SPI_EEPROM
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+struct udevice;
+
+/**
+ * struct spi_eeprom_ops - operations provided by an SPI EEPROM driver
+ *
+ * @read: Read @size bytes from @offset into @buf
+ * @write: Write @size bytes from @buf at @offset
+ * @size: Return the capacity of the device in bytes
+ */
+struct spi_eeprom_ops {
+ int (*read)(struct udevice *dev, int offset, u8 *buf, int size);
+ int (*write)(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+ int (*size)(struct udevice *dev);
+};
+
+/**
+ * struct spi_eeprom - geometry of an SPI EEPROM device
+ *
+ * @pagesize: The EEPROM's page size in bytes
+ * @size: The EEPROM's capacity in bytes
+ * @addr_len: Number of address bytes sent after the command opcode
+ */
+struct spi_eeprom {
+ unsigned long pagesize;
+ unsigned long size;
+ u8 addr_len;
+};
+
+#if CONFIG_IS_ENABLED(SPI_EEPROM)
+/**
+ * spi_eeprom_read() - read bytes from an SPI EEPROM chip
+ *
+ * @dev: Chip to read from
+ * @offset: Offset within chip to start reading
+ * @buf: Place to put data
+ * @size: Number of bytes to read
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf, int size);
+
+/**
+ * spi_eeprom_write() - write bytes to an SPI EEPROM chip
+ *
+ * @dev: Chip to write to
+ * @offset: Offset within chip to start writing
+ * @buf: Buffer containing data to write
+ * @size: Number of bytes to write
+ * Return: 0 on success, -ve on failure
+ */
+int spi_eeprom_write(struct udevice *dev, int offset, const u8 *buf,
+ int size);
+
+/**
+ * spi_eeprom_size() - get size of SPI EEPROM chip
+ *
+ * @dev: Chip to query
+ * Return: +ve size in bytes on success, -ve on failure
+ */
+int spi_eeprom_size(struct udevice *dev);
+
+#else /* !SPI_EEPROM */
+
+static inline int spi_eeprom_read(struct udevice *dev, int offset, u8 *buf,
+ int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_write(struct udevice *dev, int offset,
+ const u8 *buf, int size)
+{
+ return -ENOSYS;
+}
+
+static inline int spi_eeprom_size(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
+#endif /* SPI_EEPROM */
+
+#endif /* __SPI_EEPROM */
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v5 3/3] sandbox: spi: Add SPI EEPROM emulator and DM test
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
` (4 preceding siblings ...)
2026-09-05 10:24 ` [PATCH v5 2/3] spi: Introduce initial driver-model support for SPI EEPROMs João Loureiro
@ 2026-09-05 10:24 ` João Loureiro
5 siblings, 0 replies; 18+ messages in thread
From: João Loureiro @ 2026-09-05 10:24 UTC (permalink / raw)
To: u-boot; +Cc: Tom Rini, Simon Glass, João Loureiro
Add a sandbox emulator for an AT25-style SPI EEPROM so the new SPI
EEPROM uclass can be exercised without real hardware.
The sandbox SPI controller previously always bound the SPI flash
emulator for every chip select. Allow a slave node to select its own
emulator through a sandbox,emul phandle (mirroring the sandbox I2C
bus), falling back to the SPI flash emulator when none is given so that
existing behaviour is preserved.
A test EEPROM is wired up on chip select 3 of the sandbox SPI bus and a
test/dm test verifies size reporting, patterned reads, out-of-bounds
rejection and the unimplemented write path. The node follows the
atmel,at25 binding: it carries the "atmel,at25" fallback compatible and
the required size, pagesize and address-width properties.
Signed-off-by: João Loureiro <joaofl@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org> # sandbox
---
Simon: I have kept your Tested-by, but please do re-test rather than
rely on it. The v4 tree segfaulted on a full "ut dm" - test/dm/sf.c
took the first UCLASS_SPI_EMUL device as the flash emulator, which the
emulator added here displaces, so sandbox_sf_set_block_protect() wrote
past the end of the wrong private struct. Running "ut dm spi_eeprom"
alone passes and hides it. Patch 1 fixes the test.
Also changed since v4: the DT node now carries the "atmel,at25"
fallback compatible with the required size/pagesize/address-width, and
the emulator includes drivers/misc/spi_eeprom_priv.h.
MAINTAINERS | 2 +
arch/sandbox/dts/test.dts | 16 +++-
drivers/misc/Makefile | 1 +
drivers/misc/spi_eeprom_emul.c | 130 +++++++++++++++++++++++++++++++++
drivers/spi/sandbox_spi.c | 37 +++++++++-
test/dm/Makefile | 1 +
test/dm/spi_eeprom.c | 46 ++++++++++++
7 files changed, 231 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/spi_eeprom_emul.c
create mode 100644 test/dm/spi_eeprom.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 893cf421378..92e26829e1a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1712,8 +1712,10 @@ SPI EEPROM
M: João Loureiro <joaofl@gmail.com>
S: Maintained
F: drivers/misc/spi_eeprom.c
+F: drivers/misc/spi_eeprom_emul.c
F: drivers/misc/spi_eeprom_priv.h
F: include/spi_eeprom.h
+F: test/dm/spi_eeprom.c
SPI NAND
M: Dario Binacchi <dario.binacchi@amarulasolutions.com>
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index d24feec5422..7d7f0376348 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1618,7 +1618,7 @@
#size-cells = <0>;
reg = <0 1>;
compatible = "sandbox,spi";
- cs-gpios = <0>, <0>, <&gpio_a 0>;
+ cs-gpios = <0>, <0>, <&gpio_a 0>, <0>;
pinctrl-names = "default";
pinctrl-0 = <&pinmux_spi0_pins>;
@@ -1636,6 +1636,20 @@
spi-cpol;
spi-cpha;
};
+ eeprom@3 {
+ reg = <3>;
+ compatible = "microchip,at25160bn", "atmel,at25";
+ spi-max-frequency = <1000000>;
+ size = <2048>;
+ pagesize = <32>;
+ address-width = <16>;
+ sandbox,emul = <&spi_eeprom_emul>;
+ };
+ };
+
+ spi_eeprom_emul: spi-eeprom-emul {
+ compatible = "sandbox,spi-eeprom";
+ sandbox,size = <2048>;
};
syscon0: syscon@0 {
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 8c9f8eb9cfa..8fa0fe75b53 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_IRQ) += irq-uclass.o
obj-$(CONFIG_SANDBOX) += irq_sandbox.o irq_sandbox_test.o
obj-$(CONFIG_$(PHASE_)I2C_EEPROM) += i2c_eeprom.o
obj-$(CONFIG_$(PHASE_)SPI_EEPROM) += spi_eeprom.o
+obj-$(CONFIG_SANDBOX) += spi_eeprom_emul.o
obj-$(CONFIG_IHS_FPGA) += ihs_fpga.o
obj-$(CONFIG_IMX8) += imx8/
obj-$(CONFIG_IMX_ELE) += imx_ele/
diff --git a/drivers/misc/spi_eeprom_emul.c b/drivers/misc/spi_eeprom_emul.c
new file mode 100644
index 00000000000..397e73cf0cd
--- /dev/null
+++ b/drivers/misc/spi_eeprom_emul.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sandbox emulation of an AT25-style SPI EEPROM.
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ * Copyright (c) 2026 João Loureiro <joaofl@gmail.com>
+ */
+
+#define LOG_CATEGORY UCLASS_SPI_EMUL
+
+#include <dm.h>
+#include <malloc.h>
+#include <spi.h>
+#include <linux/err.h>
+
+#include "spi_eeprom_priv.h"
+
+#define SANDBOX_SPI_EEPROM_SIZE 2048
+
+/**
+ * struct sandbox_spi_eeprom - state of the emulated EEPROM
+ *
+ * @data: Backing store for the memory array
+ * @size: Size of the memory array in bytes
+ * @cmd: Opcode of the command currently being processed
+ * @cmd_len: Number of command/address bytes consumed so far
+ * @addr: Byte address decoded from the command
+ */
+struct sandbox_spi_eeprom {
+ u8 *data;
+ uint size;
+ u8 cmd;
+ uint cmd_len;
+ uint addr;
+};
+
+static int sandbox_spi_eeprom_xfer(struct udevice *dev, uint bitlen,
+ const void *dout, void *din, ulong flags)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ const u8 *tx = dout;
+ u8 *rx = din;
+ uint bytes = bitlen / 8;
+ uint i;
+
+ if (bitlen % 8)
+ return -EINVAL;
+
+ /* A new transaction starts with the command/address phase */
+ if (flags & SPI_XFER_BEGIN) {
+ priv->cmd = 0;
+ priv->cmd_len = 0;
+ priv->addr = 0;
+ }
+
+ for (i = 0; i < bytes; i++) {
+ if (tx) {
+ /* Command phase: first byte is the opcode */
+ if (priv->cmd_len == 0)
+ priv->cmd = tx[i];
+ else if (priv->cmd == AT25_CMD_READ_DATA)
+ priv->addr = (priv->addr << 8) | tx[i];
+ priv->cmd_len++;
+ } else if (rx) {
+ /* Data phase: serve the requested register/array */
+ switch (priv->cmd) {
+ case AT25_CMD_READ_STATUS:
+ /* Ready, write disabled, not protected */
+ rx[i] = 0x00;
+ break;
+ case AT25_CMD_READ_DATA:
+ rx[i] = priv->addr < priv->size ?
+ priv->data[priv->addr] : 0xff;
+ priv->addr++;
+ break;
+ default:
+ rx[i] = 0xff;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sandbox_spi_eeprom_probe(struct udevice *dev)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+ uint i;
+
+ priv->size = dev_read_u32_default(dev, "sandbox,size",
+ SANDBOX_SPI_EEPROM_SIZE);
+ priv->data = calloc(1, priv->size);
+ if (!priv->data)
+ return -ENOMEM;
+
+ /* Fill with a known pattern so reads can be verified */
+ for (i = 0; i < priv->size; i++)
+ priv->data[i] = i & 0xff;
+
+ return 0;
+}
+
+static int sandbox_spi_eeprom_remove(struct udevice *dev)
+{
+ struct sandbox_spi_eeprom *priv = dev_get_priv(dev);
+
+ free(priv->data);
+
+ return 0;
+}
+
+static const struct dm_spi_emul_ops sandbox_spi_eeprom_ops = {
+ .xfer = sandbox_spi_eeprom_xfer,
+};
+
+static const struct udevice_id sandbox_spi_eeprom_ids[] = {
+ { .compatible = "sandbox,spi-eeprom" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_spi_eeprom) = {
+ .name = "sandbox_spi_eeprom",
+ .id = UCLASS_SPI_EMUL,
+ .of_match = sandbox_spi_eeprom_ids,
+ .probe = sandbox_spi_eeprom_probe,
+ .remove = sandbox_spi_eeprom_remove,
+ .priv_auto = sizeof(struct sandbox_spi_eeprom),
+ .ops = &sandbox_spi_eeprom_ops,
+};
diff --git a/drivers/spi/sandbox_spi.c b/drivers/spi/sandbox_spi.c
index 3ee97d67f4a..aa4edcf64b2 100644
--- a/drivers/spi/sandbox_spi.c
+++ b/drivers/spi/sandbox_spi.c
@@ -77,6 +77,41 @@ static int sandbox_spi_set_wordlen(struct udevice *dev, unsigned int wordlen)
return 0;
}
+/**
+ * sandbox_spi_emul_get() - find the emulator for a SPI slave
+ *
+ * A slave can name a dedicated emulator through a "sandbox,emul" phandle (for
+ * example an SPI EEPROM). When no such phandle is present we fall back to the
+ * built-in SPI flash emulator, preserving the previous behaviour.
+ *
+ * @state: Sandbox state, used to cache the bound emulator per chip select
+ * @bus: SPI bus the slave is attached to
+ * @slave: SPI slave to find the emulator for
+ * @emulp: Returns the emulator device on success
+ * Return: 0 if OK, -ve on error
+ */
+static int sandbox_spi_emul_get(struct sandbox_state *state, struct udevice *bus,
+ struct udevice *slave, struct udevice **emulp)
+{
+ struct sandbox_spi_info *info;
+ int ret;
+
+ info = &state->spi[dev_seq(bus)][spi_chip_select(slave)];
+ if (info->emul) {
+ *emulp = info->emul;
+ return 0;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_SPI_EMUL, slave, "sandbox,emul",
+ emulp);
+ if (!ret) {
+ info->emul = *emulp;
+ return 0;
+ }
+
+ return sandbox_spi_get_emul(state, bus, slave, emulp);
+}
+
static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
const void *dout, void *din, unsigned long flags)
{
@@ -106,7 +141,7 @@ static int sandbox_spi_xfer(struct udevice *slave, unsigned int bitlen,
busnum, cs);
return -ENOENT;
}
- ret = sandbox_spi_get_emul(state, bus, slave, &emul);
+ ret = sandbox_spi_emul_get(state, bus, slave, &emul);
if (ret) {
printf("%s: busnum=%u, cs=%u: no emulation available (err=%d)\n",
__func__, busnum, cs, ret);
diff --git a/test/dm/Makefile b/test/dm/Makefile
index fb3e6a7008f..3a8599d6bc2 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -120,6 +120,7 @@ obj-$(CONFIG_SMEM) += smem.o
obj-$(CONFIG_SOC_DEVICE) += soc.o
obj-$(CONFIG_SOUND) += sound.o
obj-$(CONFIG_DM_SPI) += spi.o
+obj-$(CONFIG_SPI_EEPROM) += spi_eeprom.o
obj-$(CONFIG_SPMI) += spmi.o
obj-y += syscon.o
obj-$(CONFIG_RESET_SYSCON) += syscon-reset.o
diff --git a/test/dm/spi_eeprom.c b/test/dm/spi_eeprom.c
new file mode 100644
index 00000000000..b943ada0035
--- /dev/null
+++ b/test/dm/spi_eeprom.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for the SPI EEPROM uclass
+ *
+ * Copyright (c) 2024 Koninklijke Philips N.V.
+ * Copyright (c) 2026 João Loureiro <joaofl@gmail.com>
+ */
+
+#include <dm.h>
+#include <spi_eeprom.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+/* Read from the emulated SPI EEPROM and check the uclass operations */
+static int dm_test_spi_eeprom(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 buf[16];
+ int i;
+
+ ut_assertok(uclass_get_device_by_name(UCLASS_SPI_EEPROM, "eeprom@3",
+ &dev));
+
+ /* The emulator advertises a 2 KiB array */
+ ut_asserteq(2048, spi_eeprom_size(dev));
+
+ /* The backing store is filled with a (addr & 0xff) pattern */
+ ut_assertok(spi_eeprom_read(dev, 0, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq(i & 0xff, buf[i]);
+
+ /* Reads honour the requested offset */
+ ut_assertok(spi_eeprom_read(dev, 0x100, buf, sizeof(buf)));
+ for (i = 0; i < sizeof(buf); i++)
+ ut_asserteq((0x100 + i) & 0xff, buf[i]);
+
+ /* Reads past the end of the array are rejected */
+ ut_asserteq(-EINVAL, spi_eeprom_read(dev, 2040, buf, sizeof(buf)));
+
+ /* Writing is not implemented yet */
+ ut_asserteq(-ENOSYS, spi_eeprom_write(dev, 0, buf, 1));
+
+ return 0;
+}
+DM_TEST(dm_test_spi_eeprom, UTF_SCAN_PDATA | UTF_SCAN_FDT);
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs
2026-09-05 10:24 ` [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs João Loureiro
@ 2026-09-10 14:30 ` Simon Glass
0 siblings, 0 replies; 18+ messages in thread
From: Simon Glass @ 2026-09-10 14:30 UTC (permalink / raw)
To: João Loureiro; +Cc: u-boot, Tom Rini
Hi João,
On Sat, 5 Sept 2026 at 04:24, João Loureiro <joaofl@gmail.com> wrote:
>
> This series adds a UCLASS_SPI_EEPROM uclass, a driver for AT25-style
> parts, a sandbox emulator and a DM test.
>
> Simon, thank you for the detailed review of v4 - it was a genuinely
> useful read. The point about the hard-coded 16-bit address in
> particular sent this somewhere better than where I had it, and chasing
> your comment about the non-standard 'size' and 'pagesize' properties is
> what turned up the atmel,at25 binding already sitting in the tree,
> which now drives the whole geometry.
>
> v5 addresses that review. The main change is that the driver
> is no longer hard-wired to a single 16-bit-addressed part: the address
> length now comes from driver data and can be overridden from the device
> tree, so 8-, 16- and 24-bit addressed devices all work.
>
> It also fixes a crash that v3 and v4 both had, and that I only spotted
> while re-testing this round. Adding the EEPROM emulator to test.dts
> makes "ut dm" segfault in dm_test_spi_flash: that test picks up its
> emulator with uclass_first_device_err(UCLASS_SPI_EMUL), assuming the
> sandbox SPI flash emulator is the only one. The flash emulator is bound
> lazily on first transfer, so a second emulator described in the device
> tree is bound during the devicetree scan and lands first in the uclass.
> sandbox_sf_set_block_protect() then casts a struct sandbox_spi_eeprom
> to struct sandbox_spi_flash and writes past the end of it, which trips
> the dlmalloc heap check. Sorry for shipping that in v3/v4 - my testing
> had been running "ut dm spi_eeprom" rather than the whole suite. New
> patch 1 fixes the test; with it, "ut dm" on sandbox reports the same 28
> failures as an unpatched v2026.10-rc3 tree here (all fixture-related),
> plus the new spi_eeprom test passing.
>
> Changes in v5:
>
> - New patch 1: test/dm/sf.c now asks for the emulator attached to its
> own slave rather than the first one in UCLASS_SPI_EMUL, fixing the
> segfault described above.
>
> - Commit message on patch 2 reworded: imperative mood, "driver model"
> rather than "driver mode", and it now explains the motivation - what
> these EEPROMs are used for and why the uclass is wanted.
>
> - SPI_EEPROM_CMD_SIZE is gone. Address length is an addr_len field in
> driver data, overridable from the device tree, so parts larger than
> 64 KiB are handled and adding one later does not need an API change.
>
> - The AT25_CMD_* opcodes have moved out of include/spi_eeprom.h into a
> new private header, drivers/misc/spi_eeprom_priv.h, shared by the
> driver and the emulator.
>
> - The read path no longer computes "offset + size", which could
> overflow; the two ranges are checked independently as suggested.
> probe() additionally rejects a "size" that does not fit the
> configured address width, so an offset can no longer be silently
> truncated.
>
> - of_to_plat() dropped; the geometry is read in probe() instead.
>
> - "size" and "pagesize" are kept, and "address-width" added, because
> they are documented -- and in fact required for non-FRAM parts -- by
> the atmel,at25 binding in dts/upstream/Bindings/eeprom/at25.yaml,
> which is already in tree. The commit message and Kconfig help now
> point at it.
>
> - Compatible list expanded, taking the strings from that binding rather
> than inventing them: microchip,25aa010a, microchip,at25160bn,
> atmel,at25256B, st,m95640, st,m95256 and st,m95m02, plus the generic
> "atmel,at25" fallback the binding requires every node to carry. A
> node matching only the fallback is fully described by its device
> tree. The at25010b/020b/040b/080b/640b strings suggested in review
> are not part of the binding so I left them out, and microchip,25lc040
> is in the binding but uses 9-bit addressing, which this driver does
> not implement yet.
>
> - Kconfig: "depends on MISC" dropped -- the misc uclass really is not
> used here. (For the record, I2C_EEPROM in tree does still carry it.)
> The help text now describes the uclass, the AT25 driver, the
> supported address widths and the read-only limitation.
>
> - include/spi_eeprom.h converted to kerneldoc, and the outer #endif is
> now commented.
>
> - The sandbox EEPROM node in test.dts follows the binding: it carries
> the "atmel,at25" fallback compatible and the required size, pagesize
> and address-width properties.
>
> - Added my own copyright line alongside the existing Philips one on the
> new files. The first version of this series was posted while I worked
> at Philips; the rework since is my own.
>
> Patch 3 (the emulator and test) is otherwise unchanged, and I have kept
> Simon's Reviewed-by and Tested-by on it.
>
> Simon - please do re-test rather than let those tags stand. The tree
> you tested for v4 segfaulted on a full "ut dm"; only "ut dm spi_eeprom"
> on its own passes, which I assume is what we both ran. New patch 1 is
> what makes the full suite pass again. Patch 3 has also changed since
> v4: its DT node now follows the atmel,at25 binding, and the emulator
> includes the new private header instead of the uclass one. Happy to
> drop the tags if you would rather re-review from scratch.
Thanks for the v5 - but there is something a bit odd about this series
in patchwork:
https://patchwork.ozlabs.org/project/uboot/patch/20260905102431.426747-4-joaofl@gmail.com/
There are three v4 patches and then a v5. Can you please try to resend
it (perhaps as v6)? There should be three patches all connected to the
same cover letter.
>
> Changes in v4:
> - Emulator frees its backing store in a remove() method.
> - Full kerneldoc for sandbox_spi_emul_get().
>
> Changes in v3:
> - Reindented with tabs; the series is now checkpatch-clean.
> - Dropped the no-op write stub in favour of returning -ENOSYS.
> - Removed dead code, fixed the read bounds check, corrected the
> AT25160 geometry, switched to u8 types and added a MAINTAINERS entry.
> - Added the sandbox emulator and the DM test (patch 2).
>
> João Loureiro (3):
> test: dm: sf: Get the emulator attached to the flash slave
> spi: Introduce initial driver-model support for SPI EEPROMs
> sandbox: spi: Add SPI EEPROM emulator and DM test
>
> MAINTAINERS | 9 ++
> arch/sandbox/dts/test.dts | 16 +-
> configs/sandbox_defconfig | 1 +
> drivers/misc/Kconfig | 15 ++
> drivers/misc/Makefile | 2 +
> drivers/misc/spi_eeprom.c | 267 +++++++++++++++++++++++++++++++++
> drivers/misc/spi_eeprom_emul.c | 130 ++++++++++++++++
> drivers/misc/spi_eeprom_priv.h | 18 +++
> drivers/spi/sandbox_spi.c | 37 ++++-
> include/dm/uclass-id.h | 1 +
> include/spi_eeprom.h | 95 ++++++++++++
> test/dm/Makefile | 1 +
> test/dm/sf.c | 9 +-
> test/dm/spi_eeprom.c | 46 ++++++
> 14 files changed, 643 insertions(+), 4 deletions(-)
> create mode 100644 drivers/misc/spi_eeprom.c
> create mode 100644 drivers/misc/spi_eeprom_emul.c
> create mode 100644 drivers/misc/spi_eeprom_priv.h
> create mode 100644 include/spi_eeprom.h
> create mode 100644 test/dm/spi_eeprom.c
>
> --
> 2.55.0
>
Regards,
Simon
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-09-10 14:30 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-04 11:38 [PATCH] SPI: Introduce initial EEPROM driver mode support Loureiro, Joao
2025-02-24 11:43 ` [PATCH] spi: Introduce initial eeprom " Loureiro, Joao
2025-03-04 17:47 ` Tom Rini
2025-03-05 12:26 ` [PATCH v2] spi: Introduce initial EEPROM " Loureiro, Joao
2026-06-10 22:11 ` [PATCH v3 0/2] " João Loureiro
2026-06-10 22:11 ` [PATCH v3 1/2] " João Loureiro
2026-06-10 22:12 ` [PATCH v3 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
2026-06-14 12:18 ` Simon Glass
2026-06-15 17:51 ` [PATCH v4 0/2] spi: Introduce initial EEPROM driver mode support João Loureiro
2026-06-15 17:51 ` [PATCH v4 1/2] " João Loureiro
2026-07-13 13:03 ` Simon Glass
2026-09-05 10:14 ` João Loureiro
2026-06-15 17:51 ` [PATCH v4 2/2] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
2026-09-05 10:24 ` [PATCH v5 0/3] spi: Introduce driver-model support for SPI EEPROMs João Loureiro
2026-09-10 14:30 ` Simon Glass
2026-09-05 10:24 ` [PATCH v5 1/3] test: dm: sf: Get the emulator attached to the flash slave João Loureiro
2026-09-05 10:24 ` [PATCH v5 2/3] spi: Introduce initial driver-model support for SPI EEPROMs João Loureiro
2026-09-05 10:24 ` [PATCH v5 3/3] sandbox: spi: Add SPI EEPROM emulator and DM test João Loureiro
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox