Message ID | 1502156619-13127-1-git-send-email-andy.yan@rock-chips.com |
---|---|
State | Deferred |
Delegated to: | Jagannadha Sutradharudu Teki |
Headers | show |
ping On 2017年08月08日 09:43, Andy Yan wrote: > SFC stands for Serial Flash Controller on some > rockchip platforms such as RV1108/RK3128. > > This patch add support for it with Standard,Dual,Quad > mode. > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > > --- > > Changes in v2: > - fix the wrong reference in Makefile, which should be rockchip_sfc.o > not rk_sfc.o > > drivers/spi/Kconfig | 8 + > drivers/spi/Makefile | 1 + > drivers/spi/rockchip_sfc.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ > drivers/spi/rockchip_sfc.h | 82 +++++++++ > 4 files changed, 493 insertions(+) > create mode 100644 drivers/spi/rockchip_sfc.c > create mode 100644 drivers/spi/rockchip_sfc.h > > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index 8a8e8e4..52c4993 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -99,6 +99,14 @@ config ROCKCHIP_SPI > This uses driver model and requires a device tree binding to > operate. > > +config ROCKCHIP_SFC > + bool "Rockchip SFC driver" > + help > + Enable the Rockchip SFC driver, used to access SPI NOR flash > + on Rockchip SoCs. > + This uses driver model and requires a device tree binding to > + operate. > + > config SANDBOX_SPI > bool "Sandbox SPI driver" > depends on SANDBOX && DM > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 9f8b86d..052c608 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -38,6 +38,7 @@ obj-$(CONFIG_MXS_SPI) += mxs_spi.o > obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o > obj-$(CONFIG_PIC32_SPI) += pic32_spi.o > obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o > +obj-$(CONFIG_ROCKCHIP_SFC) += rockchip_sfc.o > obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o > obj-$(CONFIG_SH_SPI) += sh_spi.o > obj-$(CONFIG_SH_QSPI) += sh_qspi.o > diff --git a/drivers/spi/rockchip_sfc.c b/drivers/spi/rockchip_sfc.c > new file mode 100644 > index 0000000..4388e51 > --- /dev/null > +++ b/drivers/spi/rockchip_sfc.c > @@ -0,0 +1,402 @@ > +/* > + * sfc driver for rockchip > + * > + * (C) Copyright 2008-2016 Rockchip Electronics > + * Yifeng.zhao, Software Engineering, <zhao0116@gmail.com>. > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include <common.h> > +#include <clk.h> > +#include <dm.h> > +#include <dt-structs.h> > +#include <errno.h> > +#include <spi.h> > +#include <linux/errno.h> > +#include <asm/io.h> > +#include <asm/arch/clock.h> > +#include <asm/arch/periph.h> > +#include <dm/pinctrl.h> > +#include "rockchip_sfc.h" > + > +DECLARE_GLOBAL_DATA_PTR; > + > +enum rockchip_sfc_if_type { > + IF_TYPE_STD, > + IF_TYPE_DUAL, > + IF_TYPE_QUAD, > +}; > + > +struct rockchip_sfc_platdata { > + s32 frequency; > + fdt_addr_t base; > +}; > + > +struct rockchip_sfc { > + struct rockchip_sfc_reg *regbase; > + struct clk clk; > + unsigned int max_freq; > + unsigned int mode; > + unsigned int speed_hz; > + u32 cmd; > + u32 addr; > +}; > + > +static int rockchip_sfc_ofdata_to_platdata(struct udevice *bus) > +{ > + struct rockchip_sfc_platdata *plat = dev_get_platdata(bus); > + struct rockchip_sfc *sfc = dev_get_priv(bus); > + const void *blob = gd->fdt_blob; > + int node = dev_of_offset(bus); > + int subnode; > + int ret; > + > + plat->base = devfdt_get_addr(bus); > + > + ret = clk_get_by_index(bus, 0, &sfc->clk); > + if (ret < 0) { > + debug("Could not get clock for %s: %d\n", bus->name, ret); > + return ret; > + } > + > + subnode = fdt_first_subnode(blob, node); > + if (subnode < 0) { > + debug("Error: subnode with SPI flash config missing!\n"); > + return -ENODEV; > + } > + > + plat->frequency = fdtdec_get_int(blob, subnode, "spi-max-frequency", > + 100000000); > + > + return 0; > +} > + > +static int rockchip_sfc_probe(struct udevice *bus) > +{ > + struct rockchip_sfc_platdata *plat = dev_get_platdata(bus); > + struct rockchip_sfc *sfc = dev_get_priv(bus); > + int ret; > + > + sfc->regbase = (struct rockchip_sfc_reg *)plat->base; > + > + sfc->max_freq = plat->frequency; > + > + ret = clk_set_rate(&sfc->clk, sfc->max_freq); > + if (ret < 0) { > + debug("%s: Failed to set clock: %d\n", __func__, ret); > + return ret; > + } > + > + return 0; > +} > + > +static int rockchip_sfc_reset(struct rockchip_sfc *sfc) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + int tbase = get_timer(0); > + u32 rcvr; > + int ret = 0; > + > + writel(SFC_RESET, ®s->rcvr); > + do { > + rcvr = readl(®s->rcvr); > + if (get_timer(tbase) > 1000) { > + debug("sfc reset timeout\n"); > + ret = -ETIMEDOUT; > + break; > + } > + udelay(1); > + } while (rcvr); > + > + writel(0xFFFFFFFF, ®s->iclr); > + > + debug("sfc reset\n"); > + > + return ret; > +} > + > +static u8 rockchip_sfc_get_if_type(struct rockchip_sfc *sfc) > +{ > + int type = IF_TYPE_STD; > + > + if (sfc->cmd & SFC_WR) { > + if (sfc->mode & SPI_TX_QUAD) > + type = IF_TYPE_QUAD; > + else if (sfc->mode & SPI_TX_DUAL) > + type = IF_TYPE_DUAL; > + else > + type = IF_TYPE_STD; > + } else { > + if (sfc->mode & SPI_RX_QUAD) > + type = IF_TYPE_QUAD; > + else if (sfc->mode & SPI_RX_DUAL) > + type = IF_TYPE_DUAL; > + else > + type = IF_TYPE_STD; > + } > + > + return type; > +} > + > +static void rockchip_sfc_setup_xfer(struct rockchip_sfc *sfc) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + u32 val = 0x02; > + u32 fsr = readl(®s->fsr); > + u32 sr = readl(®s->sr); > + u8 data_width = IF_TYPE_STD; > + > + if (!(fsr & SFC_TX_EMPTY) || !(fsr & SFC_RX_EMPTY) || (sr & SFC_BUSY)) > + rockchip_sfc_reset(sfc); > + > + if (sfc->cmd & SFC_ADDR_XBITS) > + data_width = rockchip_sfc_get_if_type(sfc); > + > + val |= (data_width << SFC_DATA_WIDTH_SHIFT); > + > + writel(val, ®s->ctrl); > + writel(sfc->cmd, ®s->cmd); > + if (sfc->cmd & SFC_ADDR_XBITS) > + writel(sfc->addr, ®s->addr); > +} > + > +static int rockchip_sfc_do_dma_xfer(struct rockchip_sfc *sfc, u32 *buffer) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + int timeout = 1000; > + int ret = 0; > + int risr; > + unsigned long tbase; > + > + rockchip_sfc_setup_xfer(sfc); > + > + writel(0xFFFFFFFF, ®s->iclr); > + writel((u32)buffer, ®s->dmaaddr); > + writel(SFC_DMA_START, ®s->dmatr); > + > + tbase = get_timer(0); > + do { > + udelay(1); > + risr = readl(®s->risr); > + if (get_timer(tbase) > timeout) { > + debug("dma timeout\n"); > + ret = -ETIMEDOUT; > + break; > + } > + } while (!(risr & TRANS_FINISH_INT)); > + > + writel(0xFFFFFFFF, ®s->iclr); > + > + return ret; > +} > + > +static int rockchip_sfc_dma_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) > +{ > + u32 trb; > + u32 *p32_data = buf; > + int ret = 0; > + > + while (len) { > + trb = min(len, (u32)SFC_MAX_TRB); > + sfc->cmd |= (trb << SFC_TRB_SHIFT); > + ret = rockchip_sfc_do_dma_xfer(sfc, p32_data); > + if (ret < 0) > + break; > + len -= trb; > + sfc->addr += trb; > + p32_data += (trb >> 2); > + } > + > + return ret; > +} > + > +static int rockchip_sfc_wait_fifo_ready(struct rockchip_sfc *sfc, int wr, > + u32 timeout) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + 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) > + return -ETIMEDOUT; > + udelay(1); > + } while (!level); > + > + return level; > +} > + > +static int rockchip_sfc_write(struct rockchip_sfc *sfc, u32 *buf, u32 len) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + u32 bytes = len & 0x3; > + u32 words = len >> 2; > + u32 tx_level = 0; > + u32 val = 0; > + u8 count; > + > + while (words) { > + tx_level = rockchip_sfc_wait_fifo_ready(sfc, 1, 1000); > + if (tx_level <= 0) > + return tx_level; > + count = min(words, tx_level); > + writesl(®s->data, buf, count); > + buf += count; > + words -= count; > + } > + > + /* handle the last none word aligned bytes */ > + if (bytes) { > + tx_level = rockchip_sfc_wait_fifo_ready(sfc, 1, 1000); > + if (tx_level <= 0) > + return tx_level; > + memcpy(&val, buf, bytes); > + writel(val, ®s->data); > + } > + > + return 0; > +} > + > +static int rockchip_sfc_read(struct rockchip_sfc *sfc, u32 *buf, u32 len) > +{ > + struct rockchip_sfc_reg *regs = sfc->regbase; > + u32 bytes = len & 0x3; > + u32 words = len >> 2; > + u32 rx_level = 0; > + u32 count; > + u32 val; > + > + while (words) { > + rx_level = rockchip_sfc_wait_fifo_ready(sfc, 0, 1000); > + if (rx_level <= 0) > + return rx_level; > + count = min(words, rx_level); > + readsl(®s->data, buf, count); > + buf += count; > + words -= count; > + } > + > + /* handle the last none word aligned bytes */ > + if (bytes) { > + rx_level = rockchip_sfc_wait_fifo_ready(sfc, 0, 1000); > + if (rx_level <= 0) > + return rx_level; > + val = readl(®s->data); > + memcpy(buf, &val, bytes); > + } > + > + return 0; > +} > + > +static int rockchip_sfc_pio_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) > +{ > + int ret = 0; > + int rw = sfc->cmd & SFC_WR; > + > + sfc->cmd |= (len << SFC_TRB_SHIFT); > + rockchip_sfc_setup_xfer(sfc); > + > + if (len) { > + if (rw) > + ret = rockchip_sfc_write(sfc, buf, len); > + else > + ret = rockchip_sfc_read(sfc, buf, len); > + } > + > + return ret; > +} > + > +static int rockchip_sfc_do_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) > +{ > + int ret = 0; > + > + if (!(len & 0x03) && (len >= 4)) > + ret = rockchip_sfc_dma_xfer(sfc, buf, len); > + else > + ret = rockchip_sfc_pio_xfer(sfc, buf, len); > + > + return ret; > +} > + > +static int rockchip_sfc_xfer(struct udevice *dev, unsigned int bitlen, > + const void *dout, void *din, unsigned long flags) > +{ > + struct udevice *bus = dev->parent; > + struct rockchip_sfc *sfc = dev_get_priv(bus); > + int len = bitlen >> 3; > + u8 *pcmd = (u8 *)dout; > + int ret = 0; > + > + if (flags & SPI_XFER_BEGIN) { > + sfc->cmd = pcmd[0]; > + if (len >= 4) { > + sfc->cmd |= SFC_ADDR_24BITS | (((len - 4) * 8) << 8); > + sfc->addr = pcmd[3] | (pcmd[2] << 8) | (pcmd[1] << 16); > + } > + } > + > + if (flags == (SPI_XFER_BEGIN | SPI_XFER_END)) > + len = 0; > + > + if (flags & SPI_XFER_END) { > + if (dout && len) > + sfc->cmd |= SFC_WR; > + > + if (din) > + ret = rockchip_sfc_do_xfer(sfc, (u32 *)din, len); > + else if (dout) > + ret = rockchip_sfc_do_xfer(sfc, (u32 *)dout, len); > + } > + > + return ret; > +} > + > +static int rockchip_sfc_set_speed(struct udevice *bus, uint speed) > +{ > + struct rockchip_sfc *sfc = dev_get_priv(bus); > + > + if (speed > sfc->max_freq) > + speed = sfc->max_freq; > + > + sfc->speed_hz = speed; > + > + return 0; > +} > + > +static int rockchip_sfc_set_mode(struct udevice *bus, uint mode) > +{ > + struct rockchip_sfc *sfc = dev_get_priv(bus); > + > + sfc->mode = mode; > + > + return 0; > +} > + > +static const struct dm_spi_ops rockchip_sfc_ops = { > + .xfer = rockchip_sfc_xfer, > + .set_speed = rockchip_sfc_set_speed, > + .set_mode = rockchip_sfc_set_mode, > +}; > + > +static const struct udevice_id rockchip_sfc_ids[] = { > + { .compatible = "rockchip,sfc" }, > + { } > +}; > + > +U_BOOT_DRIVER(rockchip_sfc_driver) = { > + .name = "rockchip_sfc", > + .id = UCLASS_SPI, > + .of_match = rockchip_sfc_ids, > + .ops = &rockchip_sfc_ops, > + .ofdata_to_platdata = rockchip_sfc_ofdata_to_platdata, > + .platdata_auto_alloc_size = sizeof(struct rockchip_sfc_platdata), > + .priv_auto_alloc_size = sizeof(struct rockchip_sfc), > + .probe = rockchip_sfc_probe, > +}; > diff --git a/drivers/spi/rockchip_sfc.h b/drivers/spi/rockchip_sfc.h > new file mode 100644 > index 0000000..b865056 > --- /dev/null > +++ b/drivers/spi/rockchip_sfc.h > @@ -0,0 +1,82 @@ > +/* > + * sfc driver for rockchip > + * > + * (C) Copyright 2008-2016 Rockchip Electronics > + * Yifeng.zhao, Software Engineering, <zhao0116@gmail.com>. > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#ifndef __RK_SFC_H > +#define __RK_SFC_H > + > +struct rockchip_sfc_reg { > + 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_reg, data, 0x108); > + > +/*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_SHIT 8 > +#define SFC_CMD_WIDTH_MASK GENMASK(9, 8) > +#define SFC_DATA_SHIFT_NEGETIVE BIT(1) > + > +/*SFC_CMD*/ > +#define SFC_WR BIT(12) > +#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 (1024 << 3) > + > +#endif
On Tue, Aug 8, 2017 at 7:13 AM, Andy Yan <andy.yan@rock-chips.com> wrote: > SFC stands for Serial Flash Controller on some > rockchip platforms such as RV1108/RK3128. > > This patch add support for it with Standard,Dual,Quad > mode. What I found from the code and related kconfig, this sounds like spi-nor flash driver try to write driver under mtd with UCLASS_SPI_FLASH. thanks!
Hi Jagan: On 2017年08月24日 14:58, Jagan Teki wrote: > On Tue, Aug 8, 2017 at 7:13 AM, Andy Yan <andy.yan@rock-chips.com> wrote: >> SFC stands for Serial Flash Controller on some >> rockchip platforms such as RV1108/RK3128. >> >> This patch add support for it with Standard,Dual,Quad >> mode. > What I found from the code and related kconfig, this sounds like > spi-nor flash driver try to write driver under mtd with > UCLASS_SPI_FLASH. You mean I should write a UCLASS_SPI_FLASH U_BOOT_DRIVER(rockchip_sfc) which implement a dm_spi_flash_ops? I see you updated the spi-nor framework on your spi tree these days, How about I write the sfc driver based on your spi-nor framework? > thanks!
On Thu, Aug 24, 2017 at 12:53 PM, Andy Yan <andy.yan@rock-chips.com> wrote: > Hi Jagan: > > > On 2017年08月24日 14:58, Jagan Teki wrote: >> >> On Tue, Aug 8, 2017 at 7:13 AM, Andy Yan <andy.yan@rock-chips.com> wrote: >>> >>> SFC stands for Serial Flash Controller on some >>> rockchip platforms such as RV1108/RK3128. >>> >>> This patch add support for it with Standard,Dual,Quad >>> mode. >> >> What I found from the code and related kconfig, this sounds like >> spi-nor flash driver try to write driver under mtd with >> UCLASS_SPI_FLASH. > > > You mean I should write a UCLASS_SPI_FLASH U_BOOT_DRIVER(rockchip_sfc) > which implement a dm_spi_flash_ops? Yes, see sf_dataflash for example. > I see you updated the spi-nor framework on your spi tree these days, > How about I write the sfc driver based on your spi-nor framework? May be you can do that - certainly we need to write spi-nor, It is WIP looking for suitable design for handling spi-nor chips through MTD. thanks!
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8a8e8e4..52c4993 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -99,6 +99,14 @@ config ROCKCHIP_SPI This uses driver model and requires a device tree binding to operate. +config ROCKCHIP_SFC + bool "Rockchip SFC driver" + help + Enable the Rockchip SFC driver, used to access SPI NOR flash + on Rockchip SoCs. + This uses driver model and requires a device tree binding to + operate. + config SANDBOX_SPI bool "Sandbox SPI driver" depends on SANDBOX && DM diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9f8b86d..052c608 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_MXS_SPI) += mxs_spi.o obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o obj-$(CONFIG_PIC32_SPI) += pic32_spi.o obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o +obj-$(CONFIG_ROCKCHIP_SFC) += rockchip_sfc.o obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o obj-$(CONFIG_SH_SPI) += sh_spi.o obj-$(CONFIG_SH_QSPI) += sh_qspi.o diff --git a/drivers/spi/rockchip_sfc.c b/drivers/spi/rockchip_sfc.c new file mode 100644 index 0000000..4388e51 --- /dev/null +++ b/drivers/spi/rockchip_sfc.c @@ -0,0 +1,402 @@ +/* + * sfc driver for rockchip + * + * (C) Copyright 2008-2016 Rockchip Electronics + * Yifeng.zhao, Software Engineering, <zhao0116@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dt-structs.h> +#include <errno.h> +#include <spi.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/periph.h> +#include <dm/pinctrl.h> +#include "rockchip_sfc.h" + +DECLARE_GLOBAL_DATA_PTR; + +enum rockchip_sfc_if_type { + IF_TYPE_STD, + IF_TYPE_DUAL, + IF_TYPE_QUAD, +}; + +struct rockchip_sfc_platdata { + s32 frequency; + fdt_addr_t base; +}; + +struct rockchip_sfc { + struct rockchip_sfc_reg *regbase; + struct clk clk; + unsigned int max_freq; + unsigned int mode; + unsigned int speed_hz; + u32 cmd; + u32 addr; +}; + +static int rockchip_sfc_ofdata_to_platdata(struct udevice *bus) +{ + struct rockchip_sfc_platdata *plat = dev_get_platdata(bus); + struct rockchip_sfc *sfc = dev_get_priv(bus); + const void *blob = gd->fdt_blob; + int node = dev_of_offset(bus); + int subnode; + int ret; + + plat->base = devfdt_get_addr(bus); + + ret = clk_get_by_index(bus, 0, &sfc->clk); + if (ret < 0) { + debug("Could not get clock for %s: %d\n", bus->name, ret); + return ret; + } + + subnode = fdt_first_subnode(blob, node); + if (subnode < 0) { + debug("Error: subnode with SPI flash config missing!\n"); + return -ENODEV; + } + + plat->frequency = fdtdec_get_int(blob, subnode, "spi-max-frequency", + 100000000); + + return 0; +} + +static int rockchip_sfc_probe(struct udevice *bus) +{ + struct rockchip_sfc_platdata *plat = dev_get_platdata(bus); + struct rockchip_sfc *sfc = dev_get_priv(bus); + int ret; + + sfc->regbase = (struct rockchip_sfc_reg *)plat->base; + + sfc->max_freq = plat->frequency; + + ret = clk_set_rate(&sfc->clk, sfc->max_freq); + if (ret < 0) { + debug("%s: Failed to set clock: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int rockchip_sfc_reset(struct rockchip_sfc *sfc) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + int tbase = get_timer(0); + u32 rcvr; + int ret = 0; + + writel(SFC_RESET, ®s->rcvr); + do { + rcvr = readl(®s->rcvr); + if (get_timer(tbase) > 1000) { + debug("sfc reset timeout\n"); + ret = -ETIMEDOUT; + break; + } + udelay(1); + } while (rcvr); + + writel(0xFFFFFFFF, ®s->iclr); + + debug("sfc reset\n"); + + return ret; +} + +static u8 rockchip_sfc_get_if_type(struct rockchip_sfc *sfc) +{ + int type = IF_TYPE_STD; + + if (sfc->cmd & SFC_WR) { + if (sfc->mode & SPI_TX_QUAD) + type = IF_TYPE_QUAD; + else if (sfc->mode & SPI_TX_DUAL) + type = IF_TYPE_DUAL; + else + type = IF_TYPE_STD; + } else { + if (sfc->mode & SPI_RX_QUAD) + type = IF_TYPE_QUAD; + else if (sfc->mode & SPI_RX_DUAL) + type = IF_TYPE_DUAL; + else + type = IF_TYPE_STD; + } + + return type; +} + +static void rockchip_sfc_setup_xfer(struct rockchip_sfc *sfc) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + u32 val = 0x02; + u32 fsr = readl(®s->fsr); + u32 sr = readl(®s->sr); + u8 data_width = IF_TYPE_STD; + + if (!(fsr & SFC_TX_EMPTY) || !(fsr & SFC_RX_EMPTY) || (sr & SFC_BUSY)) + rockchip_sfc_reset(sfc); + + if (sfc->cmd & SFC_ADDR_XBITS) + data_width = rockchip_sfc_get_if_type(sfc); + + val |= (data_width << SFC_DATA_WIDTH_SHIFT); + + writel(val, ®s->ctrl); + writel(sfc->cmd, ®s->cmd); + if (sfc->cmd & SFC_ADDR_XBITS) + writel(sfc->addr, ®s->addr); +} + +static int rockchip_sfc_do_dma_xfer(struct rockchip_sfc *sfc, u32 *buffer) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + int timeout = 1000; + int ret = 0; + int risr; + unsigned long tbase; + + rockchip_sfc_setup_xfer(sfc); + + writel(0xFFFFFFFF, ®s->iclr); + writel((u32)buffer, ®s->dmaaddr); + writel(SFC_DMA_START, ®s->dmatr); + + tbase = get_timer(0); + do { + udelay(1); + risr = readl(®s->risr); + if (get_timer(tbase) > timeout) { + debug("dma timeout\n"); + ret = -ETIMEDOUT; + break; + } + } while (!(risr & TRANS_FINISH_INT)); + + writel(0xFFFFFFFF, ®s->iclr); + + return ret; +} + +static int rockchip_sfc_dma_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) +{ + u32 trb; + u32 *p32_data = buf; + int ret = 0; + + while (len) { + trb = min(len, (u32)SFC_MAX_TRB); + sfc->cmd |= (trb << SFC_TRB_SHIFT); + ret = rockchip_sfc_do_dma_xfer(sfc, p32_data); + if (ret < 0) + break; + len -= trb; + sfc->addr += trb; + p32_data += (trb >> 2); + } + + return ret; +} + +static int rockchip_sfc_wait_fifo_ready(struct rockchip_sfc *sfc, int wr, + u32 timeout) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + 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) + return -ETIMEDOUT; + udelay(1); + } while (!level); + + return level; +} + +static int rockchip_sfc_write(struct rockchip_sfc *sfc, u32 *buf, u32 len) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + u32 bytes = len & 0x3; + u32 words = len >> 2; + u32 tx_level = 0; + u32 val = 0; + u8 count; + + while (words) { + tx_level = rockchip_sfc_wait_fifo_ready(sfc, 1, 1000); + if (tx_level <= 0) + return tx_level; + count = min(words, tx_level); + writesl(®s->data, buf, count); + buf += count; + words -= count; + } + + /* handle the last none word aligned bytes */ + if (bytes) { + tx_level = rockchip_sfc_wait_fifo_ready(sfc, 1, 1000); + if (tx_level <= 0) + return tx_level; + memcpy(&val, buf, bytes); + writel(val, ®s->data); + } + + return 0; +} + +static int rockchip_sfc_read(struct rockchip_sfc *sfc, u32 *buf, u32 len) +{ + struct rockchip_sfc_reg *regs = sfc->regbase; + u32 bytes = len & 0x3; + u32 words = len >> 2; + u32 rx_level = 0; + u32 count; + u32 val; + + while (words) { + rx_level = rockchip_sfc_wait_fifo_ready(sfc, 0, 1000); + if (rx_level <= 0) + return rx_level; + count = min(words, rx_level); + readsl(®s->data, buf, count); + buf += count; + words -= count; + } + + /* handle the last none word aligned bytes */ + if (bytes) { + rx_level = rockchip_sfc_wait_fifo_ready(sfc, 0, 1000); + if (rx_level <= 0) + return rx_level; + val = readl(®s->data); + memcpy(buf, &val, bytes); + } + + return 0; +} + +static int rockchip_sfc_pio_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) +{ + int ret = 0; + int rw = sfc->cmd & SFC_WR; + + sfc->cmd |= (len << SFC_TRB_SHIFT); + rockchip_sfc_setup_xfer(sfc); + + if (len) { + if (rw) + ret = rockchip_sfc_write(sfc, buf, len); + else + ret = rockchip_sfc_read(sfc, buf, len); + } + + return ret; +} + +static int rockchip_sfc_do_xfer(struct rockchip_sfc *sfc, u32 *buf, u32 len) +{ + int ret = 0; + + if (!(len & 0x03) && (len >= 4)) + ret = rockchip_sfc_dma_xfer(sfc, buf, len); + else + ret = rockchip_sfc_pio_xfer(sfc, buf, len); + + return ret; +} + +static int rockchip_sfc_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct rockchip_sfc *sfc = dev_get_priv(bus); + int len = bitlen >> 3; + u8 *pcmd = (u8 *)dout; + int ret = 0; + + if (flags & SPI_XFER_BEGIN) { + sfc->cmd = pcmd[0]; + if (len >= 4) { + sfc->cmd |= SFC_ADDR_24BITS | (((len - 4) * 8) << 8); + sfc->addr = pcmd[3] | (pcmd[2] << 8) | (pcmd[1] << 16); + } + } + + if (flags == (SPI_XFER_BEGIN | SPI_XFER_END)) + len = 0; + + if (flags & SPI_XFER_END) { + if (dout && len) + sfc->cmd |= SFC_WR; + + if (din) + ret = rockchip_sfc_do_xfer(sfc, (u32 *)din, len); + else if (dout) + ret = rockchip_sfc_do_xfer(sfc, (u32 *)dout, len); + } + + return ret; +} + +static int rockchip_sfc_set_speed(struct udevice *bus, uint speed) +{ + struct rockchip_sfc *sfc = dev_get_priv(bus); + + if (speed > sfc->max_freq) + speed = sfc->max_freq; + + sfc->speed_hz = speed; + + return 0; +} + +static int rockchip_sfc_set_mode(struct udevice *bus, uint mode) +{ + struct rockchip_sfc *sfc = dev_get_priv(bus); + + sfc->mode = mode; + + return 0; +} + +static const struct dm_spi_ops rockchip_sfc_ops = { + .xfer = rockchip_sfc_xfer, + .set_speed = rockchip_sfc_set_speed, + .set_mode = rockchip_sfc_set_mode, +}; + +static const struct udevice_id rockchip_sfc_ids[] = { + { .compatible = "rockchip,sfc" }, + { } +}; + +U_BOOT_DRIVER(rockchip_sfc_driver) = { + .name = "rockchip_sfc", + .id = UCLASS_SPI, + .of_match = rockchip_sfc_ids, + .ops = &rockchip_sfc_ops, + .ofdata_to_platdata = rockchip_sfc_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct rockchip_sfc_platdata), + .priv_auto_alloc_size = sizeof(struct rockchip_sfc), + .probe = rockchip_sfc_probe, +}; diff --git a/drivers/spi/rockchip_sfc.h b/drivers/spi/rockchip_sfc.h new file mode 100644 index 0000000..b865056 --- /dev/null +++ b/drivers/spi/rockchip_sfc.h @@ -0,0 +1,82 @@ +/* + * sfc driver for rockchip + * + * (C) Copyright 2008-2016 Rockchip Electronics + * Yifeng.zhao, Software Engineering, <zhao0116@gmail.com>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __RK_SFC_H +#define __RK_SFC_H + +struct rockchip_sfc_reg { + 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_reg, data, 0x108); + +/*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_SHIT 8 +#define SFC_CMD_WIDTH_MASK GENMASK(9, 8) +#define SFC_DATA_SHIFT_NEGETIVE BIT(1) + +/*SFC_CMD*/ +#define SFC_WR BIT(12) +#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 (1024 << 3) + +#endif
SFC stands for Serial Flash Controller on some rockchip platforms such as RV1108/RK3128. This patch add support for it with Standard,Dual,Quad mode. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- Changes in v2: - fix the wrong reference in Makefile, which should be rockchip_sfc.o not rk_sfc.o drivers/spi/Kconfig | 8 + drivers/spi/Makefile | 1 + drivers/spi/rockchip_sfc.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ drivers/spi/rockchip_sfc.h | 82 +++++++++ 4 files changed, 493 insertions(+) create mode 100644 drivers/spi/rockchip_sfc.c create mode 100644 drivers/spi/rockchip_sfc.h