The 23LCV1024 is a 1 Mbit serial SRAM with battery backup, sitting on
an SPI bus and used as non volatile scratch storage. Register it as an
MTD device of type MTD_RAM, so the mtd command reaches it.

The device powers up in byte operation, where every command carries
exactly one data byte. Select sequential operation once during probe,
so that a read or a write of any length is served by a single command
and the bus is claimed once per transfer.

Writing needs no erase. The driver therefore implements no erase
operation and says MTD_NO_ERASE in the MTD flags.

Signed-off-by: Stefan Bigler <[email protected]>
Signed-off-by: Heiko Schocher <[email protected]>
---

Changes in v2:
Added comments from Simon:
- Moved from UCLASS_SPI_FLASH to UCLASS_MTD, the way the SPI NAND
  driver sits. The struct spi_nor shim and the dm_spi_flash_ops are
  gone with it, and the device is reached through the mtd command
  rather than sf.
- Moved from drivers/mtd/spi/ to drivers/mtd/, where the other
  standalone MTD drivers live.
- Dropped the erase operation. v1 filled the range with 0xff from a
  malloc'd buffer and claimed the bus once per 1 KB chunk. A RAM needs
  no erase, and patch 1 makes mtd_erase() answer -EOPNOTSUPP.
- Dropped the linux/mtd/spi-nor.h include, spi_flash.h pulled it in
  anyway and it advertised a NOR driver.
- sprintf() into a fixed field replaced by snprintf() into a buffer the
  driver owns.
- Copyright: dropped the trailing full stop and added Stefan Bigler's
  address.
fixed checkpatch warning:
- Added a MAINTAINERS entry, as the move out of drivers/mtd/spi/
  dropped a checkpatch warning.

 MAINTAINERS               |   5 +
 configs/sandbox_defconfig |   1 +
 drivers/mtd/Kconfig       |  14 +++
 drivers/mtd/Makefile      |   1 +
 drivers/mtd/spi_ram.c     | 222 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 243 insertions(+)
 create mode 100644 drivers/mtd/spi_ram.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2b3a9ac1f2a..1669972e724 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1649,6 +1649,11 @@ S:       Maintained
 T:     git 
https://git.u-boot-project.org/u-boot/custodians/u-boot-nand-flash.git
 F:     drivers/mtd/nand/spi/
 
+SPI RAM
+M:     Heiko Schocher <[email protected]>
+S:     Maintained
+F:     drivers/mtd/spi_ram.c
+
 SPI-NOR
 M:     Vignesh R <[email protected]>
 R:     Takahiro Kuwano <[email protected]>
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index c6edd9d2423..0210d54f462 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -260,6 +260,7 @@ CONFIG_MMC_PCI=y
 CONFIG_MMC_SANDBOX=y
 CONFIG_MMC_SDHCI=y
 CONFIG_DM_MTD=y
+CONFIG_SPI_RAM=y
 CONFIG_MTD_RAW_NAND=y
 CONFIG_SYS_MAX_NAND_DEVICE=8
 CONFIG_SYS_NAND_USE_FLASH_BBT=y
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index f6bd9f6f2e9..437e6ebd196 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -194,6 +194,20 @@ config ALTERA_QSPI
          NOR flash to parallel flash interface. Please find details on the
          "Embedded Peripherals IP User Guide" of Altera.
 
+config SPI_RAM
+       bool "Microchip 23LCV1024 SPI SRAM"
+       depends on DM_SPI && DM_MTD
+       help
+         Enable support for the Microchip 23LCV1024 serial SRAM, a 1 Mbit
+         SRAM with battery backup on an SPI bus. The device is registered
+         as an MTD device of type MTD_RAM, so the mtd command reaches it.
+
+         Writing needs no erase, so the driver implements none and the MTD
+         flags say MTD_NO_ERASE. An erase request is answered with
+         -EOPNOTSUPP rather than silently doing nothing.
+
+         If unsure, say N
+
 config RENESAS_RPC_HF
        bool "Renesas R-Car Gen3 RPC HyperFlash driver"
        depends on RCAR_64 && DM_MTD
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index ce05e206073..a19498da920 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -13,6 +13,7 @@ mtd-$(CONFIG_FLASH_CFI_MTD) += cfi_mtd.o
 mtd-$(CONFIG_FLASH_CFI_LEGACY) += jedec_flash.o
 mtd-$(CONFIG_STM32_FLASH) += stm32_flash.o
 mtd-$(CONFIG_RENESAS_RPC_HF) += renesas_rpc_hf.o
+mtd-$(CONFIG_SPI_RAM) += spi_ram.o
 mtd-$(CONFIG_HBMC_AM654) += hbmc-am654.o
 
 # U-Boot build
diff --git a/drivers/mtd/spi_ram.c b/drivers/mtd/spi_ram.c
new file mode 100644
index 00000000000..1f5c97e26ec
--- /dev/null
+++ b/drivers/mtd/spi_ram.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microchip 23LCV1024 SPI SRAM
+ *
+ * Copyright (C) 2019 Stefan Bigler <[email protected]>
+ * Copyright (C) 2023 Heiko Schocher, DENX Software Engineering, [email protected]
+ * Copyright (C) 2026 Heiko Schocher, Nabla Software Engineering 
<[email protected]>
+ */
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <log.h>
+#include <spi.h>
+#include <vsprintf.h>
+#include <linux/bitops.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sizes.h>
+
+/* Instruction set of the 23x series serial SRAMs */
+#define MCHP23_CMD_WRITE_STATUS        0x01
+#define MCHP23_CMD_WRITE       0x02
+#define MCHP23_CMD_READ                0x03
+
+/* MODE register, bits 7:6 select byte, page or sequential operation */
+#define MCHP23_MODE_SEQ                BIT(6)
+
+/* The 1 Mbit parts take a 24 bit address */
+#define MCHP23_ADDR_LEN                3
+
+/**
+ * struct spi_ram_data - what one supported device looks like
+ *
+ * @size:      usable size of the array in bytes
+ * @name:      name the MTD device is registered under
+ */
+struct spi_ram_data {
+       u32 size;
+       const char *name;
+};
+
+/**
+ * struct spi_ram_priv - per device state
+ *
+ * @name:      MTD name, built from the device name and the sequence number
+ *             so that several of these can coexist
+ */
+struct spi_ram_priv {
+       char name[16];
+};
+
+static int spi_ram_xfer(struct spi_slave *spi, const u8 *cmd, size_t cmd_len,
+                       const void *data_out, void *data_in, size_t data_len)
+{
+       unsigned long flags = SPI_XFER_BEGIN;
+       int ret;
+
+       if (!data_len)
+               flags |= SPI_XFER_END;
+
+       ret = spi_xfer(spi, cmd_len * 8, cmd, NULL, flags);
+       if (ret) {
+               log_debug("failed to send command (%zu bytes): %d\n",
+                         cmd_len, ret);
+               return ret;
+       }
+
+       if (!data_len)
+               return 0;
+
+       ret = spi_xfer(spi, data_len * 8, data_out, data_in, SPI_XFER_END);
+       if (ret)
+               log_debug("failed to transfer %zu bytes of data: %d\n",
+                         data_len, ret);
+
+       return ret;
+}
+
+/**
+ * spi_ram_command() - run one read or write command over the whole length
+ *
+ * Sequential operation was selected during probe, so the device serves a
+ * transfer of any length from a single command and the bus is claimed once.
+ *
+ * @dev:       the SRAM device
+ * @opcode:    MCHP23_CMD_READ or MCHP23_CMD_WRITE
+ * @offset:    byte offset into the array
+ * @data_out:  data to write, NULL when reading
+ * @data_in:   where to put data read, NULL when writing
+ * @len:       number of bytes to transfer
+ * Return: 0 on success, negative on error
+ */
+static int spi_ram_command(struct udevice *dev, u8 opcode, u32 offset,
+                          const void *data_out, void *data_in, size_t len)
+{
+       struct spi_slave *spi = dev_get_parent_priv(dev);
+       u8 cmd[1 + MCHP23_ADDR_LEN];
+       int ret;
+
+       cmd[0] = opcode;
+       cmd[1] = offset >> 16;
+       cmd[2] = offset >> 8;
+       cmd[3] = offset;
+
+       ret = spi_claim_bus(spi);
+       if (ret) {
+               dev_dbg(dev, "failed to claim SPI bus: %d\n", ret);
+               return ret;
+       }
+
+       ret = spi_ram_xfer(spi, cmd, sizeof(cmd), data_out, data_in, len);
+
+       spi_release_bus(spi);
+
+       return ret;
+}
+
+static int spi_ram_read(struct mtd_info *mtd, loff_t from, size_t len,
+                       size_t *retlen, u_char *buf)
+{
+       int ret;
+
+       ret = spi_ram_command(mtd->dev, MCHP23_CMD_READ, from, NULL, buf, len);
+       if (ret)
+               return ret;
+
+       *retlen = len;
+
+       return 0;
+}
+
+static int spi_ram_write(struct mtd_info *mtd, loff_t to, size_t len,
+                        size_t *retlen, const u_char *buf)
+{
+       int ret;
+
+       ret = spi_ram_command(mtd->dev, MCHP23_CMD_WRITE, to, buf, NULL, len);
+       if (ret)
+               return ret;
+
+       *retlen = len;
+
+       return 0;
+}
+
+static int spi_ram_probe(struct udevice *dev)
+{
+       const struct spi_ram_data *data =
+               (const struct spi_ram_data *)dev_get_driver_data(dev);
+       struct mtd_info *mtd = dev_get_uclass_priv(dev);
+       struct spi_ram_priv *priv = dev_get_priv(dev);
+       struct spi_slave *spi = dev_get_parent_priv(dev);
+       u8 cmd[2];
+       int ret;
+
+       ret = spi_claim_bus(spi);
+       if (ret) {
+               dev_dbg(dev, "failed to claim SPI bus: %d\n", ret);
+               return ret;
+       }
+
+       /*
+        * The device powers up in byte operation, where a command transfers
+        * exactly one byte. Select sequential operation instead, so that a
+        * transfer of any length is served by a single command.
+        */
+       cmd[0] = MCHP23_CMD_WRITE_STATUS;
+       cmd[1] = MCHP23_MODE_SEQ;
+       ret = spi_ram_xfer(spi, cmd, sizeof(cmd), NULL, NULL, 0);
+
+       spi_release_bus(spi);
+
+       if (ret) {
+               dev_dbg(dev, "failed to select sequential operation: %d\n",
+                       ret);
+               return ret;
+       }
+
+       snprintf(priv->name, sizeof(priv->name), "%s%d", data->name,
+                dev_seq(dev));
+       mtd->name = priv->name;
+       mtd->dev = dev;
+       mtd->type = MTD_RAM;
+       mtd->flags = MTD_CAP_RAM;
+       mtd->size = data->size;
+       mtd->writesize = 1;
+       mtd->writebufsize = 1;
+
+       /*
+        * No _erase: a RAM has nothing to erase, which MTD_NO_ERASE in the
+        * flags above states, and mtd_erase() answers -EOPNOTSUPP for a device
+        * that provides none.
+        */
+       mtd->_read = spi_ram_read;
+       mtd->_write = spi_ram_write;
+
+       return add_mtd_device(mtd);
+}
+
+static int spi_ram_remove(struct udevice *dev)
+{
+       return del_mtd_device(dev_get_uclass_priv(dev));
+}
+
+static const struct spi_ram_data mchp23lcv1024_data = {
+       .size = SZ_128K,
+       .name = "spi-ram",
+};
+
+static const struct udevice_id spi_ram_ids[] = {
+       { .compatible = "microchip,mchp23lcv1024",
+         .data = (ulong)&mchp23lcv1024_data },
+       { }
+};
+
+U_BOOT_DRIVER(spi_ram) = {
+       .name           = "spi_ram",
+       .id             = UCLASS_MTD,
+       .of_match       = spi_ram_ids,
+       .probe          = spi_ram_probe,
+       .remove         = spi_ram_remove,
+       .priv_auto      = sizeof(struct spi_ram_priv),
+};
-- 
2.55.0

Reply via email to