diff options
Diffstat (limited to 'target/linux/brcm63xx/patches-3.7/107-spi-bcm63xx-fix-multi-transfer-messages.patch')
-rw-r--r-- | target/linux/brcm63xx/patches-3.7/107-spi-bcm63xx-fix-multi-transfer-messages.patch | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/target/linux/brcm63xx/patches-3.7/107-spi-bcm63xx-fix-multi-transfer-messages.patch b/target/linux/brcm63xx/patches-3.7/107-spi-bcm63xx-fix-multi-transfer-messages.patch new file mode 100644 index 0000000000..2da5044492 --- /dev/null +++ b/target/linux/brcm63xx/patches-3.7/107-spi-bcm63xx-fix-multi-transfer-messages.patch @@ -0,0 +1,279 @@ +From 0f2ae1e1282ff64f74a5e36f7da874f94911225e Mon Sep 17 00:00:00 2001 +From: Jonas Gorski <jonas.gorski@gmail.com> +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 <jonas.gorski@gmail.com> +--- +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) { +@@ -241,14 +247,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) +@@ -273,42 +306,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); |