staging: mt7621-spi: revised half-duplex message handling
authorNeilBrown <neil@brown.name>
Wed, 6 Jun 2018 22:04:21 +0000 (08:04 +1000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 17 Jun 2018 07:05:11 +0000 (09:05 +0200)
The mt7621 SPI engine has a 32 byte buffer and the driver
currently only allows 32-byte read requests and 36 bytes writes
(there is a 4byte op/addr buffer).

This is an unnecessary limitation.  As the SPI clock is controlled
by the host it is quite acceptable to send a larger message in
multiple smaller transactions.  As long as Chip Select is kept asserted
the whole time, the SPI engine can be run multiple times for
a single SPI message.

This patch factors out the transaction logic and calls for each
transfer in the message.  A write transfer might leave bytes in the
buffer to be combined with a following read transfer, as this is
a common pattern.

With this in place, we can remove the current max_transfer_size limit.

In testing, this increases the read throughput for a NOR flash chip
from 1.4MB/s to 2.3MB/s, a 50% improvement.

Signed-off-by: NeilBrown <neil@brown.name>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/mt7621-spi/spi-mt7621.c

index d43576d..11474e6 100644 (file)
@@ -65,6 +65,7 @@ struct mt7621_spi {
        unsigned int            sys_freq;
        unsigned int            speed;
        struct clk              *clk;
+       int                     pending_write;
 
        struct mt7621_spi_ops   *ops;
 };
@@ -96,6 +97,7 @@ static void mt7621_spi_reset(struct mt7621_spi *rs, int duplex)
                master &= ~(1 << 10);
 
        mt7621_spi_write(rs, MT7621_SPI_MASTER, master);
+       rs->pending_write = 0;
 }
 
 static void mt7621_spi_set_cs(struct spi_device *spi, int enable)
@@ -173,90 +175,124 @@ static inline int mt7621_spi_wait_till_ready(struct mt7621_spi *rs)
        return -ETIMEDOUT;
 }
 
-static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
-                                          struct spi_message *m)
+static void mt7621_spi_read_half_duplex(struct mt7621_spi *rs,
+                                       int rx_len, u8 *buf)
 {
-       struct mt7621_spi *rs = spi_master_get_devdata(master);
-       struct spi_device *spi = m->spi;
-       unsigned int speed = spi->max_speed_hz;
-       struct spi_transfer *t = NULL;
-       int status = 0;
-       int i, len = 0;
-       int rx_len = 0;
-       u32 data[9] = { 0 };
-       u32 val;
+       /* Combine with any pending write, and perform one or
+        * more half-duplex transactions reading 'len' bytes.
+        * Data to be written is already in MT7621_SPI_DATA*
+        */
+       int tx_len = rs->pending_write;
 
-       mt7621_spi_wait_till_ready(rs);
+       rs->pending_write = 0;
 
-       list_for_each_entry(t, &m->transfers, transfer_list) {
-               const u8 *buf = t->tx_buf;
+       while (rx_len || tx_len) {
+               int i;
+               u32 val = (min(tx_len, 4) * 8) << 24;
+               int rx = min(rx_len, 32);
 
-               if (t->rx_buf)
-                       rx_len += t->len;
+               if (tx_len > 4)
+                       val |= (tx_len - 4) * 8;
+               val |= (rx * 8) << 12;
+               mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);
 
-               if (!buf)
-                       continue;
+               tx_len = 0;
 
-               if (t->speed_hz < speed)
-                       speed = t->speed_hz;
+               val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
+               val |= SPI_CTL_START;
+               mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
 
-               if (WARN_ON(len + t->len > 36)) {
-                       status = -EIO;
-                       goto msg_done;
-               }
+               mt7621_spi_wait_till_ready(rs);
 
-               for (i = 0; i < t->len; i++, len++)
-                       data[len / 4] |= buf[i] << (8 * (len & 3));
+               for (i = 0; i < rx; i++) {
+                       if ((i % 4) == 0)
+                               val = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
+                       *buf++ = val & 0xff;
+                       val >>= 8;
+               }
+               rx_len -= i;
        }
+}
 
-       if (WARN_ON(rx_len > 32)) {
-               status = -EIO;
-               goto msg_done;
-       }
+static inline void mt7621_spi_flush(struct mt7621_spi *rs)
+{
+       mt7621_spi_read_half_duplex(rs, 0, NULL);
+}
 
-       if (mt7621_spi_prepare(spi, speed)) {
-               status = -EIO;
-               goto msg_done;
+static void mt7621_spi_write_half_duplex(struct mt7621_spi *rs,
+                                        int tx_len, const u8 *buf)
+{
+       int val = 0;
+       int len = rs->pending_write;
+
+       if (len & 3) {
+               val = mt7621_spi_read(rs, MT7621_SPI_OPCODE + (len & ~3));
+               if (len < 4) {
+                       val <<= (4 - len) * 8;
+                       val = swab32(val);
+               }
        }
-       data[0] = swab32(data[0]);
-       if (len < 4)
-               data[0] >>= (4 - len) * 8;
 
-       for (i = 0; i < len; i += 4)
-               mt7621_spi_write(rs, MT7621_SPI_OPCODE + i, data[i / 4]);
-
-       val = (min_t(int, len, 4) * 8) << 24;
-       if (len > 4)
-               val |= (len - 4) * 8;
-       val |= (rx_len * 8) << 12;
-       mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);
+       while (tx_len > 0) {
+               if (len >= 36) {
+                       rs->pending_write = len;
+                       mt7621_spi_flush(rs);
+                       len = 0;
+               }
 
-       mt7621_spi_set_cs(spi, 1);
+               val |= *buf++ << (8 * (len & 3));
+               len++;
+               if ((len & 3) == 0) {
+                       if (len == 4)
+                               /* The byte-order of the opcode is weird! */
+                               val = swab32(val);
+                       mt7621_spi_write(rs, MT7621_SPI_OPCODE + len - 4, val);
+                       val = 0;
+               }
+               tx_len -= 1;
+       }
+       if (len & 3) {
+               if (len < 4) {
+                       val = swab32(val);
+                       val >>= (4 - len) * 8;
+               }
+               mt7621_spi_write(rs, MT7621_SPI_OPCODE + (len & ~3), val);
+       }
+       rs->pending_write = len;
+}
 
-       val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
-       val |= SPI_CTL_START;
-       mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
+static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
+                                          struct spi_message *m)
+{
+       struct mt7621_spi *rs = spi_master_get_devdata(master);
+       struct spi_device *spi = m->spi;
+       unsigned int speed = spi->max_speed_hz;
+       struct spi_transfer *t = NULL;
+       int status = 0;
 
        mt7621_spi_wait_till_ready(rs);
 
-       mt7621_spi_set_cs(spi, 0);
-
-       for (i = 0; i < rx_len; i += 4)
-               data[i / 4] = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
+       list_for_each_entry(t, &m->transfers, transfer_list)
+               if (t->speed_hz < speed)
+                       speed = t->speed_hz;
 
-       m->actual_length = len + rx_len;
+       if (mt7621_spi_prepare(spi, speed)) {
+               status = -EIO;
+               goto msg_done;
+       }
 
-       len = 0;
+       mt7621_spi_set_cs(spi, 1);
+       m->actual_length = 0;
        list_for_each_entry(t, &m->transfers, transfer_list) {
-               u8 *buf = t->rx_buf;
-
-               if (!buf)
-                       continue;
-
-               for (i = 0; i < t->len; i++, len++)
-                       buf[i] = data[len / 4] >> (8 * (len & 3));
+               if (t->rx_buf)
+                       mt7621_spi_read_half_duplex(rs, t->len, t->rx_buf);
+               else if (t->tx_buf)
+                       mt7621_spi_write_half_duplex(rs, t->len, t->tx_buf);
+               m->actual_length += t->len;
        }
+       mt7621_spi_flush(rs);
 
+       mt7621_spi_set_cs(spi, 0);
 msg_done:
        m->status = status;
        spi_finalize_current_message(master);
@@ -383,11 +419,6 @@ static const struct of_device_id mt7621_spi_match[] = {
 };
 MODULE_DEVICE_TABLE(of, mt7621_spi_match);
 
-static size_t max_transfer_size(struct spi_device *spi)
-{
-       return 32;
-}
-
 static int mt7621_spi_probe(struct platform_device *pdev)
 {
        const struct of_device_id *match;
@@ -433,7 +464,6 @@ static int mt7621_spi_probe(struct platform_device *pdev)
        master->bits_per_word_mask = SPI_BPW_MASK(8);
        master->dev.of_node = pdev->dev.of_node;
        master->num_chipselect = 2;
-       master->max_transfer_size = max_transfer_size;
 
        dev_set_drvdata(&pdev->dev, master);
 
@@ -443,6 +473,7 @@ static int mt7621_spi_probe(struct platform_device *pdev)
        rs->master = master;
        rs->sys_freq = clk_get_rate(rs->clk);
        rs->ops = ops;
+       rs->pending_write = 0;
        dev_info(&pdev->dev, "sys_freq: %u\n", rs->sys_freq);
 
        device_reset(&pdev->dev);