diff options
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Kconfig | 10 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/kirkwood_spi.c | 52 | ||||
-rw-r--r-- | drivers/spi/spi-mem.c | 15 | ||||
-rw-r--r-- | drivers/spi/uniphier_spi.c | 413 |
5 files changed, 450 insertions, 41 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f9b282313a..cc174dd036 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -226,7 +226,7 @@ config SANDBOX_SPI cs-gpios = <0>, <&gpio_a 0>; flash@0 { reg = <0>; - compatible = "spansion,m25p16", "sandbox,spi-flash"; + compatible = "spansion,m25p16", "jedec,spi-nor"; spi-max-frequency = <40000000>; sandbox,filename = "spi.bin"; }; @@ -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/kirkwood_spi.c b/drivers/spi/kirkwood_spi.c index 5dd1ad67cf..c725625146 100644 --- a/drivers/spi/kirkwood_spi.c +++ b/drivers/spi/kirkwood_spi.c @@ -151,10 +151,6 @@ void spi_free_slave(struct spi_slave *slave) free(slave); } -#if defined(CONFIG_SYS_KW_SPI_MPP) -u32 spi_mpp_backup[4]; -#endif - __attribute__((weak)) int board_spi_claim_bus(struct spi_slave *slave) { return 0; @@ -162,34 +158,6 @@ __attribute__((weak)) int board_spi_claim_bus(struct spi_slave *slave) int spi_claim_bus(struct spi_slave *slave) { -#if defined(CONFIG_SYS_KW_SPI_MPP) - u32 config; - u32 spi_mpp_config[4]; - - config = CONFIG_SYS_KW_SPI_MPP; - - if (config & MOSI_MPP6) - spi_mpp_config[0] = MPP6_SPI_MOSI; - else - spi_mpp_config[0] = MPP1_SPI_MOSI; - - if (config & SCK_MPP10) - spi_mpp_config[1] = MPP10_SPI_SCK; - else - spi_mpp_config[1] = MPP2_SPI_SCK; - - if (config & MISO_MPP11) - spi_mpp_config[2] = MPP11_SPI_MISO; - else - spi_mpp_config[2] = MPP3_SPI_MISO; - - spi_mpp_config[3] = 0; - spi_mpp_backup[3] = 0; - - /* set new spi mpp and save current mpp config */ - kirkwood_mpp_conf(spi_mpp_config, spi_mpp_backup); -#endif - return board_spi_claim_bus(slave); } @@ -199,10 +167,6 @@ __attribute__((weak)) void board_spi_release_bus(struct spi_slave *slave) void spi_release_bus(struct spi_slave *slave) { -#if defined(CONFIG_SYS_KW_SPI_MPP) - kirkwood_mpp_conf(spi_mpp_backup, NULL); -#endif - board_spi_release_bus(slave); } @@ -338,6 +302,11 @@ static int mvebu_spi_xfer(struct udevice *dev, unsigned int bitlen, return _spi_xfer(plat->spireg, bitlen, dout, din, flags); } +__attribute__((weak)) int mvebu_board_spi_claim_bus(struct udevice *dev) +{ + return 0; +} + static int mvebu_spi_claim_bus(struct udevice *dev) { struct udevice *bus = dev->parent; @@ -348,9 +317,19 @@ static int mvebu_spi_claim_bus(struct udevice *dev) KWSPI_CS_MASK << KWSPI_CS_SHIFT, spi_chip_select(dev) << KWSPI_CS_SHIFT); + return mvebu_board_spi_claim_bus(dev); +} + +__attribute__((weak)) int mvebu_board_spi_release_bus(struct udevice *dev) +{ return 0; } +static int mvebu_spi_release_bus(struct udevice *dev) +{ + return mvebu_board_spi_release_bus(dev); +} + static int mvebu_spi_probe(struct udevice *bus) { struct mvebu_spi_platdata *plat = dev_get_platdata(bus); @@ -377,6 +356,7 @@ static int mvebu_spi_ofdata_to_platdata(struct udevice *bus) static const struct dm_spi_ops mvebu_spi_ops = { .claim_bus = mvebu_spi_claim_bus, + .release_bus = mvebu_spi_release_bus, .xfer = mvebu_spi_xfer, .set_speed = mvebu_spi_set_speed, .set_mode = mvebu_spi_set_mode, diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index b86eee75bc..7aabebeff5 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -201,7 +201,6 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) unsigned int pos = 0; const u8 *tx_buf = NULL; u8 *rx_buf = NULL; - u8 *op_buf; int op_len; u32 flag; int ret; @@ -338,7 +337,17 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) } op_len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes; - op_buf = calloc(1, op_len); + + /* + * Avoid using malloc() here so that we can use this code in SPL where + * simple malloc may be used. That implementation does not allow free() + * so repeated calls to this code can exhaust the space. + * + * The value of op_len is small, since it does not include the actual + * data being sent, only the op-code and address. In fact, it should be + * possible to just use a small fixed value here instead of op_len. + */ + u8 op_buf[op_len]; op_buf[pos++] = op->cmd.opcode; @@ -382,8 +391,6 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) debug("%02x ", tx_buf ? tx_buf[i] : rx_buf[i]); debug("[ret %d]\n", ret); - free(op_buf); - if (ret < 0) return ret; #endif /* __UBOOT__ */ 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 <clk.h> +#include <common.h> +#include <dm.h> +#include <linux/bitfield.h> +#include <linux/io.h> +#include <spi.h> +#include <wait_bit.h> + +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, +}; |