[PATCH] SPI: Introduce initial EEPROM driver mode support
Loureiro, Joao
Joao.Loureiro at philips.com
Wed Dec 4 12:38:07 CET 2024
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 at 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 at 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 at 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.
More information about the U-Boot
mailing list