[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, &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




More information about the U-Boot mailing list