
On 24 June 2015 at 04:59, Simon Glass sjg@chromium.org wrote:
Add a SPI driver for the Rockchip RK3288, using driver model. It should work for other Rockchip SoCs also.
Signed-off-by: Simon Glass sjg@chromium.org
Changes in v3: None Changes in v2: None
drivers/spi/Kconfig | 10 ++ drivers/spi/Makefile | 1 + drivers/spi/rk_spi.c | 375 +++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/spi/rk_spi.h | 121 +++++++++++++++++ 4 files changed, 507 insertions(+) create mode 100644 drivers/spi/rk_spi.c create mode 100644 drivers/spi/rk_spi.h
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 357a335..52e1a56 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -51,3 +51,13 @@ config CADENCE_QSPI Enable the Cadence Quad-SPI (QSPI) driver. This driver can be used to access the SPI NOR flash on platforms embedding this Cadence IP core.
+config ROCKCHIP_SPI
bool "Rockchip SPI driver"
depends on DM_SPI
help
Enable the Rockchip SPI driver, used to access SPI NOR flash and
other SPI peripherals (such as the Chrome OS EC) on Rockchip SoCs.
This uses driver model and requires a device tree binding to
operate.
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index e288692..2d74ca4 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o obj-$(CONFIG_SH_SPI) += sh_spi.o obj-$(CONFIG_SH_QSPI) += sh_qspi.o obj-$(CONFIG_FSL_ESPI) += fsl_espi.o +obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o obj-$(CONFIG_TEGRA20_SFLASH) += tegra20_sflash.o obj-$(CONFIG_TEGRA20_SLINK) += tegra20_slink.o obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o diff --git a/drivers/spi/rk_spi.c b/drivers/spi/rk_spi.c new file mode 100644 index 0000000..7ef0d63 --- /dev/null +++ b/drivers/spi/rk_spi.c @@ -0,0 +1,375 @@ +/*
- spi driver for rockchip
- (C) Copyright 2015 Google, Inc
- (C) Copyright 2008-2013 Rockchip Electronics
- Peter, Software Engineering, superpeter.cai@gmail.com.
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <pinctrl.h> +#include <spi.h> +#include <asm/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/periph.h> +#include "rk_spi.h"
+DECLARE_GLOBAL_DATA_PTR;
Just move the code from .h to here, I'm always considered the driver code be part of single file ie good for reading and maintenance of-course not for too much big code like cadence_spi
+struct rockchip_spi_platdata {
enum periph_id periph_id;
struct udevice *pinctrl;
s32 frequency; /* Default clock frequency, -1 for none */
fdt_addr_t base;
uint deactivate_delay_us; /* Delay to wait after deactivate */
+};
+struct rockchip_spi_priv {
struct rockchip_spi *regs;
struct udevice *clk_gpll;
unsigned int max_freq;
unsigned int mode;
enum periph_id periph_id; /* Peripheral ID for this device */
ulong last_transaction_us; /* Time of last transaction end */
u8 bits_per_word; /* max 16 bits per word */
u8 n_bytes;
unsigned int speed_hz;
unsigned int tmode;
uint input_rate;
+};
+#define SPI_FIFO_DEPTH 32
+static void rkspi_dump_regs(struct rockchip_spi *regs) +{
debug("RK SPI registers:\n");
debug("=================================\n");
Unnecessary debugs's since we know this is reg dump why we specify.
debug("ctrl0: \t\t0x%08x\n", readl(®s->ctrlr0));
debug("ctrl1: \t\t0x%08x\n", readl(®s->ctrlr1));
debug("ssienr: \t\t0x%08x\n", readl(®s->enr));
debug("ser: \t\t0x%08x\n", readl(®s->ser));
debug("baudr: \t\t0x%08x\n", readl(®s->baudr));
debug("txftlr: \t\t0x%08x\n", readl(®s->txftlr));
debug("rxftlr: \t\t0x%08x\n", readl(®s->rxftlr));
debug("txflr: \t\t0x%08x\n", readl(®s->txflr));
debug("rxflr: \t\t0x%08x\n", readl(®s->rxflr));
debug("sr: \t\t0x%08x\n", readl(®s->sr));
debug("imr: \t\t0x%08x\n", readl(®s->imr));
debug("isr: \t\t0x%08x\n", readl(®s->isr));
debug("dmacr: \t\t0x%08x\n", readl(®s->dmacr));
debug("dmatdlr: \t0x%08x\n", readl(®s->dmatdlr));
debug("dmardlr: \t0x%08x\n", readl(®s->dmardlr));
debug("=================================\n");
-- ditto
+}
+static void rkspi_enable_chip(struct rockchip_spi *regs, bool enable) +{
writel(enable ? 1 : 0, ®s->enr);
+}
+static void rkspi_set_clk(struct rockchip_spi_priv *priv, uint speed) +{
uint clk_div;
clk_div = priv->input_rate / speed;
clk_div = (clk_div + 1) & 0xfffe;
debug("spi speed %u, div %u\n", speed, clk_div);
writel(clk_div, &priv->regs->baudr);
+}
+static int rkspi_wait_till_not_busy(struct rockchip_spi *regs) +{
unsigned int delay = 1000;
while (delay--) {
if (!(readl(®s->sr) & SR_BUSY))
return 0;
udelay(1);
}
Please try to avoid the delay - simple code as below we may re-construct.
u32 ts = get_timer(0); status = readl(®s->sr); while (!(status & SR_BUSY)) { if (get_timer(ts) > CONFIG_SYS_RK_SPI_WAIT) { printf("spi_xfer: Timeout!\n"); return -1; } status = readl(®s->sr); }
debug("RK SPI: Status keeps busy for 1000us after a read/write!\n");
return -ETIMEDOUT;
+}
+static void spi_cs_activate(struct rockchip_spi *regs, uint cs) +{
debug("activate cs%u\n", cs);
writel(1 << cs, ®s->ser);
+}
+static void spi_cs_deactivate(struct rockchip_spi *regs, uint cs) +{
debug("deactivate cs%u\n", cs);
writel(0, ®s->ser);
+}
+static int rockchip_spi_ofdata_to_platdata(struct udevice *bus) +{
struct rockchip_spi_platdata *plat = bus->platdata;
const void *blob = gd->fdt_blob;
int node = bus->of_offset;
int ret;
plat->base = dev_get_addr(bus);
ret = uclass_get_device(UCLASS_PINCTRL, 0, &plat->pinctrl);
if (ret)
return ret;
ret = pinctrl_get_periph_id(plat->pinctrl, bus);
if (ret < 0) {
debug("%s: Could not get peripheral ID for %s: %d\n", __func__,
bus->name, ret);
return -FDT_ERR_NOTFOUND;
}
plat->periph_id = ret;
plat->frequency = fdtdec_get_int(blob, node, "spi-max-frequency",
50000000);
plat->deactivate_delay_us = fdtdec_get_int(blob, node,
"spi-deactivate-delay", 0);
debug("%s: base=%x, periph_id=%d, max-frequency=%d, deactivate_delay=%d\n",
__func__, plat->base, plat->periph_id, plat->frequency,
plat->deactivate_delay_us);
return 0;
+}
+static int rockchip_spi_probe(struct udevice *bus) +{
struct rockchip_spi_platdata *plat = dev_get_platdata(bus);
struct rockchip_spi_priv *priv = dev_get_priv(bus);
int ret;
debug("%s: probe\n", __func__);
priv->regs = (struct rockchip_spi *)plat->base;
priv->last_transaction_us = timer_get_us();
priv->max_freq = plat->frequency;
priv->periph_id = plat->periph_id;
ret = uclass_get_device(UCLASS_CLK, CLK_GENERAL, &priv->clk_gpll);
if (ret) {
debug("%s: Failed to find CLK_GENERAL: %d\n", __func__, ret);
return ret;
}
/*
* Use 99 MHz as our clock since it divides nicely into 594 MHz which
* is the assumed speed for CLK_GENERAL.
*/
ret = clk_set_periph_rate(priv->clk_gpll, plat->periph_id, 99000000);
if (ret < 0) {
debug("%s: Failed to set clock: %d\n", __func__, ret);
return ret;
}
priv->input_rate = ret;
debug("%s: rate = %u\n", __func__, priv->input_rate);
priv->bits_per_word = 8;
priv->tmode = TMOD_TR; /* Tx & Rx */
return 0;
+}
+static int rockchip_spi_claim_bus(struct udevice *dev) +{
struct udevice *bus = dev->parent;
struct rockchip_spi_platdata *plat = dev_get_platdata(bus);
struct rockchip_spi_priv *priv = dev_get_priv(bus);
struct rockchip_spi *regs = priv->regs;
struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
I'm concerned little more here, why we use core/uclass structure there are some dm_spi function to get the spi_slave details.
If you need the slave cs number spi_chip_select shall use right, but this is not properly working I guess I tried on zynq_spi.
And one more concerned about cs_info - this is never called from any more if this gets called we can control the cs number from sf_cmd (from user) and doesn't require dts node and also we can get the cs from here as well.
Comments?
u8 spi_dfs, spi_tf;
uint ctrlr0;
int ret;
/* Disable the SPI hardware */
rkspi_enable_chip(regs, 0);
switch (priv->bits_per_word) {
case 8:
priv->n_bytes = 1;
spi_dfs = DFS_8BIT;
spi_tf = HALF_WORD_OFF;
break;
case 16:
priv->n_bytes = 2;
spi_dfs = DFS_16BIT;
spi_tf = HALF_WORD_ON;
break;
default:
debug("%s: unsupported bits: %dbits\n", __func__,
priv->bits_per_word);
return -EPROTONOSUPPORT;
}
rkspi_set_clk(priv, priv->speed_hz);
/* Operation Mode */
ctrlr0 = OMOD_MASTER << OMOD_SHIFT;
/* Data Frame Size */
ctrlr0 |= spi_dfs & DFS_MASK << DFS_SHIFT;
/* set SPI mode 0..3 */
if (priv->mode & SPI_CPOL)
ctrlr0 |= SCOL_HIGH << SCOL_SHIFT;
if (priv->mode & SPI_CPHA)
ctrlr0 |= SCPH_TOGSTA << SCPH_SHIFT;
/* Chip Select Mode */
ctrlr0 |= CSM_KEEP << CSM_SHIFT;
/* SSN to Sclk_out delay */
ctrlr0 |= SSN_DELAY_ONE << SSN_DELAY_SHIFT;
/* Serial Endian Mode */
ctrlr0 |= SEM_LITTLE << SEM_SHIFT;
/* First Bit Mode */
ctrlr0 |= FBM_MSB << FBM_SHIFT;
/* Byte and Halfword Transform */
ctrlr0 |= (spi_tf & HALF_WORD_MASK) << HALF_WORD_TX_SHIFT;
/* Rxd Sample Delay */
ctrlr0 |= 0 << RXDSD_SHIFT;
/* Frame Format */
ctrlr0 |= FRF_SPI << FRF_SHIFT;
/* Tx and Rx mode */
ctrlr0 |= (priv->tmode & TMOD_MASK) << TMOD_SHIFT;
writel(ctrlr0, ®s->ctrlr0);
ret = pinctrl_request(plat->pinctrl, priv->periph_id, slave_plat->cs);
if (ret) {
debug("%s: Cannot request pinctrl: %d\n", __func__, ret);
return ret;
}
return 0;
+}
+static int rockchip_spi_release_bus(struct udevice *dev) +{
return 0;
+}
+static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen,
const void *dout, void *din, unsigned long flags)
+{
struct udevice *bus = dev->parent;
struct rockchip_spi_priv *priv = dev_get_priv(bus);
struct rockchip_spi *regs = priv->regs;
struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
int len = bitlen >> 3;
const u8 *out = dout;
u8 *in = din;
int toread, towrite;
int ret;
debug("%s: dout=%p, din=%p, len=%x, flags=%lx\n", __func__, dout, din,
len, flags);
if (0)
rkspi_dump_regs(regs);
/* Assert CS before transfer */
if (flags & SPI_XFER_BEGIN)
spi_cs_activate(regs, slave_plat->cs);
while (len > 0) {
int todo = min(len, 0xffff);
rkspi_enable_chip(regs, true);
writel(todo - 1, ®s->ctrlr1);
rkspi_enable_chip(regs, true);
toread = todo;
towrite = todo;
while (toread || towrite) {
u32 status = readl(®s->sr);
if (towrite && !(status & SR_TF_FULL)) {
writel(out ? *out++ : 0, regs->txdr);
towrite--;
}
if (toread && !(status & SR_RF_EMPT)) {
u32 byte = readl(regs->rxdr);
if (in)
*in++ = byte;
toread--;
}
}
ret = rkspi_wait_till_not_busy(regs);
if (ret)
break;
len -= todo;
}
/* Deassert CS after transfer */
if (flags & SPI_XFER_END)
spi_cs_deactivate(regs, slave_plat->cs);
rkspi_enable_chip(regs, false);
return ret;
+}
+static int rockchip_spi_set_speed(struct udevice *bus, uint speed) +{
struct rockchip_spi_priv *priv = dev_get_priv(bus);
if (speed > 48000000)
return -EINVAL;
if (speed > priv->max_freq)
speed = priv->max_freq;
priv->speed_hz = speed;
return 0;
+}
+static int rockchip_spi_set_mode(struct udevice *bus, uint mode) +{
struct rockchip_spi_priv *priv = dev_get_priv(bus);
priv->mode = mode;
return 0;
+}
+static const struct dm_spi_ops rockchip_spi_ops = {
.claim_bus = rockchip_spi_claim_bus,
.release_bus = rockchip_spi_release_bus,
.xfer = rockchip_spi_xfer,
.set_speed = rockchip_spi_set_speed,
.set_mode = rockchip_spi_set_mode,
/*
* cs_info is not needed, since we require all chip selects to be
* in the device tree explicitly
*/
Does this means cs number is getting from device tree, but the code get the cs from dm_spi_slave?
+};
+static const struct udevice_id rockchip_spi_ids[] = {
{ .compatible = "rockchip,rk3288-spi" },
{ }
+};
+U_BOOT_DRIVER(rockchip_spi) = {
.name = "rockchip_spi",
.id = UCLASS_SPI,
.of_match = rockchip_spi_ids,
.ops = &rockchip_spi_ops,
.ofdata_to_platdata = rockchip_spi_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct rockchip_spi_platdata),
.priv_auto_alloc_size = sizeof(struct rockchip_spi_priv),
.probe = rockchip_spi_probe,
+}; diff --git a/drivers/spi/rk_spi.h b/drivers/spi/rk_spi.h new file mode 100644 index 0000000..bfb1739 --- /dev/null +++ b/drivers/spi/rk_spi.h @@ -0,0 +1,121 @@ +/*
- SPI driver for rockchip
- (C) Copyright 2015 Google, Inc
- (C) Copyright 2008-2013 Rockchip Electronics
- Peter, Software Engineering, superpeter.cai@gmail.com.
- SPDX-License-Identifier: GPL-2.0+
- */
+#ifndef __RK_SPI_H +#define __RK_SPI_H
+struct rockchip_spi {
u32 ctrlr0;
u32 ctrlr1;
u32 enr;
u32 ser;
u32 baudr;
u32 txftlr;
u32 rxftlr;
u32 txflr;
u32 rxflr;
u32 sr;
u32 ipr;
u32 imr;
u32 isr;
u32 risr;
u32 icr;
u32 dmacr;
u32 dmatdlr;
u32 dmardlr; /* 0x44 */
u32 reserved[0xef];
u32 txdr[0x100]; /* 0x400 */
u32 rxdr[0x100]; /* 0x800 */
+};
+/* CTRLR0 */ +enum {
DFS_SHIFT = 0, /* Data Frame Size */
DFS_MASK = 3,
DFS_4BIT = 0,
DFS_8BIT,
DFS_16BIT,
DFS_RESV,
CFS_SHIFT = 2, /* Control Frame Size */
CFS_MASK = 0xf,
SCPH_SHIFT = 6, /* Serial Clock Phase */
SCPH_MASK = 1,
SCPH_TOGMID = 0, /* SCLK toggles in middle of first data bit */
SCPH_TOGSTA, /* SCLK toggles at start of first data bit */
SCOL_SHIFT = 7, /* Serial Clock Polarity */
SCOL_MASK = 1,
SCOL_LOW = 0, /* Inactive state of serial clock is low */
SCOL_HIGH, /* Inactive state of serial clock is high */
CSM_SHIFT = 8, /* Chip Select Mode */
CSM_MASK = 0x3,
CSM_KEEP = 0, /* ss_n stays low after each frame */
CSM_HALF, /* ss_n high for half sclk_out cycles */
CSM_ONE, /* ss_n high for one sclk_out cycle */
CSM_RESV,
SSN_DELAY_SHIFT = 10, /* SSN to Sclk_out delay */
SSN_DELAY_MASK = 1,
SSN_DELAY_HALF = 0, /* 1/2 sclk_out cycle */
SSN_DELAY_ONE = 1, /* 1 sclk_out cycle */
SEM_SHIFT = 11, /* Serial Endian Mode */
SEM_MASK = 1,
SEM_LITTLE = 0, /* little endian */
SEM_BIG, /* big endian */
FBM_SHIFT = 12, /* First Bit Mode */
FBM_MASK = 1,
FBM_MSB = 0, /* first bit is MSB */
FBM_LSB, /* first bit in LSB */
HALF_WORD_TX_SHIFT = 13, /* Byte and Halfword Transform */
HALF_WORD_MASK = 1,
HALF_WORD_ON = 0, /* apb 16bit write/read, spi 8bit write/read */
HALF_WORD_OFF, /* apb 8bit write/read, spi 8bit write/read */
RXDSD_SHIFT = 14, /* Rxd Sample Delay, in cycles */
RXDSD_MASK = 3,
FRF_SHIFT = 16, /* Frame Format */
FRF_MASK = 3,
FRF_SPI = 0, /* Motorola SPI */
FRF_SSP, /* Texas Instruments SSP*/
FRF_MICROWIRE, /* National Semiconductors Microwire */
FRF_RESV,
TMOD_SHIFT = 18, /* Transfer Mode */
TMOD_MASK = 3,
TMOD_TR = 0, /* xmit & recv */
TMOD_TO, /* xmit only */
TMOD_RO, /* recv only */
TMOD_RESV,
OMOD_SHIFT = 20, /* Operation Mode */
OMOD_MASK = 1,
OMOD_MASTER = 0, /* Master Mode */
OMOD_SLAVE, /* Slave Mode */
+};
+/* SR */ +enum {
SR_MASK = 0x7f,
SR_BUSY = 1 << 0,
SR_TF_FULL = 1 << 1,
SR_TF_EMPT = 1 << 2,
SR_RF_EMPT = 1 << 3,
SR_RF_FULL = 1 << 4,
+};
+#endif /* __RK_SPI_H */
thanks!