From 9424ecd735e73af7d47ca18b705af3a90804c676 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Fri, 5 Jul 2019 10:03:18 +0900 Subject: [PATCH] spi: Add SPI controller driver for UniPhier SoCs Add SPI controller driver implemented in Socionext UniPhier SoCs. This controller has the SPI master mode only. Signed-off-by: Kunihiko Hayashi --- drivers/spi/Kconfig | 8 + drivers/spi/Makefile | 1 + drivers/spi/uniphier_spi.c | 413 +++++++++++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 drivers/spi/uniphier_spi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f9b282313a..509dd0ec58 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -293,6 +293,14 @@ config TI_QSPI Enable the TI Quad-SPI (QSPI) driver for DRA7xx and AM43xx evms. This driver support spi flash single, quad and memory reads. +config UNIPHIER_SPI + bool "Socionext UniPhier SPI driver" + depends on ARCH_UNIPHIER + help + Enable the Socionext UniPhier SPI driver. This driver can + be used to access SPI chips on platforms embedding this + UniPhier IP core. + config XILINX_SPI bool "Xilinx SPI driver" help diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 64c407e2ed..ab84122f08 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o obj-$(CONFIG_TEGRA20_SFLASH) += tegra20_sflash.o obj-$(CONFIG_TEGRA20_SLINK) += tegra20_slink.o obj-$(CONFIG_TEGRA210_QSPI) += tegra210_qspi.o +obj-$(CONFIG_UNIPHIER_SPI) += uniphier_spi.o obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o obj-$(CONFIG_ZYNQ_SPI) += zynq_spi.o obj-$(CONFIG_ZYNQ_QSPI) += zynq_qspi.o diff --git a/drivers/spi/uniphier_spi.c b/drivers/spi/uniphier_spi.c new file mode 100644 index 0000000000..ef02d07aa4 --- /dev/null +++ b/drivers/spi/uniphier_spi.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * uniphier_spi.c - Socionext UniPhier SPI driver + * Copyright 2019 Socionext, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define SSI_CTL 0x00 +#define SSI_CTL_EN BIT(0) + +#define SSI_CKS 0x04 +#define SSI_CKS_CKRAT_MASK GENMASK(7, 0) +#define SSI_CKS_CKPHS BIT(14) +#define SSI_CKS_CKINIT BIT(13) +#define SSI_CKS_CKDLY BIT(12) + +#define SSI_TXWDS 0x08 +#define SSI_TXWDS_WDLEN_MASK GENMASK(13, 8) +#define SSI_TXWDS_TDTF_MASK GENMASK(7, 6) +#define SSI_TXWDS_DTLEN_MASK GENMASK(5, 0) + +#define SSI_RXWDS 0x0c +#define SSI_RXWDS_RDTF_MASK GENMASK(7, 6) +#define SSI_RXWDS_DTLEN_MASK GENMASK(5, 0) + +#define SSI_FPS 0x10 +#define SSI_FPS_FSPOL BIT(15) +#define SSI_FPS_FSTRT BIT(14) + +#define SSI_SR 0x14 +#define SSI_SR_BUSY BIT(7) +#define SSI_SR_TNF BIT(5) +#define SSI_SR_RNE BIT(0) + +#define SSI_IE 0x18 + +#define SSI_IC 0x1c +#define SSI_IC_TCIC BIT(4) +#define SSI_IC_RCIC BIT(3) +#define SSI_IC_RORIC BIT(0) + +#define SSI_FC 0x20 +#define SSI_FC_TXFFL BIT(12) +#define SSI_FC_TXFTH_MASK GENMASK(11, 8) +#define SSI_FC_RXFFL BIT(4) +#define SSI_FC_RXFTH_MASK GENMASK(3, 0) + +#define SSI_XDR 0x24 /* TXDR for write, RXDR for read */ + +#define SSI_FIFO_DEPTH 8U + +#define SSI_REG_TIMEOUT (CONFIG_SYS_HZ / 100) /* 10 ms */ +#define SSI_XFER_TIMEOUT (CONFIG_SYS_HZ) /* 1 sec */ + +#define SSI_CLK 50000000 /* internal I/O clock: 50MHz */ + +struct uniphier_spi_platdata { + void __iomem *base; + u32 frequency; /* input frequency */ + u32 speed_hz; + uint deactivate_delay_us; /* Delay to wait after deactivate */ + uint activate_delay_us; /* Delay to wait after activate */ +}; + +struct uniphier_spi_priv { + void __iomem *base; + u8 mode; + u8 fifo_depth; + u8 bits_per_word; + ulong last_transaction_us; /* Time of last transaction end */ +}; + +static void uniphier_spi_enable(struct uniphier_spi_priv *priv, int enable) +{ + u32 val; + + val = readl(priv->base + SSI_CTL); + if (enable) + val |= SSI_CTL_EN; + else + val &= ~SSI_CTL_EN; + writel(val, priv->base + SSI_CTL); +} + +static void uniphier_spi_regdump(struct uniphier_spi_priv *priv) +{ + pr_debug("CTL %08x\n", readl(priv->base + SSI_CTL)); + pr_debug("CKS %08x\n", readl(priv->base + SSI_CKS)); + pr_debug("TXWDS %08x\n", readl(priv->base + SSI_TXWDS)); + pr_debug("RXWDS %08x\n", readl(priv->base + SSI_RXWDS)); + pr_debug("FPS %08x\n", readl(priv->base + SSI_FPS)); + pr_debug("SR %08x\n", readl(priv->base + SSI_SR)); + pr_debug("IE %08x\n", readl(priv->base + SSI_IE)); + pr_debug("IC %08x\n", readl(priv->base + SSI_IC)); + pr_debug("FC %08x\n", readl(priv->base + SSI_FC)); + pr_debug("XDR %08x\n", readl(priv->base + SSI_XDR)); +} + +static void spi_cs_activate(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct uniphier_spi_platdata *plat = bus->platdata; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + ulong delay_us; /* The delay completed so far */ + u32 val; + + /* If it's too soon to do another transaction, wait */ + if (plat->deactivate_delay_us && priv->last_transaction_us) { + delay_us = timer_get_us() - priv->last_transaction_us; + if (delay_us < plat->deactivate_delay_us) + udelay(plat->deactivate_delay_us - delay_us); + } + + val = readl(priv->base + SSI_FPS); + if (priv->mode & SPI_CS_HIGH) + val |= SSI_FPS_FSPOL; + else + val &= ~SSI_FPS_FSPOL; + writel(val, priv->base + SSI_FPS); + + if (plat->activate_delay_us) + udelay(plat->activate_delay_us); +} + +static void spi_cs_deactivate(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct uniphier_spi_platdata *plat = bus->platdata; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + u32 val; + + val = readl(priv->base + SSI_FPS); + if (priv->mode & SPI_CS_HIGH) + val &= ~SSI_FPS_FSPOL; + else + val |= SSI_FPS_FSPOL; + writel(val, priv->base + SSI_FPS); + + /* Remember time of this transaction so we can honour the bus delay */ + if (plat->deactivate_delay_us) + priv->last_transaction_us = timer_get_us(); +} + +static int uniphier_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + u32 val, size; + + uniphier_spi_enable(priv, false); + + /* disable interrupts */ + writel(0, priv->base + SSI_IE); + + /* bits_per_word */ + size = priv->bits_per_word; + val = readl(priv->base + SSI_TXWDS); + val &= ~(SSI_TXWDS_WDLEN_MASK | SSI_TXWDS_DTLEN_MASK); + val |= FIELD_PREP(SSI_TXWDS_WDLEN_MASK, size); + val |= FIELD_PREP(SSI_TXWDS_DTLEN_MASK, size); + writel(val, priv->base + SSI_TXWDS); + + val = readl(priv->base + SSI_RXWDS); + val &= ~SSI_RXWDS_DTLEN_MASK; + val |= FIELD_PREP(SSI_RXWDS_DTLEN_MASK, size); + writel(val, priv->base + SSI_RXWDS); + + /* reset FIFOs */ + val = SSI_FC_TXFFL | SSI_FC_RXFFL; + writel(val, priv->base + SSI_FC); + + /* FIFO threthold */ + val = readl(priv->base + SSI_FC); + val &= ~(SSI_FC_TXFTH_MASK | SSI_FC_RXFTH_MASK); + val |= FIELD_PREP(SSI_FC_TXFTH_MASK, priv->fifo_depth); + val |= FIELD_PREP(SSI_FC_RXFTH_MASK, priv->fifo_depth); + writel(val, priv->base + SSI_FC); + + /* clear interrupts */ + writel(SSI_IC_TCIC | SSI_IC_RCIC | SSI_IC_RORIC, + priv->base + SSI_IC); + + uniphier_spi_enable(priv, true); + + return 0; +} + +static int uniphier_spi_release_bus(struct udevice *dev) +{ + struct udevice *bus = dev->parent; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + + uniphier_spi_enable(priv, false); + + return 0; +} + +static int uniphier_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + const u8 *tx_buf = dout; + u8 *rx_buf = din, buf; + u32 len = bitlen / 8; + u32 tx_len, rx_len; + u32 ts, status; + int ret = 0; + + if (bitlen % 8) { + dev_err(dev, "Non byte aligned SPI transfer\n"); + return -EINVAL; + } + + if (flags & SPI_XFER_BEGIN) + spi_cs_activate(dev); + + uniphier_spi_enable(priv, true); + + ts = get_timer(0); + tx_len = len; + rx_len = len; + + uniphier_spi_regdump(priv); + + while (tx_len || rx_len) { + ret = wait_for_bit_le32(priv->base + SSI_SR, SSI_SR_BUSY, false, + SSI_REG_TIMEOUT * 1000, false); + if (ret) { + if (ret == -ETIMEDOUT) + dev_err(dev, "access timeout\n"); + break; + } + + status = readl(priv->base + SSI_SR); + /* write the data into TX */ + if (tx_len && (status & SSI_SR_TNF)) { + buf = tx_buf ? *tx_buf++ : 0; + writel(buf, priv->base + SSI_XDR); + tx_len--; + } + + /* read the data from RX */ + if (rx_len && (status & SSI_SR_RNE)) { + buf = readl(priv->base + SSI_XDR); + if (rx_buf) + *rx_buf++ = buf; + rx_len--; + } + + if (get_timer(ts) >= SSI_XFER_TIMEOUT) { + dev_err(dev, "transfer timeout\n"); + ret = -ETIMEDOUT; + break; + } + } + + if (flags & SPI_XFER_END) + spi_cs_deactivate(dev); + + uniphier_spi_enable(priv, false); + + return ret; +} + +static int uniphier_spi_set_speed(struct udevice *bus, uint speed) +{ + struct uniphier_spi_platdata *plat = bus->platdata; + struct uniphier_spi_priv *priv = dev_get_priv(bus); + u32 val, ckdiv; + + if (speed > plat->frequency) + speed = plat->frequency; + + /* baudrate */ + ckdiv = DIV_ROUND_UP(SSI_CLK, speed); + ckdiv = round_up(ckdiv, 2); + + val = readl(priv->base + SSI_CKS); + val &= ~SSI_CKS_CKRAT_MASK; + val |= ckdiv & SSI_CKS_CKRAT_MASK; + writel(val, priv->base + SSI_CKS); + + return 0; +} + +static int uniphier_spi_set_mode(struct udevice *bus, uint mode) +{ + struct uniphier_spi_priv *priv = dev_get_priv(bus); + u32 val1, val2; + + /* + * clock setting + * CKPHS capture timing. 0:rising edge, 1:falling edge + * CKINIT clock initial level. 0:low, 1:high + * CKDLY clock delay. 0:no delay, 1:delay depending on FSTRT + * (FSTRT=0: 1 clock, FSTRT=1: 0.5 clock) + * + * frame setting + * FSPOL frame signal porarity. 0: low, 1: high + * FSTRT start frame timing + * 0: rising edge of clock, 1: falling edge of clock + */ + val1 = readl(priv->base + SSI_CKS); + val2 = readl(priv->base + SSI_FPS); + + switch (mode & (SPI_CPOL | SPI_CPHA)) { + case SPI_MODE_0: + /* CKPHS=1, CKINIT=0, CKDLY=1, FSTRT=0 */ + val1 |= SSI_CKS_CKPHS | SSI_CKS_CKDLY; + val1 &= ~SSI_CKS_CKINIT; + val2 &= ~SSI_FPS_FSTRT; + break; + case SPI_MODE_1: + /* CKPHS=0, CKINIT=0, CKDLY=0, FSTRT=1 */ + val1 &= ~(SSI_CKS_CKPHS | SSI_CKS_CKINIT | SSI_CKS_CKDLY); + val2 |= SSI_FPS_FSTRT; + break; + case SPI_MODE_2: + /* CKPHS=0, CKINIT=1, CKDLY=1, FSTRT=1 */ + val1 |= SSI_CKS_CKINIT | SSI_CKS_CKDLY; + val1 &= ~SSI_CKS_CKPHS; + val2 |= SSI_FPS_FSTRT; + break; + case SPI_MODE_3: + /* CKPHS=1, CKINIT=1, CKDLY=0, FSTRT=0 */ + val1 |= SSI_CKS_CKPHS | SSI_CKS_CKINIT; + val1 &= ~SSI_CKS_CKDLY; + val2 &= ~SSI_FPS_FSTRT; + break; + } + + writel(val1, priv->base + SSI_CKS); + writel(val2, priv->base + SSI_FPS); + + /* format */ + val1 = readl(priv->base + SSI_TXWDS); + val2 = readl(priv->base + SSI_RXWDS); + if (mode & SPI_LSB_FIRST) { + val1 |= FIELD_PREP(SSI_TXWDS_TDTF_MASK, 1); + val2 |= FIELD_PREP(SSI_RXWDS_RDTF_MASK, 1); + } + writel(val1, priv->base + SSI_TXWDS); + writel(val2, priv->base + SSI_RXWDS); + + priv->mode = mode; + + return 0; +} + +static int uniphier_spi_ofdata_to_platdata(struct udevice *bus) +{ + struct uniphier_spi_platdata *plat = bus->platdata; + const void *blob = gd->fdt_blob; + int node = dev_of_offset(bus); + + plat->base = devfdt_get_addr_ptr(bus); + + plat->frequency = + fdtdec_get_int(blob, node, "spi-max-frequency", 12500000); + plat->deactivate_delay_us = + fdtdec_get_int(blob, node, "spi-deactivate-delay", 0); + plat->activate_delay_us = + fdtdec_get_int(blob, node, "spi-activate-delay", 0); + plat->speed_hz = plat->frequency / 2; + + return 0; +} + +static int uniphier_spi_probe(struct udevice *bus) +{ + struct uniphier_spi_platdata *plat = dev_get_platdata(bus); + struct uniphier_spi_priv *priv = dev_get_priv(bus); + + priv->base = plat->base; + priv->fifo_depth = SSI_FIFO_DEPTH; + priv->bits_per_word = 8; + + return 0; +} + +static const struct dm_spi_ops uniphier_spi_ops = { + .claim_bus = uniphier_spi_claim_bus, + .release_bus = uniphier_spi_release_bus, + .xfer = uniphier_spi_xfer, + .set_speed = uniphier_spi_set_speed, + .set_mode = uniphier_spi_set_mode, +}; + +static const struct udevice_id uniphier_spi_ids[] = { + { .compatible = "socionext,uniphier-scssi" }, + { /* Sentinel */ } +}; + +U_BOOT_DRIVER(uniphier_spi) = { + .name = "uniphier_spi", + .id = UCLASS_SPI, + .of_match = uniphier_spi_ids, + .ops = &uniphier_spi_ops, + .ofdata_to_platdata = uniphier_spi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct uniphier_spi_platdata), + .priv_auto_alloc_size = sizeof(struct uniphier_spi_priv), + .probe = uniphier_spi_probe, +}; -- 2.39.5