]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/commitdiff
spi: bcm2835: Speed up FIFO access if fill level is known
authorLukas Wunner <lukas@wunner.de>
Thu, 29 Nov 2018 15:45:24 +0000 (16:45 +0100)
committerMark Brown <broonie@kernel.org>
Tue, 4 Dec 2018 16:51:41 +0000 (16:51 +0000)
The RX and TX FIFO of the BCM2835 SPI master each accommodate 64 bytes
(16 32-bit dwords).  The CS register provides hints on their fill level:

   "Bit 19  RXR - RX FIFO needs Reading ([¾] full)
    0 = RX FIFO is less than [¾] full (or not active TA = 0).
    1 = RX FIFO is [¾] or more full. Cleared by reading sufficient
        data from the RX FIFO or setting TA to 0."

   "Bit 16  DONE - Transfer Done
    0 = Transfer is in progress (or not active TA = 0).
    1 = Transfer is complete. Cleared by writing more data to the
        TX FIFO or setting TA to 0."

   "If DONE is set [...], write up to 16 [dwords] to SPI_FIFO. [...]
    If RXR is set read 12 [dwords] data from SPI_FIFO."

   [Source: Pages 153, 154 and 158 of
    https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
    Note: The spec is missing the "¾" character, presumably due to
    copy-pasting from a different charset.  It also incorrectly
    refers to 16 and 12 "bytes" instead of 32-bit dwords.]

In short, the RXR bit indicates that 48 bytes can be read and the DONE
bit indicates 64 bytes can be written.  Leverage this knowledge to read
or write bytes blindly to the FIFO, without polling whether data can be
read or free space is available to write.  Moreover, when a transfer is
starting, the TX FIFO is known to be empty, likewise allowing a blind
write of 64 bytes.

This cuts the number of bus accesses in half if the fill level is known.
Also, the (posted) write accesses can be pipelined on the AXI bus since
they are no longer interleaved with (non-posted) reads.

bcm2835_spi_transfer_one_poll() switches to interrupt mode when a time
limit is exceeded by calling bcm2835_spi_transfer_one_irq().  The TX
FIFO may contain data in this case, but is known to be empty when the
function is called from bcm2835_spi_transfer_one().  Hence only blindly
fill the TX FIFO in the latter case but not the former.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
Tested-by: Eric Anholt <eric@anholt.net>
Cc: Frank Pavlic <f.pavlic@kunbus.de>
Cc: Martin Sperl <kernel@martin.sperl.org>
Cc: Noralf Trønnes <noralf@tronnes.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi-bcm2835.c

index 8c121b3374dd14aeaa52778f6c5b82bbe8fb18af..5af4cf74392c699f42931457e68e8a31f99e9f9b 100644 (file)
@@ -71,6 +71,8 @@
 #define BCM2835_SPI_CS_CS_10           0x00000002
 #define BCM2835_SPI_CS_CS_01           0x00000001
 
+#define BCM2835_SPI_FIFO_SIZE          64
+#define BCM2835_SPI_FIFO_SIZE_3_4      48
 #define BCM2835_SPI_POLLING_LIMIT_US   30
 #define BCM2835_SPI_POLLING_JIFFIES    2
 #define BCM2835_SPI_DMA_MIN_LENGTH     96
@@ -216,6 +218,45 @@ static inline void bcm2835_wait_tx_fifo_empty(struct bcm2835_spi *bs)
                cpu_relax();
 }
 
+/**
+ * bcm2835_rd_fifo_blind() - blindly read up to @count bytes from RX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes available for reading in RX FIFO
+ */
+static inline void bcm2835_rd_fifo_blind(struct bcm2835_spi *bs, int count)
+{
+       u8 val;
+
+       count = min(count, bs->rx_len);
+       bs->rx_len -= count;
+
+       while (count) {
+               val = bcm2835_rd(bs, BCM2835_SPI_FIFO);
+               if (bs->rx_buf)
+                       *bs->rx_buf++ = val;
+               count--;
+       }
+}
+
+/**
+ * bcm2835_wr_fifo_blind() - blindly write up to @count bytes to TX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes available for writing in TX FIFO
+ */
+static inline void bcm2835_wr_fifo_blind(struct bcm2835_spi *bs, int count)
+{
+       u8 val;
+
+       count = min(count, bs->tx_len);
+       bs->tx_len -= count;
+
+       while (count) {
+               val = bs->tx_buf ? *bs->tx_buf++ : 0;
+               bcm2835_wr(bs, BCM2835_SPI_FIFO, val);
+               count--;
+       }
+}
+
 static void bcm2835_spi_reset_hw(struct spi_master *master)
 {
        struct bcm2835_spi *bs = spi_master_get_devdata(master);
@@ -239,6 +280,19 @@ static irqreturn_t bcm2835_spi_interrupt(int irq, void *dev_id)
 {
        struct spi_master *master = dev_id;
        struct bcm2835_spi *bs = spi_master_get_devdata(master);
+       u32 cs = bcm2835_rd(bs, BCM2835_SPI_CS);
+
+       /*
+        * An interrupt is signaled either if DONE is set (TX FIFO empty)
+        * or if RXR is set (RX FIFO >= ¾ full).
+        */
+       if (cs & BCM2835_SPI_CS_RXF)
+               bcm2835_rd_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
+       else if (cs & BCM2835_SPI_CS_RXR)
+               bcm2835_rd_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE_3_4);
+
+       if (bs->tx_len && cs & BCM2835_SPI_CS_DONE)
+               bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
 
        /* Read as many bytes as possible from FIFO */
        bcm2835_rd_fifo(bs);
@@ -258,7 +312,7 @@ static irqreturn_t bcm2835_spi_interrupt(int irq, void *dev_id)
 static int bcm2835_spi_transfer_one_irq(struct spi_master *master,
                                        struct spi_device *spi,
                                        struct spi_transfer *tfr,
-                                       u32 cs)
+                                       u32 cs, bool fifo_empty)
 {
        struct bcm2835_spi *bs = spi_master_get_devdata(master);
 
@@ -269,6 +323,8 @@ static int bcm2835_spi_transfer_one_irq(struct spi_master *master,
        bcm2835_wr(bs, BCM2835_SPI_CS, cs | BCM2835_SPI_CS_TA);
 
        /* fill TX FIFO as much as possible */
+       if (fifo_empty)
+               bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
        bcm2835_wr_fifo(bs);
 
        /* enable interrupts */
@@ -682,7 +738,7 @@ static int bcm2835_spi_transfer_one_poll(struct spi_master *master,
         * if we are interrupted here, then the data is
         * getting transferred by the HW while we are interrupted
         */
-       bcm2835_wr_fifo(bs);
+       bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
 
        /* set the timeout */
        timeout = jiffies + BCM2835_SPI_POLLING_JIFFIES;
@@ -705,7 +761,7 @@ static int bcm2835_spi_transfer_one_poll(struct spi_master *master,
                                            bs->tx_len, bs->rx_len);
                        /* fall back to interrupt mode */
                        return bcm2835_spi_transfer_one_irq(master, spi,
-                                                           tfr, cs);
+                                                           tfr, cs, false);
                }
        }
 
@@ -779,7 +835,7 @@ static int bcm2835_spi_transfer_one(struct spi_master *master,
                return bcm2835_spi_transfer_one_dma(master, spi, tfr, cs);
 
        /* run in interrupt-mode */
-       return bcm2835_spi_transfer_one_irq(master, spi, tfr, cs);
+       return bcm2835_spi_transfer_one_irq(master, spi, tfr, cs, true);
 }
 
 static int bcm2835_spi_prepare_message(struct spi_master *master,