diff options
Diffstat (limited to 'target/linux/brcm63xx/patches-3.3/013-spi-bcm63xx-convert-to-the-pump-message-infrastructu.patch')
-rw-r--r-- | target/linux/brcm63xx/patches-3.3/013-spi-bcm63xx-convert-to-the-pump-message-infrastructu.patch | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/target/linux/brcm63xx/patches-3.3/013-spi-bcm63xx-convert-to-the-pump-message-infrastructu.patch b/target/linux/brcm63xx/patches-3.3/013-spi-bcm63xx-convert-to-the-pump-message-infrastructu.patch new file mode 100644 index 0000000000..d48225bf08 --- /dev/null +++ b/target/linux/brcm63xx/patches-3.3/013-spi-bcm63xx-convert-to-the-pump-message-infrastructu.patch @@ -0,0 +1,289 @@ +From cde4384e1037c15e5dd04c68d19c75798b6281dd Mon Sep 17 00:00:00 2001 +From: Florian Fainelli <florian@openwrt.org> +Date: Fri, 20 Apr 2012 15:37:33 +0200 +Subject: [PATCH] spi/bcm63xx: convert to the pump message infrastructure + +This patch converts the bcm63xx SPI driver to use the SPI infrastructure +pump message queue. Since we were previously sleeping in the SPI +driver's transfer() function (which is not allowed) this is now fixed as well. + +To complete that conversion a certain number of changes have been made: +- the transfer len is split into multiple hardware transfers in case its + size is bigger than the hardware FIFO size +- the FIFO refill is no longer done in the interrupt context, which was a + bad idea leading to quick interrupt handler re-entrancy + +Tested-by: Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> +Signed-off-by: Florian Fainelli <florian@openwrt.org> +Signed-off-by: Grant Likely <grant.likely@secretlab.ca> +--- + drivers/spi/spi-bcm63xx.c | 149 +++++++++++++++++++++++++++------------------ + 1 file changed, 89 insertions(+), 60 deletions(-) + +--- a/drivers/spi/spi-bcm63xx.c ++++ b/drivers/spi/spi-bcm63xx.c +@@ -1,7 +1,7 @@ + /* + * Broadcom BCM63xx SPI controller support + * +- * Copyright (C) 2009-2011 Florian Fainelli <florian@openwrt.org> ++ * Copyright (C) 2009-2012 Florian Fainelli <florian@openwrt.org> + * Copyright (C) 2010 Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> + * + * This program is free software; you can redistribute it and/or +@@ -30,6 +30,8 @@ + #include <linux/spi/spi.h> + #include <linux/completion.h> + #include <linux/err.h> ++#include <linux/workqueue.h> ++#include <linux/pm_runtime.h> + + #include <bcm63xx_dev_spi.h> + +@@ -96,17 +98,12 @@ static const unsigned bcm63xx_spi_freq_t + { 391000, SPI_CLK_0_391MHZ } + }; + +-static int bcm63xx_spi_setup_transfer(struct spi_device *spi, +- struct spi_transfer *t) ++static int bcm63xx_spi_check_transfer(struct spi_device *spi, ++ struct spi_transfer *t) + { +- struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); + u8 bits_per_word; +- u8 clk_cfg, reg; +- u32 hz; +- int i; + + bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word; +- hz = (t) ? t->speed_hz : spi->max_speed_hz; + if (bits_per_word != 8) { + dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n", + __func__, bits_per_word); +@@ -119,6 +116,19 @@ static int bcm63xx_spi_setup_transfer(st + return -EINVAL; + } + ++ return 0; ++} ++ ++static void bcm63xx_spi_setup_transfer(struct spi_device *spi, ++ struct spi_transfer *t) ++{ ++ struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); ++ u32 hz; ++ u8 clk_cfg, reg; ++ int i; ++ ++ hz = (t) ? t->speed_hz : spi->max_speed_hz; ++ + /* Find the closest clock configuration */ + for (i = 0; i < SPI_CLK_MASK; i++) { + if (hz <= bcm63xx_spi_freq_table[i][0]) { +@@ -139,8 +149,6 @@ static int bcm63xx_spi_setup_transfer(st + bcm_spi_writeb(bs, reg, SPI_CLK_CFG); + dev_dbg(&spi->dev, "Setting clock register to %02x (hz %d)\n", + clk_cfg, hz); +- +- return 0; + } + + /* the spi->mode bits understood by this driver: */ +@@ -165,7 +173,7 @@ static int bcm63xx_spi_setup(struct spi_ + return -EINVAL; + } + +- ret = bcm63xx_spi_setup_transfer(spi, NULL); ++ ret = bcm63xx_spi_check_transfer(spi, NULL); + if (ret < 0) { + dev_err(&spi->dev, "setup: unsupported mode bits %x\n", + spi->mode & ~MODEBITS); +@@ -190,28 +198,29 @@ static void bcm63xx_spi_fill_tx_fifo(str + bs->remaining_bytes -= size; + } + +-static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) ++static unsigned int bcm63xx_txrx_bufs(struct spi_device *spi, ++ struct spi_transfer *t) + { + struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); + u16 msg_ctl; + u16 cmd; + ++ /* 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); + + /* Transmitter is inhibited */ + bs->tx_ptr = t->tx_buf; + bs->rx_ptr = t->rx_buf; +- init_completion(&bs->done); + + if (t->tx_buf) { + bs->remaining_bytes = t->len; + bcm63xx_spi_fill_tx_fifo(bs); + } + +- /* Enable the command done interrupt which +- * we use to determine completion of a command */ +- bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); ++ init_completion(&bs->done); + + /* Fill in the Message control register */ + msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT); +@@ -230,33 +239,76 @@ static int bcm63xx_txrx_bufs(struct spi_ + cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); + cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); + bcm_spi_writew(bs, cmd, SPI_CMD); +- wait_for_completion(&bs->done); + +- /* Disable the CMD_DONE interrupt */ +- bcm_spi_writeb(bs, 0, SPI_INT_MASK); ++ /* Enable the CMD_DONE interrupt */ ++ bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); + + return t->len - bs->remaining_bytes; + } + +-static int bcm63xx_transfer(struct spi_device *spi, struct spi_message *m) ++static int bcm63xx_spi_prepare_transfer(struct spi_master *master) + { +- struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); +- struct spi_transfer *t; +- int ret = 0; ++ struct bcm63xx_spi *bs = spi_master_get_devdata(master); + +- if (unlikely(list_empty(&m->transfers))) +- return -EINVAL; ++ pm_runtime_get_sync(&bs->pdev->dev); + +- if (bs->stopping) +- return -ESHUTDOWN; ++ return 0; ++} ++ ++static int bcm63xx_spi_unprepare_transfer(struct spi_master *master) ++{ ++ struct bcm63xx_spi *bs = spi_master_get_devdata(master); ++ ++ pm_runtime_put(&bs->pdev->dev); ++ ++ return 0; ++} ++ ++static int bcm63xx_spi_transfer_one(struct spi_master *master, ++ struct spi_message *m) ++{ ++ struct bcm63xx_spi *bs = spi_master_get_devdata(master); ++ struct spi_transfer *t; ++ struct spi_device *spi = m->spi; ++ int status = 0; ++ unsigned int timeout = 0; + + list_for_each_entry(t, &m->transfers, transfer_list) { +- ret += bcm63xx_txrx_bufs(spi, t); +- } ++ unsigned int len = t->len; ++ u8 rx_tail; + +- m->complete(m->context); ++ status = bcm63xx_spi_check_transfer(spi, t); ++ if (status < 0) ++ goto exit; ++ ++ /* configure adapter for a new transfer */ ++ bcm63xx_spi_setup_transfer(spi, 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; ++ } ++ ++ /* read out all data */ ++ rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); ++ ++ /* Read out all the data */ ++ if (rx_tail) ++ memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail); ++ } ++ ++ m->actual_length += t->len; ++ } ++exit: ++ m->status = status; ++ spi_finalize_current_message(master); + +- return ret; ++ return 0; + } + + /* This driver supports single master mode only. Hence +@@ -267,39 +319,15 @@ static irqreturn_t bcm63xx_spi_interrupt + struct spi_master *master = (struct spi_master *)dev_id; + struct bcm63xx_spi *bs = spi_master_get_devdata(master); + u8 intr; +- u16 cmd; + + /* Read interupts and clear them immediately */ + intr = bcm_spi_readb(bs, SPI_INT_STATUS); + bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); + bcm_spi_writeb(bs, 0, SPI_INT_MASK); + +- /* A tansfer completed */ +- if (intr & SPI_INTR_CMD_DONE) { +- u8 rx_tail; +- +- rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); +- +- /* Read out all the data */ +- if (rx_tail) +- memcpy_fromio(bs->rx_ptr, bs->rx_io, rx_tail); +- +- /* See if there is more data to send */ +- if (bs->remaining_bytes > 0) { +- bcm63xx_spi_fill_tx_fifo(bs); +- +- /* Start the transfer */ +- bcm_spi_writew(bs, SPI_HD_W << SPI_MSG_TYPE_SHIFT, +- SPI_MSG_CTL); +- cmd = bcm_spi_readw(bs, SPI_CMD); +- cmd |= SPI_CMD_START_IMMEDIATE; +- cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); +- bcm_spi_writeb(bs, SPI_INTR_CMD_DONE, SPI_INT_MASK); +- bcm_spi_writew(bs, cmd, SPI_CMD); +- } else { +- complete(&bs->done); +- } +- } ++ /* A transfer completed */ ++ if (intr & SPI_INTR_CMD_DONE) ++ complete(&bs->done); + + return IRQ_HANDLED; + } +@@ -345,7 +373,6 @@ static int __devinit bcm63xx_spi_probe(s + } + + bs = spi_master_get_devdata(master); +- init_completion(&bs->done); + + platform_set_drvdata(pdev, master); + bs->pdev = pdev; +@@ -379,7 +406,9 @@ static int __devinit bcm63xx_spi_probe(s + master->bus_num = pdata->bus_num; + master->num_chipselect = pdata->num_chipselect; + master->setup = bcm63xx_spi_setup; +- master->transfer = bcm63xx_transfer; ++ master->prepare_transfer_hardware = bcm63xx_spi_prepare_transfer; ++ master->unprepare_transfer_hardware = bcm63xx_spi_unprepare_transfer; ++ master->transfer_one_message = bcm63xx_spi_transfer_one; + bs->speed_hz = pdata->speed_hz; + bs->stopping = 0; + bs->tx_io = (u8 *)(bs->regs + bcm63xx_spireg(SPI_MSG_DATA)); |