Commit d9b9692a authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'spi-v3.18-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi

Pull spi fixes from Mark Brown:
 "Quite a few driver fixes in here, including some fairly substantial
  ones for the recently added Rockchip driver, plus a fix for spidev to
  more reliably support bidirectional transfers which is fairly large
  but basically mechanical.

  It's a bit more code than I'd like but all fixes"

* tag 'spi-v3.18-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi:
  spi: orion: fix potential NULL pointer de-reference
  spi/rockchip: spi controller must be disabled in tx callback too
  spi/rockchip: fix bug that cause spi transfer timed out in DMA duplex mode
  spi/rockchip: fix bug that case spi can't go as fast as slave request
  spi: pl022: Fix incorrect dma_unmap_sg
  spi: spidev: Use separate TX and RX bounce buffers
  spi: dw: Initialize of_node to discover DT node children
parents d855c5c9 1e2cf73e
...@@ -669,6 +669,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) ...@@ -669,6 +669,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
master->cleanup = dw_spi_cleanup; master->cleanup = dw_spi_cleanup;
master->transfer_one_message = dw_spi_transfer_one_message; master->transfer_one_message = dw_spi_transfer_one_message;
master->max_speed_hz = dws->max_freq; master->max_speed_hz = dws->max_freq;
master->dev.of_node = dev->of_node;
/* Basic HW init */ /* Basic HW init */
spi_hw_init(dws); spi_hw_init(dws);
......
...@@ -454,7 +454,7 @@ static int orion_spi_probe(struct platform_device *pdev) ...@@ -454,7 +454,7 @@ static int orion_spi_probe(struct platform_device *pdev)
spi->master = master; spi->master = master;
of_id = of_match_device(orion_spi_of_match_table, &pdev->dev); of_id = of_match_device(orion_spi_of_match_table, &pdev->dev);
devdata = of_id->data; devdata = (of_id) ? of_id->data : &orion_spi_dev_data;
spi->devdata = devdata; spi->devdata = devdata;
spi->clk = devm_clk_get(&pdev->dev, NULL); spi->clk = devm_clk_get(&pdev->dev, NULL);
......
...@@ -1106,7 +1106,7 @@ static int configure_dma(struct pl022 *pl022) ...@@ -1106,7 +1106,7 @@ static int configure_dma(struct pl022 *pl022)
pl022->sgt_tx.nents, DMA_TO_DEVICE); pl022->sgt_tx.nents, DMA_TO_DEVICE);
err_tx_sgmap: err_tx_sgmap:
dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl, dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl,
pl022->sgt_tx.nents, DMA_FROM_DEVICE); pl022->sgt_rx.nents, DMA_FROM_DEVICE);
err_rx_sgmap: err_rx_sgmap:
sg_free_table(&pl022->sgt_tx); sg_free_table(&pl022->sgt_tx);
err_alloc_tx_sg: err_alloc_tx_sg:
......
...@@ -145,6 +145,9 @@ ...@@ -145,6 +145,9 @@
#define RXBUSY (1 << 0) #define RXBUSY (1 << 0)
#define TXBUSY (1 << 1) #define TXBUSY (1 << 1)
/* sclk_out: spi master internal logic in rk3x can support 50Mhz */
#define MAX_SCLK_OUT 50000000
enum rockchip_ssi_type { enum rockchip_ssi_type {
SSI_MOTO_SPI = 0, SSI_MOTO_SPI = 0,
SSI_TI_SSP, SSI_TI_SSP,
...@@ -325,6 +328,8 @@ static int rockchip_spi_unprepare_message(struct spi_master *master, ...@@ -325,6 +328,8 @@ static int rockchip_spi_unprepare_message(struct spi_master *master,
spin_unlock_irqrestore(&rs->lock, flags); spin_unlock_irqrestore(&rs->lock, flags);
spi_enable_chip(rs, 0);
return 0; return 0;
} }
...@@ -381,6 +386,8 @@ static int rockchip_spi_pio_transfer(struct rockchip_spi *rs) ...@@ -381,6 +386,8 @@ static int rockchip_spi_pio_transfer(struct rockchip_spi *rs)
if (rs->tx) if (rs->tx)
wait_for_idle(rs); wait_for_idle(rs);
spi_enable_chip(rs, 0);
return 0; return 0;
} }
...@@ -392,8 +399,10 @@ static void rockchip_spi_dma_rxcb(void *data) ...@@ -392,8 +399,10 @@ static void rockchip_spi_dma_rxcb(void *data)
spin_lock_irqsave(&rs->lock, flags); spin_lock_irqsave(&rs->lock, flags);
rs->state &= ~RXBUSY; rs->state &= ~RXBUSY;
if (!(rs->state & TXBUSY)) if (!(rs->state & TXBUSY)) {
spi_enable_chip(rs, 0);
spi_finalize_current_transfer(rs->master); spi_finalize_current_transfer(rs->master);
}
spin_unlock_irqrestore(&rs->lock, flags); spin_unlock_irqrestore(&rs->lock, flags);
} }
...@@ -409,8 +418,10 @@ static void rockchip_spi_dma_txcb(void *data) ...@@ -409,8 +418,10 @@ static void rockchip_spi_dma_txcb(void *data)
spin_lock_irqsave(&rs->lock, flags); spin_lock_irqsave(&rs->lock, flags);
rs->state &= ~TXBUSY; rs->state &= ~TXBUSY;
if (!(rs->state & RXBUSY)) if (!(rs->state & RXBUSY)) {
spi_enable_chip(rs, 0);
spi_finalize_current_transfer(rs->master); spi_finalize_current_transfer(rs->master);
}
spin_unlock_irqrestore(&rs->lock, flags); spin_unlock_irqrestore(&rs->lock, flags);
} }
...@@ -496,12 +507,19 @@ static void rockchip_spi_config(struct rockchip_spi *rs) ...@@ -496,12 +507,19 @@ static void rockchip_spi_config(struct rockchip_spi *rs)
dmacr |= RF_DMA_EN; dmacr |= RF_DMA_EN;
} }
if (WARN_ON(rs->speed > MAX_SCLK_OUT))
rs->speed = MAX_SCLK_OUT;
/* the minimum divsor is 2 */
if (rs->max_freq < 2 * rs->speed) {
clk_set_rate(rs->spiclk, 2 * rs->speed);
rs->max_freq = clk_get_rate(rs->spiclk);
}
/* div doesn't support odd number */ /* div doesn't support odd number */
div = max_t(u32, rs->max_freq / rs->speed, 1); div = max_t(u32, rs->max_freq / rs->speed, 1);
div = (div + 1) & 0xfffe; div = (div + 1) & 0xfffe;
spi_enable_chip(rs, 0);
writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0); writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0);
writel_relaxed(rs->len - 1, rs->regs + ROCKCHIP_SPI_CTRLR1); writel_relaxed(rs->len - 1, rs->regs + ROCKCHIP_SPI_CTRLR1);
...@@ -515,8 +533,6 @@ static void rockchip_spi_config(struct rockchip_spi *rs) ...@@ -515,8 +533,6 @@ static void rockchip_spi_config(struct rockchip_spi *rs)
spi_set_clk(rs, div); spi_set_clk(rs, div);
dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div); dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div);
spi_enable_chip(rs, 1);
} }
static int rockchip_spi_transfer_one( static int rockchip_spi_transfer_one(
...@@ -524,7 +540,7 @@ static int rockchip_spi_transfer_one( ...@@ -524,7 +540,7 @@ static int rockchip_spi_transfer_one(
struct spi_device *spi, struct spi_device *spi,
struct spi_transfer *xfer) struct spi_transfer *xfer)
{ {
int ret = 0; int ret = 1;
struct rockchip_spi *rs = spi_master_get_devdata(master); struct rockchip_spi *rs = spi_master_get_devdata(master);
WARN_ON(readl_relaxed(rs->regs + ROCKCHIP_SPI_SSIENR) && WARN_ON(readl_relaxed(rs->regs + ROCKCHIP_SPI_SSIENR) &&
...@@ -556,17 +572,27 @@ static int rockchip_spi_transfer_one( ...@@ -556,17 +572,27 @@ static int rockchip_spi_transfer_one(
rs->tmode = CR0_XFM_RO; rs->tmode = CR0_XFM_RO;
/* we need prepare dma before spi was enabled */ /* we need prepare dma before spi was enabled */
if (master->can_dma && master->can_dma(master, spi, xfer)) { if (master->can_dma && master->can_dma(master, spi, xfer))
rs->use_dma = 1; rs->use_dma = 1;
rockchip_spi_prepare_dma(rs); else
} else {
rs->use_dma = 0; rs->use_dma = 0;
}
rockchip_spi_config(rs); rockchip_spi_config(rs);
if (!rs->use_dma) if (rs->use_dma) {
if (rs->tmode == CR0_XFM_RO) {
/* rx: dma must be prepared first */
rockchip_spi_prepare_dma(rs);
spi_enable_chip(rs, 1);
} else {
/* tx or tr: spi must be enabled first */
spi_enable_chip(rs, 1);
rockchip_spi_prepare_dma(rs);
}
} else {
spi_enable_chip(rs, 1);
ret = rockchip_spi_pio_transfer(rs); ret = rockchip_spi_pio_transfer(rs);
}
return ret; return ret;
} }
......
...@@ -82,10 +82,11 @@ struct spidev_data { ...@@ -82,10 +82,11 @@ struct spidev_data {
struct spi_device *spi; struct spi_device *spi;
struct list_head device_entry; struct list_head device_entry;
/* buffer is NULL unless this device is open (users > 0) */ /* TX/RX buffers are NULL unless this device is open (users > 0) */
struct mutex buf_lock; struct mutex buf_lock;
unsigned users; unsigned users;
u8 *buffer; u8 *tx_buffer;
u8 *rx_buffer;
}; };
static LIST_HEAD(device_list); static LIST_HEAD(device_list);
...@@ -135,7 +136,7 @@ static inline ssize_t ...@@ -135,7 +136,7 @@ static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len) spidev_sync_write(struct spidev_data *spidev, size_t len)
{ {
struct spi_transfer t = { struct spi_transfer t = {
.tx_buf = spidev->buffer, .tx_buf = spidev->tx_buffer,
.len = len, .len = len,
}; };
struct spi_message m; struct spi_message m;
...@@ -149,7 +150,7 @@ static inline ssize_t ...@@ -149,7 +150,7 @@ static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len) spidev_sync_read(struct spidev_data *spidev, size_t len)
{ {
struct spi_transfer t = { struct spi_transfer t = {
.rx_buf = spidev->buffer, .rx_buf = spidev->rx_buffer,
.len = len, .len = len,
}; };
struct spi_message m; struct spi_message m;
...@@ -179,7 +180,7 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) ...@@ -179,7 +180,7 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
if (status > 0) { if (status > 0) {
unsigned long missing; unsigned long missing;
missing = copy_to_user(buf, spidev->buffer, status); missing = copy_to_user(buf, spidev->rx_buffer, status);
if (missing == status) if (missing == status)
status = -EFAULT; status = -EFAULT;
else else
...@@ -206,7 +207,7 @@ spidev_write(struct file *filp, const char __user *buf, ...@@ -206,7 +207,7 @@ spidev_write(struct file *filp, const char __user *buf,
spidev = filp->private_data; spidev = filp->private_data;
mutex_lock(&spidev->buf_lock); mutex_lock(&spidev->buf_lock);
missing = copy_from_user(spidev->buffer, buf, count); missing = copy_from_user(spidev->tx_buffer, buf, count);
if (missing == 0) if (missing == 0)
status = spidev_sync_write(spidev, count); status = spidev_sync_write(spidev, count);
else else
...@@ -224,7 +225,7 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -224,7 +225,7 @@ static int spidev_message(struct spidev_data *spidev,
struct spi_transfer *k_tmp; struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp; struct spi_ioc_transfer *u_tmp;
unsigned n, total; unsigned n, total;
u8 *buf; u8 *tx_buf, *rx_buf;
int status = -EFAULT; int status = -EFAULT;
spi_message_init(&msg); spi_message_init(&msg);
...@@ -236,7 +237,8 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -236,7 +237,8 @@ static int spidev_message(struct spidev_data *spidev,
* We walk the array of user-provided transfers, using each one * We walk the array of user-provided transfers, using each one
* to initialize a kernel version of the same transfer. * to initialize a kernel version of the same transfer.
*/ */
buf = spidev->buffer; tx_buf = spidev->tx_buffer;
rx_buf = spidev->rx_buffer;
total = 0; total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n; n;
...@@ -250,20 +252,21 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -250,20 +252,21 @@ static int spidev_message(struct spidev_data *spidev,
} }
if (u_tmp->rx_buf) { if (u_tmp->rx_buf) {
k_tmp->rx_buf = buf; k_tmp->rx_buf = rx_buf;
if (!access_ok(VERIFY_WRITE, (u8 __user *) if (!access_ok(VERIFY_WRITE, (u8 __user *)
(uintptr_t) u_tmp->rx_buf, (uintptr_t) u_tmp->rx_buf,
u_tmp->len)) u_tmp->len))
goto done; goto done;
} }
if (u_tmp->tx_buf) { if (u_tmp->tx_buf) {
k_tmp->tx_buf = buf; k_tmp->tx_buf = tx_buf;
if (copy_from_user(buf, (const u8 __user *) if (copy_from_user(tx_buf, (const u8 __user *)
(uintptr_t) u_tmp->tx_buf, (uintptr_t) u_tmp->tx_buf,
u_tmp->len)) u_tmp->len))
goto done; goto done;
} }
buf += k_tmp->len; tx_buf += k_tmp->len;
rx_buf += k_tmp->len;
k_tmp->cs_change = !!u_tmp->cs_change; k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->tx_nbits = u_tmp->tx_nbits; k_tmp->tx_nbits = u_tmp->tx_nbits;
...@@ -290,17 +293,17 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -290,17 +293,17 @@ static int spidev_message(struct spidev_data *spidev,
goto done; goto done;
/* copy any rx data out of bounce buffer */ /* copy any rx data out of bounce buffer */
buf = spidev->buffer; rx_buf = spidev->rx_buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) { if (u_tmp->rx_buf) {
if (__copy_to_user((u8 __user *) if (__copy_to_user((u8 __user *)
(uintptr_t) u_tmp->rx_buf, buf, (uintptr_t) u_tmp->rx_buf, rx_buf,
u_tmp->len)) { u_tmp->len)) {
status = -EFAULT; status = -EFAULT;
goto done; goto done;
} }
} }
buf += u_tmp->len; rx_buf += u_tmp->len;
} }
status = total; status = total;
...@@ -508,22 +511,41 @@ static int spidev_open(struct inode *inode, struct file *filp) ...@@ -508,22 +511,41 @@ static int spidev_open(struct inode *inode, struct file *filp)
break; break;
} }
} }
if (status == 0) {
if (!spidev->buffer) { if (status) {
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL); pr_debug("spidev: nothing for minor %d\n", iminor(inode));
if (!spidev->buffer) { goto err_find_dev;
}
if (!spidev->tx_buffer) {
spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->tx_buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM; status = -ENOMEM;
goto err_find_dev;
} }
} }
if (status == 0) {
if (!spidev->rx_buffer) {
spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->rx_buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
goto err_alloc_rx_buf;
}
}
spidev->users++; spidev->users++;
filp->private_data = spidev; filp->private_data = spidev;
nonseekable_open(inode, filp); nonseekable_open(inode, filp);
}
} else
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
mutex_unlock(&device_list_lock);
return 0;
err_alloc_rx_buf:
kfree(spidev->tx_buffer);
spidev->tx_buffer = NULL;
err_find_dev:
mutex_unlock(&device_list_lock); mutex_unlock(&device_list_lock);
return status; return status;
} }
...@@ -542,8 +564,11 @@ static int spidev_release(struct inode *inode, struct file *filp) ...@@ -542,8 +564,11 @@ static int spidev_release(struct inode *inode, struct file *filp)
if (!spidev->users) { if (!spidev->users) {
int dofree; int dofree;
kfree(spidev->buffer); kfree(spidev->tx_buffer);
spidev->buffer = NULL; spidev->tx_buffer = NULL;
kfree(spidev->rx_buffer);
spidev->rx_buffer = NULL;
/* ... after we unbound from the underlying device? */ /* ... after we unbound from the underlying device? */
spin_lock_irq(&spidev->spi_lock); spin_lock_irq(&spidev->spi_lock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment