SFC stands for Serial Flash Controller on some
rockchip platforms such as RV1108 / RK3128.

This patch add support for it under mtd/spi-nor
framework

Signed-off-by: Andy Yan <andy....@rock-chips.com>

---

 drivers/mtd/spi-nor/Kconfig        |   8 +
 drivers/mtd/spi-nor/Makefile       |   1 +
 drivers/mtd/spi-nor/rockchip_sfc.c | 432 +++++++++++++++++++++++++++++++++++++
 3 files changed, 441 insertions(+)
 create mode 100644 drivers/mtd/spi-nor/rockchip_sfc.c

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index fb53afb..07e9b18 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -55,6 +55,14 @@ config MTD_ZYNQ_QSPI
          Zynq QSPI IP core. This IP is used to connect the flash in
          4-bit qspi, 8-bit dual stacked and shared 4-bit dual parallel.
 
+config MTD_ROCKCHIP_SFC
+       bool "Rockchip SFC driver"
+       depends on ARCH_ROCKCHIP
+       help
+         Enable the Rockchip SFC(Serial Flash Controller). This driver
+         can be used to access the SPI NOR flash with standard SPI,
+         dual SPI and quad SPI mode.
+
 config SPI_NOR_MISC
        bool "Miscellaneous SPI NOR flash's support"
        help
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index ded3bfa..2c4577f 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_MTD_M25P80)      += m25p80.o
 
 ## spi-nor drivers
 obj-$(CONFIG_MTD_ZYNQ_QSPI)    += zynq_qspinor.o
+obj-$(CONFIG_MTD_ROCKCHIP_SFC)  += rockchip_sfc.o
diff --git a/drivers/mtd/spi-nor/rockchip_sfc.c 
b/drivers/mtd/spi-nor/rockchip_sfc.c
new file mode 100644
index 0000000..fc880b3
--- /dev/null
+++ b/drivers/mtd/spi-nor/rockchip_sfc.c
@@ -0,0 +1,432 @@
+/*
+ * Rockchip SPI-NOR flash controller driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+
+#include <linux/iopoll.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spi-nor.h>
+
+/* SFC_CTRL */
+#define SFC_DATA_WIDTH_SHIFT   12
+#define SFC_DATA_WIDTH_MASK    GENMASK(13, 12)
+#define SFC_ADDR_WIDTH_SHIFT   10
+#define SFC_ADDR_WIDTH_MASK    GENMASK(11, 10)
+#define SFC_CMD_WIDTH_SHIFT    8
+#define SFC_CMD_WIDTH_MASK     GENMASK(9, 8)
+#define SFC_DATA_SHIFT_NEGETIVE        BIT(1)
+
+/* SFC_CMD */
+#define SFC_READ_DUMMY_SHIFT   8
+#define SFC_RDWR_SHIFT         12
+#define SFC_WR                 1
+#define SFC_RD                 0
+#define SFC_ADDR_0BITS         (0 << 14)
+#define SFC_ADDR_24BITS                (1 << 14)
+#define SFC_ADDR_32BITS                (2 << 14)
+#define SFC_ADDR_XBITS         (3 << 14)
+#define SFC_TRB_SHIFT          (16)
+#define SFC_TRB_MASK           GENMASK(29, 16)
+
+/* Dma start trigger signal. Auto cleared after write */
+#define SFC_DMA_START          BIT(0)
+
+#define SFC_RESET              BIT(0)
+
+/* SFC_FSR */
+#define SFC_RXLV_SHIFT         (16)
+#define SFC_RXLV_MASK          GENMASK(20, 16)
+#define SFC_TXLV_SHIFT         (8)
+#define SFC_TXLV_MASK          GENMASK(12, 8)
+#define SFC_RX_FULL            BIT(3)  /* rx fifo full */
+#define SFC_RX_EMPTY           BIT(2)  /* rx fifo empty */
+#define SFC_TX_EMPTY           BIT(1)  /* tx fifo empty */
+#define SFC_TX_FULL            BIT(0)  /* tx fifo full */
+
+#define SFC_BUSY               BIT(0)  /* sfc busy flag */
+
+/* SFC_RISR */
+#define DMA_FINISH_INT         BIT(7)        /* dma interrupt */
+#define SPI_ERR_INT            BIT(6)        /* Nspi error interrupt */
+#define AHB_ERR_INT            BIT(5)        /* Ahb bus error interrupt */
+#define TRANS_FINISH_INT       BIT(4)        /* Transfer finish interrupt */
+#define TX_EMPTY_INT           BIT(3)        /* Tx fifo empty interrupt */
+#define TX_OF_INT              BIT(2)        /* Tx fifo overflow interrupt */
+#define RX_UF_INT              BIT(1)        /* Rx fifo underflow interrupt */
+#define RX_FULL_INT            BIT(0)        /* Rx fifo full interrupt */
+
+#define SFC_MAX_TRB            (15 * 1024)
+
+enum rockchip_sfc_if_type {
+       IF_TYPE_STD,
+       IF_TYPE_DUAL,
+       IF_TYPE_QUAD,
+};
+
+struct rockchip_sfc_regs {
+       u32 ctrl;
+       u32 imr;
+       u32 iclr;
+       u32 ftlr;
+       u32 rcvr;
+       u32 ax;
+       u32 abit;
+       u32 isr;
+       u32 fsr;
+       u32 sr;
+       u32 risr;
+       u32 reserved[21];
+       u32 dmatr;
+       u32 dmaaddr;
+       u32 reserved1[30];
+       u32 cmd;
+       u32 addr;
+       u32 data;
+};
+check_member(rockchip_sfc_regs, data, 0x108);
+
+struct rockchip_sfc {
+       struct udevice *dev;
+       struct rockchip_sfc_regs *base;
+};
+
+struct rockchip_sfc_platdata {
+       struct spi_nor spi_nor;
+};
+
+static u8 rockchip_sfc_get_if_type(struct spi_nor *nor)
+{
+       if (nor->read_opcode == SNOR_OP_READ ||
+           nor->read_opcode == SNOR_OP_READ_FAST)
+               return IF_TYPE_STD;
+       else if (nor->read_opcode == SNOR_OP_READ_1_1_2)
+               return IF_TYPE_DUAL;
+       else if (nor->read_opcode == SNOR_OP_READ_1_1_4)
+               return IF_TYPE_QUAD;
+       else
+               return IF_TYPE_STD;
+}
+
+static int rockchip_sfc_reset(struct rockchip_sfc *sfc)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       u32 val;
+       int ret = 0;
+
+       writel(SFC_RESET, &regs->rcvr);
+       ret = readl_poll_timeout(&regs->rcvr, val, !(val & SFC_RESET), 1000);
+       if (ret < 0)
+               dev_err(sfc->dev, "sfc reset timeout\n");
+
+       writel(0xFFFFFFFF, &regs->iclr);
+
+       debug("sfc reset\n");
+
+       return ret;
+}
+
+static int rockchip_sfc_wait_fifo_ready(struct rockchip_sfc *sfc, int wr,
+                                       u32 timeout_ms)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       unsigned long tbase = get_timer(0);
+       u8 level;
+       u32 fsr;
+
+       do {
+               fsr = readl(&regs->fsr);
+               if (wr)
+                       level = (fsr & SFC_TXLV_MASK) >> SFC_TXLV_SHIFT;
+               else
+                       level = (fsr & SFC_RXLV_MASK) >> SFC_RXLV_SHIFT;
+               if (get_timer(tbase) > timeout_ms) {
+                       dev_err(sfc->dev, "wait fifo timeout\n");
+                       return -ETIMEDOUT;
+               }
+       } while (!level);
+
+       return level;
+}
+
+/* The SFC_CTRL register is a global control register,
+ * when the controller is in busy state(SFC_SR),
+ * SFC_CTRL cannot be set.
+ */
+static int rockchip_sfc_wait_idle(struct rockchip_sfc *sfc,
+                                 u32 timeout_ms)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       unsigned long tbase = get_timer(0);
+       u32 sr, fsr;
+
+       while (1) {
+               sr = readl(&regs->sr);
+               fsr = readl(&regs->fsr);
+               if ((fsr & SFC_TX_EMPTY) && (fsr & SFC_RX_EMPTY) && !(sr & 
SFC_BUSY))
+                       break;
+               if (get_timer(tbase) > timeout_ms) {
+                       dev_err(sfc->dev, "sfc idle timeout(sr:0x%08x 
fsr:0x%08x)\n",
+                               sr, fsr);
+                       rockchip_sfc_reset(sfc);
+                       return -ETIMEDOUT;
+               }
+               udelay(100);
+       }
+
+       return 0;
+}
+
+static int rockchip_sfc_read_fifo(struct rockchip_sfc *sfc, u8 *buf, int len)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       u32 val;
+       u8 count;
+       u8 rx_level;
+       u32 words;
+
+       /* word aligned access only */
+       if (len >= 4) {
+               words = len >> 2;
+               while (words) {
+                       rx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_RD, 
1000);
+                       if (rx_level <= 0)
+                               return rx_level;
+                       count = min_t(u32, rx_level, words);
+                       readsl(&regs->data, buf, count);
+                       buf += count << 2;
+                       words -= count;
+               }
+               len &= 3;
+       }
+
+       /* read the rest none word aligned bytes */
+       if (len) {
+               rx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_RD, 1000);
+               if (rx_level <= 0)
+                       return rx_level;
+               val = readl(&regs->data);
+               memcpy(buf, &val, len);
+       }
+
+       return 0;
+}
+
+static int rockchip_sfc_write_fifo(struct rockchip_sfc *sfc,
+                                  u_char *buf, u32 len)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       u32 bytes = len & 0x3;
+       u32 words = len >> 2;
+       int tx_level;
+       u32 val = 0;
+       u8 count;
+
+       while (words) {
+               tx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_WR, 1000);
+               if (tx_level <= 0)
+                       return tx_level;
+               count = min_t(u32, words, tx_level);
+               writesl(&regs->data, buf, count);
+               buf += count << 2;
+               words -= count;
+       }
+
+       /* handle the rest none word aligned bytes */
+       if (bytes) {
+               tx_level = rockchip_sfc_wait_fifo_ready(sfc, SFC_WR, 1000);
+               if (tx_level <= 0)
+                       return tx_level;
+               memcpy(&val, buf, bytes);
+               writel(val, &regs->data);
+       }
+
+       return 0;
+}
+
+static int rockchip_sfc_op_reg(struct rockchip_sfc *sfc,
+                              u8 cmd, int len, uint optype)
+{
+       struct rockchip_sfc_regs *regs = sfc->base;
+       u32 val;
+
+       val = IF_TYPE_STD << SFC_DATA_WIDTH_SHIFT;
+       val |= IF_TYPE_STD << SFC_ADDR_WIDTH_SHIFT;
+       val |= IF_TYPE_STD << SFC_CMD_WIDTH_SHIFT;
+       val |= SFC_DATA_SHIFT_NEGETIVE;
+
+       rockchip_sfc_wait_idle(sfc, 10);
+       writel(val, &regs->ctrl);
+
+       val = cmd;
+       val |= len << SFC_TRB_SHIFT;
+       val |= optype << SFC_RDWR_SHIFT;
+       writel(val, &regs->cmd);
+
+       return 0;
+}
+
+static int rockchip_sfc_read_reg(struct udevice *dev, u8 cmd,
+                                u8 *buf, int len)
+{
+       struct rockchip_sfc *sfc = dev_get_priv(dev);
+       int ret;
+
+       ret = rockchip_sfc_op_reg(sfc, cmd, len, SFC_RD);
+       if (ret)
+               return ret;
+
+       return rockchip_sfc_read_fifo(sfc, buf, len);
+}
+
+static int rockchip_sfc_write_reg(struct udevice *dev, u8 cmd,
+                                 u8 *buf, int len)
+{
+       struct rockchip_sfc *sfc = dev_get_priv(dev);
+       int ret;
+
+       ret = rockchip_sfc_op_reg(sfc, cmd, len, SFC_WR);
+       if (ret)
+               return ret;
+       if (buf && len)
+               ret =  rockchip_sfc_write_fifo(sfc, buf, len);
+
+       return ret;
+}
+
+static void rockchip_sfc_setup_xfer(struct rockchip_sfc *sfc,
+                                   loff_t offset, size_t len,
+                                    uint op_type)
+{
+       struct spi_nor *nor = spi_nor_get_spi_nor_dev(sfc->dev);
+       struct rockchip_sfc_regs *regs = sfc->base;
+       u8 if_type = IF_TYPE_STD;
+       u32 val;
+
+       if (op_type == SFC_RD)
+               if_type = rockchip_sfc_get_if_type(nor);
+       val = if_type << SFC_DATA_WIDTH_SHIFT;
+       val |= IF_TYPE_STD << SFC_ADDR_WIDTH_SHIFT;
+       val |= IF_TYPE_STD << SFC_CMD_WIDTH_SHIFT;
+       val |= SFC_DATA_SHIFT_NEGETIVE;
+
+       rockchip_sfc_wait_idle(sfc, 10);
+       writel(val, &regs->ctrl);
+
+       if (op_type == SFC_RD) {
+               val = nor->read_opcode;
+               val |= nor->read_dummy << SFC_READ_DUMMY_SHIFT;
+       } else {
+               val = nor->program_opcode;
+       }
+
+       val |= op_type << SFC_RDWR_SHIFT;
+       val |= (nor->addr_width == 4) ? SFC_ADDR_32BITS : SFC_ADDR_24BITS;
+       val |= len << SFC_TRB_SHIFT;
+
+       /* Should minus one as 0x0 means 1 bit flash address */
+       writel(nor->addr_width * 8 - 1, &regs->abit);
+       writel(val, &regs->cmd);
+       writel(offset, &regs->addr);
+}
+
+static int rockchip_sfc_pio_xfer(struct rockchip_sfc *sfc,
+                                loff_t offset, size_t len,
+                                u_char *buf, uint op_type)
+{
+       size_t trans;
+       int ret;
+
+       while (len > 0) {
+               trans = min_t(size_t, SFC_MAX_TRB, len);
+               rockchip_sfc_setup_xfer(sfc, offset, trans, op_type);
+
+               if (op_type == SFC_WR) {
+                       ret = rockchip_sfc_write_fifo(sfc, buf, trans);
+                       if (ret)
+                               return ret;
+               } else {
+                       ret = rockchip_sfc_read_fifo(sfc, buf, trans);
+                       if (ret)
+                               return ret;
+               }
+               len -= trans;
+               buf += trans;
+               offset += trans;
+       }
+
+       return 0;
+}
+
+static int rockchip_sfc_read(struct udevice *dev, loff_t from,
+                            size_t len, u_char *buf)
+{
+       struct rockchip_sfc *sfc = dev_get_priv(dev);
+
+       rockchip_sfc_pio_xfer(sfc, from, len, buf, SFC_RD);
+
+       return 0;
+}
+
+static int rockchip_sfc_write(struct udevice *dev, loff_t to,
+                             size_t len, const u_char *buf)
+{
+       struct spi_nor *nor = spi_nor_get_spi_nor_dev(dev);
+       struct rockchip_sfc *sfc = dev_get_priv(dev);
+
+       if (!buf)
+               rockchip_sfc_write_reg(dev, nor->erase_opcode, (u8 *)&to, 
nor->addr_width);
+       else
+               rockchip_sfc_pio_xfer(sfc, to, len, (u_char *)buf, SFC_WR);
+
+       return 0;
+}
+
+const struct spi_nor_ops rockchip_sfc_ops = {
+       .read           = rockchip_sfc_read,
+       .write          = rockchip_sfc_write,
+       .read_reg       = rockchip_sfc_read_reg,
+       .write_reg      = rockchip_sfc_write_reg,
+};
+
+static int rockchip_sfc_bind(struct udevice *dev)
+{
+       struct rockchip_sfc_platdata *plat = dev_get_platdata(dev);
+
+       return spi_nor_bind(dev, &plat->spi_nor);
+}
+
+static int rockchip_sfc_probe(struct udevice *dev)
+{
+       struct rockchip_sfc_platdata *plat = dev_get_platdata(dev);
+       struct spi_nor_uclass_priv *upriv = dev_get_uclass_priv(dev);
+       struct rockchip_sfc *sfc = dev_get_priv(dev);
+
+       sfc->base = (void *)dev_read_addr(dev);
+       sfc->dev = dev;
+       upriv->spi_nor = &plat->spi_nor;
+
+       return 0;
+}
+
+static const struct udevice_id rockchip_sfc_ids[] = {
+       { .compatible = "rockchip,sfc" },
+       { }
+};
+
+U_BOOT_DRIVER(rockchip_sfc) = {
+       .name   = "rockchip_sfc",
+       .id     = UCLASS_SPI_NOR,
+       .of_match = rockchip_sfc_ids,
+       .ops    = &rockchip_sfc_ops,
+       .bind   = rockchip_sfc_bind,
+       .probe  = rockchip_sfc_probe,
+       .priv_auto_alloc_size = sizeof(struct rockchip_sfc),
+       .platdata_auto_alloc_size = sizeof(struct rockchip_sfc_platdata),
+};
-- 
2.7.4


_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
https://lists.denx.de/listinfo/u-boot

Reply via email to