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, ®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 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot