aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/spi/spi-mpc512x-psc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi-mpc512x-psc.c')
-rw-r--r--drivers/spi/spi-mpc512x-psc.c341
1 files changed, 186 insertions, 155 deletions
diff --git a/drivers/spi/spi-mpc512x-psc.c b/drivers/spi/spi-mpc512x-psc.c
index dfddf336912d..29fce6af5145 100644
--- a/drivers/spi/spi-mpc512x-psc.c
+++ b/drivers/spi/spi-mpc512x-psc.c
@@ -21,7 +21,6 @@
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
-#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/io.h>
#include <linux/delay.h>
@@ -33,24 +32,15 @@
struct mpc512x_psc_spi {
void (*cs_control)(struct spi_device *spi, bool on);
- u32 sysclk;
/* driver internal data */
struct mpc52xx_psc __iomem *psc;
struct mpc512x_psc_fifo __iomem *fifo;
unsigned int irq;
u8 bits_per_word;
- u8 busy;
u32 mclk;
- u8 eofbyte;
- struct workqueue_struct *workqueue;
- struct work_struct work;
-
- struct list_head queue;
- spinlock_t lock; /* Message queue lock */
-
- struct completion done;
+ struct completion txisrdone;
};
/* controller state */
@@ -136,145 +126,223 @@ static int mpc512x_psc_spi_transfer_rxtx(struct spi_device *spi,
struct spi_transfer *t)
{
struct mpc512x_psc_spi *mps = spi_master_get_devdata(spi->master);
- struct mpc52xx_psc __iomem *psc = mps->psc;
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
- size_t len = t->len;
+ size_t tx_len = t->len;
+ size_t rx_len = t->len;
u8 *tx_buf = (u8 *)t->tx_buf;
u8 *rx_buf = (u8 *)t->rx_buf;
if (!tx_buf && !rx_buf && t->len)
return -EINVAL;
- /* Zero MR2 */
- in_8(&psc->mode);
- out_8(&psc->mode, 0x0);
-
- /* enable transmiter/receiver */
- out_8(&psc->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
-
- while (len) {
- int count;
- int i;
+ while (rx_len || tx_len) {
+ size_t txcount;
u8 data;
size_t fifosz;
- int rxcount;
+ size_t rxcount;
+ int rxtries;
/*
- * The number of bytes that can be sent at a time
- * depends on the fifo size.
+ * send the TX bytes in as large a chunk as possible
+ * but neither exceed the TX nor the RX FIFOs
*/
fifosz = MPC512x_PSC_FIFO_SZ(in_be32(&fifo->txsz));
- count = min(fifosz, len);
-
- for (i = count; i > 0; i--) {
- data = tx_buf ? *tx_buf++ : 0;
- if (len == EOFBYTE && t->cs_change)
- setbits32(&fifo->txcmd, MPC512x_PSC_FIFO_EOF);
- out_8(&fifo->txdata_8, data);
- len--;
+ txcount = min(fifosz, tx_len);
+ fifosz = MPC512x_PSC_FIFO_SZ(in_be32(&fifo->rxsz));
+ fifosz -= in_be32(&fifo->rxcnt) + 1;
+ txcount = min(fifosz, txcount);
+ if (txcount) {
+
+ /* fill the TX FIFO */
+ while (txcount-- > 0) {
+ data = tx_buf ? *tx_buf++ : 0;
+ if (tx_len == EOFBYTE && t->cs_change)
+ setbits32(&fifo->txcmd,
+ MPC512x_PSC_FIFO_EOF);
+ out_8(&fifo->txdata_8, data);
+ tx_len--;
+ }
+
+ /* have the ISR trigger when the TX FIFO is empty */
+ INIT_COMPLETION(mps->txisrdone);
+ out_be32(&fifo->txisr, MPC512x_PSC_FIFO_EMPTY);
+ out_be32(&fifo->tximr, MPC512x_PSC_FIFO_EMPTY);
+ wait_for_completion(&mps->txisrdone);
}
- INIT_COMPLETION(mps->done);
+ /*
+ * consume as much RX data as the FIFO holds, while we
+ * iterate over the transfer's TX data length
+ *
+ * only insist in draining all the remaining RX bytes
+ * when the TX bytes were exhausted (that's at the very
+ * end of this transfer, not when still iterating over
+ * the transfer's chunks)
+ */
+ rxtries = 50;
+ do {
+
+ /*
+ * grab whatever was in the FIFO when we started
+ * looking, don't bother fetching what was added to
+ * the FIFO while we read from it -- we'll return
+ * here eventually and prefer sending out remaining
+ * TX data
+ */
+ fifosz = in_be32(&fifo->rxcnt);
+ rxcount = min(fifosz, rx_len);
+ while (rxcount-- > 0) {
+ data = in_8(&fifo->rxdata_8);
+ if (rx_buf)
+ *rx_buf++ = data;
+ rx_len--;
+ }
- /* interrupt on tx fifo empty */
- out_be32(&fifo->txisr, MPC512x_PSC_FIFO_EMPTY);
- out_be32(&fifo->tximr, MPC512x_PSC_FIFO_EMPTY);
+ /*
+ * come back later if there still is TX data to send,
+ * bail out of the RX drain loop if all of the TX data
+ * was sent and all of the RX data was received (i.e.
+ * when the transmission has completed)
+ */
+ if (tx_len)
+ break;
+ if (!rx_len)
+ break;
- wait_for_completion(&mps->done);
+ /*
+ * TX data transmission has completed while RX data
+ * is still pending -- that's a transient situation
+ * which depends on wire speed and specific
+ * hardware implementation details (buffering) yet
+ * should resolve very quickly
+ *
+ * just yield for a moment to not hog the CPU for
+ * too long when running SPI at low speed
+ *
+ * the timeout range is rather arbitrary and tries
+ * to balance throughput against system load; the
+ * chosen values result in a minimal timeout of 50
+ * times 10us and thus work at speeds as low as
+ * some 20kbps, while the maximum timeout at the
+ * transfer's end could be 5ms _if_ nothing else
+ * ticks in the system _and_ RX data still wasn't
+ * received, which only occurs in situations that
+ * are exceptional; removing the unpredictability
+ * of the timeout either decreases throughput
+ * (longer timeouts), or puts more load on the
+ * system (fixed short timeouts) or requires the
+ * use of a timeout API instead of a counter and an
+ * unknown inner delay
+ */
+ usleep_range(10, 100);
+
+ } while (--rxtries > 0);
+ if (!tx_len && rx_len && !rxtries) {
+ /*
+ * not enough RX bytes even after several retries
+ * and the resulting rather long timeout?
+ */
+ rxcount = in_be32(&fifo->rxcnt);
+ dev_warn(&spi->dev,
+ "short xfer, missing %zd RX bytes, FIFO level %zd\n",
+ rx_len, rxcount);
+ }
- mdelay(1);
+ /*
+ * drain and drop RX data which "should not be there" in
+ * the first place, for undisturbed transmission this turns
+ * into a NOP (except for the FIFO level fetch)
+ */
+ if (!tx_len && !rx_len) {
+ while (in_be32(&fifo->rxcnt))
+ in_8(&fifo->rxdata_8);
+ }
- /* rx fifo should have count bytes in it */
- rxcount = in_be32(&fifo->rxcnt);
- if (rxcount != count)
- mdelay(1);
+ }
+ return 0;
+}
- rxcount = in_be32(&fifo->rxcnt);
- if (rxcount != count) {
- dev_warn(&spi->dev, "expected %d bytes in rx fifo "
- "but got %d\n", count, rxcount);
+static int mpc512x_psc_spi_msg_xfer(struct spi_master *master,
+ struct spi_message *m)
+{
+ struct spi_device *spi;
+ unsigned cs_change;
+ int status;
+ struct spi_transfer *t;
+
+ spi = m->spi;
+ cs_change = 1;
+ status = 0;
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+ if (t->bits_per_word || t->speed_hz) {
+ status = mpc512x_psc_spi_transfer_setup(spi, t);
+ if (status < 0)
+ break;
}
- rxcount = min(rxcount, count);
- for (i = rxcount; i > 0; i--) {
- data = in_8(&fifo->rxdata_8);
- if (rx_buf)
- *rx_buf++ = data;
- }
- while (in_be32(&fifo->rxcnt)) {
- in_8(&fifo->rxdata_8);
- }
+ if (cs_change)
+ mpc512x_psc_spi_activate_cs(spi);
+ cs_change = t->cs_change;
+
+ status = mpc512x_psc_spi_transfer_rxtx(spi, t);
+ if (status)
+ break;
+ m->actual_length += t->len;
+
+ if (t->delay_usecs)
+ udelay(t->delay_usecs);
+
+ if (cs_change)
+ mpc512x_psc_spi_deactivate_cs(spi);
}
- /* disable transmiter/receiver and fifo interrupt */
- out_8(&psc->command, MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
- out_be32(&fifo->tximr, 0);
- return 0;
+
+ m->status = status;
+ m->complete(m->context);
+
+ if (status || !cs_change)
+ mpc512x_psc_spi_deactivate_cs(spi);
+
+ mpc512x_psc_spi_transfer_setup(spi, NULL);
+
+ spi_finalize_current_message(master);
+ return status;
}
-static void mpc512x_psc_spi_work(struct work_struct *work)
+static int mpc512x_psc_spi_prep_xfer_hw(struct spi_master *master)
{
- struct mpc512x_psc_spi *mps = container_of(work,
- struct mpc512x_psc_spi,
- work);
-
- spin_lock_irq(&mps->lock);
- mps->busy = 1;
- while (!list_empty(&mps->queue)) {
- struct spi_message *m;
- struct spi_device *spi;
- struct spi_transfer *t = NULL;
- unsigned cs_change;
- int status;
-
- m = container_of(mps->queue.next, struct spi_message, queue);
- list_del_init(&m->queue);
- spin_unlock_irq(&mps->lock);
-
- spi = m->spi;
- cs_change = 1;
- status = 0;
- list_for_each_entry(t, &m->transfers, transfer_list) {
- if (t->bits_per_word || t->speed_hz) {
- status = mpc512x_psc_spi_transfer_setup(spi, t);
- if (status < 0)
- break;
- }
+ struct mpc512x_psc_spi *mps = spi_master_get_devdata(master);
+ struct mpc52xx_psc __iomem *psc = mps->psc;
- if (cs_change)
- mpc512x_psc_spi_activate_cs(spi);
- cs_change = t->cs_change;
+ dev_dbg(&master->dev, "%s()\n", __func__);
- status = mpc512x_psc_spi_transfer_rxtx(spi, t);
- if (status)
- break;
- m->actual_length += t->len;
+ /* Zero MR2 */
+ in_8(&psc->mode);
+ out_8(&psc->mode, 0x0);
- if (t->delay_usecs)
- udelay(t->delay_usecs);
+ /* enable transmitter/receiver */
+ out_8(&psc->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
- if (cs_change)
- mpc512x_psc_spi_deactivate_cs(spi);
- }
+ return 0;
+}
- m->status = status;
- m->complete(m->context);
+static int mpc512x_psc_spi_unprep_xfer_hw(struct spi_master *master)
+{
+ struct mpc512x_psc_spi *mps = spi_master_get_devdata(master);
+ struct mpc52xx_psc __iomem *psc = mps->psc;
+ struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
- if (status || !cs_change)
- mpc512x_psc_spi_deactivate_cs(spi);
+ dev_dbg(&master->dev, "%s()\n", __func__);
- mpc512x_psc_spi_transfer_setup(spi, NULL);
+ /* disable transmitter/receiver and fifo interrupt */
+ out_8(&psc->command, MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
+ out_be32(&fifo->tximr, 0);
- spin_lock_irq(&mps->lock);
- }
- mps->busy = 0;
- spin_unlock_irq(&mps->lock);
+ return 0;
}
static int mpc512x_psc_spi_setup(struct spi_device *spi)
{
- struct mpc512x_psc_spi *mps = spi_master_get_devdata(spi->master);
struct mpc512x_psc_spi_cs *cs = spi->controller_state;
- unsigned long flags;
int ret;
if (spi->bits_per_word % 8)
@@ -303,28 +371,6 @@ static int mpc512x_psc_spi_setup(struct spi_device *spi)
cs->bits_per_word = spi->bits_per_word;
cs->speed_hz = spi->max_speed_hz;
- spin_lock_irqsave(&mps->lock, flags);
- if (!mps->busy)
- mpc512x_psc_spi_deactivate_cs(spi);
- spin_unlock_irqrestore(&mps->lock, flags);
-
- return 0;
-}
-
-static int mpc512x_psc_spi_transfer(struct spi_device *spi,
- struct spi_message *m)
-{
- struct mpc512x_psc_spi *mps = spi_master_get_devdata(spi->master);
- unsigned long flags;
-
- m->actual_length = 0;
- m->status = -EINPROGRESS;
-
- spin_lock_irqsave(&mps->lock, flags);
- list_add_tail(&m->queue, &mps->queue);
- queue_work(mps->workqueue, &mps->work);
- spin_unlock_irqrestore(&mps->lock, flags);
-
return 0;
}
@@ -407,12 +453,12 @@ static irqreturn_t mpc512x_psc_spi_isr(int irq, void *dev_id)
struct mpc512x_psc_spi *mps = (struct mpc512x_psc_spi *)dev_id;
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
- /* clear interrupt and wake up the work queue */
+ /* clear interrupt and wake up the rx/tx routine */
if (in_be32(&fifo->txisr) &
in_be32(&fifo->tximr) & MPC512x_PSC_FIFO_EMPTY) {
out_be32(&fifo->txisr, MPC512x_PSC_FIFO_EMPTY);
out_be32(&fifo->tximr, 0);
- complete(&mps->done);
+ complete(&mps->txisrdone);
return IRQ_HANDLED;
}
return IRQ_NONE;
@@ -444,18 +490,18 @@ static int mpc512x_psc_spi_do_probe(struct device *dev, u32 regaddr,
if (pdata == NULL) {
mps->cs_control = mpc512x_spi_cs_control;
- mps->sysclk = 0;
master->bus_num = bus_num;
} else {
mps->cs_control = pdata->cs_control;
- mps->sysclk = pdata->sysclk;
master->bus_num = pdata->bus_num;
master->num_chipselect = pdata->max_chipselect;
}
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
master->setup = mpc512x_psc_spi_setup;
- master->transfer = mpc512x_psc_spi_transfer;
+ master->prepare_transfer_hardware = mpc512x_psc_spi_prep_xfer_hw;
+ master->transfer_one_message = mpc512x_psc_spi_msg_xfer;
+ master->unprepare_transfer_hardware = mpc512x_psc_spi_unprep_xfer_hw;
master->cleanup = mpc512x_psc_spi_cleanup;
master->dev.of_node = dev->of_node;
@@ -473,31 +519,18 @@ static int mpc512x_psc_spi_do_probe(struct device *dev, u32 regaddr,
"mpc512x-psc-spi", mps);
if (ret)
goto free_master;
+ init_completion(&mps->txisrdone);
ret = mpc512x_psc_spi_port_config(master, mps);
if (ret < 0)
goto free_irq;
- spin_lock_init(&mps->lock);
- init_completion(&mps->done);
- INIT_WORK(&mps->work, mpc512x_psc_spi_work);
- INIT_LIST_HEAD(&mps->queue);
-
- mps->workqueue =
- create_singlethread_workqueue(dev_name(master->dev.parent));
- if (mps->workqueue == NULL) {
- ret = -EBUSY;
- goto free_irq;
- }
-
ret = spi_register_master(master);
if (ret < 0)
- goto unreg_master;
+ goto free_irq;
return ret;
-unreg_master:
- destroy_workqueue(mps->workqueue);
free_irq:
free_irq(mps->irq, mps);
free_master:
@@ -513,8 +546,6 @@ static int mpc512x_psc_spi_do_remove(struct device *dev)
struct spi_master *master = spi_master_get(dev_get_drvdata(dev));
struct mpc512x_psc_spi *mps = spi_master_get_devdata(master);
- flush_workqueue(mps->workqueue);
- destroy_workqueue(mps->workqueue);
spi_unregister_master(master);
free_irq(mps->irq, mps);
if (mps->psc)