@@ -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>
@@ -267,6 +267,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
@@ -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"
@@ -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/
new file mode 100644
@@ -0,0 +1,266 @@
+// 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 into @status. Returns 0 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;
+
+ 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;
+ }
+
+ /*
+ * Diagnostic only. SPI has no acknowledge, so this cannot tell whether
+ * a chip is present: an empty socket simply reads back 0x00 or 0xff.
+ * Do not fail probe() if the read itself goes wrong.
+ */
+ if (!spi_eeprom_read_status(dev, &status))
+ 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",
+};
new file mode 100644
@@ -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 */
@@ -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 */
new file mode 100644
@@ -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 */