This driver supports the spiflash core in all RTL838x/RTL839x SoCs,
and likely some older models as well (RTL8196C).

Signed-off-by: Bert Vermeulen <b...@biot.com>
---
 drivers/mtd/spi-nor/controllers/Kconfig       |   7 +
 drivers/mtd/spi-nor/controllers/Makefile      |   1 +
 .../spi-nor/controllers/rtl8380-spiflash.c    | 347 ++++++++++++++++++
 .../spi-nor/controllers/rtl8380-spiflash.h    |  34 ++
 4 files changed, 389 insertions(+)
 create mode 100644 drivers/mtd/spi-nor/controllers/rtl8380-spiflash.c
 create mode 100644 drivers/mtd/spi-nor/controllers/rtl8380-spiflash.h

diff --git a/drivers/mtd/spi-nor/controllers/Kconfig 
b/drivers/mtd/spi-nor/controllers/Kconfig
index 5c0e0ec2e6d1..1e035c511ea5 100644
--- a/drivers/mtd/spi-nor/controllers/Kconfig
+++ b/drivers/mtd/spi-nor/controllers/Kconfig
@@ -62,3 +62,10 @@ config SPI_INTEL_SPI_PLATFORM
 
          To compile this driver as a module, choose M here: the module
          will be called intel-spi-platform.
+
+config SPI_REALTEK_RTL8380
+       tristate "Realtek RTL8380 SPI flash controller"
+       depends on MACH_REALTEK
+       help
+         This enables support for the SPI flash controller in the Realtek
+         RTL8380 series SoCs.
diff --git a/drivers/mtd/spi-nor/controllers/Makefile 
b/drivers/mtd/spi-nor/controllers/Makefile
index e7abba491d98..f03a79e88a78 100644
--- a/drivers/mtd/spi-nor/controllers/Makefile
+++ b/drivers/mtd/spi-nor/controllers/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_SPI_NXP_SPIFI)     += nxp-spifi.o
 obj-$(CONFIG_SPI_INTEL_SPI)    += intel-spi.o
 obj-$(CONFIG_SPI_INTEL_SPI_PCI)        += intel-spi-pci.o
 obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM)   += intel-spi-platform.o
+obj-$(CONFIG_SPI_REALTEK_RTL8380)      += rtl8380-spiflash.o
diff --git a/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.c 
b/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.c
new file mode 100644
index 000000000000..cd1f48f1e3d0
--- /dev/null
+++ b/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mtd/spi-nor.h>
+
+#include "rtl8380-spiflash.h"
+
+
+extern struct realtek_soc_info soc_info;
+
+static int rtl8380_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, 
size_t len);
+static int rtl8380_nor_write_reg(struct spi_nor *nor, u8 opcode, const u8 
*buf, size_t len);
+
+
+static inline u32 rtl8380_read(struct rtl8380_nor *rtnor, u32 reg, bool wait)
+{
+       if (wait)
+               while (!(readl(rtnor->base + RTL8380_SPI_SFCSR) & 
RTL8380_SPI_SFCSR_RDY))
+                       ;
+       return readl(rtnor->base + reg);
+}
+
+static inline void rtl8380_write(struct rtl8380_nor *rtnor, u32 reg, u32 
value, bool wait)
+{
+       if (wait)
+               while (!(readl(rtnor->base + RTL8380_SPI_SFCSR) & 
RTL8380_SPI_SFCSR_RDY))
+                       ;
+       writel(value, rtnor->base + reg);
+}
+
+/*
+ * Deactivate both chip selects and return base SFCSR register settings: 
correct
+ * CS, data length 1, IO width 1.
+ */
+static uint32_t spi_prep(struct rtl8380_nor *rtnor)
+{
+       uint32_t init, ret;
+
+       /* Deactivate CS0 and CS1 first */
+       init = (RTL8380_SPI_SFCSR_CSB0 | RTL8380_SPI_SFCSR_CSB1) & 
RTL8380_SPI_SFCSR_LEN_MASK;
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, init, true);
+
+       /* CS bitfield is active low, so reversed logic */
+       if (rtnor->cs == 0)
+               ret = RTL8380_SPI_SFCSR_LEN1 | RTL8380_SPI_SFCSR_CSB1;
+       else
+               ret = RTL8380_SPI_SFCSR_LEN1 | RTL8380_SPI_SFCSR_CSB0 | 
RTL8380_SPI_SFCSR_CS;
+
+       return ret;
+}
+
+static int rtl8380_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, 
size_t len)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       uint32_t sfcsr;
+
+       sfcsr = spi_prep(rtnor);
+       sfcsr &= RTL8380_SPI_SFCSR_LEN_MASK | RTL8380_SPI_SFCSR_LEN1;
+
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr, true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, opcode << 24, true);
+
+       while (len > 0) {
+               *(buf) = rtl8380_read(rtnor, RTL8380_SPI_SFDR, true) >> 24;
+               buf++;
+               len--;
+       }
+
+       return 0;
+}
+
+static int rtl8380_nor_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
+                                size_t len)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       uint32_t sfcsr, sfdr, len_bits;
+
+       sfcsr = spi_prep(rtnor);
+       sfdr = opcode << 24;
+
+       if (len == 0) {
+               len_bits = RTL8380_SPI_SFCSR_LEN1;
+       } else if (len == 1) {
+               sfdr |= buf[0] << 16;
+               len_bits = RTL8380_SPI_SFCSR_LEN2;
+       } else if (len == 2) {
+               sfdr |= buf[0] << 16;
+               sfdr |= buf[1] << 8;
+               len_bits = RTL8380_SPI_SFCSR_LEN3;
+       } else {
+               return -EINVAL;
+       }
+       sfcsr |= len_bits;
+
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr, true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, sfdr, true);
+
+       return 0;
+}
+
+static ssize_t rtl8380_nor_read(struct spi_nor *nor, loff_t from, size_t len,
+                               u_char *buf)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       uint32_t sfcsr;
+       ssize_t ret = 0;
+       int sfcsr_addrlen_bits, i;
+
+       if (rtnor->nor.addr_width == 4) {
+               sfcsr_addrlen_bits = 0x03;
+       } else {
+               sfcsr_addrlen_bits = 0x02;
+               from <<= 8;
+       }
+
+       sfcsr = spi_prep(rtnor);
+       sfcsr &= RTL8380_SPI_SFCSR_LEN_MASK;
+
+       /* Send read command */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN1, 
true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, rtnor->nor.read_opcode << 24, 
false);
+
+       /* Send address */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | (sfcsr_addrlen_bits << 
28), true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, from, false);
+
+       /* Dummy cycles */
+       for (i = 0; i < nor->read_dummy / 8; i++) {
+               rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr, true);
+               rtl8380_write(rtnor, RTL8380_SPI_SFDR, 0, false);
+       }
+
+       /* Read 4 bytes at a time */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN4, 
true);
+       while (len >= 4){
+               *((uint32_t*) buf) = rtl8380_read(rtnor, RTL8380_SPI_SFDR, 
false);
+               buf += 4;
+               len -= 4;
+               ret += 4;
+       }
+
+       /* Read remainder 1 byte at a time */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN1, 
true);
+       while (len > 0) {
+               *(buf) = rtl8380_read(rtnor, RTL8380_SPI_SFDR, false) >> 24;
+               buf++;
+               len--;
+               ret++;
+       }
+
+       return ret;
+}
+
+static ssize_t rtl8380_nor_write(struct spi_nor *nor, loff_t to, size_t len,
+                                const u_char *buf)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       uint32_t sfcsr;
+       ssize_t ret = 0;
+       int sfcsr_addrlen_bits;
+
+       if (rtnor->nor.addr_width == 4) {
+               sfcsr_addrlen_bits = 0x03;
+       } else {
+               sfcsr_addrlen_bits = 0x02;
+               to <<= 8;
+       }
+
+       sfcsr = spi_prep(rtnor);
+       sfcsr &= RTL8380_SPI_SFCSR_LEN_MASK;
+
+       /* Send write command */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN1, 
true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, nor->program_opcode << 24, 
false);
+
+       /* Send address */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | (sfcsr_addrlen_bits << 
28), true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, to, false);
+
+       /* Write 4 bytes at a time */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN4, 
true);
+       while (len >= 4) {
+               rtl8380_write(rtnor, RTL8380_SPI_SFDR, *((uint32_t*)buf), true);
+               buf += 4;
+               len -= 4;
+               ret += 4;
+       }
+
+       /* Write remainder 1 byte at a time */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN1, 
true);
+       while (len > 0) {
+               rtl8380_write(rtnor, RTL8380_SPI_SFDR, *buf << 24, true);
+               buf++;
+               len--;
+               ret++;
+       }
+
+       return ret;
+}
+
+static int rtl8380_erase(struct spi_nor *nor, loff_t offset)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       uint32_t sfcsr;
+       int sfcsr_addrlen_bits;
+
+       if (rtnor->nor.addr_width == 4) {
+               sfcsr_addrlen_bits = 0x03;
+       } else {
+               sfcsr_addrlen_bits = 0x02;
+               offset <<= 8;
+       }
+
+       sfcsr = spi_prep(rtnor);
+
+       /* Send erase command */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | RTL8380_SPI_SFCSR_LEN1, 
true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, nor->erase_opcode << 24, false);
+
+       /* Send address */
+       rtl8380_write(rtnor, RTL8380_SPI_SFCSR, sfcsr | (sfcsr_addrlen_bits << 
28), true);
+       rtl8380_write(rtnor, RTL8380_SPI_SFDR, offset, false);
+
+       return 0;
+}
+
+static const struct spi_nor_controller_ops rtl8380_controller_ops = {
+       .read_reg = rtl8380_nor_read_reg,
+       .write_reg = rtl8380_nor_write_reg,
+       .read = rtl8380_nor_read,
+       .write = rtl8380_nor_write,
+       .erase = rtl8380_erase,
+};
+
+static int rtl8380_spi_nor_scan(struct spi_nor *nor)
+{
+       struct rtl8380_nor *rtnor = nor->priv;
+       static const struct spi_nor_hwcaps hwcaps = {
+               .mask = SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST | 
SNOR_HWCAPS_PP
+       };
+       u32 sfcr;
+       int ret;
+
+       /* Turn on big-endian byte ordering */
+       sfcr = rtl8380_read(rtnor, RTL8380_SPI_SFCR, true);
+       sfcr |= RTL8380_SPI_SFCR_RBO | RTL8380_SPI_SFCR_WBO;
+       rtl8380_write(rtnor, RTL8380_SPI_SFCR, sfcr, true);
+
+       ret = spi_nor_scan(nor, NULL, &hwcaps);
+
+       return ret;
+}
+
+static int rtl8380_nor_init(struct rtl8380_nor *rtnor,
+                    struct device_node *flash_node)
+{
+       struct spi_nor *nor;
+       int ret;
+
+       nor = &rtnor->nor;
+       nor->priv = rtnor;
+       spi_nor_set_flash_node(nor, flash_node);
+       nor->controller_ops = &rtl8380_controller_ops;
+       nor->mtd.name = "rtl8380-spiflash";
+
+       ret = rtl8380_spi_nor_scan(nor);
+       if (ret)
+               return ret;
+       pr_info("SPI flash address width is %d bytes\n", nor->addr_width);
+
+       ret = mtd_device_parse_register(&nor->mtd, NULL, NULL, NULL, 0);
+
+       return ret;
+}
+
+static int rtl8380_nor_drv_probe(struct platform_device *pdev)
+{
+       struct device_node *flash_np;
+       struct resource *res;
+       struct rtl8380_nor *rtnor;
+       int ret;
+
+       if (!pdev->dev.of_node) {
+               dev_err(&pdev->dev, "No DT found\n");
+               return -EINVAL;
+       }
+
+       rtnor = devm_kzalloc(&pdev->dev, sizeof(*rtnor), GFP_KERNEL);
+       if (!rtnor)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, rtnor);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       rtnor->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(rtnor->base))
+               return PTR_ERR(rtnor->base);
+
+       rtnor->nor.dev = &pdev->dev;
+
+       /* Only support one attached flash */
+       flash_np = of_get_next_available_child(pdev->dev.of_node, NULL);
+       if (!flash_np) {
+               dev_err(&pdev->dev, "no SPI flash device to configure\n");
+               return -ENODEV;
+       }
+
+       /* Optional chip select, defaults to 0 */
+       ret = of_property_read_u32(flash_np, "reg", &rtnor->cs);
+       if (ret)
+               rtnor->cs = 0;
+
+       ret = rtl8380_nor_init(rtnor, flash_np);
+
+       return ret;
+}
+
+static int rtl8380_nor_drv_remove(struct platform_device *pdev)
+{
+       struct rtl8380_nor *rtnor;
+
+       rtnor = platform_get_drvdata(pdev);
+       mtd_device_unregister(&rtnor->nor.mtd);
+
+       return 0;
+}
+
+static const struct of_device_id rtl8380_nor_of_ids[] = {
+       { .compatible = "realtek,rtl8380-spiflash"},
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtl8380_nor_of_ids);
+
+static struct platform_driver rtl8380_nor_driver = {
+       .probe = rtl8380_nor_drv_probe,
+       .remove = rtl8380_nor_drv_remove,
+       .driver = {
+               .name = "rtl8380-spiflash",
+               .pm = NULL,
+               .of_match_table = rtl8380_nor_of_ids,
+       },
+};
+
+module_platform_driver(rtl8380_nor_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("RTL8380 SPI NOR Flash Driver");
diff --git a/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.h 
b/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.h
new file mode 100644
index 000000000000..754e67087ed1
--- /dev/null
+++ b/drivers/mtd/spi-nor/controllers/rtl8380-spiflash.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef _RTL8380_SPIFLASH_H_
+#define _RTL8380_SPIFLASH_H_
+
+struct rtl8380_nor {
+       struct spi_nor nor;
+       void __iomem *base;
+       u32 cs;
+};
+
+/* SPI Flash Configuration Register */
+#define RTL8380_SPI_SFCR               0x00
+#define RTL8380_SPI_SFCR_RBO           BIT(28)
+#define RTL8380_SPI_SFCR_WBO           BIT(27)
+
+/* SPI Flash Control and Status Register */
+#define RTL8380_SPI_SFCSR              0x08
+#define RTL8380_SPI_SFCSR_CSB0         BIT(31)
+#define RTL8380_SPI_SFCSR_CSB1         BIT(30)
+#define RTL8380_SPI_SFCSR_RDY          BIT(27)
+#define RTL8380_SPI_SFCSR_CS           BIT(24)
+#define RTL8380_SPI_SFCSR_LEN_MASK     ~(0x03 << 28)
+#define RTL8380_SPI_SFCSR_LEN1         (0x00 << 28)
+#define RTL8380_SPI_SFCSR_LEN2         (0x01 << 28)
+#define RTL8380_SPI_SFCSR_LEN3         (0x02 << 28)
+#define RTL8380_SPI_SFCSR_LEN4         (0x03 << 28)
+
+/* SPI Flash Data Registers */
+#define RTL8380_SPI_SFDR       0x0c
+#define RTL8380_SPI_SFDR2      0x10
+
+#endif /* _RTL8380_SPIFLASH_H_ */
+
-- 
2.25.1

Reply via email to