[U-Boot] [RFC PATCH] mtd: spi-nor: add rockchip sfc driver
Andy Yan
andy.yan at rock-chips.com
Fri Jan 5 09:55:28 UTC 2018
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.yan at 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, ®s->rcvr);
+ ret = readl_poll_timeout(®s->rcvr, val, !(val & SFC_RESET), 1000);
+ if (ret < 0)
+ dev_err(sfc->dev, "sfc reset timeout\n");
+
+ writel(0xFFFFFFFF, ®s->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(®s->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(®s->sr);
+ fsr = readl(®s->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(®s->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(®s->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(®s->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, ®s->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, ®s->ctrl);
+
+ val = cmd;
+ val |= len << SFC_TRB_SHIFT;
+ val |= optype << SFC_RDWR_SHIFT;
+ writel(val, ®s->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, ®s->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, ®s->abit);
+ writel(val, ®s->cmd);
+ writel(offset, ®s->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
More information about the U-Boot
mailing list