]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
Merge remote-tracking branch 'spi/fix/grant' into spi-linus
authorMark Brown <broonie@opensource.wolfsonmicro.com>
Mon, 13 May 2013 14:27:18 +0000 (18:27 +0400)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Mon, 13 May 2013 14:27:18 +0000 (18:27 +0400)
1  2 
drivers/spi/spi-atmel.c
drivers/spi/spi-davinci.c
drivers/spi/spi.c
include/linux/spi/spi.h

diff --combined drivers/spi/spi-atmel.c
index d8cb7da65efe6b48baa84980e93cb53880dc4340,80f5867c088b0c1dda382568b1869656d4544aa2..380387a47b1d86fe4e6a8a7ef22a537bfb85517a
  #include <linux/platform_device.h>
  #include <linux/delay.h>
  #include <linux/dma-mapping.h>
 +#include <linux/dmaengine.h>
  #include <linux/err.h>
  #include <linux/interrupt.h>
  #include <linux/spi/spi.h>
  #include <linux/slab.h>
  #include <linux/platform_data/atmel.h>
 +#include <linux/platform_data/dma-atmel.h>
  #include <linux/of.h>
  
 -#include <asm/io.h>
 -#include <asm/gpio.h>
 -#include <mach/cpu.h>
 +#include <linux/io.h>
 +#include <linux/gpio.h>
  
  /* SPI register offsets */
  #define SPI_CR                                        0x0000
@@@ -40,7 -39,6 +40,7 @@@
  #define SPI_CSR1                              0x0034
  #define SPI_CSR2                              0x0038
  #define SPI_CSR3                              0x003c
 +#define SPI_VERSION                           0x00fc
  #define SPI_RPR                                       0x0100
  #define SPI_RCR                                       0x0104
  #define SPI_TPR                                       0x0108
@@@ -73,8 -71,6 +73,8 @@@
  #define SPI_FDIV_SIZE                         1
  #define SPI_MODFDIS_OFFSET                    4
  #define SPI_MODFDIS_SIZE                      1
 +#define SPI_WDRBT_OFFSET                      5
 +#define SPI_WDRBT_SIZE                                1
  #define SPI_LLB_OFFSET                                7
  #define SPI_LLB_SIZE                          1
  #define SPI_PCS_OFFSET                                16
  #define spi_writel(port,reg,value) \
        __raw_writel((value), (port)->regs + SPI_##reg)
  
 +/* use PIO for small transfers, avoiding DMA setup/teardown overhead and
 + * cache operations; better heuristics consider wordsize and bitrate.
 + */
 +#define DMA_MIN_BYTES 16
 +
 +struct atmel_spi_dma {
 +      struct dma_chan                 *chan_rx;
 +      struct dma_chan                 *chan_tx;
 +      struct scatterlist              sgrx;
 +      struct scatterlist              sgtx;
 +      struct dma_async_tx_descriptor  *data_desc_rx;
 +      struct dma_async_tx_descriptor  *data_desc_tx;
 +
 +      struct at_dma_slave     dma_slave;
 +};
 +
 +struct atmel_spi_caps {
 +      bool    is_spi2;
 +      bool    has_wdrbt;
 +      bool    has_dma_support;
 +};
  
  /*
   * The core SPI transfer engine just talks to a register bank to set up
   */
  struct atmel_spi {
        spinlock_t              lock;
 +      unsigned long           flags;
  
 +      phys_addr_t             phybase;
        void __iomem            *regs;
        int                     irq;
        struct clk              *clk;
  
        u8                      stopping;
        struct list_head        queue;
 +      struct tasklet_struct   tasklet;
        struct spi_transfer     *current_transfer;
        unsigned long           current_remaining_bytes;
        struct spi_transfer     *next_transfer;
        unsigned long           next_remaining_bytes;
 +      int                     done_status;
  
 +      /* scratch buffer */
        void                    *buffer;
        dma_addr_t              buffer_dma;
 +
 +      struct atmel_spi_caps   caps;
 +
 +      bool                    use_dma;
 +      bool                    use_pdc;
 +      /* dmaengine data */
 +      struct atmel_spi_dma    dma;
  };
  
  /* Controller-specific per-slave state */
@@@ -259,10 -222,14 +259,10 @@@ struct atmel_spi_device 
   *  - SPI_SR.TXEMPTY, SPI_SR.NSSR (and corresponding irqs)
   *  - SPI_CSRx.CSAAT
   *  - SPI_CSRx.SBCR allows faster clocking
 - *
 - * We can determine the controller version by reading the VERSION
 - * register, but I haven't checked that it exists on all chips, and
 - * this is cheaper anyway.
   */
 -static bool atmel_spi_is_v2(void)
 +static bool atmel_spi_is_v2(struct atmel_spi *as)
  {
 -      return !cpu_is_at91rm9200();
 +      return as->caps.is_spi2;
  }
  
  /*
   * Master on Chip Select 0.")  No workaround exists for that ... so for
   * nCS0 on that chip, we (a) don't use the GPIO, (b) can't support CS_HIGH,
   * and (c) will trigger that first erratum in some cases.
 - *
 - * TODO: Test if the atmel_spi_is_v2() branch below works on
 - * AT91RM9200 if we use some other register than CSR0. However, don't
 - * do this unconditionally since AP7000 has an errata where the BITS
 - * field in CSR0 overrides all other CSRs.
   */
  
  static void cs_activate(struct atmel_spi *as, struct spi_device *spi)
        unsigned active = spi->mode & SPI_CS_HIGH;
        u32 mr;
  
 -      if (atmel_spi_is_v2()) {
 -              /*
 -               * Always use CSR0. This ensures that the clock
 -               * switches to the correct idle polarity before we
 -               * toggle the CS.
 +      if (atmel_spi_is_v2(as)) {
 +              spi_writel(as, CSR0 + 4 * spi->chip_select, asd->csr);
 +              /* For the low SPI version, there is a issue that PDC transfer
 +               * on CS1,2,3 needs SPI_CSR0.BITS config as SPI_CSR1,2,3.BITS
                 */
                spi_writel(as, CSR0, asd->csr);
 -              spi_writel(as, MR, SPI_BF(PCS, 0x0e) | SPI_BIT(MODFDIS)
 -                              | SPI_BIT(MSTR));
 +              if (as->caps.has_wdrbt) {
 +                      spi_writel(as, MR,
 +                                      SPI_BF(PCS, ~(0x01 << spi->chip_select))
 +                                      | SPI_BIT(WDRBT)
 +                                      | SPI_BIT(MODFDIS)
 +                                      | SPI_BIT(MSTR));
 +              } else {
 +                      spi_writel(as, MR,
 +                                      SPI_BF(PCS, ~(0x01 << spi->chip_select))
 +                                      | SPI_BIT(MODFDIS)
 +                                      | SPI_BIT(MSTR));
 +              }
 +
                mr = spi_readl(as, MR);
                gpio_set_value(asd->npcs_pin, active);
        } else {
@@@ -356,26 -318,10 +356,26 @@@ static void cs_deactivate(struct atmel_
                        asd->npcs_pin, active ? " (low)" : "",
                        mr);
  
 -      if (atmel_spi_is_v2() || spi->chip_select != 0)
 +      if (atmel_spi_is_v2(as) || spi->chip_select != 0)
                gpio_set_value(asd->npcs_pin, !active);
  }
  
 +static void atmel_spi_lock(struct atmel_spi *as)
 +{
 +      spin_lock_irqsave(&as->lock, as->flags);
 +}
 +
 +static void atmel_spi_unlock(struct atmel_spi *as)
 +{
 +      spin_unlock_irqrestore(&as->lock, as->flags);
 +}
 +
 +static inline bool atmel_spi_use_dma(struct atmel_spi *as,
 +                              struct spi_transfer *xfer)
 +{
 +      return as->use_dma && xfer->len >= DMA_MIN_BYTES;
 +}
 +
  static inline int atmel_spi_xfer_is_last(struct spi_message *msg,
                                        struct spi_transfer *xfer)
  {
@@@ -387,269 -333,6 +387,269 @@@ static inline int atmel_spi_xfer_can_be
        return xfer->delay_usecs == 0 && !xfer->cs_change;
  }
  
 +static int atmel_spi_dma_slave_config(struct atmel_spi *as,
 +                              struct dma_slave_config *slave_config,
 +                              u8 bits_per_word)
 +{
 +      int err = 0;
 +
 +      if (bits_per_word > 8) {
 +              slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 +              slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
 +      } else {
 +              slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 +              slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
 +      }
 +
 +      slave_config->dst_addr = (dma_addr_t)as->phybase + SPI_TDR;
 +      slave_config->src_addr = (dma_addr_t)as->phybase + SPI_RDR;
 +      slave_config->src_maxburst = 1;
 +      slave_config->dst_maxburst = 1;
 +      slave_config->device_fc = false;
 +
 +      slave_config->direction = DMA_MEM_TO_DEV;
 +      if (dmaengine_slave_config(as->dma.chan_tx, slave_config)) {
 +              dev_err(&as->pdev->dev,
 +                      "failed to configure tx dma channel\n");
 +              err = -EINVAL;
 +      }
 +
 +      slave_config->direction = DMA_DEV_TO_MEM;
 +      if (dmaengine_slave_config(as->dma.chan_rx, slave_config)) {
 +              dev_err(&as->pdev->dev,
 +                      "failed to configure rx dma channel\n");
 +              err = -EINVAL;
 +      }
 +
 +      return err;
 +}
 +
 +static bool filter(struct dma_chan *chan, void *slave)
 +{
 +      struct  at_dma_slave *sl = slave;
 +
 +      if (sl->dma_dev == chan->device->dev) {
 +              chan->private = sl;
 +              return true;
 +      } else {
 +              return false;
 +      }
 +}
 +
 +static int atmel_spi_configure_dma(struct atmel_spi *as)
 +{
 +      struct at_dma_slave *sdata = &as->dma.dma_slave;
 +      struct dma_slave_config slave_config;
 +      int err;
 +
 +      if (sdata && sdata->dma_dev) {
 +              dma_cap_mask_t mask;
 +
 +              /* Try to grab two DMA channels */
 +              dma_cap_zero(mask);
 +              dma_cap_set(DMA_SLAVE, mask);
 +              as->dma.chan_tx = dma_request_channel(mask, filter, sdata);
 +              if (as->dma.chan_tx)
 +                      as->dma.chan_rx =
 +                              dma_request_channel(mask, filter, sdata);
 +      }
 +      if (!as->dma.chan_rx || !as->dma.chan_tx) {
 +              dev_err(&as->pdev->dev,
 +                      "DMA channel not available, SPI unable to use DMA\n");
 +              err = -EBUSY;
 +              goto error;
 +      }
 +
 +      err = atmel_spi_dma_slave_config(as, &slave_config, 8);
 +      if (err)
 +              goto error;
 +
 +      dev_info(&as->pdev->dev,
 +                      "Using %s (tx) and %s (rx) for DMA transfers\n",
 +                      dma_chan_name(as->dma.chan_tx),
 +                      dma_chan_name(as->dma.chan_rx));
 +      return 0;
 +error:
 +      if (as->dma.chan_rx)
 +              dma_release_channel(as->dma.chan_rx);
 +      if (as->dma.chan_tx)
 +              dma_release_channel(as->dma.chan_tx);
 +      return err;
 +}
 +
 +static void atmel_spi_stop_dma(struct atmel_spi *as)
 +{
 +      if (as->dma.chan_rx)
 +              as->dma.chan_rx->device->device_control(as->dma.chan_rx,
 +                                                      DMA_TERMINATE_ALL, 0);
 +      if (as->dma.chan_tx)
 +              as->dma.chan_tx->device->device_control(as->dma.chan_tx,
 +                                                      DMA_TERMINATE_ALL, 0);
 +}
 +
 +static void atmel_spi_release_dma(struct atmel_spi *as)
 +{
 +      if (as->dma.chan_rx)
 +              dma_release_channel(as->dma.chan_rx);
 +      if (as->dma.chan_tx)
 +              dma_release_channel(as->dma.chan_tx);
 +}
 +
 +/* This function is called by the DMA driver from tasklet context */
 +static void dma_callback(void *data)
 +{
 +      struct spi_master       *master = data;
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +
 +      /* trigger SPI tasklet */
 +      tasklet_schedule(&as->tasklet);
 +}
 +
 +/*
 + * Next transfer using PIO.
 + * lock is held, spi tasklet is blocked
 + */
 +static void atmel_spi_next_xfer_pio(struct spi_master *master,
 +                              struct spi_transfer *xfer)
 +{
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +
 +      dev_vdbg(master->dev.parent, "atmel_spi_next_xfer_pio\n");
 +
 +      as->current_remaining_bytes = xfer->len;
 +
 +      /* Make sure data is not remaining in RDR */
 +      spi_readl(as, RDR);
 +      while (spi_readl(as, SR) & SPI_BIT(RDRF)) {
 +              spi_readl(as, RDR);
 +              cpu_relax();
 +      }
 +
 +      if (xfer->tx_buf)
 +              if (xfer->bits_per_word > 8)
 +                      spi_writel(as, TDR, *(u16 *)(xfer->tx_buf));
 +              else
 +                      spi_writel(as, TDR, *(u8 *)(xfer->tx_buf));
 +      else
 +              spi_writel(as, TDR, 0);
 +
 +      dev_dbg(master->dev.parent,
 +              "  start pio xfer %p: len %u tx %p rx %p bitpw %d\n",
 +              xfer, xfer->len, xfer->tx_buf, xfer->rx_buf,
 +              xfer->bits_per_word);
 +
 +      /* Enable relevant interrupts */
 +      spi_writel(as, IER, SPI_BIT(RDRF) | SPI_BIT(OVRES));
 +}
 +
 +/*
 + * Submit next transfer for DMA.
 + * lock is held, spi tasklet is blocked
 + */
 +static int atmel_spi_next_xfer_dma_submit(struct spi_master *master,
 +                              struct spi_transfer *xfer,
 +                              u32 *plen)
 +{
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +      struct dma_chan         *rxchan = as->dma.chan_rx;
 +      struct dma_chan         *txchan = as->dma.chan_tx;
 +      struct dma_async_tx_descriptor *rxdesc;
 +      struct dma_async_tx_descriptor *txdesc;
 +      struct dma_slave_config slave_config;
 +      dma_cookie_t            cookie;
 +      u32     len = *plen;
 +
 +      dev_vdbg(master->dev.parent, "atmel_spi_next_xfer_dma_submit\n");
 +
 +      /* Check that the channels are available */
 +      if (!rxchan || !txchan)
 +              return -ENODEV;
 +
 +      /* release lock for DMA operations */
 +      atmel_spi_unlock(as);
 +
 +      /* prepare the RX dma transfer */
 +      sg_init_table(&as->dma.sgrx, 1);
 +      if (xfer->rx_buf) {
 +              as->dma.sgrx.dma_address = xfer->rx_dma + xfer->len - *plen;
 +      } else {
 +              as->dma.sgrx.dma_address = as->buffer_dma;
 +              if (len > BUFFER_SIZE)
 +                      len = BUFFER_SIZE;
 +      }
 +
 +      /* prepare the TX dma transfer */
 +      sg_init_table(&as->dma.sgtx, 1);
 +      if (xfer->tx_buf) {
 +              as->dma.sgtx.dma_address = xfer->tx_dma + xfer->len - *plen;
 +      } else {
 +              as->dma.sgtx.dma_address = as->buffer_dma;
 +              if (len > BUFFER_SIZE)
 +                      len = BUFFER_SIZE;
 +              memset(as->buffer, 0, len);
 +      }
 +
 +      sg_dma_len(&as->dma.sgtx) = len;
 +      sg_dma_len(&as->dma.sgrx) = len;
 +
 +      *plen = len;
 +
 +      if (atmel_spi_dma_slave_config(as, &slave_config, 8))
 +              goto err_exit;
 +
 +      /* Send both scatterlists */
 +      rxdesc = rxchan->device->device_prep_slave_sg(rxchan,
 +                                      &as->dma.sgrx,
 +                                      1,
 +                                      DMA_FROM_DEVICE,
 +                                      DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
 +                                      NULL);
 +      if (!rxdesc)
 +              goto err_dma;
 +
 +      txdesc = txchan->device->device_prep_slave_sg(txchan,
 +                                      &as->dma.sgtx,
 +                                      1,
 +                                      DMA_TO_DEVICE,
 +                                      DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
 +                                      NULL);
 +      if (!txdesc)
 +              goto err_dma;
 +
 +      dev_dbg(master->dev.parent,
 +              "  start dma xfer %p: len %u tx %p/%08x rx %p/%08x\n",
 +              xfer, xfer->len, xfer->tx_buf, xfer->tx_dma,
 +              xfer->rx_buf, xfer->rx_dma);
 +
 +      /* Enable relevant interrupts */
 +      spi_writel(as, IER, SPI_BIT(OVRES));
 +
 +      /* Put the callback on the RX transfer only, that should finish last */
 +      rxdesc->callback = dma_callback;
 +      rxdesc->callback_param = master;
 +
 +      /* Submit and fire RX and TX with TX last so we're ready to read! */
 +      cookie = rxdesc->tx_submit(rxdesc);
 +      if (dma_submit_error(cookie))
 +              goto err_dma;
 +      cookie = txdesc->tx_submit(txdesc);
 +      if (dma_submit_error(cookie))
 +              goto err_dma;
 +      rxchan->device->device_issue_pending(rxchan);
 +      txchan->device->device_issue_pending(txchan);
 +
 +      /* take back lock */
 +      atmel_spi_lock(as);
 +      return 0;
 +
 +err_dma:
 +      spi_writel(as, IDR, SPI_BIT(OVRES));
 +      atmel_spi_stop_dma(as);
 +err_exit:
 +      atmel_spi_lock(as);
 +      return -ENOMEM;
 +}
 +
  static void atmel_spi_next_xfer_data(struct spi_master *master,
                                struct spi_transfer *xfer,
                                dma_addr_t *tx_dma,
                if (len > BUFFER_SIZE)
                        len = BUFFER_SIZE;
        }
 +
        if (xfer->tx_buf)
                *tx_dma = xfer->tx_dma + xfer->len - *plen;
        else {
  }
  
  /*
 - * Submit next transfer for DMA.
 + * Submit next transfer for PDC.
   * lock is held, spi irq is blocked
   */
 -static void atmel_spi_next_xfer(struct spi_master *master,
 +static void atmel_spi_pdc_next_xfer(struct spi_master *master,
                                struct spi_message *msg)
  {
        struct atmel_spi        *as = spi_master_get_devdata(master);
        spi_writel(as, PTCR, SPI_BIT(TXTEN) | SPI_BIT(RXTEN));
  }
  
 +/*
 + * Choose way to submit next transfer and start it.
 + * lock is held, spi tasklet is blocked
 + */
 +static void atmel_spi_dma_next_xfer(struct spi_master *master,
 +                              struct spi_message *msg)
 +{
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +      struct spi_transfer     *xfer;
 +      u32     remaining, len;
 +
 +      remaining = as->current_remaining_bytes;
 +      if (remaining) {
 +              xfer = as->current_transfer;
 +              len = remaining;
 +      } else {
 +              if (!as->current_transfer)
 +                      xfer = list_entry(msg->transfers.next,
 +                              struct spi_transfer, transfer_list);
 +              else
 +                      xfer = list_entry(
 +                              as->current_transfer->transfer_list.next,
 +                                      struct spi_transfer, transfer_list);
 +
 +              as->current_transfer = xfer;
 +              len = xfer->len;
 +      }
 +
 +      if (atmel_spi_use_dma(as, xfer)) {
 +              u32 total = len;
 +              if (!atmel_spi_next_xfer_dma_submit(master, xfer, &len)) {
 +                      as->current_remaining_bytes = total - len;
 +                      return;
 +              } else {
 +                      dev_err(&msg->spi->dev, "unable to use DMA, fallback to PIO\n");
 +              }
 +      }
 +
 +      /* use PIO if error appened using DMA */
 +      atmel_spi_next_xfer_pio(master, xfer);
 +}
 +
  static void atmel_spi_next_message(struct spi_master *master)
  {
        struct atmel_spi        *as = spi_master_get_devdata(master);
        } else
                cs_activate(as, spi);
  
 -      atmel_spi_next_xfer(master, msg);
 +      if (as->use_pdc)
 +              atmel_spi_pdc_next_xfer(master, msg);
 +      else
 +              atmel_spi_dma_next_xfer(master, msg);
  }
  
  /*
@@@ -905,231 -542,38 +905,231 @@@ static void atmel_spi_dma_unmap_xfer(st
                                 xfer->len, DMA_FROM_DEVICE);
  }
  
 +static void atmel_spi_disable_pdc_transfer(struct atmel_spi *as)
 +{
 +      spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
 +}
 +
  static void
  atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
 -              struct spi_message *msg, int status, int stay)
 +              struct spi_message *msg, int stay)
  {
 -      if (!stay || status < 0)
 +      if (!stay || as->done_status < 0)
                cs_deactivate(as, msg->spi);
        else
                as->stay = msg->spi;
  
        list_del(&msg->queue);
 -      msg->status = status;
 +      msg->status = as->done_status;
  
        dev_dbg(master->dev.parent,
                "xfer complete: %u bytes transferred\n",
                msg->actual_length);
  
 -      spin_unlock(&as->lock);
 +      atmel_spi_unlock(as);
        msg->complete(msg->context);
 -      spin_lock(&as->lock);
 +      atmel_spi_lock(as);
  
        as->current_transfer = NULL;
        as->next_transfer = NULL;
 +      as->done_status = 0;
  
        /* continue if needed */
 -      if (list_empty(&as->queue) || as->stopping)
 -              spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
 -      else
 +      if (list_empty(&as->queue) || as->stopping) {
 +              if (as->use_pdc)
 +                      atmel_spi_disable_pdc_transfer(as);
 +      } else {
                atmel_spi_next_message(master);
 +      }
 +}
 +
 +/* Called from IRQ
 + * lock is held
 + *
 + * Must update "current_remaining_bytes" to keep track of data
 + * to transfer.
 + */
 +static void
 +atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
 +{
 +      u8              *txp;
 +      u8              *rxp;
 +      u16             *txp16;
 +      u16             *rxp16;
 +      unsigned long   xfer_pos = xfer->len - as->current_remaining_bytes;
 +
 +      if (xfer->rx_buf) {
 +              if (xfer->bits_per_word > 8) {
 +                      rxp16 = (u16 *)(((u8 *)xfer->rx_buf) + xfer_pos);
 +                      *rxp16 = spi_readl(as, RDR);
 +              } else {
 +                      rxp = ((u8 *)xfer->rx_buf) + xfer_pos;
 +                      *rxp = spi_readl(as, RDR);
 +              }
 +      } else {
 +              spi_readl(as, RDR);
 +      }
 +      if (xfer->bits_per_word > 8) {
 +              as->current_remaining_bytes -= 2;
 +              if (as->current_remaining_bytes < 0)
 +                      as->current_remaining_bytes = 0;
 +      } else {
 +              as->current_remaining_bytes--;
 +      }
 +
 +      if (as->current_remaining_bytes) {
 +              if (xfer->tx_buf) {
 +                      if (xfer->bits_per_word > 8) {
 +                              txp16 = (u16 *)(((u8 *)xfer->tx_buf)
 +                                                      + xfer_pos + 2);
 +                              spi_writel(as, TDR, *txp16);
 +                      } else {
 +                              txp = ((u8 *)xfer->tx_buf) + xfer_pos + 1;
 +                              spi_writel(as, TDR, *txp);
 +                      }
 +              } else {
 +                      spi_writel(as, TDR, 0);
 +              }
 +      }
 +}
 +
 +/* Tasklet
 + * Called from DMA callback + pio transfer and overrun IRQ.
 + */
 +static void atmel_spi_tasklet_func(unsigned long data)
 +{
 +      struct spi_master       *master = (struct spi_master *)data;
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +      struct spi_message      *msg;
 +      struct spi_transfer     *xfer;
 +
 +      dev_vdbg(master->dev.parent, "atmel_spi_tasklet_func\n");
 +
 +      atmel_spi_lock(as);
 +
 +      xfer = as->current_transfer;
 +
 +      if (xfer == NULL)
 +              /* already been there */
 +              goto tasklet_out;
 +
 +      msg = list_entry(as->queue.next, struct spi_message, queue);
 +
 +      if (as->current_remaining_bytes == 0) {
 +              if (as->done_status < 0) {
 +                      /* error happened (overrun) */
 +                      if (atmel_spi_use_dma(as, xfer))
 +                              atmel_spi_stop_dma(as);
 +              } else {
 +                      /* only update length if no error */
 +                      msg->actual_length += xfer->len;
 +              }
 +
 +              if (atmel_spi_use_dma(as, xfer))
 +                      if (!msg->is_dma_mapped)
 +                              atmel_spi_dma_unmap_xfer(master, xfer);
 +
 +              if (xfer->delay_usecs)
 +                      udelay(xfer->delay_usecs);
 +
 +              if (atmel_spi_xfer_is_last(msg, xfer) || as->done_status < 0) {
 +                      /* report completed (or erroneous) message */
 +                      atmel_spi_msg_done(master, as, msg, xfer->cs_change);
 +              } else {
 +                      if (xfer->cs_change) {
 +                              cs_deactivate(as, msg->spi);
 +                              udelay(1);
 +                              cs_activate(as, msg->spi);
 +                      }
 +
 +                      /*
 +                       * Not done yet. Submit the next transfer.
 +                       *
 +                       * FIXME handle protocol options for xfer
 +                       */
 +                      atmel_spi_dma_next_xfer(master, msg);
 +              }
 +      } else {
 +              /*
 +               * Keep going, we still have data to send in
 +               * the current transfer.
 +               */
 +              atmel_spi_dma_next_xfer(master, msg);
 +      }
 +
 +tasklet_out:
 +      atmel_spi_unlock(as);
  }
  
 +/* Interrupt
 + *
 + * No need for locking in this Interrupt handler: done_status is the
 + * only information modified. What we need is the update of this field
 + * before tasklet runs. This is ensured by using barrier.
 + */
  static irqreturn_t
 -atmel_spi_interrupt(int irq, void *dev_id)
 +atmel_spi_pio_interrupt(int irq, void *dev_id)
 +{
 +      struct spi_master       *master = dev_id;
 +      struct atmel_spi        *as = spi_master_get_devdata(master);
 +      u32                     status, pending, imr;
 +      struct spi_transfer     *xfer;
 +      int                     ret = IRQ_NONE;
 +
 +      imr = spi_readl(as, IMR);
 +      status = spi_readl(as, SR);
 +      pending = status & imr;
 +
 +      if (pending & SPI_BIT(OVRES)) {
 +              ret = IRQ_HANDLED;
 +              spi_writel(as, IDR, SPI_BIT(OVRES));
 +              dev_warn(master->dev.parent, "overrun\n");
 +
 +              /*
 +               * When we get an overrun, we disregard the current
 +               * transfer. Data will not be copied back from any
 +               * bounce buffer and msg->actual_len will not be
 +               * updated with the last xfer.
 +               *
 +               * We will also not process any remaning transfers in
 +               * the message.
 +               *
 +               * All actions are done in tasklet with done_status indication
 +               */
 +              as->done_status = -EIO;
 +              smp_wmb();
 +
 +              /* Clear any overrun happening while cleaning up */
 +              spi_readl(as, SR);
 +
 +              tasklet_schedule(&as->tasklet);
 +
 +      } else if (pending & SPI_BIT(RDRF)) {
 +              atmel_spi_lock(as);
 +
 +              if (as->current_remaining_bytes) {
 +                      ret = IRQ_HANDLED;
 +                      xfer = as->current_transfer;
 +                      atmel_spi_pump_pio_data(as, xfer);
 +                      if (!as->current_remaining_bytes) {
 +                              /* no more data to xfer, kick tasklet */
 +                              spi_writel(as, IDR, pending);
 +                              tasklet_schedule(&as->tasklet);
 +                      }
 +              }
 +
 +              atmel_spi_unlock(as);
 +      } else {
 +              WARN_ONCE(pending, "IRQ not handled, pending = %x\n", pending);
 +              ret = IRQ_HANDLED;
 +              spi_writel(as, IDR, pending);
 +      }
 +
 +      return ret;
 +}
 +
 +static irqreturn_t
 +atmel_spi_pdc_interrupt(int irq, void *dev_id)
  {
        struct spi_master       *master = dev_id;
        struct atmel_spi        *as = spi_master_get_devdata(master);
        u32                     status, pending, imr;
        int                     ret = IRQ_NONE;
  
 -      spin_lock(&as->lock);
 +      atmel_spi_lock(as);
  
        xfer = as->current_transfer;
        msg = list_entry(as->queue.next, struct spi_message, queue);
                /* Clear any overrun happening while cleaning up */
                spi_readl(as, SR);
  
 -              atmel_spi_msg_done(master, as, msg, -EIO, 0);
 +              as->done_status = -EIO;
 +              atmel_spi_msg_done(master, as, msg, 0);
        } else if (pending & (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX))) {
                ret = IRQ_HANDLED;
  
  
                        if (atmel_spi_xfer_is_last(msg, xfer)) {
                                /* report completed message */
 -                              atmel_spi_msg_done(master, as, msg, 0,
 +                              atmel_spi_msg_done(master, as, msg,
                                                xfer->cs_change);
                        } else {
                                if (xfer->cs_change) {
                                 *
                                 * FIXME handle protocol options for xfer
                                 */
 -                              atmel_spi_next_xfer(master, msg);
 +                              atmel_spi_pdc_next_xfer(master, msg);
                        }
                } else {
                        /*
                         * Keep going, we still have data to send in
                         * the current transfer.
                         */
 -                      atmel_spi_next_xfer(master, msg);
 +                      atmel_spi_pdc_next_xfer(master, msg);
                }
        }
  
 -      spin_unlock(&as->lock);
 +      atmel_spi_unlock(as);
  
        return ret;
  }
@@@ -1276,7 -719,7 +1276,7 @@@ static int atmel_spi_setup(struct spi_d
        }
  
        /* see notes above re chipselect */
 -      if (!atmel_spi_is_v2()
 +      if (!atmel_spi_is_v2(as)
                        && spi->chip_select == 0
                        && (spi->mode & SPI_CS_HIGH)) {
                dev_dbg(&spi->dev, "setup: can't be active-high\n");
  
        /* v1 chips start out at half the peripheral bus speed. */
        bus_hz = clk_get_rate(as->clk);
 -      if (!atmel_spi_is_v2())
 +      if (!atmel_spi_is_v2(as))
                bus_hz /= 2;
  
        if (spi->max_speed_hz) {
                spi->controller_state = asd;
                gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
        } else {
 -              unsigned long           flags;
 -
 -              spin_lock_irqsave(&as->lock, flags);
 +              atmel_spi_lock(as);
                if (as->stay == spi)
                        as->stay = NULL;
                cs_deactivate(as, spi);
 -              spin_unlock_irqrestore(&as->lock, flags);
 +              atmel_spi_unlock(as);
        }
  
        asd->csr = csr;
                "setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n",
                bus_hz / scbr, bits, spi->mode, spi->chip_select, csr);
  
 -      if (!atmel_spi_is_v2())
 +      if (!atmel_spi_is_v2(as))
                spi_writel(as, CSR0 + 4 * spi->chip_select, csr);
  
        return 0;
@@@ -1369,6 -814,7 +1369,6 @@@ static int atmel_spi_transfer(struct sp
  {
        struct atmel_spi        *as;
        struct spi_transfer     *xfer;
 -      unsigned long           flags;
        struct device           *controller = spi->master->dev.parent;
        u8                      bits;
        struct atmel_spi_device *asd;
                        }
                }
  
 +              if (xfer->bits_per_word > 8) {
 +                      if (xfer->len % 2) {
 +                              dev_dbg(&spi->dev, "buffer len should be 16 bits aligned\n");
 +                              return -EINVAL;
 +                      }
 +              }
 +
                /* FIXME implement these protocol options!! */
-               if (xfer->speed_hz) {
-                       dev_dbg(&spi->dev, "no protocol options yet\n");
+               if (xfer->speed_hz < spi->max_speed_hz) {
+                       dev_dbg(&spi->dev, "can't change speed in transfer\n");
                        return -ENOPROTOOPT;
                }
  
                /*
                 * DMA map early, for performance (empties dcache ASAP) and
 -               * better fault reporting.  This is a DMA-only driver.
 -               *
 -               * NOTE that if dma_unmap_single() ever starts to do work on
 -               * platforms supported by this driver, we would need to clean
 -               * up mappings for previously-mapped transfers.
 +               * better fault reporting.
                 */
 -              if (!msg->is_dma_mapped) {
 +              if ((!msg->is_dma_mapped) && (atmel_spi_use_dma(as, xfer)
 +                      || as->use_pdc)) {
                        if (atmel_spi_dma_map_xfer(as, xfer) < 0)
                                return -ENOMEM;
                }
        msg->status = -EINPROGRESS;
        msg->actual_length = 0;
  
 -      spin_lock_irqsave(&as->lock, flags);
 +      atmel_spi_lock(as);
        list_add_tail(&msg->queue, &as->queue);
        if (!as->current_transfer)
                atmel_spi_next_message(spi->master);
 -      spin_unlock_irqrestore(&as->lock, flags);
 +      atmel_spi_unlock(as);
  
        return 0;
  }
@@@ -1451,39 -893,23 +1451,39 @@@ static void atmel_spi_cleanup(struct sp
        struct atmel_spi        *as = spi_master_get_devdata(spi->master);
        struct atmel_spi_device *asd = spi->controller_state;
        unsigned                gpio = (unsigned) spi->controller_data;
 -      unsigned long           flags;
  
        if (!asd)
                return;
  
 -      spin_lock_irqsave(&as->lock, flags);
 +      atmel_spi_lock(as);
        if (as->stay == spi) {
                as->stay = NULL;
                cs_deactivate(as, spi);
        }
 -      spin_unlock_irqrestore(&as->lock, flags);
 +      atmel_spi_unlock(as);
  
        spi->controller_state = NULL;
        gpio_free(gpio);
        kfree(asd);
  }
  
 +static inline unsigned int atmel_get_version(struct atmel_spi *as)
 +{
 +      return spi_readl(as, VERSION) & 0x00000fff;
 +}
 +
 +static void atmel_get_caps(struct atmel_spi *as)
 +{
 +      unsigned int version;
 +
 +      version = atmel_get_version(as);
 +      dev_info(&as->pdev->dev, "version: 0x%x\n", version);
 +
 +      as->caps.is_spi2 = version > 0x121;
 +      as->caps.has_wdrbt = version >= 0x210;
 +      as->caps.has_dma_support = version >= 0x212;
 +}
 +
  /*-------------------------------------------------------------------------*/
  
  static int atmel_spi_probe(struct platform_device *pdev)
  
        spin_lock_init(&as->lock);
        INIT_LIST_HEAD(&as->queue);
 +
        as->pdev = pdev;
        as->regs = ioremap(regs->start, resource_size(regs));
        if (!as->regs)
                goto out_free_buffer;
 +      as->phybase = regs->start;
        as->irq = irq;
        as->clk = clk;
  
 -      ret = request_irq(irq, atmel_spi_interrupt, 0,
 -                      dev_name(&pdev->dev), master);
 +      atmel_get_caps(as);
 +
 +      as->use_dma = false;
 +      as->use_pdc = false;
 +      if (as->caps.has_dma_support) {
 +              if (atmel_spi_configure_dma(as) == 0)
 +                      as->use_dma = true;
 +      } else {
 +              as->use_pdc = true;
 +      }
 +
 +      if (as->caps.has_dma_support && !as->use_dma)
 +              dev_info(&pdev->dev, "Atmel SPI Controller using PIO only\n");
 +
 +      if (as->use_pdc) {
 +              ret = request_irq(irq, atmel_spi_pdc_interrupt, 0,
 +                                      dev_name(&pdev->dev), master);
 +      } else {
 +              tasklet_init(&as->tasklet, atmel_spi_tasklet_func,
 +                                      (unsigned long)master);
 +
 +              ret = request_irq(irq, atmel_spi_pio_interrupt, 0,
 +                                      dev_name(&pdev->dev), master);
 +      }
        if (ret)
                goto out_unmap_regs;
  
        clk_enable(clk);
        spi_writel(as, CR, SPI_BIT(SWRST));
        spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */
 -      spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));
 -      spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
 +      if (as->caps.has_wdrbt) {
 +              spi_writel(as, MR, SPI_BIT(WDRBT) | SPI_BIT(MODFDIS)
 +                              | SPI_BIT(MSTR));
 +      } else {
 +              spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));
 +      }
 +
 +      if (as->use_pdc)
 +              spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
        spi_writel(as, CR, SPI_BIT(SPIEN));
  
        /* go! */
  
        ret = spi_register_master(master);
        if (ret)
 -              goto out_reset_hw;
 +              goto out_free_dma;
  
        return 0;
  
 -out_reset_hw:
 +out_free_dma:
 +      if (as->use_dma)
 +              atmel_spi_release_dma(as);
 +
        spi_writel(as, CR, SPI_BIT(SWRST));
        spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */
        clk_disable(clk);
  out_unmap_regs:
        iounmap(as->regs);
  out_free_buffer:
 +      if (!as->use_pdc)
 +              tasklet_kill(&as->tasklet);
        dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,
                        as->buffer_dma);
  out_free:
@@@ -1624,16 -1014,10 +1624,16 @@@ static int atmel_spi_remove(struct plat
        struct spi_master       *master = platform_get_drvdata(pdev);
        struct atmel_spi        *as = spi_master_get_devdata(master);
        struct spi_message      *msg;
 +      struct spi_transfer     *xfer;
  
        /* reset the hardware and block queue progress */
        spin_lock_irq(&as->lock);
        as->stopping = 1;
 +      if (as->use_dma) {
 +              atmel_spi_stop_dma(as);
 +              atmel_spi_release_dma(as);
 +      }
 +
        spi_writel(as, CR, SPI_BIT(SWRST));
        spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */
        spi_readl(as, SR);
  
        /* Terminate remaining queued transfers */
        list_for_each_entry(msg, &as->queue, queue) {
 -              /* REVISIT unmapping the dma is a NOP on ARM and AVR32
 -               * but we shouldn't depend on that...
 -               */
 +              list_for_each_entry(xfer, &msg->transfers, transfer_list) {
 +                      if (!msg->is_dma_mapped
 +                              && (atmel_spi_use_dma(as, xfer)
 +                                      || as->use_pdc))
 +                              atmel_spi_dma_unmap_xfer(master, xfer);
 +              }
                msg->status = -ESHUTDOWN;
                msg->complete(msg->context);
        }
  
 +      if (!as->use_pdc)
 +              tasklet_kill(&as->tasklet);
        dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,
                        as->buffer_dma);
  
index 2e8f24a1fb952cbfd86b161ad50ac3e315d0850b,6287c8315d0d2d9987df069f4e9e70206fc11c4c..50b13c9b1ab691fd5defcae44b98dc4bfccb5557
@@@ -776,15 -776,15 +776,15 @@@ rx_dma_failed
  #if defined(CONFIG_OF)
  static const struct of_device_id davinci_spi_of_match[] = {
        {
 -              .compatible = "ti,dm644x-spi",
 +              .compatible = "ti,dm6441-spi",
        },
        {
 -              .compatible = "ti,da8xx-spi",
 +              .compatible = "ti,da830-spi",
                .data = (void *)SPI_VERSION_2,
        },
        { },
  };
- MODULE_DEVICE_TABLE(of, davini_spi_of_match);
+ MODULE_DEVICE_TABLE(of, davinci_spi_of_match);
  
  /**
   * spi_davinci_get_pdata - Get platform data from DTS binding
diff --combined drivers/spi/spi.c
index 163fd802b7aced2217494397297de76862056ea5,3738e7cbff33208acd6963111d342d7df05914e3..32b7bb111eb6b53d9e96808f7bd5fd0982cffd9d
@@@ -334,7 -334,7 +334,7 @@@ struct spi_device *spi_alloc_device(str
        spi->dev.parent = &master->dev;
        spi->dev.bus = &spi_bus_type;
        spi->dev.release = spidev_release;
-       spi->cs_gpio = -EINVAL;
+       spi->cs_gpio = -ENOENT;
        device_initialize(&spi->dev);
        return spi;
  }
@@@ -1067,8 -1067,11 +1067,11 @@@ static int of_spi_register_master(struc
        nb = of_gpio_named_count(np, "cs-gpios");
        master->num_chipselect = max(nb, (int)master->num_chipselect);
  
-       if (nb < 1)
+       /* Return error only for an incorrectly formed cs-gpios property */
+       if (nb == 0 || nb == -ENOENT)
                return 0;
+       else if (nb < 0)
+               return nb;
  
        cs = devm_kzalloc(&master->dev,
                          sizeof(int) * master->num_chipselect,
                return -ENOMEM;
  
        for (i = 0; i < master->num_chipselect; i++)
-               cs[i] = -EINVAL;
+               cs[i] = -ENOENT;
  
        for (i = 0; i < nb; i++)
                cs[i] = of_get_named_gpio(np, "cs-gpios", i);
@@@ -1376,14 -1379,6 +1379,14 @@@ static int __spi_async(struct spi_devic
                        xfer->bits_per_word = spi->bits_per_word;
                if (!xfer->speed_hz)
                        xfer->speed_hz = spi->max_speed_hz;
 +              if (master->bits_per_word_mask) {
 +                      /* Only 32 bits fit in the mask */
 +                      if (xfer->bits_per_word > 32)
 +                              return -EINVAL;
 +                      if (!(master->bits_per_word_mask &
 +                                      BIT(xfer->bits_per_word - 1)))
 +                              return -EINVAL;
 +              }
        }
  
        message->spi = spi;
diff --combined include/linux/spi/spi.h
index 733eb5ee31c5446cbe6a11bb0b3603a6534edf49,c395f32b53b3ac47a7770edbd2357248b3537271..6ff26c8db7b923853527cee3ee1ffff1ca55bdd6
@@@ -57,7 -57,7 +57,7 @@@ extern struct bus_type spi_bus_type
   * @modalias: Name of the driver to use with this device, or an alias
   *    for that name.  This appears in the sysfs "modalias" attribute
   *    for driver coldplugging, and in uevents used for hotplugging
-  * @cs_gpio: gpio number of the chipselect line (optional, -EINVAL when
+  * @cs_gpio: gpio number of the chipselect line (optional, -ENOENT when
   *    when not using a GPIO line)
   *
   * A @spi_device is used to interchange data between an SPI slave
@@@ -228,11 -228,6 +228,11 @@@ static inline void spi_unregister_drive
   *    every chipselect is connected to a slave.
   * @dma_alignment: SPI controller constraint on DMA buffers alignment.
   * @mode_bits: flags understood by this controller driver
 + * @bits_per_word_mask: A mask indicating which values of bits_per_word are
 + *    supported by the driver. Bit n indicates that a bits_per_word n+1 is
 + *    suported. If set, the SPI core will reject any transfer with an
 + *    unsupported bits_per_word. If not set, this value is simply ignored,
 + *    and it's up to the individual driver to perform any validation.
   * @flags: other constraints relevant to this driver
   * @bus_lock_spinlock: spinlock for SPI bus locking
   * @bus_lock_mutex: mutex for SPI bus locking
   *    queue so the subsystem notifies the driver that it may relax the
   *    hardware by issuing this call
   * @cs_gpios: Array of GPIOs to use as chip select lines; one per CS
-  *    number. Any individual value may be -EINVAL for CS lines that
+  *    number. Any individual value may be -ENOENT for CS lines that
   *    are not GPIOs (driven by the SPI controller itself).
   *
   * Each SPI master controller can communicate with one or more @spi_device
@@@ -306,9 -301,6 +306,9 @@@ struct spi_master 
        /* spi_device.mode flags understood by this controller driver */
        u16                     mode_bits;
  
 +      /* bitmask of supported bits_per_word for transfers */
 +      u32                     bits_per_word_mask;
 +
        /* other constraints relevant to this driver */
        u16                     flags;
  #define SPI_MASTER_HALF_DUPLEX        BIT(0)          /* can't do full duplex */