Merge tag 'u-boot-imx-20200502' of https://gitlab.denx.de/u-boot/custodians/u-boot-imx
[oweals/u-boot.git] / drivers / spi / spi-sifive.c
index 5e612edcff7953bf434c9d0daa0f4d573b25b8bc..4cab0391f7d12cdbee03f64e3c1ae47c13ffed88 100644 (file)
@@ -11,6 +11,7 @@
 #include <dm/device_compat.h>
 #include <malloc.h>
 #include <spi-mem.h>
+#include <wait_bit.h>
 #include <asm/io.h>
 #include <linux/log2.h>
 #include <clk.h>
 #define SIFIVE_SPI_IP_TXWM               BIT(0)
 #define SIFIVE_SPI_IP_RXWM               BIT(1)
 
+/* format protocol */
+#define SIFIVE_SPI_PROTO_QUAD          4 /* 4 lines I/O protocol transfer */
+#define SIFIVE_SPI_PROTO_DUAL          2 /* 2 lines I/O protocol transfer */
+#define SIFIVE_SPI_PROTO_SINGLE                1 /* 1 line I/O protocol transfer */
+
 struct sifive_spi {
        void            *regs;          /* base address of the registers */
        u32             fifo_depth;
@@ -93,6 +99,7 @@ struct sifive_spi {
        u32             cs_inactive;    /* Level of the CS pins when inactive*/
        u32             freq;
        u32             num_cs;
+       u8              fmt_proto;
 };
 
 static void sifive_spi_prep_device(struct sifive_spi *spi,
@@ -128,8 +135,8 @@ static void sifive_spi_clear_cs(struct sifive_spi *spi)
 }
 
 static void sifive_spi_prep_transfer(struct sifive_spi *spi,
-                                    bool is_rx_xfer,
-                                    struct dm_spi_slave_platdata *slave_plat)
+                                    struct dm_spi_slave_platdata *slave_plat,
+                                    u8 *rx_ptr)
 {
        u32 cr;
 
@@ -147,16 +154,21 @@ static void sifive_spi_prep_transfer(struct sifive_spi *spi,
 
        /* Number of wires ? */
        cr &= ~SIFIVE_SPI_FMT_PROTO_MASK;
-       if ((slave_plat->mode & SPI_TX_QUAD) || (slave_plat->mode & SPI_RX_QUAD))
+       switch (spi->fmt_proto) {
+       case SIFIVE_SPI_PROTO_QUAD:
                cr |= SIFIVE_SPI_FMT_PROTO_QUAD;
-       else if ((slave_plat->mode & SPI_TX_DUAL) || (slave_plat->mode & SPI_RX_DUAL))
+               break;
+       case SIFIVE_SPI_PROTO_DUAL:
                cr |= SIFIVE_SPI_FMT_PROTO_DUAL;
-       else
+               break;
+       default:
                cr |= SIFIVE_SPI_FMT_PROTO_SINGLE;
+               break;
+       }
 
        /* SPI direction in/out ? */
        cr &= ~SIFIVE_SPI_FMT_DIR;
-       if (!is_rx_xfer)
+       if (!rx_ptr)
                cr |= SIFIVE_SPI_FMT_DIR;
 
        writel(cr, spi->regs + SIFIVE_SPI_REG_FMT);
@@ -187,13 +199,19 @@ static void sifive_spi_tx(struct sifive_spi *spi, const u8 *tx_ptr)
        writel(tx_data, spi->regs + SIFIVE_SPI_REG_TXDATA);
 }
 
+static int sifive_spi_wait(struct sifive_spi *spi, u32 bit)
+{
+       return wait_for_bit_le32(spi->regs + SIFIVE_SPI_REG_IP,
+                                bit, true, 100, false);
+}
+
 static int sifive_spi_xfer(struct udevice *dev, unsigned int bitlen,
                           const void *dout, void *din, unsigned long flags)
 {
        struct udevice *bus = dev->parent;
        struct sifive_spi *spi = dev_get_priv(bus);
        struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
-       const unsigned char *tx_ptr = dout;
+       const u8 *tx_ptr = dout;
        u8 *rx_ptr = din;
        u32 remaining_len;
        int ret;
@@ -206,31 +224,37 @@ static int sifive_spi_xfer(struct udevice *dev, unsigned int bitlen,
                        return ret;
        }
 
-       sifive_spi_prep_transfer(spi, true, slave_plat);
+       sifive_spi_prep_transfer(spi, slave_plat, rx_ptr);
 
        remaining_len = bitlen / 8;
 
        while (remaining_len) {
-               int n_words, tx_words, rx_words;
-
-               n_words = min(remaining_len, spi->fifo_depth);
+               unsigned int n_words = min(remaining_len, spi->fifo_depth);
+               unsigned int tx_words, rx_words;
 
                /* Enqueue n_words for transmission */
-               if (tx_ptr) {
-                       for (tx_words = 0; tx_words < n_words; ++tx_words) {
-                               sifive_spi_tx(spi, tx_ptr);
-                               sifive_spi_rx(spi, NULL);
-                               tx_ptr++;
-                       }
+               for (tx_words = 0; tx_words < n_words; tx_words++) {
+                       if (!tx_ptr)
+                               sifive_spi_tx(spi, NULL);
+                       else
+                               sifive_spi_tx(spi, tx_ptr++);
                }
 
-               /* Read out all the data from the RX FIFO */
                if (rx_ptr) {
-                       for (rx_words = 0; rx_words < n_words; ++rx_words) {
-                               sifive_spi_tx(spi, NULL);
-                               sifive_spi_rx(spi, rx_ptr);
-                               rx_ptr++;
-                       }
+                       /* Wait for transmission + reception to complete */
+                       writel(n_words - 1, spi->regs + SIFIVE_SPI_REG_RXMARK);
+                       ret = sifive_spi_wait(spi, SIFIVE_SPI_IP_RXWM);
+                       if (ret)
+                               return ret;
+
+                       /* Read out all the data from the RX FIFO */
+                       for (rx_words = 0; rx_words < n_words; rx_words++)
+                               sifive_spi_rx(spi, rx_ptr++);
+               } else {
+                       /* Wait for transmission to complete */
+                       ret = sifive_spi_wait(spi, SIFIVE_SPI_IP_TXWM);
+                       if (ret)
+                               return ret;
                }
 
                remaining_len -= n_words;
@@ -246,6 +270,7 @@ static int sifive_spi_exec_op(struct spi_slave *slave,
                              const struct spi_mem_op *op)
 {
        struct udevice *dev = slave->dev;
+       struct sifive_spi *spi = dev_get_priv(dev->parent);
        unsigned long flags = SPI_XFER_BEGIN;
        u8 opcode = op->cmd.opcode;
        unsigned int pos = 0;
@@ -257,6 +282,8 @@ static int sifive_spi_exec_op(struct spi_slave *slave,
        if (!op->addr.nbytes && !op->dummy.nbytes && !op->data.nbytes)
                flags |= SPI_XFER_END;
 
+       spi->fmt_proto = op->cmd.buswidth;
+
        /* send the opcode */
        ret = sifive_spi_xfer(dev, 8, (void *)&opcode, NULL, flags);
        if (ret < 0) {
@@ -284,6 +311,8 @@ static int sifive_spi_exec_op(struct spi_slave *slave,
                if (!op->data.nbytes)
                        flags |= SPI_XFER_END;
 
+               spi->fmt_proto = op->addr.buswidth;
+
                ret = sifive_spi_xfer(dev, op_len * 8, op_buf, NULL, flags);
                if (ret < 0) {
                        dev_err(dev, "failed to xfer addr + dummy\n");
@@ -298,6 +327,8 @@ static int sifive_spi_exec_op(struct spi_slave *slave,
                else
                        tx_buf = op->data.buf.out;
 
+               spi->fmt_proto = op->data.buswidth;
+
                ret = sifive_spi_xfer(dev, op->data.nbytes * 8,
                                      tx_buf, rx_buf, SPI_XFER_END);
                if (ret) {
@@ -377,6 +408,10 @@ static void sifive_spi_init_hw(struct sifive_spi *spi)
        /* Watermark interrupts are disabled by default */
        writel(0, spi->regs + SIFIVE_SPI_REG_IE);
 
+       /* Default watermark FIFO threshold values */
+       writel(1, spi->regs + SIFIVE_SPI_REG_TXMARK);
+       writel(0, spi->regs + SIFIVE_SPI_REG_RXMARK);
+
        /* Set CS/SCK Delays and Inactive Time to defaults */
        writel(SIFIVE_SPI_DELAY0_CSSCK(1) | SIFIVE_SPI_DELAY0_SCKCS(1),
               spi->regs + SIFIVE_SPI_REG_DELAY0);