From 0cf942d75a6acfa11a41f63330d8780901eda4af Mon Sep 17 00:00:00 2001 From: Eric BENARD Date: Mon, 12 May 2008 14:02:01 -0700 Subject: spi: pxa2xx_spi clock resume bugfix There is a typo in pxa2xx_spi.c, comment says "Enable the SSP clock", code says: clk_disable ... so after resume, the SSP is dead. Signed-off-by: David Brownell Cc: Ned Forrester Cc: Stephen Street Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/spi/pxa2xx_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/pxa2xx_spi.c b/drivers/spi/pxa2xx_spi.c index 654bb58be630..0c452c46ab07 100644 --- a/drivers/spi/pxa2xx_spi.c +++ b/drivers/spi/pxa2xx_spi.c @@ -1567,7 +1567,7 @@ static int pxa2xx_spi_resume(struct platform_device *pdev) int status = 0; /* Enable the SSP clock */ - clk_disable(ssp->clk); + clk_enable(ssp->clk); /* Start the queue running */ status = start_queue(drv_data); -- cgit v1.2.3 From c9bfcb3151040cff6714542d1da04ccd7e2d3efc Mon Sep 17 00:00:00 2001 From: Joakim Tjernlund Date: Mon, 12 May 2008 14:02:30 -0700 Subject: spi_mpc83xx: much improved driver The current driver may cause glitches on SPI CLK line since one must disable the SPI controller before changing any HW settings. Fix this by implementing a local spi_transfer function that won't change speed and/or word size while CS is active. While doing that heavy lifting a few other issues were addressed too: - Make word size 16 and 32 work too. - Honor bits_per_word and speed_hz in spi transaction. - Optimize the common path. This also stops using the "bitbang" framework (except for a few constants). [Roel Kluin <12o3l@tiscali.nl>: "irq" needs to be signed] Signed-off-by: Joakim Tjernlund Signed-off-by: David Brownell Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/spi/Kconfig | 1 - drivers/spi/spi_mpc83xx.c | 411 +++++++++++++++++++++++++++++++--------------- 2 files changed, 282 insertions(+), 130 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index fae9e8f3d092..66ec5d8808de 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -126,7 +126,6 @@ config SPI_MPC52xx_PSC config SPI_MPC83xx tristate "Freescale MPC83xx/QUICC Engine SPI controller" depends on SPI_MASTER && (PPC_83xx || QUICC_ENGINE) && EXPERIMENTAL - select SPI_BITBANG help This enables using the Freescale MPC83xx and QUICC Engine SPI controllers in master mode. diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c index 189f706b9e4b..6832da6f7109 100644 --- a/drivers/spi/spi_mpc83xx.c +++ b/drivers/spi/spi_mpc83xx.c @@ -49,6 +49,7 @@ struct mpc83xx_spi_reg { #define SPMODE_LEN(x) ((x) << 20) #define SPMODE_PM(x) ((x) << 16) #define SPMODE_OP (1 << 14) +#define SPMODE_CG(x) ((x) << 7) /* * Default for SPI Mode: @@ -67,10 +68,6 @@ struct mpc83xx_spi_reg { /* SPI Controller driver's private data. */ struct mpc83xx_spi { - /* bitbang has to be first */ - struct spi_bitbang bitbang; - struct completion done; - struct mpc83xx_spi_reg __iomem *base; /* rx & tx bufs from the spi_transfer */ @@ -82,7 +79,7 @@ struct mpc83xx_spi { u32(*get_tx) (struct mpc83xx_spi *); unsigned int count; - u32 irq; + int irq; unsigned nsecs; /* (clock cycle time)/2 */ @@ -94,6 +91,25 @@ struct mpc83xx_spi { void (*activate_cs) (u8 cs, u8 polarity); void (*deactivate_cs) (u8 cs, u8 polarity); + + u8 busy; + + struct workqueue_struct *workqueue; + struct work_struct work; + + struct list_head queue; + spinlock_t lock; + + struct completion done; +}; + +struct spi_mpc83xx_cs { + /* functions to deal with different sized buffers */ + void (*get_rx) (u32 rx_data, struct mpc83xx_spi *); + u32 (*get_tx) (struct mpc83xx_spi *); + u32 rx_shift; /* RX data reg shift when in qe mode */ + u32 tx_shift; /* TX data reg shift when in qe mode */ + u32 hw_mode; /* Holds HW mode register settings */ }; static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val) @@ -137,6 +153,7 @@ static void mpc83xx_spi_chipselect(struct spi_device *spi, int value) { struct mpc83xx_spi *mpc83xx_spi; u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0; + struct spi_mpc83xx_cs *cs = spi->controller_state; mpc83xx_spi = spi_master_get_devdata(spi->master); @@ -147,50 +164,26 @@ static void mpc83xx_spi_chipselect(struct spi_device *spi, int value) if (value == BITBANG_CS_ACTIVE) { u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode); - u32 len = spi->bits_per_word; - u8 pm; - if (len == 32) - len = 0; - else - len = len - 1; - - /* mask out bits we are going to set */ - regval &= ~(SPMODE_CP_BEGIN_EDGECLK | SPMODE_CI_INACTIVEHIGH - | SPMODE_LEN(0xF) | SPMODE_DIV16 - | SPMODE_PM(0xF) | SPMODE_REV | SPMODE_LOOP); - - if (spi->mode & SPI_CPHA) - regval |= SPMODE_CP_BEGIN_EDGECLK; - if (spi->mode & SPI_CPOL) - regval |= SPMODE_CI_INACTIVEHIGH; - if (!(spi->mode & SPI_LSB_FIRST)) - regval |= SPMODE_REV; - if (spi->mode & SPI_LOOP) - regval |= SPMODE_LOOP; - - regval |= SPMODE_LEN(len); - - if ((mpc83xx_spi->spibrg / spi->max_speed_hz) >= 64) { - pm = mpc83xx_spi->spibrg / (spi->max_speed_hz * 64) - 1; - if (pm > 0x0f) { - dev_err(&spi->dev, "Requested speed is too " - "low: %d Hz. Will use %d Hz instead.\n", - spi->max_speed_hz, - mpc83xx_spi->spibrg / 1024); - pm = 0x0f; - } - regval |= SPMODE_PM(pm) | SPMODE_DIV16; - } else { - pm = mpc83xx_spi->spibrg / (spi->max_speed_hz * 4); - if (pm) - pm--; - regval |= SPMODE_PM(pm); + mpc83xx_spi->rx_shift = cs->rx_shift; + mpc83xx_spi->tx_shift = cs->tx_shift; + mpc83xx_spi->get_rx = cs->get_rx; + mpc83xx_spi->get_tx = cs->get_tx; + + if (cs->hw_mode != regval) { + unsigned long flags; + void *tmp_ptr = &mpc83xx_spi->base->mode; + + regval = cs->hw_mode; + /* Turn off IRQs locally to minimize time that + * SPI is disabled + */ + local_irq_save(flags); + /* Turn off SPI unit prior changing mode */ + mpc83xx_spi_write_reg(tmp_ptr, regval & ~SPMODE_ENABLE); + mpc83xx_spi_write_reg(tmp_ptr, regval); + local_irq_restore(flags); } - - /* Turn off SPI unit prior changing mode */ - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0); - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval); if (mpc83xx_spi->activate_cs) mpc83xx_spi->activate_cs(spi->chip_select, pol); } @@ -201,8 +194,9 @@ int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) { struct mpc83xx_spi *mpc83xx_spi; u32 regval; - u8 bits_per_word; + u8 bits_per_word, pm; u32 hz; + struct spi_mpc83xx_cs *cs = spi->controller_state; mpc83xx_spi = spi_master_get_devdata(spi->master); @@ -223,61 +217,191 @@ int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) || ((bits_per_word > 16) && (bits_per_word != 32))) return -EINVAL; - mpc83xx_spi->rx_shift = 0; - mpc83xx_spi->tx_shift = 0; + if (!hz) + hz = spi->max_speed_hz; + + cs->rx_shift = 0; + cs->tx_shift = 0; if (bits_per_word <= 8) { - mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8; - mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8; + cs->get_rx = mpc83xx_spi_rx_buf_u8; + cs->get_tx = mpc83xx_spi_tx_buf_u8; if (mpc83xx_spi->qe_mode) { - mpc83xx_spi->rx_shift = 16; - mpc83xx_spi->tx_shift = 24; + cs->rx_shift = 16; + cs->tx_shift = 24; } } else if (bits_per_word <= 16) { - mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16; - mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16; + cs->get_rx = mpc83xx_spi_rx_buf_u16; + cs->get_tx = mpc83xx_spi_tx_buf_u16; if (mpc83xx_spi->qe_mode) { - mpc83xx_spi->rx_shift = 16; - mpc83xx_spi->tx_shift = 16; + cs->rx_shift = 16; + cs->tx_shift = 16; } } else if (bits_per_word <= 32) { - mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32; - mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32; + cs->get_rx = mpc83xx_spi_rx_buf_u32; + cs->get_tx = mpc83xx_spi_tx_buf_u32; } else return -EINVAL; if (mpc83xx_spi->qe_mode && spi->mode & SPI_LSB_FIRST) { - mpc83xx_spi->tx_shift = 0; + cs->tx_shift = 0; if (bits_per_word <= 8) - mpc83xx_spi->rx_shift = 8; + cs->rx_shift = 8; else - mpc83xx_spi->rx_shift = 0; + cs->rx_shift = 0; } - /* nsecs = (clock period)/2 */ - if (!hz) - hz = spi->max_speed_hz; - mpc83xx_spi->nsecs = (1000000000 / 2) / hz; - if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000) - return -EINVAL; + mpc83xx_spi->rx_shift = cs->rx_shift; + mpc83xx_spi->tx_shift = cs->tx_shift; + mpc83xx_spi->get_rx = cs->get_rx; + mpc83xx_spi->get_tx = cs->get_tx; if (bits_per_word == 32) bits_per_word = 0; else bits_per_word = bits_per_word - 1; - regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode); - /* mask out bits we are going to set */ - regval &= ~(SPMODE_LEN(0xF) | SPMODE_REV); - regval |= SPMODE_LEN(bits_per_word); - if (!(spi->mode & SPI_LSB_FIRST)) - regval |= SPMODE_REV; + cs->hw_mode &= ~(SPMODE_LEN(0xF) | SPMODE_DIV16 + | SPMODE_PM(0xF)); + + cs->hw_mode |= SPMODE_LEN(bits_per_word); + + if ((mpc83xx_spi->spibrg / hz) >= 64) { + pm = mpc83xx_spi->spibrg / (hz * 64) - 1; + if (pm > 0x0f) { + dev_err(&spi->dev, "Requested speed is too " + "low: %d Hz. Will use %d Hz instead.\n", + hz, mpc83xx_spi->spibrg / 1024); + pm = 0x0f; + } + cs->hw_mode |= SPMODE_PM(pm) | SPMODE_DIV16; + } else { + pm = mpc83xx_spi->spibrg / (hz * 4); + if (pm) + pm--; + cs->hw_mode |= SPMODE_PM(pm); + } + regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode); + if (cs->hw_mode != regval) { + unsigned long flags; + void *tmp_ptr = &mpc83xx_spi->base->mode; + + regval = cs->hw_mode; + /* Turn off IRQs locally to minimize time + * that SPI is disabled + */ + local_irq_save(flags); + /* Turn off SPI unit prior changing mode */ + mpc83xx_spi_write_reg(tmp_ptr, regval & ~SPMODE_ENABLE); + mpc83xx_spi_write_reg(tmp_ptr, regval); + local_irq_restore(flags); + } + return 0; +} - /* Turn off SPI unit prior changing mode */ - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0); - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval); +static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct mpc83xx_spi *mpc83xx_spi; + u32 word, len, bits_per_word; - return 0; + mpc83xx_spi = spi_master_get_devdata(spi->master); + + mpc83xx_spi->tx = t->tx_buf; + mpc83xx_spi->rx = t->rx_buf; + bits_per_word = spi->bits_per_word; + if (t->bits_per_word) + bits_per_word = t->bits_per_word; + len = t->len; + if (bits_per_word > 8) + len /= 2; + if (bits_per_word > 16) + len /= 2; + mpc83xx_spi->count = len; + INIT_COMPLETION(mpc83xx_spi->done); + + /* enable rx ints */ + mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE); + + /* transmit word */ + word = mpc83xx_spi->get_tx(mpc83xx_spi); + mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word); + + wait_for_completion(&mpc83xx_spi->done); + + /* disable rx ints */ + mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0); + + return mpc83xx_spi->count; +} + +static void mpc83xx_spi_work(struct work_struct *work) +{ + struct mpc83xx_spi *mpc83xx_spi = + container_of(work, struct mpc83xx_spi, work); + + spin_lock_irq(&mpc83xx_spi->lock); + mpc83xx_spi->busy = 1; + while (!list_empty(&mpc83xx_spi->queue)) { + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + unsigned cs_change; + int status, nsecs = 50; + + m = container_of(mpc83xx_spi->queue.next, + struct spi_message, queue); + list_del_init(&m->queue); + spin_unlock_irq(&mpc83xx_spi->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) { + /* Don't allow changes if CS is active */ + status = -EINVAL; + + if (cs_change) + status = mpc83xx_spi_setup_transfer(spi, t); + if (status < 0) + break; + } + + if (cs_change) + mpc83xx_spi_chipselect(spi, BITBANG_CS_ACTIVE); + cs_change = t->cs_change; + if (t->len) + status = mpc83xx_spi_bufs(spi, t); + if (status) { + status = -EMSGSIZE; + break; + } + m->actual_length += t->len; + + if (t->delay_usecs) + udelay(t->delay_usecs); + + if (cs_change) { + ndelay(nsecs); + mpc83xx_spi_chipselect(spi, BITBANG_CS_INACTIVE); + ndelay(nsecs); + } + } + + m->status = status; + m->complete(m->context); + + if (status || !cs_change) { + ndelay(nsecs); + mpc83xx_spi_chipselect(spi, BITBANG_CS_INACTIVE); + } + + mpc83xx_spi_setup_transfer(spi, NULL); + + spin_lock_irq(&mpc83xx_spi->lock); + } + mpc83xx_spi->busy = 0; + spin_unlock_irq(&mpc83xx_spi->lock); } /* the spi->mode bits understood by this driver: */ @@ -286,9 +410,10 @@ int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) static int mpc83xx_spi_setup(struct spi_device *spi) { - struct spi_bitbang *bitbang; struct mpc83xx_spi *mpc83xx_spi; int retval; + u32 hw_mode; + struct spi_mpc83xx_cs *cs = spi->controller_state; if (spi->mode & ~MODEBITS) { dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", @@ -299,63 +424,56 @@ static int mpc83xx_spi_setup(struct spi_device *spi) if (!spi->max_speed_hz) return -EINVAL; - bitbang = spi_master_get_devdata(spi->master); + if (!cs) { + cs = kzalloc(sizeof *cs, GFP_KERNEL); + if (!cs) + return -ENOMEM; + spi->controller_state = cs; + } mpc83xx_spi = spi_master_get_devdata(spi->master); if (!spi->bits_per_word) spi->bits_per_word = 8; + hw_mode = cs->hw_mode; /* Save orginal settings */ + cs->hw_mode = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode); + /* mask out bits we are going to set */ + cs->hw_mode &= ~(SPMODE_CP_BEGIN_EDGECLK | SPMODE_CI_INACTIVEHIGH + | SPMODE_REV | SPMODE_LOOP); + + if (spi->mode & SPI_CPHA) + cs->hw_mode |= SPMODE_CP_BEGIN_EDGECLK; + if (spi->mode & SPI_CPOL) + cs->hw_mode |= SPMODE_CI_INACTIVEHIGH; + if (!(spi->mode & SPI_LSB_FIRST)) + cs->hw_mode |= SPMODE_REV; + if (spi->mode & SPI_LOOP) + cs->hw_mode |= SPMODE_LOOP; + retval = mpc83xx_spi_setup_transfer(spi, NULL); - if (retval < 0) + if (retval < 0) { + cs->hw_mode = hw_mode; /* Restore settings */ return retval; + } - dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n", + dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u Hz\n", __func__, spi->mode & (SPI_CPOL | SPI_CPHA), - spi->bits_per_word, 2 * mpc83xx_spi->nsecs); - + spi->bits_per_word, spi->max_speed_hz); +#if 0 /* Don't think this is needed */ /* NOTE we _need_ to call chipselect() early, ideally with adapter * setup, unless the hardware defaults cooperate to avoid confusion * between normal (active low) and inverted chipselects. */ /* deselect chip (low or high) */ - spin_lock(&bitbang->lock); - if (!bitbang->busy) { - bitbang->chipselect(spi, BITBANG_CS_INACTIVE); - ndelay(mpc83xx_spi->nsecs); - } - spin_unlock(&bitbang->lock); - + spin_lock(&mpc83xx_spi->lock); + if (!mpc83xx_spi->busy) + mpc83xx_spi_chipselect(spi, BITBANG_CS_INACTIVE); + spin_unlock(&mpc83xx_spi->lock); +#endif return 0; } -static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t) -{ - struct mpc83xx_spi *mpc83xx_spi; - u32 word; - - mpc83xx_spi = spi_master_get_devdata(spi->master); - - mpc83xx_spi->tx = t->tx_buf; - mpc83xx_spi->rx = t->rx_buf; - mpc83xx_spi->count = t->len; - INIT_COMPLETION(mpc83xx_spi->done); - - /* enable rx ints */ - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE); - - /* transmit word */ - word = mpc83xx_spi->get_tx(mpc83xx_spi); - mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word); - - wait_for_completion(&mpc83xx_spi->done); - - /* disable rx ints */ - mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0); - - return t->len - mpc83xx_spi->count; -} - irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data) { struct mpc83xx_spi *mpc83xx_spi = context_data; @@ -395,6 +513,28 @@ irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data) return ret; } +static int mpc83xx_spi_transfer(struct spi_device *spi, + struct spi_message *m) +{ + struct mpc83xx_spi *mpc83xx_spi = spi_master_get_devdata(spi->master); + unsigned long flags; + + m->actual_length = 0; + m->status = -EINPROGRESS; + + spin_lock_irqsave(&mpc83xx_spi->lock, flags); + list_add_tail(&m->queue, &mpc83xx_spi->queue); + queue_work(mpc83xx_spi->workqueue, &mpc83xx_spi->work); + spin_unlock_irqrestore(&mpc83xx_spi->lock, flags); + + return 0; +} + + +static void mpc83xx_spi_cleanup(struct spi_device *spi) +{ + kfree(spi->controller_state); +} static int __init mpc83xx_spi_probe(struct platform_device *dev) { @@ -426,11 +566,11 @@ static int __init mpc83xx_spi_probe(struct platform_device *dev) ret = -ENODEV; goto free_master; } + master->setup = mpc83xx_spi_setup; + master->transfer = mpc83xx_spi_transfer; + master->cleanup = mpc83xx_spi_cleanup; + mpc83xx_spi = spi_master_get_devdata(master); - mpc83xx_spi->bitbang.master = spi_master_get(master); - mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect; - mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer; - mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs; mpc83xx_spi->activate_cs = pdata->activate_cs; mpc83xx_spi->deactivate_cs = pdata->deactivate_cs; mpc83xx_spi->qe_mode = pdata->qe_mode; @@ -445,7 +585,6 @@ static int __init mpc83xx_spi_probe(struct platform_device *dev) mpc83xx_spi->tx_shift = 24; } - mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup; init_completion(&mpc83xx_spi->done); mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1); @@ -483,11 +622,21 @@ static int __init mpc83xx_spi_probe(struct platform_device *dev) regval |= SPMODE_OP; mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval); + spin_lock_init(&mpc83xx_spi->lock); + init_completion(&mpc83xx_spi->done); + INIT_WORK(&mpc83xx_spi->work, mpc83xx_spi_work); + INIT_LIST_HEAD(&mpc83xx_spi->queue); - ret = spi_bitbang_start(&mpc83xx_spi->bitbang); - - if (ret != 0) + mpc83xx_spi->workqueue = create_singlethread_workqueue( + master->dev.parent->bus_id); + if (mpc83xx_spi->workqueue == NULL) { + ret = -EBUSY; goto free_irq; + } + + ret = spi_register_master(master); + if (ret < 0) + goto unreg_master; printk(KERN_INFO "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n", @@ -495,6 +644,8 @@ static int __init mpc83xx_spi_probe(struct platform_device *dev) return ret; +unreg_master: + destroy_workqueue(mpc83xx_spi->workqueue); free_irq: free_irq(mpc83xx_spi->irq, mpc83xx_spi); unmap_io: @@ -515,10 +666,12 @@ static int __exit mpc83xx_spi_remove(struct platform_device *dev) master = platform_get_drvdata(dev); mpc83xx_spi = spi_master_get_devdata(master); - spi_bitbang_stop(&mpc83xx_spi->bitbang); + flush_workqueue(mpc83xx_spi->workqueue); + destroy_workqueue(mpc83xx_spi->workqueue); + spi_unregister_master(master); + free_irq(mpc83xx_spi->irq, mpc83xx_spi); iounmap(mpc83xx_spi->base); - spi_master_put(mpc83xx_spi->bitbang.master); return 0; } -- cgit v1.2.3 From 57cc097931e2d28a27e19515c549dc301ba6b6b2 Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Wed, 14 May 2008 16:05:29 -0700 Subject: mpc5200_psc_spi: typo fix in header block Signed-off-by: Grant Likely Acked-by: David Brownell Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/spi/mpc52xx_psc_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/mpc52xx_psc_spi.c b/drivers/spi/mpc52xx_psc_spi.c index 90729469d481..681d62325d3d 100644 --- a/drivers/spi/mpc52xx_psc_spi.c +++ b/drivers/spi/mpc52xx_psc_spi.c @@ -1,5 +1,5 @@ /* - * MPC52xx SPC in SPI mode driver. + * MPC52xx PSC in SPI mode driver. * * Maintainer: Dragos Carp * -- cgit v1.2.3 From 25d5cb4b0375e5864ec0ccf35e12ff1d1b5cf3f0 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Fri, 23 May 2008 13:05:03 -0700 Subject: spi: remove some spidev oops-on-rmmod paths Somehow the spidev code forgot to include a critical mechanism: when the underlying device is removed (e.g. spi_master rmmod), open file descriptors must be prevented from issuing new I/O requests to that device. On penalty of the oopsing reported by Sebastian Siewior ... This is a partial fix, adding handshaking between the lower level (SPI messaging) and the file operations using the spi_dev. (It also fixes an issue where reads and writes didn't return the number of bytes sent or received.) There's still a refcounting issue to be addressed (separately). Signed-off-by: David Brownell Reported-by: Sebastian Siewior Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/spi/spidev.c | 102 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 13 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index b3518ca9f04e..41620c0fb046 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -68,6 +68,7 @@ static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; struct spidev_data { struct device dev; + spinlock_t spi_lock; struct spi_device *spi; struct list_head device_entry; @@ -85,12 +86,75 @@ MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); /*-------------------------------------------------------------------------*/ +/* + * We can't use the standard synchronous wrappers for file I/O; we + * need to protect against async removal of the underlying spi_device. + */ +static void spidev_complete(void *arg) +{ + complete(arg); +} + +static ssize_t +spidev_sync(struct spidev_data *spidev, struct spi_message *message) +{ + DECLARE_COMPLETION_ONSTACK(done); + int status; + + message->complete = spidev_complete; + message->context = &done; + + spin_lock_irq(&spidev->spi_lock); + if (spidev->spi == NULL) + status = -ESHUTDOWN; + else + status = spi_async(spidev->spi, message); + spin_unlock_irq(&spidev->spi_lock); + + if (status == 0) { + wait_for_completion(&done); + status = message->status; + if (status == 0) + status = message->actual_length; + } + return status; +} + +static inline ssize_t +spidev_sync_write(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .tx_buf = spidev->buffer, + .len = len, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +static inline ssize_t +spidev_sync_read(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .rx_buf = spidev->buffer, + .len = len, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +/*-------------------------------------------------------------------------*/ + /* Read-only message with current device setup */ static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct spidev_data *spidev; - struct spi_device *spi; ssize_t status = 0; /* chipselect only toggles at start or end of operation */ @@ -98,10 +162,9 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) return -EMSGSIZE; spidev = filp->private_data; - spi = spidev->spi; mutex_lock(&spidev->buf_lock); - status = spi_read(spi, spidev->buffer, count); + status = spidev_sync_read(spidev, count); if (status == 0) { unsigned long missing; @@ -122,7 +185,6 @@ spidev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct spidev_data *spidev; - struct spi_device *spi; ssize_t status = 0; unsigned long missing; @@ -131,12 +193,11 @@ spidev_write(struct file *filp, const char __user *buf, return -EMSGSIZE; spidev = filp->private_data; - spi = spidev->spi; mutex_lock(&spidev->buf_lock); missing = copy_from_user(spidev->buffer, buf, count); if (missing == 0) { - status = spi_write(spi, spidev->buffer, count); + status = spidev_sync_write(spidev, count); if (status == 0) status = count; } else @@ -153,7 +214,6 @@ static int spidev_message(struct spidev_data *spidev, struct spi_transfer *k_xfers; struct spi_transfer *k_tmp; struct spi_ioc_transfer *u_tmp; - struct spi_device *spi = spidev->spi; unsigned n, total; u8 *buf; int status = -EFAULT; @@ -215,7 +275,7 @@ static int spidev_message(struct spidev_data *spidev, spi_message_add_tail(k_tmp, &msg); } - status = spi_sync(spi, &msg); + status = spidev_sync(spidev, &msg); if (status < 0) goto done; @@ -269,8 +329,16 @@ spidev_ioctl(struct inode *inode, struct file *filp, if (err) return -EFAULT; + /* guard against device removal before, or while, + * we issue this ioctl. + */ spidev = filp->private_data; - spi = spidev->spi; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; switch (cmd) { /* read requests */ @@ -356,8 +424,10 @@ spidev_ioctl(struct inode *inode, struct file *filp, default: /* segmented and/or full-duplex I/O request */ if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) - || _IOC_DIR(cmd) != _IOC_WRITE) - return -ENOTTY; + || _IOC_DIR(cmd) != _IOC_WRITE) { + retval = -ENOTTY; + break; + } tmp = _IOC_SIZE(cmd); if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { @@ -385,6 +455,7 @@ spidev_ioctl(struct inode *inode, struct file *filp, kfree(ioc); break; } + spi_dev_put(spi); return retval; } @@ -488,6 +559,7 @@ static int spidev_probe(struct spi_device *spi) /* Initialize the driver data */ spidev->spi = spi; + spin_lock_init(&spidev->spi_lock); mutex_init(&spidev->buf_lock); INIT_LIST_HEAD(&spidev->device_entry); @@ -526,13 +598,17 @@ static int spidev_remove(struct spi_device *spi) { struct spidev_data *spidev = dev_get_drvdata(&spi->dev); - mutex_lock(&device_list_lock); + /* make sure ops on existing fds can abort cleanly */ + spin_lock_irq(&spidev->spi_lock); + spidev->spi = NULL; + spin_unlock_irq(&spidev->spi_lock); + /* prevent new opens */ + mutex_lock(&device_list_lock); list_del(&spidev->device_entry); dev_set_drvdata(&spi->dev, NULL); clear_bit(MINOR(spidev->dev.devt), minors); device_unregister(&spidev->dev); - mutex_unlock(&device_list_lock); return 0; -- cgit v1.2.3 From b2c8daddcbe03a22402ecf943bb88302601c6835 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Thu, 5 Jun 2008 22:45:50 -0700 Subject: spi: fix refcount-related spidev oops-on-rmmod This addresses other oopsing paths in "spidev" by changing how it manages refcounting. It decouples the lifecycle of the per-device data from the class device (not just the spi device): - Use class_{create,destroy} not class_{register,unregister}. - Use device_{create,destroy} not device_{register,unregister}. - Free the per-device data only when TWO conditions are true: * Driver is unbound from underlying SPI device, and * Device is no longer open (new) Also, spi_{get,set}_drvdata not dev_{get,set}_drvdata for simpler code. Signed-off-by: David Brownell Sebastian Siewior Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/spi/spidev.c | 64 ++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 30 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 41620c0fb046..799337f7fde1 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -67,11 +68,12 @@ static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP) struct spidev_data { - struct device dev; + dev_t devt; spinlock_t spi_lock; struct spi_device *spi; struct list_head device_entry; + /* buffer is NULL unless this device is open (users > 0) */ struct mutex buf_lock; unsigned users; u8 *buffer; @@ -467,7 +469,7 @@ static int spidev_open(struct inode *inode, struct file *filp) mutex_lock(&device_list_lock); list_for_each_entry(spidev, &device_list, device_entry) { - if (spidev->dev.devt == inode->i_rdev) { + if (spidev->devt == inode->i_rdev) { status = 0; break; } @@ -500,10 +502,22 @@ static int spidev_release(struct inode *inode, struct file *filp) mutex_lock(&device_list_lock); spidev = filp->private_data; filp->private_data = NULL; + + /* last close? */ spidev->users--; if (!spidev->users) { + int dofree; + kfree(spidev->buffer); spidev->buffer = NULL; + + /* ... after we unbound from the underlying device? */ + spin_lock_irq(&spidev->spi_lock); + dofree = (spidev->spi == NULL); + spin_unlock_irq(&spidev->spi_lock); + + if (dofree) + kfree(spidev); } mutex_unlock(&device_list_lock); @@ -530,19 +544,7 @@ static struct file_operations spidev_fops = { * It also simplifies memory management. */ -static void spidev_classdev_release(struct device *dev) -{ - struct spidev_data *spidev; - - spidev = container_of(dev, struct spidev_data, dev); - kfree(spidev); -} - -static struct class spidev_class = { - .name = "spidev", - .owner = THIS_MODULE, - .dev_release = spidev_classdev_release, -}; +static struct class *spidev_class; /*-------------------------------------------------------------------------*/ @@ -570,20 +572,20 @@ static int spidev_probe(struct spi_device *spi) mutex_lock(&device_list_lock); minor = find_first_zero_bit(minors, N_SPI_MINORS); if (minor < N_SPI_MINORS) { - spidev->dev.parent = &spi->dev; - spidev->dev.class = &spidev_class; - spidev->dev.devt = MKDEV(SPIDEV_MAJOR, minor); - snprintf(spidev->dev.bus_id, sizeof spidev->dev.bus_id, + struct device *dev; + + spidev->devt = MKDEV(SPIDEV_MAJOR, minor); + dev = device_create(spidev_class, &spi->dev, spidev->devt, "spidev%d.%d", spi->master->bus_num, spi->chip_select); - status = device_register(&spidev->dev); + status = IS_ERR(dev) ? PTR_ERR(dev) : 0; } else { dev_dbg(&spi->dev, "no minor number available!\n"); status = -ENODEV; } if (status == 0) { set_bit(minor, minors); - dev_set_drvdata(&spi->dev, spidev); + spi_set_drvdata(spi, spidev); list_add(&spidev->device_entry, &device_list); } mutex_unlock(&device_list_lock); @@ -596,19 +598,21 @@ static int spidev_probe(struct spi_device *spi) static int spidev_remove(struct spi_device *spi) { - struct spidev_data *spidev = dev_get_drvdata(&spi->dev); + struct spidev_data *spidev = spi_get_drvdata(spi); /* make sure ops on existing fds can abort cleanly */ spin_lock_irq(&spidev->spi_lock); spidev->spi = NULL; + spi_set_drvdata(spi, NULL); spin_unlock_irq(&spidev->spi_lock); /* prevent new opens */ mutex_lock(&device_list_lock); list_del(&spidev->device_entry); - dev_set_drvdata(&spi->dev, NULL); - clear_bit(MINOR(spidev->dev.devt), minors); - device_unregister(&spidev->dev); + device_destroy(spidev_class, spidev->devt); + clear_bit(MINOR(spidev->devt), minors); + if (spidev->users == 0) + kfree(spidev); mutex_unlock(&device_list_lock); return 0; @@ -644,15 +648,15 @@ static int __init spidev_init(void) if (status < 0) return status; - status = class_register(&spidev_class); - if (status < 0) { + spidev_class = class_create(THIS_MODULE, "spidev"); + if (IS_ERR(spidev_class)) { unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); - return status; + return PTR_ERR(spidev_class); } status = spi_register_driver(&spidev_spi); if (status < 0) { - class_unregister(&spidev_class); + class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); } return status; @@ -662,7 +666,7 @@ module_init(spidev_init); static void __exit spidev_exit(void) { spi_unregister_driver(&spidev_spi); - class_unregister(&spidev_class); + class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); } module_exit(spidev_exit); -- cgit v1.2.3