spi: rspi: Add support for Quad and Dual SPI Transfers on QSPI
authorGeert Uytterhoeven <geert+renesas@linux-m68k.org>
Thu, 30 Jan 2014 08:43:50 +0000 (09:43 +0100)
committerMark Brown <broonie@linaro.org>
Thu, 30 Jan 2014 16:51:21 +0000 (16:51 +0000)
Add support for Quad and Dual SPI Transfers on the Renesas Quad Serial
Peripheral Interface, as found in R-Car Gen2 SoCs like R-Car H2 (r8a7790)
and R-Car M2 (r8a7791):
  - Add unidirectional transfer methods for Quad/Dual SPI Transfers.
  - Program the sequencer to handle SPI messages with multiple transfer
    modes when Quad or Dual transfers are enabled for an SPI slave.
    Up to 4 transfer modes per SPI message are supported by the hardware.
  - Advertise the availability of Quad and Dual SPI modes on QSPI.

Signed-off-by: Geert Uytterhoeven <geert+renesas@linux-m68k.org>
Signed-off-by: Mark Brown <broonie@linaro.org>
drivers/spi/spi-rspi.c

index e56fcb5f7f997b87b0225ef7140c2eff87aa9d0e..34ad4bca8a41210e6b7afdbda9122be9f2ac7944 100644 (file)
@@ -2,6 +2,7 @@
  * SH RSPI driver
  *
  * Copyright (C) 2012, 2013  Renesas Solutions Corp.
+ * Copyright (C) 2014 Glider bvba
  *
  * Based on spi-sh.c:
  * Copyright (C) 2011 Renesas Solutions Corp.
 #define RSPI_SPCMD5            0x1a    /* Command Register 5 */
 #define RSPI_SPCMD6            0x1c    /* Command Register 6 */
 #define RSPI_SPCMD7            0x1e    /* Command Register 7 */
+#define RSPI_SPCMD(i)          (RSPI_SPCMD0 + (i) * 2)
+#define RSPI_NUM_SPCMD         8
+#define RSPI_RZ_NUM_SPCMD      4
+#define QSPI_NUM_SPCMD         4
 
 /* RSPI on RZ only */
 #define RSPI_SPBFCR            0x20    /* Buffer Control Register */
@@ -69,6 +74,7 @@
 #define QSPI_SPBMUL1           0x20    /* Transfer Data Length Multiplier Setting Register 1 */
 #define QSPI_SPBMUL2           0x24    /* Transfer Data Length Multiplier Setting Register 2 */
 #define QSPI_SPBMUL3           0x28    /* Transfer Data Length Multiplier Setting Register 3 */
+#define QSPI_SPBMUL(i)         (QSPI_SPBMUL0 + (i) * 4)
 
 /* SPCR - Control Register */
 #define SPCR_SPRIE             0x80    /* Receive Interrupt Enable */
 #define SPCMD_LSBF             0x1000  /* LSB First */
 #define SPCMD_SPB_MASK         0x0f00  /* Data Length Setting */
 #define SPCMD_SPB_8_TO_16(bit) (((bit - 1) << 8) & SPCMD_SPB_MASK)
-#define SPCMD_SPB_8BIT         0x0000  /* qspi only */
+#define SPCMD_SPB_8BIT         0x0000  /* QSPI only */
 #define SPCMD_SPB_16BIT                0x0100
 #define SPCMD_SPB_20BIT                0x0000
 #define SPCMD_SPB_24BIT                0x0100
@@ -245,6 +251,7 @@ struct spi_ops {
        int (*set_config_register)(struct rspi_data *rspi, int access_size);
        int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
                            struct spi_transfer *xfer);
+       u16 mode_bits;
 };
 
 /*
@@ -274,8 +281,8 @@ static int rspi_set_config_register(struct rspi_data *rspi, int access_size)
        rspi_write8(rspi, 0x00, RSPI_SPCR2);
 
        /* Sets SPCMD */
-       rspi_write16(rspi, SPCMD_SPB_8_TO_16(access_size) | rspi->spcmd,
-                    RSPI_SPCMD0);
+       rspi->spcmd |= SPCMD_SPB_8_TO_16(access_size);
+       rspi_write16(rspi, rspi->spcmd, RSPI_SPCMD0);
 
        /* Sets RSPI mode */
        rspi_write8(rspi, SPCR_MSTR, RSPI_SPCR);
@@ -321,7 +328,6 @@ static int rspi_rz_set_config_register(struct rspi_data *rspi, int access_size)
  */
 static int qspi_set_config_register(struct rspi_data *rspi, int access_size)
 {
-       u16 spcmd;
        int spbr;
 
        /* Sets output mode, MOSI signal, and (optionally) loopback */
@@ -342,13 +348,13 @@ static int qspi_set_config_register(struct rspi_data *rspi, int access_size)
 
        /* Data Length Setting */
        if (access_size == 8)
-               spcmd = SPCMD_SPB_8BIT;
+               rspi->spcmd |= SPCMD_SPB_8BIT;
        else if (access_size == 16)
-               spcmd = SPCMD_SPB_16BIT;
+               rspi->spcmd |= SPCMD_SPB_16BIT;
        else
-               spcmd = SPCMD_SPB_32BIT;
+               rspi->spcmd |= SPCMD_SPB_32BIT;
 
-       spcmd |= SPCMD_SCKDEN | SPCMD_SLNDEN | rspi->spcmd | SPCMD_SPNDEN;
+       rspi->spcmd |= SPCMD_SCKDEN | SPCMD_SLNDEN | SPCMD_SPNDEN;
 
        /* Resets transfer data length */
        rspi_write32(rspi, 0, QSPI_SPBMUL0);
@@ -359,9 +365,9 @@ static int qspi_set_config_register(struct rspi_data *rspi, int access_size)
        rspi_write8(rspi, 0x00, QSPI_SPBFCR);
 
        /* Sets SPCMD */
-       rspi_write16(rspi, spcmd, RSPI_SPCMD0);
+       rspi_write16(rspi, rspi->spcmd, RSPI_SPCMD0);
 
-       /* Enables SPI function in master mode */
+       /* Enables SPI function in master mode */
        rspi_write8(rspi, SPCR_SPE | SPCR_MSTR, RSPI_SPCR);
 
        return 0;
@@ -811,12 +817,55 @@ static int qspi_transfer_out_in(struct rspi_data *rspi,
        return 0;
 }
 
+static int qspi_transfer_out(struct rspi_data *rspi, struct spi_transfer *xfer)
+{
+       const u8 *buf = xfer->tx_buf;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < xfer->len; i++) {
+               ret = rspi_data_out(rspi, *buf++);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* Wait for the last transmission */
+       rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE);
+
+       return 0;
+}
+
+static int qspi_transfer_in(struct rspi_data *rspi, struct spi_transfer *xfer)
+{
+       u8 *buf = xfer->rx_buf;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < xfer->len; i++) {
+               ret = rspi_data_in(rspi);
+               if (ret < 0)
+                       return ret;
+               *buf++ = ret;
+       }
+
+       return 0;
+}
+
 static int qspi_transfer_one(struct spi_master *master, struct spi_device *spi,
                             struct spi_transfer *xfer)
 {
        struct rspi_data *rspi = spi_master_get_devdata(master);
 
-       return qspi_transfer_out_in(rspi, xfer);
+       if (xfer->tx_buf && xfer->tx_nbits > SPI_NBITS_SINGLE) {
+               /* Quad or Dual SPI Write */
+               return qspi_transfer_out(rspi, xfer);
+       } else if (xfer->rx_buf && xfer->rx_nbits > SPI_NBITS_SINGLE) {
+               /* Quad or Dual SPI Read */
+               return qspi_transfer_in(rspi, xfer);
+       } else {
+               /* Single SPI Transfer */
+               return qspi_transfer_out_in(rspi, xfer);
+       }
 }
 
 static int rspi_setup(struct spi_device *spi)
@@ -845,21 +894,101 @@ static void rspi_cleanup(struct spi_device *spi)
 {
 }
 
+static u16 qspi_transfer_mode(const struct spi_transfer *xfer)
+{
+       if (xfer->tx_buf)
+               switch (xfer->tx_nbits) {
+               case SPI_NBITS_QUAD:
+                       return SPCMD_SPIMOD_QUAD;
+               case SPI_NBITS_DUAL:
+                       return SPCMD_SPIMOD_DUAL;
+               default:
+                       return 0;
+               }
+       if (xfer->rx_buf)
+               switch (xfer->rx_nbits) {
+               case SPI_NBITS_QUAD:
+                       return SPCMD_SPIMOD_QUAD | SPCMD_SPRW;
+               case SPI_NBITS_DUAL:
+                       return SPCMD_SPIMOD_DUAL | SPCMD_SPRW;
+               default:
+                       return 0;
+               }
+
+       return 0;
+}
+
+static int qspi_setup_sequencer(struct rspi_data *rspi,
+                               const struct spi_message *msg)
+{
+       const struct spi_transfer *xfer;
+       unsigned int i = 0, len = 0;
+       u16 current_mode = 0xffff, mode;
+
+       list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+               mode = qspi_transfer_mode(xfer);
+               if (mode == current_mode) {
+                       len += xfer->len;
+                       continue;
+               }
+
+               /* Transfer mode change */
+               if (i) {
+                       /* Set transfer data length of previous transfer */
+                       rspi_write32(rspi, len, QSPI_SPBMUL(i - 1));
+               }
+
+               if (i >= QSPI_NUM_SPCMD) {
+                       dev_err(&msg->spi->dev,
+                               "Too many different transfer modes");
+                       return -EINVAL;
+               }
+
+               /* Program transfer mode for this transfer */
+               rspi_write16(rspi, rspi->spcmd | mode, RSPI_SPCMD(i));
+               current_mode = mode;
+               len = xfer->len;
+               i++;
+       }
+       if (i) {
+               /* Set final transfer data length and sequence length */
+               rspi_write32(rspi, len, QSPI_SPBMUL(i - 1));
+               rspi_write8(rspi, i - 1, RSPI_SPSCR);
+       }
+
+       return 0;
+}
+
 static int rspi_prepare_message(struct spi_master *master,
-                               struct spi_message *message)
+                               struct spi_message *msg)
 {
        struct rspi_data *rspi = spi_master_get_devdata(master);
+       int ret;
 
+       if (msg->spi->mode &
+           (SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD)) {
+               /* Setup sequencer for messages with multiple transfer modes */
+               ret = qspi_setup_sequencer(rspi, msg);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* Enable SPI function in master mode */
        rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | SPCR_SPE, RSPI_SPCR);
        return 0;
 }
 
 static int rspi_unprepare_message(struct spi_master *master,
-                                 struct spi_message *message)
+                                 struct spi_message *msg)
 {
        struct rspi_data *rspi = spi_master_get_devdata(master);
 
+       /* Disable SPI function */
        rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~SPCR_SPE, RSPI_SPCR);
+
+       /* Reset sequencer for Single SPI Transfers */
+       rspi_write16(rspi, rspi->spcmd, RSPI_SPCMD0);
+       rspi_write8(rspi, 0, RSPI_SPSCR);
        return 0;
 }
 
@@ -989,16 +1118,21 @@ static int rspi_remove(struct platform_device *pdev)
 static const struct spi_ops rspi_ops = {
        .set_config_register =          rspi_set_config_register,
        .transfer_one =                 rspi_transfer_one,
+       .mode_bits =                    SPI_CPHA | SPI_CPOL | SPI_LOOP,
 };
 
 static const struct spi_ops rspi_rz_ops = {
        .set_config_register =          rspi_rz_set_config_register,
        .transfer_one =                 rspi_rz_transfer_one,
+       .mode_bits =                    SPI_CPHA | SPI_CPOL | SPI_LOOP,
 };
 
 static const struct spi_ops qspi_ops = {
        .set_config_register =          qspi_set_config_register,
        .transfer_one =                 qspi_transfer_one,
+       .mode_bits =                    SPI_CPHA | SPI_CPOL | SPI_LOOP |
+                                       SPI_TX_DUAL | SPI_TX_QUAD |
+                                       SPI_RX_DUAL | SPI_RX_QUAD,
 };
 
 #ifdef CONFIG_OF
@@ -1120,7 +1254,7 @@ static int rspi_probe(struct platform_device *pdev)
        master->cleanup = rspi_cleanup;
        master->prepare_message = rspi_prepare_message;
        master->unprepare_message = rspi_unprepare_message;
-       master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LOOP;
+       master->mode_bits = ops->mode_bits;
        master->dev.of_node = pdev->dev.of_node;
 
        ret = platform_get_irq_byname(pdev, "rx");