diff options
-rw-r--r-- | drivers/spi/spi-omap2-mcspi.c | 147 |
1 files changed, 130 insertions, 17 deletions
diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 5a93a0df551..6802806d1f1 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -38,12 +38,15 @@ #include <linux/pm_runtime.h> #include <linux/of.h> #include <linux/of_device.h> +#include <linux/gcd.h> #include <linux/spi/spi.h> #include <linux/platform_data/spi-omap2-mcspi.h> #define OMAP2_MCSPI_MAX_FREQ 48000000 +#define OMAP2_MCSPI_MAX_FIFODEPTH 64 +#define OMAP2_MCSPI_MAX_FIFOWCNT 0xFFFF #define SPI_AUTOSUSPEND_TIMEOUT 2000 #define OMAP2_MCSPI_REVISION 0x00 @@ -53,6 +56,7 @@ #define OMAP2_MCSPI_WAKEUPENABLE 0x20 #define OMAP2_MCSPI_SYST 0x24 #define OMAP2_MCSPI_MODULCTRL 0x28 +#define OMAP2_MCSPI_XFERLEVEL 0x7c /* per-channel banks, 0x14 bytes each, first is: */ #define OMAP2_MCSPI_CHCONF0 0x2c @@ -62,6 +66,7 @@ #define OMAP2_MCSPI_RX0 0x3c /* per-register bitmasks: */ +#define OMAP2_MCSPI_IRQSTATUS_EOW BIT(17) #define OMAP2_MCSPI_MODULCTRL_SINGLE BIT(0) #define OMAP2_MCSPI_MODULCTRL_MS BIT(2) @@ -82,10 +87,13 @@ #define OMAP2_MCSPI_CHCONF_IS BIT(18) #define OMAP2_MCSPI_CHCONF_TURBO BIT(19) #define OMAP2_MCSPI_CHCONF_FORCE BIT(20) +#define OMAP2_MCSPI_CHCONF_FFET BIT(27) +#define OMAP2_MCSPI_CHCONF_FFER BIT(28) #define OMAP2_MCSPI_CHSTAT_RXS BIT(0) #define OMAP2_MCSPI_CHSTAT_TXS BIT(1) #define OMAP2_MCSPI_CHSTAT_EOT BIT(2) +#define OMAP2_MCSPI_CHSTAT_TXFFE BIT(3) #define OMAP2_MCSPI_CHCTRL_EN BIT(0) @@ -128,6 +136,7 @@ struct omap2_mcspi { struct omap2_mcspi_dma *dma_channels; struct device *dev; struct omap2_mcspi_regs ctx; + int fifo_depth; unsigned int pin_dir:1; }; @@ -257,6 +266,58 @@ static void omap2_mcspi_set_master_mode(struct spi_master *master) ctx->modulctrl = l; } +static void omap2_mcspi_set_fifo(const struct spi_device *spi, + struct spi_transfer *t, int enable) +{ + struct spi_master *master = spi->master; + struct omap2_mcspi_cs *cs = spi->controller_state; + struct omap2_mcspi *mcspi; + unsigned int wcnt; + int fifo_depth, bytes_per_word; + u32 chconf, xferlevel; + + mcspi = spi_master_get_devdata(master); + + chconf = mcspi_cached_chconf0(spi); + if (enable) { + bytes_per_word = mcspi_bytes_per_word(cs->word_len); + if (t->len % bytes_per_word != 0) + goto disable_fifo; + + fifo_depth = gcd(t->len, OMAP2_MCSPI_MAX_FIFODEPTH); + if (fifo_depth < 2 || fifo_depth % bytes_per_word != 0) + goto disable_fifo; + + wcnt = t->len / bytes_per_word; + if (wcnt > OMAP2_MCSPI_MAX_FIFOWCNT) + goto disable_fifo; + + xferlevel = wcnt << 16; + if (t->rx_buf != NULL) { + chconf |= OMAP2_MCSPI_CHCONF_FFER; + xferlevel |= (fifo_depth - 1) << 8; + } else { + chconf |= OMAP2_MCSPI_CHCONF_FFET; + xferlevel |= fifo_depth - 1; + } + + mcspi_write_reg(master, OMAP2_MCSPI_XFERLEVEL, xferlevel); + mcspi_write_chconf0(spi, chconf); + mcspi->fifo_depth = fifo_depth; + + return; + } + +disable_fifo: + if (t->rx_buf != NULL) + chconf &= ~OMAP2_MCSPI_CHCONF_FFER; + else + chconf &= ~OMAP2_MCSPI_CHCONF_FFET; + + mcspi_write_chconf0(spi, chconf); + mcspi->fifo_depth = 0; +} + static void omap2_mcspi_restore_ctx(struct omap2_mcspi *mcspi) { struct spi_master *spi_cntrl = mcspi->master; @@ -373,7 +434,7 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, { struct omap2_mcspi *mcspi; struct omap2_mcspi_dma *mcspi_dma; - unsigned int count; + unsigned int count, dma_count; u32 l; int elements = 0; int word_len, element_count; @@ -381,6 +442,11 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, mcspi = spi_master_get_devdata(spi->master); mcspi_dma = &mcspi->dma_channels[spi->chip_select]; count = xfer->len; + dma_count = xfer->len; + + if (mcspi->fifo_depth == 0) + dma_count -= es; + word_len = cs->word_len; l = mcspi_cached_chconf0(spi); @@ -394,16 +460,15 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, if (mcspi_dma->dma_rx) { struct dma_async_tx_descriptor *tx; struct scatterlist sg; - size_t len = xfer->len - es; dmaengine_slave_config(mcspi_dma->dma_rx, &cfg); - if (l & OMAP2_MCSPI_CHCONF_TURBO) - len -= es; + if ((l & OMAP2_MCSPI_CHCONF_TURBO) && mcspi->fifo_depth == 0) + dma_count -= es; sg_init_table(&sg, 1); sg_dma_address(&sg) = xfer->rx_dma; - sg_dma_len(&sg) = len; + sg_dma_len(&sg) = dma_count; tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, &sg, 1, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | @@ -423,6 +488,10 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, wait_for_completion(&mcspi_dma->dma_rx_completion); dma_unmap_single(mcspi->dev, xfer->rx_dma, count, DMA_FROM_DEVICE); + + if (mcspi->fifo_depth > 0) + return count; + omap2_mcspi_set_enable(spi, 0); elements = element_count - 1; @@ -481,7 +550,10 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) struct dma_slave_config cfg; enum dma_slave_buswidth width; unsigned es; + u32 burst; void __iomem *chstat_reg; + void __iomem *irqstat_reg; + int wait_res; mcspi = spi_master_get_devdata(spi->master); mcspi_dma = &mcspi->dma_channels[spi->chip_select]; @@ -499,19 +571,27 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) es = 4; } + count = xfer->len; + burst = 1; + + if (mcspi->fifo_depth > 0) { + if (count > mcspi->fifo_depth) + burst = mcspi->fifo_depth / es; + else + burst = count / es; + } + memset(&cfg, 0, sizeof(cfg)); cfg.src_addr = cs->phys + OMAP2_MCSPI_RX0; cfg.dst_addr = cs->phys + OMAP2_MCSPI_TX0; cfg.src_addr_width = width; cfg.dst_addr_width = width; - cfg.src_maxburst = 1; - cfg.dst_maxburst = 1; + cfg.src_maxburst = burst; + cfg.dst_maxburst = burst; rx = xfer->rx_buf; tx = xfer->tx_buf; - count = xfer->len; - if (tx != NULL) omap2_mcspi_tx_dma(spi, xfer, cfg); @@ -519,18 +599,38 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) count = omap2_mcspi_rx_dma(spi, xfer, cfg, es); if (tx != NULL) { - chstat_reg = cs->base + OMAP2_MCSPI_CHSTAT0; wait_for_completion(&mcspi_dma->dma_tx_completion); dma_unmap_single(mcspi->dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE); + if (mcspi->fifo_depth > 0) { + irqstat_reg = mcspi->base + OMAP2_MCSPI_IRQSTATUS; + + if (mcspi_wait_for_reg_bit(irqstat_reg, + OMAP2_MCSPI_IRQSTATUS_EOW) < 0) + dev_err(&spi->dev, "EOW timed out\n"); + + mcspi_write_reg(mcspi->master, OMAP2_MCSPI_IRQSTATUS, + OMAP2_MCSPI_IRQSTATUS_EOW); + } + /* for TX_ONLY mode, be sure all words have shifted out */ if (rx == NULL) { - if (mcspi_wait_for_reg_bit(chstat_reg, - OMAP2_MCSPI_CHSTAT_TXS) < 0) - dev_err(&spi->dev, "TXS timed out\n"); - else if (mcspi_wait_for_reg_bit(chstat_reg, - OMAP2_MCSPI_CHSTAT_EOT) < 0) + chstat_reg = cs->base + OMAP2_MCSPI_CHSTAT0; + if (mcspi->fifo_depth > 0) { + wait_res = mcspi_wait_for_reg_bit(chstat_reg, + OMAP2_MCSPI_CHSTAT_TXFFE); + if (wait_res < 0) + dev_err(&spi->dev, "TXFFE timed out\n"); + } else { + wait_res = mcspi_wait_for_reg_bit(chstat_reg, + OMAP2_MCSPI_CHSTAT_TXS); + if (wait_res < 0) + dev_err(&spi->dev, "TXS timed out\n"); + } + if (wait_res >= 0 && + (mcspi_wait_for_reg_bit(chstat_reg, + OMAP2_MCSPI_CHSTAT_EOT) < 0)) dev_err(&spi->dev, "EOT timed out\n"); } } @@ -957,7 +1057,7 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) cs = spi->controller_state; cd = spi->controller_data; - omap2_mcspi_set_enable(spi, 1); + omap2_mcspi_set_enable(spi, 0); list_for_each_entry(t, &m->transfers, transfer_list) { if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) { status = -EINVAL; @@ -1005,6 +1105,12 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) if (t->len) { unsigned count; + if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) && + (m->is_dma_mapped || t->len >= DMA_MIN_BYTES)) + omap2_mcspi_set_fifo(spi, t, 1); + + omap2_mcspi_set_enable(spi, 1); + /* RX_ONLY mode needs dummy data in TX reg */ if (t->tx_buf == NULL) __raw_writel(0, cs->base @@ -1031,6 +1137,11 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) omap2_mcspi_force_cs(spi, 0); cs_active = 0; } + + omap2_mcspi_set_enable(spi, 0); + + if (mcspi->fifo_depth > 0) + omap2_mcspi_set_fifo(spi, t, 0); } /* Restore defaults if they were overriden */ if (par_override) { @@ -1051,8 +1162,10 @@ static void omap2_mcspi_work(struct omap2_mcspi *mcspi, struct spi_message *m) omap2_mcspi_set_enable(spi, 0); - m->status = status; + if (mcspi->fifo_depth > 0 && t) + omap2_mcspi_set_fifo(spi, t, 0); + m->status = status; } static int omap2_mcspi_transfer_one_message(struct spi_master *master, |