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 <[email protected]> --- 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 <[email protected]> +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 <[email protected]> M: Michael Trimarchi <[email protected]> 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 <[email protected]> + */ + +#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 <[email protected]> + */ + +#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 <[email protected]> + */ + +#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
