From 0f2ae1e1282ff64f74a5e36f7da874f94911225e Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Wed, 14 Nov 2012 22:22:33 +0100 Subject: [PATCH] spi/bcm63xx: fix multi transfer messages The BCM63XX SPI controller does not support keeping CS asserted after sending its buffer. This breaks common usages like spi_write_then_read, where it is expected to be kept active during the whole transfers. Work around this by combining the transfers into one if the buffer allows. For spi_write_then_read, use the prepend byte feature to write to "prepend" the write if it is less than 15 bytes, allowing the whole fifo size for the read. Signed-off-by: Jonas Gorski --- Tested on a SPI conntected switch which required keeping CS active between the register read command and reading the register contents. Based on Mark's spi/next. Not sure if this is stable material, as it's quite invasive. drivers/spi/spi-bcm63xx.c | 172 ++++++++++++++++++++++++++++++--------------- 1 file changed, 117 insertions(+), 55 deletions(-) --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -38,6 +38,8 @@ #define PFX KBUILD_MODNAME #define DRV_VER "0.1.2" +#define BCM63XX_SPI_MAX_PREPEND 15 + struct bcm63xx_spi { struct completion done; @@ -50,16 +52,10 @@ struct bcm63xx_spi { unsigned int msg_type_shift; unsigned int msg_ctl_width; - /* Data buffers */ - const unsigned char *tx_ptr; - unsigned char *rx_ptr; - /* data iomem */ u8 __iomem *tx_io; const u8 __iomem *rx_io; - int remaining_bytes; - struct clk *clk; struct platform_device *pdev; }; @@ -184,50 +180,60 @@ static int bcm63xx_spi_setup(struct spi_ return 0; } -/* Fill the TX FIFO with as many bytes as possible */ -static void bcm63xx_spi_fill_tx_fifo(struct bcm63xx_spi *bs) -{ - u8 size; - - /* Fill the Tx FIFO with as many bytes as possible */ - size = bs->remaining_bytes < bs->fifo_size ? bs->remaining_bytes : - bs->fifo_size; - memcpy_toio(bs->tx_io, bs->tx_ptr, size); - bs->remaining_bytes -= size; -} - static unsigned int bcm63xx_txrx_bufs(struct spi_device *spi, - struct spi_transfer *t) + struct spi_transfer *first, + unsigned int n_transfers) { struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); u16 msg_ctl; u16 cmd; + unsigned int i, timeout, total_len = 0, prepend_len = 0, len = 0; + struct spi_transfer *t = first; + u8 rx_tail; + bool do_rx = false; + bool do_tx = false; /* Disable the CMD_DONE interrupt */ bcm_spi_writeb(bs, 0, SPI_INT_MASK); - dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", - t->tx_buf, t->rx_buf, t->len); + if (n_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND) + prepend_len = t->len; + + /* prepare the buffer */ + for (i = 0; i < n_transfers; i++) { + if (t->tx_buf) { + do_tx = true; + memcpy_toio(bs->tx_io + total_len, t->tx_buf, t->len); + + /* don't prepend more than one tx */ + if (t != first) + prepend_len = 0; + } + + if (t->rx_buf) { + do_rx = true; + if (t == first) + prepend_len = 0; + } - /* Transmitter is inhibited */ - bs->tx_ptr = t->tx_buf; - bs->rx_ptr = t->rx_buf; - - if (t->tx_buf) { - bs->remaining_bytes = t->len; - bcm63xx_spi_fill_tx_fifo(bs); + total_len += t->len; + + t = list_entry(t->transfer_list.next, struct spi_transfer, + transfer_list); } + len = total_len - prepend_len; + init_completion(&bs->done); /* Fill in the Message control register */ - msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT); + msg_ctl = (len << SPI_BYTE_CNT_SHIFT); - if (t->rx_buf && t->tx_buf) + if (do_rx && do_tx && prepend_len == 0) msg_ctl |= (SPI_FD_RW << bs->msg_type_shift); - else if (t->rx_buf) + else if (do_rx) msg_ctl |= (SPI_HD_R << bs->msg_type_shift); - else if (t->tx_buf) + else if (do_tx) msg_ctl |= (SPI_HD_W << bs->msg_type_shift); switch (bs->msg_ctl_width) { @@ -245,14 +251,41 @@ static unsigned int bcm63xx_txrx_bufs(st /* Issue the transfer */ cmd = SPI_CMD_START_IMMEDIATE; - cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); + cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); bcm_spi_writew(bs, cmd, SPI_CMD); /* Enable the CMD_DONE interrupt */ bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); - return t->len - bs->remaining_bytes; + timeout = wait_for_completion_timeout(&bs->done, HZ); + if (!timeout) + return -ETIMEDOUT; + + /* read out all data */ + rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); + + if (do_rx && rx_tail != len) + return -EINVAL; + + if (!rx_tail) + return total_len; + + len = 0; + t = first; + /* Read out all the data */ + for (i = 0; i < n_transfers; i++) { + if (t->rx_buf) + memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len); + + if (t != first || prepend_len == 0) + len += t->len; + + t = list_entry(t->transfer_list.next, struct spi_transfer, + transfer_list); + } + + return total_len; } static int bcm63xx_spi_prepare_transfer(struct spi_master *master) @@ -277,42 +310,71 @@ static int bcm63xx_spi_transfer_one(stru struct spi_message *m) { struct bcm63xx_spi *bs = spi_master_get_devdata(master); - struct spi_transfer *t; + struct spi_transfer *t, *first = NULL; struct spi_device *spi = m->spi; int status = 0; - unsigned int timeout = 0; + unsigned int n_transfers = 0, total_len = 0; + bool can_use_prepend = false; + /* + * This SPI controller does not support keeping CS active after a + * transfer, so we need to combine the transfers into one until we may + * deassert CS. + */ list_for_each_entry(t, &m->transfers, transfer_list) { - unsigned int len = t->len; - u8 rx_tail; - status = bcm63xx_spi_check_transfer(spi, t); if (status < 0) goto exit; - /* configure adapter for a new transfer */ - bcm63xx_spi_setup_transfer(spi, t); + if (!first) + first = t; - while (len) { - /* send the data */ - len -= bcm63xx_txrx_bufs(spi, t); - - timeout = wait_for_completion_timeout(&bs->done, HZ); - if (!timeout) { - status = -ETIMEDOUT; - goto exit; - } + n_transfers++; + total_len += t->len; + + if (n_transfers == 2 && !first->rx_buf && !t->tx_buf && + first->len <= BCM63XX_SPI_MAX_PREPEND) + can_use_prepend = true; + else if (can_use_prepend && t->tx_buf) + can_use_prepend = false; + + if ((can_use_prepend && + total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) || + (!can_use_prepend && total_len > bs->fifo_size)) { + status = -EINVAL; + goto exit; + } - /* read out all data */ - rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); + /* all transfers have to be made at the same speed */ + if (t->speed_hz != first->speed_hz) { + status = -EINVAL; + goto exit; + } - /* Read out all the data */ - if (rx_tail) - memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail); + /* CS will be deasserted directly after the transfer */ + if (t->delay_usecs) { + status = -EINVAL; + goto exit; } - m->actual_length += t->len; + if (t->cs_change || + list_is_last(&t->transfer_list, &m->transfers)) { + /* configure adapter for a new transfer */ + bcm63xx_spi_setup_transfer(spi, first); + + status = bcm63xx_txrx_bufs(spi, first, n_transfers); + if (status < 0) + goto exit; + + m->actual_length += status; + first = NULL; + status = 0; + n_transfers = 0; + total_len = 0; + can_use_prepend = false; + } } + exit: m->status = status; spi_finalize_current_message(master);