spi: exynos: Support word transfers
Since SPI register access is so expensive, it is worth transferring data a word at a time if we can. This complicates the driver unfortunately. Use the byte-swapping feature to avoid having to convert to/from big endian in software. This change increases speed from about 2MB/s to about 4.5MB/s. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Rajeshwari S Shinde <rajeshwari.s@samsung.com> Reviewed-by: Jagannadha Sutradharudu Teki <jagannadh.teki@gmail.com>
This commit is contained in:
parent
120af1572a
commit
c4a796329d
@ -22,7 +22,7 @@ struct exynos_spi {
|
||||
unsigned int rx_data; /* 0x1c */
|
||||
unsigned int pkt_cnt; /* 0x20 */
|
||||
unsigned char reserved2[4];
|
||||
unsigned char reserved3[4];
|
||||
unsigned int swap_cfg; /* 0x28 */
|
||||
unsigned int fb_clk; /* 0x2c */
|
||||
unsigned char padding[0xffd0];
|
||||
};
|
||||
@ -62,5 +62,14 @@ struct exynos_spi {
|
||||
/* Packet Count */
|
||||
#define SPI_PACKET_CNT_EN (1 << 16)
|
||||
|
||||
/* Swap config */
|
||||
#define SPI_TX_SWAP_EN (1 << 0)
|
||||
#define SPI_TX_BYTE_SWAP (1 << 2)
|
||||
#define SPI_TX_HWORD_SWAP (1 << 3)
|
||||
#define SPI_TX_BYTE_SWAP (1 << 2)
|
||||
#define SPI_RX_SWAP_EN (1 << 4)
|
||||
#define SPI_RX_BYTE_SWAP (1 << 6)
|
||||
#define SPI_RX_HWORD_SWAP (1 << 7)
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
#endif
|
||||
|
@ -204,12 +204,29 @@ static void spi_get_fifo_levels(struct exynos_spi *regs,
|
||||
*
|
||||
* @param regs SPI peripheral registers
|
||||
* @param count Number of bytes to transfer
|
||||
* @param step Number of bytes to transfer in each packet (1 or 4)
|
||||
*/
|
||||
static void spi_request_bytes(struct exynos_spi *regs, int count)
|
||||
static void spi_request_bytes(struct exynos_spi *regs, int count, int step)
|
||||
{
|
||||
/* For word address we need to swap bytes */
|
||||
if (step == 4) {
|
||||
setbits_le32(®s->mode_cfg,
|
||||
SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD);
|
||||
count /= 4;
|
||||
setbits_le32(®s->swap_cfg, SPI_TX_SWAP_EN | SPI_RX_SWAP_EN |
|
||||
SPI_TX_BYTE_SWAP | SPI_RX_BYTE_SWAP |
|
||||
SPI_TX_HWORD_SWAP | SPI_RX_HWORD_SWAP);
|
||||
} else {
|
||||
/* Select byte access and clear the swap configuration */
|
||||
clrbits_le32(®s->mode_cfg,
|
||||
SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD);
|
||||
writel(0, ®s->swap_cfg);
|
||||
}
|
||||
|
||||
assert(count && count < (1 << 16));
|
||||
setbits_le32(®s->ch_cfg, SPI_CH_RST);
|
||||
clrbits_le32(®s->ch_cfg, SPI_CH_RST);
|
||||
|
||||
writel(count | SPI_PACKET_CNT_EN, ®s->pkt_cnt);
|
||||
}
|
||||
|
||||
@ -224,17 +241,27 @@ static int spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo,
|
||||
int toread;
|
||||
unsigned start = get_timer(0);
|
||||
int stopping;
|
||||
int step;
|
||||
|
||||
out_bytes = in_bytes = todo;
|
||||
|
||||
stopping = spi_slave->skip_preamble && (flags & SPI_XFER_END) &&
|
||||
!(spi_slave->mode & SPI_SLAVE);
|
||||
|
||||
/*
|
||||
* Try to transfer words if we can. This helps read performance at
|
||||
* SPI clock speeds above about 20MHz.
|
||||
*/
|
||||
step = 1;
|
||||
if (!((todo | (uintptr_t)rxp | (uintptr_t)txp) & 3) &&
|
||||
!spi_slave->skip_preamble)
|
||||
step = 4;
|
||||
|
||||
/*
|
||||
* If there's something to send, do a software reset and set a
|
||||
* transaction size.
|
||||
*/
|
||||
spi_request_bytes(regs, todo);
|
||||
spi_request_bytes(regs, todo, step);
|
||||
|
||||
/*
|
||||
* Bytes are transmitted/received in pairs. Wait to receive all the
|
||||
@ -247,14 +274,26 @@ static int spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo,
|
||||
|
||||
/* Keep the fifos full/empty. */
|
||||
spi_get_fifo_levels(regs, &rx_lvl, &tx_lvl);
|
||||
|
||||
/*
|
||||
* Don't completely fill the txfifo, since we don't want our
|
||||
* rxfifo to overflow, and it may already contain data.
|
||||
*/
|
||||
while (tx_lvl < spi_slave->fifo_size/2 && out_bytes) {
|
||||
temp = txp ? *txp++ : 0xff;
|
||||
if (!txp)
|
||||
temp = -1;
|
||||
else if (step == 4)
|
||||
temp = *(uint32_t *)txp;
|
||||
else
|
||||
temp = *txp;
|
||||
writel(temp, ®s->tx_data);
|
||||
out_bytes--;
|
||||
tx_lvl++;
|
||||
out_bytes -= step;
|
||||
if (txp)
|
||||
txp += step;
|
||||
tx_lvl += step;
|
||||
}
|
||||
if (rx_lvl > 0) {
|
||||
while (rx_lvl > 0) {
|
||||
if (rx_lvl >= step) {
|
||||
while (rx_lvl >= step) {
|
||||
temp = readl(®s->rx_data);
|
||||
if (spi_slave->skip_preamble) {
|
||||
if (temp == SPI_PREAMBLE_END_BYTE) {
|
||||
@ -262,12 +301,15 @@ static int spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo,
|
||||
stopping = 0;
|
||||
}
|
||||
} else {
|
||||
if (rxp || stopping)
|
||||
*rxp++ = temp;
|
||||
in_bytes--;
|
||||
if (rxp || stopping) {
|
||||
*rxp = temp;
|
||||
rxp += step;
|
||||
}
|
||||
in_bytes -= step;
|
||||
}
|
||||
toread--;
|
||||
rx_lvl--;
|
||||
toread -= step;
|
||||
rx_lvl -= step;
|
||||
}
|
||||
} else if (!toread) {
|
||||
/*
|
||||
* We have run out of input data, but haven't read
|
||||
@ -279,7 +321,7 @@ static int spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo,
|
||||
out_bytes = in_bytes;
|
||||
toread = in_bytes;
|
||||
txp = NULL;
|
||||
spi_request_bytes(regs, toread);
|
||||
spi_request_bytes(regs, toread, step);
|
||||
}
|
||||
if (spi_slave->skip_preamble && get_timer(start) > 100) {
|
||||
printf("SPI timeout: in_bytes=%d, out_bytes=%d, ",
|
||||
@ -323,10 +365,14 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
|
||||
if ((flags & SPI_XFER_BEGIN))
|
||||
spi_cs_activate(slave);
|
||||
|
||||
/* Exynos SPI limits each transfer to 65535 bytes */
|
||||
/*
|
||||
* Exynos SPI limits each transfer to 65535 transfers. To keep
|
||||
* things simple, allow a maximum of 65532 bytes. We could allow
|
||||
* more in word mode, but the performance difference is small.
|
||||
*/
|
||||
bytelen = bitlen / 8;
|
||||
for (upto = 0; !ret && upto < bytelen; upto += todo) {
|
||||
todo = min(bytelen - upto, (1 << 16) - 1);
|
||||
todo = min(bytelen - upto, (1 << 16) - 4);
|
||||
ret = spi_rx_tx(spi_slave, todo, &din, &dout, flags);
|
||||
if (ret)
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user