spi: Fixes for v3.18
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. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJUTi+NAAoJECTWi3JdVIfQExgH/ijdgp1PZ2iU9J6G2/uhs5Ko EjDoKDdpzmJ+0fMmmBZOFQLwaIJIxnVxnKW6Pg3Vn5bsRPv2rKsmarzKcCs7R7pt 9U4GOsYCrzwbJ/QGQ0zS2JA2/CWb6B9IsPUGc8SPfTQGy5toK0aphfCDHb0IWysb 73G1UatkCX623H7dqNR6gRC0JSEbkas+6PiJWxa5w33jV3dqekcGUgwBl847rOZ6 jw7EPT5Vuvq6YowJsQIhIPQ2hZ4rSoXUnQI+5LsgS9nIPKAyy8zzOXlMuWClHU9s 6o/N5F132X0JIYpCROwsQ6yBlYfXCMWKu/VIF0y6LBk9jAbwGP+OgfLakFFGDGE= =NWPw -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
d9b9692af5
5 changed files with 94 additions and 42 deletions
|
@ -669,6 +669,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
|
|||
master->cleanup = dw_spi_cleanup;
|
||||
master->transfer_one_message = dw_spi_transfer_one_message;
|
||||
master->max_speed_hz = dws->max_freq;
|
||||
master->dev.of_node = dev->of_node;
|
||||
|
||||
/* Basic HW init */
|
||||
spi_hw_init(dws);
|
||||
|
|
|
@ -454,7 +454,7 @@ static int orion_spi_probe(struct platform_device *pdev)
|
|||
spi->master = master;
|
||||
|
||||
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->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
|
|
|
@ -1106,7 +1106,7 @@ static int configure_dma(struct pl022 *pl022)
|
|||
pl022->sgt_tx.nents, DMA_TO_DEVICE);
|
||||
err_tx_sgmap:
|
||||
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:
|
||||
sg_free_table(&pl022->sgt_tx);
|
||||
err_alloc_tx_sg:
|
||||
|
|
|
@ -145,6 +145,9 @@
|
|||
#define RXBUSY (1 << 0)
|
||||
#define TXBUSY (1 << 1)
|
||||
|
||||
/* sclk_out: spi master internal logic in rk3x can support 50Mhz */
|
||||
#define MAX_SCLK_OUT 50000000
|
||||
|
||||
enum rockchip_ssi_type {
|
||||
SSI_MOTO_SPI = 0,
|
||||
SSI_TI_SSP,
|
||||
|
@ -325,6 +328,8 @@ static int rockchip_spi_unprepare_message(struct spi_master *master,
|
|||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
|
||||
spi_enable_chip(rs, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -381,6 +386,8 @@ static int rockchip_spi_pio_transfer(struct rockchip_spi *rs)
|
|||
if (rs->tx)
|
||||
wait_for_idle(rs);
|
||||
|
||||
spi_enable_chip(rs, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -392,8 +399,10 @@ static void rockchip_spi_dma_rxcb(void *data)
|
|||
spin_lock_irqsave(&rs->lock, flags);
|
||||
|
||||
rs->state &= ~RXBUSY;
|
||||
if (!(rs->state & TXBUSY))
|
||||
if (!(rs->state & TXBUSY)) {
|
||||
spi_enable_chip(rs, 0);
|
||||
spi_finalize_current_transfer(rs->master);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
}
|
||||
|
@ -409,8 +418,10 @@ static void rockchip_spi_dma_txcb(void *data)
|
|||
spin_lock_irqsave(&rs->lock, flags);
|
||||
|
||||
rs->state &= ~TXBUSY;
|
||||
if (!(rs->state & RXBUSY))
|
||||
if (!(rs->state & RXBUSY)) {
|
||||
spi_enable_chip(rs, 0);
|
||||
spi_finalize_current_transfer(rs->master);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
}
|
||||
|
@ -496,12 +507,19 @@ static void rockchip_spi_config(struct rockchip_spi *rs)
|
|||
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 = max_t(u32, rs->max_freq / rs->speed, 1);
|
||||
div = (div + 1) & 0xfffe;
|
||||
|
||||
spi_enable_chip(rs, 0);
|
||||
|
||||
writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0);
|
||||
|
||||
writel_relaxed(rs->len - 1, rs->regs + ROCKCHIP_SPI_CTRLR1);
|
||||
|
@ -515,8 +533,6 @@ static void rockchip_spi_config(struct rockchip_spi *rs)
|
|||
spi_set_clk(rs, div);
|
||||
|
||||
dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div);
|
||||
|
||||
spi_enable_chip(rs, 1);
|
||||
}
|
||||
|
||||
static int rockchip_spi_transfer_one(
|
||||
|
@ -524,7 +540,7 @@ static int rockchip_spi_transfer_one(
|
|||
struct spi_device *spi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret = 1;
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
WARN_ON(readl_relaxed(rs->regs + ROCKCHIP_SPI_SSIENR) &&
|
||||
|
@ -556,17 +572,27 @@ static int rockchip_spi_transfer_one(
|
|||
rs->tmode = CR0_XFM_RO;
|
||||
|
||||
/* 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;
|
||||
rockchip_spi_prepare_dma(rs);
|
||||
} else {
|
||||
else
|
||||
rs->use_dma = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -82,10 +82,11 @@ struct spidev_data {
|
|||
struct spi_device *spi;
|
||||
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;
|
||||
unsigned users;
|
||||
u8 *buffer;
|
||||
u8 *tx_buffer;
|
||||
u8 *rx_buffer;
|
||||
};
|
||||
|
||||
static LIST_HEAD(device_list);
|
||||
|
@ -135,7 +136,7 @@ static inline ssize_t
|
|||
spidev_sync_write(struct spidev_data *spidev, size_t len)
|
||||
{
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = spidev->buffer,
|
||||
.tx_buf = spidev->tx_buffer,
|
||||
.len = len,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
@ -149,7 +150,7 @@ static inline ssize_t
|
|||
spidev_sync_read(struct spidev_data *spidev, size_t len)
|
||||
{
|
||||
struct spi_transfer t = {
|
||||
.rx_buf = spidev->buffer,
|
||||
.rx_buf = spidev->rx_buffer,
|
||||
.len = len,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
@ -179,7 +180,7 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
|
|||
if (status > 0) {
|
||||
unsigned long missing;
|
||||
|
||||
missing = copy_to_user(buf, spidev->buffer, status);
|
||||
missing = copy_to_user(buf, spidev->rx_buffer, status);
|
||||
if (missing == status)
|
||||
status = -EFAULT;
|
||||
else
|
||||
|
@ -206,7 +207,7 @@ spidev_write(struct file *filp, const char __user *buf,
|
|||
spidev = filp->private_data;
|
||||
|
||||
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)
|
||||
status = spidev_sync_write(spidev, count);
|
||||
else
|
||||
|
@ -224,7 +225,7 @@ static int spidev_message(struct spidev_data *spidev,
|
|||
struct spi_transfer *k_tmp;
|
||||
struct spi_ioc_transfer *u_tmp;
|
||||
unsigned n, total;
|
||||
u8 *buf;
|
||||
u8 *tx_buf, *rx_buf;
|
||||
int status = -EFAULT;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
@ -236,7 +237,8 @@ static int spidev_message(struct spidev_data *spidev,
|
|||
* We walk the array of user-provided transfers, using each one
|
||||
* to initialize a kernel version of the same transfer.
|
||||
*/
|
||||
buf = spidev->buffer;
|
||||
tx_buf = spidev->tx_buffer;
|
||||
rx_buf = spidev->rx_buffer;
|
||||
total = 0;
|
||||
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
|
||||
n;
|
||||
|
@ -250,20 +252,21 @@ static int spidev_message(struct spidev_data *spidev,
|
|||
}
|
||||
|
||||
if (u_tmp->rx_buf) {
|
||||
k_tmp->rx_buf = buf;
|
||||
k_tmp->rx_buf = rx_buf;
|
||||
if (!access_ok(VERIFY_WRITE, (u8 __user *)
|
||||
(uintptr_t) u_tmp->rx_buf,
|
||||
u_tmp->len))
|
||||
goto done;
|
||||
}
|
||||
if (u_tmp->tx_buf) {
|
||||
k_tmp->tx_buf = buf;
|
||||
if (copy_from_user(buf, (const u8 __user *)
|
||||
k_tmp->tx_buf = tx_buf;
|
||||
if (copy_from_user(tx_buf, (const u8 __user *)
|
||||
(uintptr_t) u_tmp->tx_buf,
|
||||
u_tmp->len))
|
||||
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->tx_nbits = u_tmp->tx_nbits;
|
||||
|
@ -290,17 +293,17 @@ static int spidev_message(struct spidev_data *spidev,
|
|||
goto done;
|
||||
|
||||
/* 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++) {
|
||||
if (u_tmp->rx_buf) {
|
||||
if (__copy_to_user((u8 __user *)
|
||||
(uintptr_t) u_tmp->rx_buf, buf,
|
||||
(uintptr_t) u_tmp->rx_buf, rx_buf,
|
||||
u_tmp->len)) {
|
||||
status = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
buf += u_tmp->len;
|
||||
rx_buf += u_tmp->len;
|
||||
}
|
||||
status = total;
|
||||
|
||||
|
@ -508,22 +511,41 @@ static int spidev_open(struct inode *inode, struct file *filp)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (status == 0) {
|
||||
if (!spidev->buffer) {
|
||||
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
|
||||
if (!spidev->buffer) {
|
||||
|
||||
if (status) {
|
||||
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
|
||||
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");
|
||||
status = -ENOMEM;
|
||||
goto err_find_dev;
|
||||
}
|
||||
}
|
||||
if (status == 0) {
|
||||
spidev->users++;
|
||||
filp->private_data = spidev;
|
||||
nonseekable_open(inode, filp);
|
||||
}
|
||||
} else
|
||||
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
|
||||
|
||||
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++;
|
||||
filp->private_data = spidev;
|
||||
nonseekable_open(inode, filp);
|
||||
|
||||
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);
|
||||
return status;
|
||||
}
|
||||
|
@ -542,8 +564,11 @@ static int spidev_release(struct inode *inode, struct file *filp)
|
|||
if (!spidev->users) {
|
||||
int dofree;
|
||||
|
||||
kfree(spidev->buffer);
|
||||
spidev->buffer = NULL;
|
||||
kfree(spidev->tx_buffer);
|
||||
spidev->tx_buffer = NULL;
|
||||
|
||||
kfree(spidev->rx_buffer);
|
||||
spidev->rx_buffer = NULL;
|
||||
|
||||
/* ... after we unbound from the underlying device? */
|
||||
spin_lock_irq(&spidev->spi_lock);
|
||||
|
|
Loading…
Reference in a new issue