spi: bcm2835: Speed up FIFO access if fill level is known

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>
This commit is contained in:
Lukas Wunner 2018-11-29 16:45:24 +01:00 committed by Mark Brown
parent b31a9299bc
commit 2e0733bc5a
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
1 changed files with 60 additions and 4 deletions

View File

@ -71,6 +71,8 @@
#define BCM2835_SPI_CS_CS_10 0x00000002 #define BCM2835_SPI_CS_CS_10 0x00000002
#define BCM2835_SPI_CS_CS_01 0x00000001 #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_LIMIT_US 30
#define BCM2835_SPI_POLLING_JIFFIES 2 #define BCM2835_SPI_POLLING_JIFFIES 2
#define BCM2835_SPI_DMA_MIN_LENGTH 96 #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(); 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) static void bcm2835_spi_reset_hw(struct spi_master *master)
{ {
struct bcm2835_spi *bs = spi_master_get_devdata(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 spi_master *master = dev_id;
struct bcm2835_spi *bs = spi_master_get_devdata(master); 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 */ /* Read as many bytes as possible from FIFO */
bcm2835_rd_fifo(bs); 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, static int bcm2835_spi_transfer_one_irq(struct spi_master *master,
struct spi_device *spi, struct spi_device *spi,
struct spi_transfer *tfr, struct spi_transfer *tfr,
u32 cs) u32 cs, bool fifo_empty)
{ {
struct bcm2835_spi *bs = spi_master_get_devdata(master); 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); bcm2835_wr(bs, BCM2835_SPI_CS, cs | BCM2835_SPI_CS_TA);
/* fill TX FIFO as much as possible */ /* fill TX FIFO as much as possible */
if (fifo_empty)
bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
bcm2835_wr_fifo(bs); bcm2835_wr_fifo(bs);
/* enable interrupts */ /* 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 * if we are interrupted here, then the data is
* getting transferred by the HW while we are interrupted * 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 */ /* set the timeout */
timeout = jiffies + BCM2835_SPI_POLLING_JIFFIES; 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); bs->tx_len, bs->rx_len);
/* fall back to interrupt mode */ /* fall back to interrupt mode */
return bcm2835_spi_transfer_one_irq(master, spi, 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); return bcm2835_spi_transfer_one_dma(master, spi, tfr, cs);
/* run in interrupt-mode */ /* 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, static int bcm2835_spi_prepare_message(struct spi_master *master,