From c2573128ad1ff36a7e231799c102be2413a2f756 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Nov 2011 10:57:32 +0000 Subject: spi/s3c64xx: Log error interrupts Although the hardware supports interrupts we're not currently using them at all since for small transfers the overhead is greater than that for busy waiting and for large transfers we have interrupts from the DMA. This means that if the hardware reports an error (especially one which might not stall transfer) we might miss it. Take a first pass at dealing with such errors by enabling the interrupt if we can and logging the errors if they happen. Ideally we'd report the error via the affected transfer but since we're in master mode it's very difficult to trigger errors at present and this code is much simpler. Signed-off-by: Mark Brown Acked-by: Linus Walleij --- drivers/spi/spi-s3c64xx.c | 57 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 019a7163572f..d56066bcbb94 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,7 @@ struct s3c64xx_spi_dma_data { * @tx_dmach: Controller's DMA channel for Tx. * @sfr_start: BUS address of SPI controller regs. * @regs: Pointer to ioremap'ed controller registers. + * @irq: interrupt * @xfer_completion: To indicate completion of xfer task. * @cur_mode: Stores the active configuration of the controller. * @cur_bpw: Stores the active bits per word settings. @@ -930,6 +932,33 @@ setup_exit: return err; } +static irqreturn_t s3c64xx_spi_irq(int irq, void *data) +{ + struct s3c64xx_spi_driver_data *sdd = data; + struct spi_master *spi = sdd->master; + unsigned int val; + + val = readl(sdd->regs + S3C64XX_SPI_PENDING_CLR); + + val &= S3C64XX_SPI_PND_RX_OVERRUN_CLR | + S3C64XX_SPI_PND_RX_UNDERRUN_CLR | + S3C64XX_SPI_PND_TX_OVERRUN_CLR | + S3C64XX_SPI_PND_TX_UNDERRUN_CLR; + + writel(val, sdd->regs + S3C64XX_SPI_PENDING_CLR); + + if (val & S3C64XX_SPI_PND_RX_OVERRUN_CLR) + dev_err(&spi->dev, "RX overrun\n"); + if (val & S3C64XX_SPI_PND_RX_UNDERRUN_CLR) + dev_err(&spi->dev, "RX underrun\n"); + if (val & S3C64XX_SPI_PND_TX_OVERRUN_CLR) + dev_err(&spi->dev, "TX overrun\n"); + if (val & S3C64XX_SPI_PND_TX_UNDERRUN_CLR) + dev_err(&spi->dev, "TX underrun\n"); + + return IRQ_HANDLED; +} + static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel) { struct s3c64xx_spi_info *sci = sdd->cntrlr_info; @@ -970,7 +999,8 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev) struct s3c64xx_spi_driver_data *sdd; struct s3c64xx_spi_info *sci; struct spi_master *master; - int ret; + int ret, irq; + char clk_name[16]; if (pdev->id < 0) { dev_err(&pdev->dev, @@ -1010,6 +1040,12 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev) return -ENXIO; } + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_warn(&pdev->dev, "Failed to get IRQ: %d\n", irq); + return irq; + } + master = spi_alloc_master(&pdev->dev, sizeof(struct s3c64xx_spi_driver_data)); if (master == NULL) { @@ -1104,10 +1140,21 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev) INIT_WORK(&sdd->work, s3c64xx_spi_work); INIT_LIST_HEAD(&sdd->queue); + ret = request_irq(irq, s3c64xx_spi_irq, 0, "spi-s3c64xx", sdd); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + goto err8; + } + + writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN | + S3C64XX_SPI_INT_TX_OVERRUN_EN | S3C64XX_SPI_INT_TX_UNDERRUN_EN, + sdd->regs + S3C64XX_SPI_INT_EN); + if (spi_register_master(master)) { dev_err(&pdev->dev, "cannot register SPI master\n"); ret = -EBUSY; - goto err8; + goto err9; } dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d " @@ -1119,6 +1166,8 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev) return 0; +err9: + free_irq(irq, sdd); err8: destroy_workqueue(sdd->workqueue); err7: @@ -1157,6 +1206,10 @@ static int s3c64xx_spi_remove(struct platform_device *pdev) spi_unregister_master(master); + writel(0, sdd->regs + S3C64XX_SPI_INT_EN); + + free_irq(platform_get_irq(pdev, 0), sdd); + destroy_workqueue(sdd->workqueue); clk_disable(sdd->src_clk); -- cgit v1.2.3 From e25d0bf917e8f3b6b5bafdc2fe666ca81eb9099d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 4 Dec 2011 00:36:18 +0000 Subject: spi/s3c64xx: Convert to dev_pm_ops In preparation for the addition of runtime PM ops. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index d56066bcbb94..56dbdf15cba1 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1231,9 +1231,9 @@ static int s3c64xx_spi_remove(struct platform_device *pdev) } #ifdef CONFIG_PM -static int s3c64xx_spi_suspend(struct platform_device *pdev, pm_message_t state) +static int s3c64xx_spi_suspend(struct device *dev) { - struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct spi_master *master = spi_master_get(dev_get_drvdata(dev)); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); unsigned long flags; @@ -1253,9 +1253,10 @@ static int s3c64xx_spi_suspend(struct platform_device *pdev, pm_message_t state) return 0; } -static int s3c64xx_spi_resume(struct platform_device *pdev) +static int s3c64xx_spi_resume(struct device *dev) { - struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct platform_device *pdev = to_platform_device(dev); + struct spi_master *master = spi_master_get(dev_get_drvdata(dev)); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); struct s3c64xx_spi_info *sci = sdd->cntrlr_info; unsigned long flags; @@ -1274,19 +1275,19 @@ static int s3c64xx_spi_resume(struct platform_device *pdev) return 0; } -#else -#define s3c64xx_spi_suspend NULL -#define s3c64xx_spi_resume NULL #endif /* CONFIG_PM */ +static const struct dev_pm_ops s3c64xx_spi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume) +}; + static struct platform_driver s3c64xx_spi_driver = { .driver = { .name = "s3c64xx-spi", .owner = THIS_MODULE, + .pm = &s3c64xx_spi_pm, }, .remove = s3c64xx_spi_remove, - .suspend = s3c64xx_spi_suspend, - .resume = s3c64xx_spi_resume, }; MODULE_ALIAS("platform:s3c64xx-spi"); -- cgit v1.2.3 From b97b662174162b44944abd0fa9faea50006ba711 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 4 Dec 2011 00:58:06 +0000 Subject: spi/s3c64xx: Implement runtime PM support Enable and disable the clocks to the SPI controller using runtime PM. This serves the dual purpose of reducing power consumption a little and letting the core know when the device is idle. Signed-off-by: Mark Brown Acked-by: Linus Walleij Acked-by: Heiko Stuebner --- drivers/spi/spi-s3c64xx.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 56dbdf15cba1..b0b843b321bb 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -782,6 +783,8 @@ static void s3c64xx_spi_work(struct work_struct *work) while (!acquire_dma(sdd)) msleep(10); + pm_runtime_get_sync(&sdd->pdev->dev); + spin_lock_irqsave(&sdd->lock, flags); while (!list_empty(&sdd->queue) @@ -810,6 +813,8 @@ static void s3c64xx_spi_work(struct work_struct *work) /* Free DMA channels */ sdd->ops->release(sdd->rx_dma.ch, &s3c64xx_spi_dma_client); sdd->ops->release(sdd->tx_dma.ch, &s3c64xx_spi_dma_client); + + pm_runtime_put(&sdd->pdev->dev); } static int s3c64xx_spi_transfer(struct spi_device *spi, @@ -892,6 +897,8 @@ static int s3c64xx_spi_setup(struct spi_device *spi) goto setup_exit; } + pm_runtime_get_sync(&sdd->pdev->dev); + /* Check if we can provide the requested rate */ if (!sci->clk_from_cmu) { u32 psr, speed; @@ -924,6 +931,8 @@ static int s3c64xx_spi_setup(struct spi_device *spi) err = -EINVAL; } + pm_runtime_put(&sdd->pdev->dev); + setup_exit: /* setup() returns with device de-selected */ @@ -1164,6 +1173,8 @@ static int __init s3c64xx_spi_probe(struct platform_device *pdev) mem_res->end, mem_res->start, sdd->rx_dma.dmach, sdd->tx_dma.dmach); + pm_runtime_enable(&pdev->dev); + return 0; err9: @@ -1197,6 +1208,8 @@ static int s3c64xx_spi_remove(struct platform_device *pdev) struct resource *mem_res; unsigned long flags; + pm_runtime_disable(&pdev->dev); + spin_lock_irqsave(&sdd->lock, flags); sdd->state |= SUSPND; spin_unlock_irqrestore(&sdd->lock, flags); @@ -1277,8 +1290,34 @@ static int s3c64xx_spi_resume(struct device *dev) } #endif /* CONFIG_PM */ +#ifdef CONFIG_PM_RUNTIME +static int s3c64xx_spi_runtime_suspend(struct device *dev) +{ + struct spi_master *master = spi_master_get(dev_get_drvdata(dev)); + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + + clk_disable(sdd->clk); + clk_disable(sdd->src_clk); + + return 0; +} + +static int s3c64xx_spi_runtime_resume(struct device *dev) +{ + struct spi_master *master = spi_master_get(dev_get_drvdata(dev)); + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + + clk_enable(sdd->src_clk); + clk_enable(sdd->clk); + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + static const struct dev_pm_ops s3c64xx_spi_pm = { SET_SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume) + SET_RUNTIME_PM_OPS(s3c64xx_spi_runtime_suspend, + s3c64xx_spi_runtime_resume, NULL) }; static struct platform_driver s3c64xx_spi_driver = { -- cgit v1.2.3 From 90bbf4fdf2dc64aa7c20a93a9744c56a566baf26 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Wed, 1 Feb 2012 13:33:42 +0100 Subject: spi/nuc900: Remove unnecessary memset of struct nuc900_spi The memory allocated using kzalloc by spi_alloc_master so it doesn't need to be set to 0 again. Signed-off-by: Tobias Klauser Signed-off-by: Grant Likely --- drivers/spi/spi-nuc900.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-nuc900.c b/drivers/spi/spi-nuc900.c index 182e9c873822..dae8be229c5d 100644 --- a/drivers/spi/spi-nuc900.c +++ b/drivers/spi/spi-nuc900.c @@ -360,8 +360,6 @@ static int __devinit nuc900_spi_probe(struct platform_device *pdev) } hw = spi_master_get_devdata(master); - memset(hw, 0, sizeof(struct nuc900_spi)); - hw->master = spi_master_get(master); hw->pdata = pdev->dev.platform_data; hw->dev = &pdev->dev; -- cgit v1.2.3 From 14af60b6fb3b76634278364b697dae2f9f360abf Mon Sep 17 00:00:00 2001 From: Chris Blair Date: Thu, 2 Feb 2012 13:59:34 +0100 Subject: spi/pl022: Add high priority message pump support This switches the PL022 worker to a kthread in order to get hold of a mechanism to control the message pump priority. On low-latency systems elevating the message kthread to realtime priority give a real sleek response curve. This has been confirmed by measurements. Realtime priority elevation for a certain PL022 port can be requested from platform data. Cc: Mark Brown Acked-by: Viresh Kumar Signed-off-by: Chris Blair Signed-off-by: Linus Walleij Signed-off-by: Grant Likely --- drivers/spi/spi-pl022.c | 77 +++++++++++++++++++++++++++++++--------------- include/linux/amba/pl022.h | 3 ++ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 2f9cb43a2398..81847c9a7586 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -41,6 +41,7 @@ #include #include #include +#include /* * This macro is used to define some register default values. @@ -330,12 +331,13 @@ struct vendor_data { * @clk: outgoing clock "SPICLK" for the SPI bus * @master: SPI framework hookup * @master_info: controller-specific data from machine setup - * @workqueue: a workqueue on which any spi_message request is queued - * @pump_messages: work struct for scheduling work to the workqueue + * @kworker: thread struct for message pump + * @kworker_task: pointer to task for message pump kworker thread + * @pump_messages: work struct for scheduling work to the message pump * @queue_lock: spinlock to syncronise access to message queue * @queue: message queue - * @busy: workqueue is busy - * @running: workqueue is running + * @busy: message pump is busy + * @running: message pump is running * @pump_transfers: Tasklet used in Interrupt Transfer mode * @cur_msg: Pointer to current spi_message being processed * @cur_transfer: Pointer to current spi_transfer @@ -365,9 +367,10 @@ struct pl022 { struct clk *clk; struct spi_master *master; struct pl022_ssp_controller *master_info; - /* Driver message queue */ - struct workqueue_struct *workqueue; - struct work_struct pump_messages; + /* Driver message pump */ + struct kthread_worker kworker; + struct task_struct *kworker_task; + struct kthread_work pump_messages; spinlock_t queue_lock; struct list_head queue; bool busy; @@ -504,7 +507,7 @@ static void giveback(struct pl022 *pl022) pl022->cur_msg = NULL; pl022->cur_transfer = NULL; pl022->cur_chip = NULL; - queue_work(pl022->workqueue, &pl022->pump_messages); + queue_kthread_work(&pl022->kworker, &pl022->pump_messages); spin_unlock_irqrestore(&pl022->queue_lock, flags); msg->state = NULL; @@ -1494,8 +1497,8 @@ out: } /** - * pump_messages - Workqueue function which processes spi message queue - * @data: pointer to private data of SSP driver + * pump_messages - kthread work function which processes spi message queue + * @work: pointer to kthread work struct contained in the pl022 private struct * * This function checks if there is any spi message in the queue that * needs processing and delegate control to appropriate function @@ -1503,7 +1506,7 @@ out: * based on the kind of the transfer * */ -static void pump_messages(struct work_struct *work) +static void pump_messages(struct kthread_work *work) { struct pl022 *pl022 = container_of(work, struct pl022, pump_messages); @@ -1556,7 +1559,7 @@ static void pump_messages(struct work_struct *work) if (!was_busy) /* * We enable the core voltage and clocks here, then the clocks - * and core will be disabled when this workqueue is run again + * and core will be disabled when this thread is run again * and there is no more work to be done. */ pm_runtime_get_sync(&pl022->adev->dev); @@ -1572,6 +1575,8 @@ static void pump_messages(struct work_struct *work) static int __init init_queue(struct pl022 *pl022) { + struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; + INIT_LIST_HEAD(&pl022->queue); spin_lock_init(&pl022->queue_lock); @@ -1581,11 +1586,29 @@ static int __init init_queue(struct pl022 *pl022) tasklet_init(&pl022->pump_transfers, pump_transfers, (unsigned long)pl022); - INIT_WORK(&pl022->pump_messages, pump_messages); - pl022->workqueue = create_singlethread_workqueue( + init_kthread_worker(&pl022->kworker); + pl022->kworker_task = kthread_run(kthread_worker_fn, + &pl022->kworker, dev_name(pl022->master->dev.parent)); - if (pl022->workqueue == NULL) - return -EBUSY; + if (IS_ERR(pl022->kworker_task)) { + dev_err(&pl022->adev->dev, + "failed to create message pump task\n"); + return -ENOMEM; + } + init_kthread_work(&pl022->pump_messages, pump_messages); + + /* + * Board config will indicate if this controller should run the + * message pump with high (realtime) priority to reduce the transfer + * latency on the bus by minimising the delay between a transfer + * request and the scheduling of the message pump thread. Without this + * setting the message pump thread will remain at default priority. + */ + if (pl022->master_info->rt) { + dev_info(&pl022->adev->dev, + "will run message pump with realtime priority\n"); + sched_setscheduler(pl022->kworker_task, SCHED_FIFO, ¶m); + } return 0; } @@ -1608,7 +1631,7 @@ static int start_queue(struct pl022 *pl022) pl022->next_msg_cs_active = false; spin_unlock_irqrestore(&pl022->queue_lock, flags); - queue_work(pl022->workqueue, &pl022->pump_messages); + queue_kthread_work(&pl022->kworker, &pl022->pump_messages); return 0; } @@ -1646,16 +1669,20 @@ static int destroy_queue(struct pl022 *pl022) int status; status = stop_queue(pl022); - /* we are unloading the module or failing to load (only two calls + + /* + * We are unloading the module or failing to load (only two calls * to this routine), and neither call can handle a return value. - * However, destroy_workqueue calls flush_workqueue, and that will - * block until all work is done. If the reason that stop_queue - * timed out is that the work will never finish, then it does no - * good to call destroy_workqueue, so return anyway. */ + * However, flush_kthread_worker will block until all work is done. + * If the reason that stop_queue timed out is that the work will never + * finish, then it does no good to call flush/stop thread, so + * return anyway. + */ if (status != 0) return status; - destroy_workqueue(pl022->workqueue); + flush_kthread_worker(&pl022->kworker); + kthread_stop(pl022->kworker_task); return 0; } @@ -1802,7 +1829,7 @@ static int pl022_transfer(struct spi_device *spi, struct spi_message *msg) list_add_tail(&msg->queue, &pl022->queue); if (pl022->running && !pl022->busy) - queue_work(pl022->workqueue, &pl022->pump_messages); + queue_kthread_work(&pl022->kworker, &pl022->pump_messages); spin_unlock_irqrestore(&pl022->queue_lock, flags); return 0; diff --git a/include/linux/amba/pl022.h b/include/linux/amba/pl022.h index 572f637299c9..3672f40f3455 100644 --- a/include/linux/amba/pl022.h +++ b/include/linux/amba/pl022.h @@ -241,6 +241,8 @@ struct dma_chan; * @autosuspend_delay: delay in ms following transfer completion before the * runtime power management system suspends the device. A setting of 0 * indicates no delay and the device will be suspended immediately. + * @rt: indicates the controller should run the message pump with realtime + * priority to minimise the transfer latency on the bus. */ struct pl022_ssp_controller { u16 bus_id; @@ -250,6 +252,7 @@ struct pl022_ssp_controller { void *dma_rx_param; void *dma_tx_param; int autosuspend_delay; + bool rt; }; /** -- cgit v1.2.3 From d5a8003135da7afe311e4e13ff42000ab7cd2078 Mon Sep 17 00:00:00 2001 From: Benoit Cousson Date: Wed, 15 Feb 2012 18:37:34 +0100 Subject: spi/omap: Add DT support to McSPI driver Add device tree support to the OMAP2+ McSPI driver. Add the bindings documentation. Based on original code from Rajendra. Signed-off-by: Benoit Cousson Cc: Grant Likely Cc: Rajendra Nayak Signed-off-by: Grant Likely --- Documentation/devicetree/bindings/spi/omap-spi.txt | 20 ++++++++ drivers/spi/spi-omap2-mcspi.c | 56 ++++++++++++++++++---- 2 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/omap-spi.txt diff --git a/Documentation/devicetree/bindings/spi/omap-spi.txt b/Documentation/devicetree/bindings/spi/omap-spi.txt new file mode 100644 index 000000000000..81df374adbb9 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/omap-spi.txt @@ -0,0 +1,20 @@ +OMAP2+ McSPI device + +Required properties: +- compatible : + - "ti,omap2-spi" for OMAP2 & OMAP3. + - "ti,omap4-spi" for OMAP4+. +- ti,spi-num-cs : Number of chipselect supported by the instance. +- ti,hwmods: Name of the hwmod associated to the McSPI + + +Example: + +mcspi1: mcspi@1 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "ti,omap4-mcspi"; + ti,hwmods = "mcspi1"; + ti,spi-num-cs = <4>; +}; + diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 0b0dfb71c640..bb9274c2526d 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include @@ -1079,15 +1081,39 @@ static int omap_mcspi_runtime_resume(struct device *dev) return 0; } +static struct omap2_mcspi_platform_config omap2_pdata = { + .regs_offset = 0, +}; + +static struct omap2_mcspi_platform_config omap4_pdata = { + .regs_offset = OMAP4_MCSPI_REG_OFFSET, +}; + +static const struct of_device_id omap_mcspi_of_match[] = { + { + .compatible = "ti,omap2-mcspi", + .data = &omap2_pdata, + }, + { + .compatible = "ti,omap4-mcspi", + .data = &omap4_pdata, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, omap_mcspi_of_match); static int __init omap2_mcspi_probe(struct platform_device *pdev) { struct spi_master *master; - struct omap2_mcspi_platform_config *pdata = pdev->dev.platform_data; + struct omap2_mcspi_platform_config *pdata; struct omap2_mcspi *mcspi; struct resource *r; int status = 0, i; char wq_name[20]; + u32 regs_offset = 0; + static int bus_num = 1; + struct device_node *node = pdev->dev.of_node; + const struct of_device_id *match; master = spi_alloc_master(&pdev->dev, sizeof *mcspi); if (master == NULL) { @@ -1098,13 +1124,26 @@ static int __init omap2_mcspi_probe(struct platform_device *pdev) /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; - if (pdev->id != -1) - master->bus_num = pdev->id; - master->setup = omap2_mcspi_setup; master->transfer = omap2_mcspi_transfer; master->cleanup = omap2_mcspi_cleanup; - master->num_chipselect = pdata->num_cs; + master->dev.of_node = node; + + match = of_match_device(omap_mcspi_of_match, &pdev->dev); + if (match) { + u32 num_cs = 1; /* default number of chipselect */ + pdata = match->data; + + of_property_read_u32(node, "ti,spi-num-cs", &num_cs); + master->num_chipselect = num_cs; + master->bus_num = bus_num++; + } else { + pdata = pdev->dev.platform_data; + master->num_chipselect = pdata->num_cs; + if (pdev->id != -1) + master->bus_num = pdev->id; + } + regs_offset = pdata->regs_offset; dev_set_drvdata(&pdev->dev, master); @@ -1124,8 +1163,8 @@ static int __init omap2_mcspi_probe(struct platform_device *pdev) goto free_master; } - r->start += pdata->regs_offset; - r->end += pdata->regs_offset; + r->start += regs_offset; + r->end += regs_offset; mcspi->phys = r->start; if (!request_mem_region(r->start, resource_size(r), dev_name(&pdev->dev))) { @@ -1285,7 +1324,8 @@ static struct platform_driver omap2_mcspi_driver = { .driver = { .name = "omap2_mcspi", .owner = THIS_MODULE, - .pm = &omap2_mcspi_pm_ops + .pm = &omap2_mcspi_pm_ops, + .of_match_table = omap_mcspi_of_match, }, .remove = __exit_p(omap2_mcspi_remove), }; -- cgit v1.2.3 From 0eb8880fac7b0f32ebab33f99e758c6b308e3aa1 Mon Sep 17 00:00:00 2001 From: "Shimoda, Yoshihiro" Date: Wed, 7 Mar 2012 14:45:37 +0900 Subject: spi/spi-sh: add IORESOURCE_MEM_TYPE_MASK decoding for access size This SPI controller's access size is 32, or 8-bit. The previous driver supported 32-bit only. So, this patch adds IORESOURCE_MEM_TYPE_MASK decoding, an then, the driver can handle the SPI controller of 8-bit. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Grant Likely --- drivers/spi/spi-sh.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-sh.c b/drivers/spi/spi-sh.c index 70c8af9f7ccc..79442c31bcd9 100644 --- a/drivers/spi/spi-sh.c +++ b/drivers/spi/spi-sh.c @@ -92,17 +92,26 @@ struct spi_sh_data { unsigned long cr1; wait_queue_head_t wait; spinlock_t lock; + int width; }; static void spi_sh_write(struct spi_sh_data *ss, unsigned long data, unsigned long offset) { - writel(data, ss->addr + offset); + if (ss->width == 8) + iowrite8(data, ss->addr + (offset >> 2)); + else if (ss->width == 32) + iowrite32(data, ss->addr + offset); } static unsigned long spi_sh_read(struct spi_sh_data *ss, unsigned long offset) { - return readl(ss->addr + offset); + if (ss->width == 8) + return ioread8(ss->addr + (offset >> 2)); + else if (ss->width == 32) + return ioread32(ss->addr + offset); + else + return 0; } static void spi_sh_set_bit(struct spi_sh_data *ss, unsigned long val, @@ -464,6 +473,18 @@ static int __devinit spi_sh_probe(struct platform_device *pdev) ss = spi_master_get_devdata(master); dev_set_drvdata(&pdev->dev, ss); + switch (res->flags & IORESOURCE_MEM_TYPE_MASK) { + case IORESOURCE_MEM_8BIT: + ss->width = 8; + break; + case IORESOURCE_MEM_32BIT: + ss->width = 32; + break; + default: + dev_err(&pdev->dev, "No support width\n"); + ret = -ENODEV; + goto error1; + } ss->irq = irq; ss->master = master; ss->addr = ioremap(res->start, resource_size(res)); -- cgit v1.2.3 From 0b2182ddac4b8837bbba996d03b7b28f4346db0a Mon Sep 17 00:00:00 2001 From: "Shimoda, Yoshihiro" Date: Wed, 7 Mar 2012 14:46:25 +0900 Subject: spi: add support for Renesas RSPI The SH7757 has RSPI module. This patch supports it. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Grant Likely --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-rspi.c | 521 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 528 insertions(+) create mode 100644 drivers/spi/spi-rspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 3f9a47ec67dc..7609c9cecd35 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -279,6 +279,12 @@ config SPI_PXA2XX config SPI_PXA2XX_PCI def_bool SPI_PXA2XX && X86_32 && PCI +config SPI_RSPI + tristate "Renesas RSPI controller" + depends on SUPERH + help + SPI driver for Renesas RSPI blocks. + config SPI_S3C24XX tristate "Samsung S3C24XX series SPI" depends on ARCH_S3C2410 && EXPERIMENTAL diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 61c3261c388c..326e6a1ac0ca 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_SPI_PL022) += spi-pl022.o obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx.o obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o +obj-$(CONFIG_SPI_RSPI) += spi-rspi.o obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o spi-s3c24xx-hw-y := spi-s3c24xx.o spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c new file mode 100644 index 000000000000..354f170eab95 --- /dev/null +++ b/drivers/spi/spi-rspi.c @@ -0,0 +1,521 @@ +/* + * SH RSPI driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Based on spi-sh.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RSPI_SPCR 0x00 +#define RSPI_SSLP 0x01 +#define RSPI_SPPCR 0x02 +#define RSPI_SPSR 0x03 +#define RSPI_SPDR 0x04 +#define RSPI_SPSCR 0x08 +#define RSPI_SPSSR 0x09 +#define RSPI_SPBR 0x0a +#define RSPI_SPDCR 0x0b +#define RSPI_SPCKD 0x0c +#define RSPI_SSLND 0x0d +#define RSPI_SPND 0x0e +#define RSPI_SPCR2 0x0f +#define RSPI_SPCMD0 0x10 +#define RSPI_SPCMD1 0x12 +#define RSPI_SPCMD2 0x14 +#define RSPI_SPCMD3 0x16 +#define RSPI_SPCMD4 0x18 +#define RSPI_SPCMD5 0x1a +#define RSPI_SPCMD6 0x1c +#define RSPI_SPCMD7 0x1e + +/* SPCR */ +#define SPCR_SPRIE 0x80 +#define SPCR_SPE 0x40 +#define SPCR_SPTIE 0x20 +#define SPCR_SPEIE 0x10 +#define SPCR_MSTR 0x08 +#define SPCR_MODFEN 0x04 +#define SPCR_TXMD 0x02 +#define SPCR_SPMS 0x01 + +/* SSLP */ +#define SSLP_SSL1P 0x02 +#define SSLP_SSL0P 0x01 + +/* SPPCR */ +#define SPPCR_MOIFE 0x20 +#define SPPCR_MOIFV 0x10 +#define SPPCR_SPOM 0x04 +#define SPPCR_SPLP2 0x02 +#define SPPCR_SPLP 0x01 + +/* SPSR */ +#define SPSR_SPRF 0x80 +#define SPSR_SPTEF 0x20 +#define SPSR_PERF 0x08 +#define SPSR_MODF 0x04 +#define SPSR_IDLNF 0x02 +#define SPSR_OVRF 0x01 + +/* SPSCR */ +#define SPSCR_SPSLN_MASK 0x07 + +/* SPSSR */ +#define SPSSR_SPECM_MASK 0x70 +#define SPSSR_SPCP_MASK 0x07 + +/* SPDCR */ +#define SPDCR_SPLW 0x20 +#define SPDCR_SPRDTD 0x10 +#define SPDCR_SLSEL1 0x08 +#define SPDCR_SLSEL0 0x04 +#define SPDCR_SLSEL_MASK 0x0c +#define SPDCR_SPFC1 0x02 +#define SPDCR_SPFC0 0x01 + +/* SPCKD */ +#define SPCKD_SCKDL_MASK 0x07 + +/* SSLND */ +#define SSLND_SLNDL_MASK 0x07 + +/* SPND */ +#define SPND_SPNDL_MASK 0x07 + +/* SPCR2 */ +#define SPCR2_PTE 0x08 +#define SPCR2_SPIE 0x04 +#define SPCR2_SPOE 0x02 +#define SPCR2_SPPE 0x01 + +/* SPCMDn */ +#define SPCMD_SCKDEN 0x8000 +#define SPCMD_SLNDEN 0x4000 +#define SPCMD_SPNDEN 0x2000 +#define SPCMD_LSBF 0x1000 +#define SPCMD_SPB_MASK 0x0f00 +#define SPCMD_SPB_8_TO_16(bit) (((bit - 1) << 8) & SPCMD_SPB_MASK) +#define SPCMD_SPB_20BIT 0x0000 +#define SPCMD_SPB_24BIT 0x0100 +#define SPCMD_SPB_32BIT 0x0200 +#define SPCMD_SSLKP 0x0080 +#define SPCMD_SSLA_MASK 0x0030 +#define SPCMD_BRDV_MASK 0x000c +#define SPCMD_CPOL 0x0002 +#define SPCMD_CPHA 0x0001 + +struct rspi_data { + void __iomem *addr; + u32 max_speed_hz; + struct spi_master *master; + struct list_head queue; + struct work_struct ws; + wait_queue_head_t wait; + spinlock_t lock; + struct clk *clk; + unsigned char spsr; +}; + +static void rspi_write8(struct rspi_data *rspi, u8 data, u16 offset) +{ + iowrite8(data, rspi->addr + offset); +} + +static void rspi_write16(struct rspi_data *rspi, u16 data, u16 offset) +{ + iowrite16(data, rspi->addr + offset); +} + +static u8 rspi_read8(struct rspi_data *rspi, u16 offset) +{ + return ioread8(rspi->addr + offset); +} + +static u16 rspi_read16(struct rspi_data *rspi, u16 offset) +{ + return ioread16(rspi->addr + offset); +} + +static unsigned char rspi_calc_spbr(struct rspi_data *rspi) +{ + int tmp; + unsigned char spbr; + + tmp = clk_get_rate(rspi->clk) / (2 * rspi->max_speed_hz) - 1; + spbr = clamp(tmp, 0, 255); + + return spbr; +} + +static void rspi_enable_irq(struct rspi_data *rspi, u8 enable) +{ + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | enable, RSPI_SPCR); +} + +static void rspi_disable_irq(struct rspi_data *rspi, u8 disable) +{ + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~disable, RSPI_SPCR); +} + +static int rspi_wait_for_interrupt(struct rspi_data *rspi, u8 wait_mask, + u8 enable_bit) +{ + int ret; + + rspi->spsr = rspi_read8(rspi, RSPI_SPSR); + rspi_enable_irq(rspi, enable_bit); + ret = wait_event_timeout(rspi->wait, rspi->spsr & wait_mask, HZ); + if (ret == 0 && !(rspi->spsr & wait_mask)) + return -ETIMEDOUT; + + return 0; +} + +static void rspi_assert_ssl(struct rspi_data *rspi) +{ + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | SPCR_SPE, RSPI_SPCR); +} + +static void rspi_negate_ssl(struct rspi_data *rspi) +{ + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~SPCR_SPE, RSPI_SPCR); +} + +static int rspi_set_config_register(struct rspi_data *rspi, int access_size) +{ + /* Sets output mode(CMOS) and MOSI signal(from previous transfer) */ + rspi_write8(rspi, 0x00, RSPI_SPPCR); + + /* Sets transfer bit rate */ + rspi_write8(rspi, rspi_calc_spbr(rspi), RSPI_SPBR); + + /* Sets number of frames to be used: 1 frame */ + rspi_write8(rspi, 0x00, RSPI_SPDCR); + + /* Sets RSPCK, SSL, next-access delay value */ + rspi_write8(rspi, 0x00, RSPI_SPCKD); + rspi_write8(rspi, 0x00, RSPI_SSLND); + rspi_write8(rspi, 0x00, RSPI_SPND); + + /* Sets parity, interrupt mask */ + rspi_write8(rspi, 0x00, RSPI_SPCR2); + + /* Sets SPCMD */ + rspi_write16(rspi, SPCMD_SPB_8_TO_16(access_size) | SPCMD_SSLKP, + RSPI_SPCMD0); + + /* Sets RSPI mode */ + rspi_write8(rspi, SPCR_MSTR, RSPI_SPCR); + + return 0; +} + +static int rspi_send_pio(struct rspi_data *rspi, struct spi_message *mesg, + struct spi_transfer *t) +{ + int remain = t->len; + u8 *data; + + data = (u8 *)t->tx_buf; + while (remain > 0) { + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | SPCR_TXMD, + RSPI_SPCR); + + if (rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE) < 0) { + dev_err(&rspi->master->dev, + "%s: tx empty timeout\n", __func__); + return -ETIMEDOUT; + } + + rspi_write16(rspi, *data, RSPI_SPDR); + data++; + remain--; + } + + /* Waiting for the last transmition */ + rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE); + + return 0; +} + +static int rspi_receive_pio(struct rspi_data *rspi, struct spi_message *mesg, + struct spi_transfer *t) +{ + int remain = t->len; + u8 *data; + unsigned char spsr; + + spsr = rspi_read8(rspi, RSPI_SPSR); + if (spsr & SPSR_SPRF) + rspi_read16(rspi, RSPI_SPDR); /* dummy read */ + if (spsr & SPSR_OVRF) + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPSR) & ~SPSR_OVRF, + RSPI_SPCR); + + data = (u8 *)t->rx_buf; + while (remain > 0) { + rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~SPCR_TXMD, + RSPI_SPCR); + + if (rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE) < 0) { + dev_err(&rspi->master->dev, + "%s: tx empty timeout\n", __func__); + return -ETIMEDOUT; + } + /* dummy write for generate clock */ + rspi_write16(rspi, 0x00, RSPI_SPDR); + + if (rspi_wait_for_interrupt(rspi, SPSR_SPRF, SPCR_SPRIE) < 0) { + dev_err(&rspi->master->dev, + "%s: receive timeout\n", __func__); + return -ETIMEDOUT; + } + /* SPDR allows 16 or 32-bit access only */ + *data = (u8)rspi_read16(rspi, RSPI_SPDR); + + data++; + remain--; + } + + return 0; +} + +static void rspi_work(struct work_struct *work) +{ + struct rspi_data *rspi = container_of(work, struct rspi_data, ws); + struct spi_message *mesg; + struct spi_transfer *t; + unsigned long flags; + int ret; + + spin_lock_irqsave(&rspi->lock, flags); + while (!list_empty(&rspi->queue)) { + mesg = list_entry(rspi->queue.next, struct spi_message, queue); + list_del_init(&mesg->queue); + spin_unlock_irqrestore(&rspi->lock, flags); + + rspi_assert_ssl(rspi); + + list_for_each_entry(t, &mesg->transfers, transfer_list) { + if (t->tx_buf) { + ret = rspi_send_pio(rspi, mesg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = rspi_receive_pio(rspi, mesg, t); + if (ret < 0) + goto error; + } + mesg->actual_length += t->len; + } + rspi_negate_ssl(rspi); + + mesg->status = 0; + mesg->complete(mesg->context); + + spin_lock_irqsave(&rspi->lock, flags); + } + + return; + +error: + mesg->status = ret; + mesg->complete(mesg->context); +} + +static int rspi_setup(struct spi_device *spi) +{ + struct rspi_data *rspi = spi_master_get_devdata(spi->master); + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + rspi->max_speed_hz = spi->max_speed_hz; + + rspi_set_config_register(rspi, 8); + + return 0; +} + +static int rspi_transfer(struct spi_device *spi, struct spi_message *mesg) +{ + struct rspi_data *rspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + mesg->actual_length = 0; + mesg->status = -EINPROGRESS; + + spin_lock_irqsave(&rspi->lock, flags); + list_add_tail(&mesg->queue, &rspi->queue); + schedule_work(&rspi->ws); + spin_unlock_irqrestore(&rspi->lock, flags); + + return 0; +} + +static void rspi_cleanup(struct spi_device *spi) +{ +} + +static irqreturn_t rspi_irq(int irq, void *_sr) +{ + struct rspi_data *rspi = (struct rspi_data *)_sr; + unsigned long spsr; + irqreturn_t ret = IRQ_NONE; + unsigned char disable_irq = 0; + + rspi->spsr = spsr = rspi_read8(rspi, RSPI_SPSR); + if (spsr & SPSR_SPRF) + disable_irq |= SPCR_SPRIE; + if (spsr & SPSR_SPTEF) + disable_irq |= SPCR_SPTIE; + + if (disable_irq) { + ret = IRQ_HANDLED; + rspi_disable_irq(rspi, disable_irq); + wake_up(&rspi->wait); + } + + return ret; +} + +static int __devexit rspi_remove(struct platform_device *pdev) +{ + struct rspi_data *rspi = dev_get_drvdata(&pdev->dev); + + spi_unregister_master(rspi->master); + free_irq(platform_get_irq(pdev, 0), rspi); + clk_put(rspi->clk); + iounmap(rspi->addr); + spi_master_put(rspi->master); + + return 0; +} + +static int __devinit rspi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct rspi_data *rspi; + int ret, irq; + char clk_name[16]; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(res == NULL)) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "platform_get_irq error\n"); + return -ENODEV; + } + + master = spi_alloc_master(&pdev->dev, sizeof(struct rspi_data)); + if (master == NULL) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + rspi = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, rspi); + + rspi->master = master; + rspi->addr = ioremap(res->start, resource_size(res)); + if (rspi->addr == NULL) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + + snprintf(clk_name, sizeof(clk_name), "rspi%d", pdev->id); + rspi->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(rspi->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + ret = PTR_ERR(rspi->clk); + goto error2; + } + clk_enable(rspi->clk); + + INIT_LIST_HEAD(&rspi->queue); + spin_lock_init(&rspi->lock); + INIT_WORK(&rspi->ws, rspi_work); + init_waitqueue_head(&rspi->wait); + + master->num_chipselect = 2; + master->bus_num = pdev->id; + master->setup = rspi_setup; + master->transfer = rspi_transfer; + master->cleanup = rspi_cleanup; + + ret = request_irq(irq, rspi_irq, 0, dev_name(&pdev->dev), rspi); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq error\n"); + goto error3; + } + + ret = spi_register_master(master); + if (ret < 0) { + dev_err(&pdev->dev, "spi_register_master error.\n"); + goto error4; + } + + dev_info(&pdev->dev, "probed\n"); + + return 0; + +error4: + free_irq(irq, rspi); +error3: + clk_put(rspi->clk); +error2: + iounmap(rspi->addr); +error1: + spi_master_put(master); + + return ret; +} + +static struct platform_driver rspi_driver = { + .probe = rspi_probe, + .remove = __devexit_p(rspi_remove), + .driver = { + .name = "rspi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(rspi_driver); + +MODULE_DESCRIPTION("Renesas RSPI bus driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:rspi"); -- cgit v1.2.3 From ffbbdd21329f3e15eeca6df2d4bc11c04d9d91c0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 22 Feb 2012 10:05:38 +0100 Subject: spi: create a message queueing infrastructure This rips the message queue in the PL022 driver out and pushes it into (optional) common infrastructure. Drivers that want to use the message pumping thread will need to define the new per-messags transfer methods and leave the deprecated transfer() method as NULL. Most of the design is described in the documentation changes that are included in this patch. Since there is a queue that need to be stopped when the system is suspending/resuming, two new calls are implemented for the device drivers to call in their suspend()/resume() functions: spi_master_suspend() and spi_master_resume(). ChangeLog v1->v2: - Remove Kconfig entry and do not make the queue support optional at all, instead be more agressive and have it as part of the compulsory infrastructure. - If the .transfer() method is implemented, delete print a small deprecation notice and do not start the transfer pump. - Fix a bitrotted comment. ChangeLog v2->v3: - Fix up a problematic sequence courtesy of Chris Blair. - Stop rather than destroy the queue on suspend() courtesy of Chris Blair. Signed-off-by: Chris Blair Signed-off-by: Linus Walleij Tested-by: Mark Brown Reviewed-by: Mark Brown Signed-off-by: Grant Likely --- Documentation/spi/spi-summary | 58 ++++++-- drivers/spi/spi-pl022.c | 303 +++++++------------------------------ drivers/spi/spi.c | 339 +++++++++++++++++++++++++++++++++++++++++- include/linux/spi/spi.h | 51 +++++++ 4 files changed, 487 insertions(+), 264 deletions(-) diff --git a/Documentation/spi/spi-summary b/Documentation/spi/spi-summary index 4884cb33845d..7312ec14dd89 100644 --- a/Documentation/spi/spi-summary +++ b/Documentation/spi/spi-summary @@ -1,7 +1,7 @@ Overview of Linux kernel SPI support ==================================== -21-May-2007 +02-Feb-2012 What is SPI? ------------ @@ -483,9 +483,9 @@ also initialize its own internal state. (See below about bus numbering and those methods.) After you initialize the spi_master, then use spi_register_master() to -publish it to the rest of the system. At that time, device nodes for -the controller and any predeclared spi devices will be made available, -and the driver model core will take care of binding them to drivers. +publish it to the rest of the system. At that time, device nodes for the +controller and any predeclared spi devices will be made available, and +the driver model core will take care of binding them to drivers. If you need to remove your SPI controller driver, spi_unregister_master() will reverse the effect of spi_register_master(). @@ -521,21 +521,53 @@ SPI MASTER METHODS ** When you code setup(), ASSUME that the controller ** is actively processing transfers for another device. - master->transfer(struct spi_device *spi, struct spi_message *message) - This must not sleep. Its responsibility is arrange that the - transfer happens and its complete() callback is issued. The two - will normally happen later, after other transfers complete, and - if the controller is idle it will need to be kickstarted. - master->cleanup(struct spi_device *spi) Your controller driver may use spi_device.controller_state to hold state it dynamically associates with that device. If you do that, be sure to provide the cleanup() method to free that state. + master->prepare_transfer_hardware(struct spi_master *master) + This will be called by the queue mechanism to signal to the driver + that a message is coming in soon, so the subsystem requests the + driver to prepare the transfer hardware by issuing this call. + This may sleep. + + master->unprepare_transfer_hardware(struct spi_master *master) + This will be called by the queue mechanism to signal to the driver + that there are no more messages pending in the queue and it may + relax the hardware (e.g. by power management calls). This may sleep. + + master->transfer_one_message(struct spi_master *master, + struct spi_message *mesg) + The subsystem calls the driver to transfer a single message while + queuing transfers that arrive in the meantime. When the driver is + finished with this message, it must call + spi_finalize_current_message() so the subsystem can issue the next + transfer. This may sleep. + + DEPRECATED METHODS + + master->transfer(struct spi_device *spi, struct spi_message *message) + This must not sleep. Its responsibility is arrange that the + transfer happens and its complete() callback is issued. The two + will normally happen later, after other transfers complete, and + if the controller is idle it will need to be kickstarted. This + method is not used on queued controllers and must be NULL if + transfer_one_message() and (un)prepare_transfer_hardware() are + implemented. + SPI MESSAGE QUEUE -The bulk of the driver will be managing the I/O queue fed by transfer(). +If you are happy with the standard queueing mechanism provided by the +SPI subsystem, just implement the queued methods specified above. Using +the message queue has the upside of centralizing a lot of code and +providing pure process-context execution of methods. The message queue +can also be elevated to realtime priority on high-priority SPI traffic. + +Unless the queueing mechanism in the SPI subsystem is selected, the bulk +of the driver will be managing the I/O queue fed by the now deprecated +function transfer(). That queue could be purely conceptual. For example, a driver used only for low-frequency sensor access might be fine using synchronous PIO. @@ -561,4 +593,6 @@ Stephen Street Mark Underwood Andrew Victor Vitaly Wool - +Grant Likely +Mark Brown +Linus Walleij diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 81847c9a7586..ec17a7af7e28 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +40,6 @@ #include #include #include -#include /* * This macro is used to define some register default values. @@ -367,15 +365,7 @@ struct pl022 { struct clk *clk; struct spi_master *master; struct pl022_ssp_controller *master_info; - /* Driver message pump */ - struct kthread_worker kworker; - struct task_struct *kworker_task; - struct kthread_work pump_messages; - spinlock_t queue_lock; - struct list_head queue; - bool busy; - bool running; - /* Message transfer pump */ + /* Message per-transfer pump */ struct tasklet_struct pump_transfers; struct spi_message *cur_msg; struct spi_transfer *cur_transfer; @@ -397,6 +387,7 @@ struct pl022 { struct sg_table sgt_rx; struct sg_table sgt_tx; char *dummypage; + bool dma_running; #endif }; @@ -451,8 +442,6 @@ static void null_cs_control(u32 command) static void giveback(struct pl022 *pl022) { struct spi_transfer *last_transfer; - unsigned long flags; - struct spi_message *msg; pl022->next_msg_cs_active = false; last_transfer = list_entry(pl022->cur_msg->transfers.prev, @@ -480,15 +469,8 @@ static void giveback(struct pl022 *pl022) * sent the current message could be unloaded, which * could invalidate the cs_control() callback... */ - /* get a pointer to the next message, if any */ - spin_lock_irqsave(&pl022->queue_lock, flags); - if (list_empty(&pl022->queue)) - next_msg = NULL; - else - next_msg = list_entry(pl022->queue.next, - struct spi_message, queue); - spin_unlock_irqrestore(&pl022->queue_lock, flags); + next_msg = spi_get_next_queued_message(pl022->master); /* * see if the next and current messages point @@ -500,19 +482,13 @@ static void giveback(struct pl022 *pl022) pl022->cur_chip->cs_control(SSP_CHIP_DESELECT); else pl022->next_msg_cs_active = true; + } - spin_lock_irqsave(&pl022->queue_lock, flags); - msg = pl022->cur_msg; pl022->cur_msg = NULL; pl022->cur_transfer = NULL; pl022->cur_chip = NULL; - queue_kthread_work(&pl022->kworker, &pl022->pump_messages); - spin_unlock_irqrestore(&pl022->queue_lock, flags); - - msg->state = NULL; - if (msg->complete) - msg->complete(msg->context); + spi_finalize_current_message(pl022->master); } /** @@ -1066,6 +1042,7 @@ static int configure_dma(struct pl022 *pl022) dmaengine_submit(txdesc); dma_async_issue_pending(rxchan); dma_async_issue_pending(txchan); + pl022->dma_running = true; return 0; @@ -1144,11 +1121,12 @@ static void terminate_dma(struct pl022 *pl022) dmaengine_terminate_all(rxchan); dmaengine_terminate_all(txchan); unmap_free_dma_scatter(pl022); + pl022->dma_running = false; } static void pl022_dma_remove(struct pl022 *pl022) { - if (pl022->busy) + if (pl022->dma_running) terminate_dma(pl022); if (pl022->dma_tx_channel) dma_release_channel(pl022->dma_tx_channel); @@ -1496,73 +1474,20 @@ out: return; } -/** - * pump_messages - kthread work function which processes spi message queue - * @work: pointer to kthread work struct contained in the pl022 private struct - * - * This function checks if there is any spi message in the queue that - * needs processing and delegate control to appropriate function - * do_polling_transfer()/do_interrupt_dma_transfer() - * based on the kind of the transfer - * - */ -static void pump_messages(struct kthread_work *work) +static int pl022_transfer_one_message(struct spi_master *master, + struct spi_message *msg) { - struct pl022 *pl022 = - container_of(work, struct pl022, pump_messages); - unsigned long flags; - bool was_busy = false; - - /* Lock queue and check for queue work */ - spin_lock_irqsave(&pl022->queue_lock, flags); - if (list_empty(&pl022->queue) || !pl022->running) { - if (pl022->busy) { - /* nothing more to do - disable spi/ssp and power off */ - writew((readw(SSP_CR1(pl022->virtbase)) & - (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); - - if (pl022->master_info->autosuspend_delay > 0) { - pm_runtime_mark_last_busy(&pl022->adev->dev); - pm_runtime_put_autosuspend(&pl022->adev->dev); - } else { - pm_runtime_put(&pl022->adev->dev); - } - } - pl022->busy = false; - spin_unlock_irqrestore(&pl022->queue_lock, flags); - return; - } - - /* Make sure we are not already running a message */ - if (pl022->cur_msg) { - spin_unlock_irqrestore(&pl022->queue_lock, flags); - return; - } - /* Extract head of queue */ - pl022->cur_msg = - list_entry(pl022->queue.next, struct spi_message, queue); - - list_del_init(&pl022->cur_msg->queue); - if (pl022->busy) - was_busy = true; - else - pl022->busy = true; - spin_unlock_irqrestore(&pl022->queue_lock, flags); + struct pl022 *pl022 = spi_master_get_devdata(master); /* Initial message state */ - pl022->cur_msg->state = STATE_START; - pl022->cur_transfer = list_entry(pl022->cur_msg->transfers.next, - struct spi_transfer, transfer_list); + pl022->cur_msg = msg; + msg->state = STATE_START; + + pl022->cur_transfer = list_entry(msg->transfers.next, + struct spi_transfer, transfer_list); /* Setup the SPI using the per chip configuration */ - pl022->cur_chip = spi_get_ctldata(pl022->cur_msg->spi); - if (!was_busy) - /* - * We enable the core voltage and clocks here, then the clocks - * and core will be disabled when this thread is run again - * and there is no more work to be done. - */ - pm_runtime_get_sync(&pl022->adev->dev); + pl022->cur_chip = spi_get_ctldata(msg->spi); restore_state(pl022); flush(pl022); @@ -1571,119 +1496,37 @@ static void pump_messages(struct kthread_work *work) do_polling_transfer(pl022); else do_interrupt_dma_transfer(pl022); -} - -static int __init init_queue(struct pl022 *pl022) -{ - struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; - - INIT_LIST_HEAD(&pl022->queue); - spin_lock_init(&pl022->queue_lock); - - pl022->running = false; - pl022->busy = false; - - tasklet_init(&pl022->pump_transfers, pump_transfers, - (unsigned long)pl022); - - init_kthread_worker(&pl022->kworker); - pl022->kworker_task = kthread_run(kthread_worker_fn, - &pl022->kworker, - dev_name(pl022->master->dev.parent)); - if (IS_ERR(pl022->kworker_task)) { - dev_err(&pl022->adev->dev, - "failed to create message pump task\n"); - return -ENOMEM; - } - init_kthread_work(&pl022->pump_messages, pump_messages); - - /* - * Board config will indicate if this controller should run the - * message pump with high (realtime) priority to reduce the transfer - * latency on the bus by minimising the delay between a transfer - * request and the scheduling of the message pump thread. Without this - * setting the message pump thread will remain at default priority. - */ - if (pl022->master_info->rt) { - dev_info(&pl022->adev->dev, - "will run message pump with realtime priority\n"); - sched_setscheduler(pl022->kworker_task, SCHED_FIFO, ¶m); - } return 0; } -static int start_queue(struct pl022 *pl022) +static int pl022_prepare_transfer_hardware(struct spi_master *master) { - unsigned long flags; - - spin_lock_irqsave(&pl022->queue_lock, flags); - - if (pl022->running || pl022->busy) { - spin_unlock_irqrestore(&pl022->queue_lock, flags); - return -EBUSY; - } - - pl022->running = true; - pl022->cur_msg = NULL; - pl022->cur_transfer = NULL; - pl022->cur_chip = NULL; - pl022->next_msg_cs_active = false; - spin_unlock_irqrestore(&pl022->queue_lock, flags); - - queue_kthread_work(&pl022->kworker, &pl022->pump_messages); + struct pl022 *pl022 = spi_master_get_devdata(master); + /* + * Just make sure we have all we need to run the transfer by syncing + * with the runtime PM framework. + */ + pm_runtime_get_sync(&pl022->adev->dev); return 0; } -static int stop_queue(struct pl022 *pl022) +static int pl022_unprepare_transfer_hardware(struct spi_master *master) { - unsigned long flags; - unsigned limit = 500; - int status = 0; + struct pl022 *pl022 = spi_master_get_devdata(master); - spin_lock_irqsave(&pl022->queue_lock, flags); + /* nothing more to do - disable spi/ssp and power off */ + writew((readw(SSP_CR1(pl022->virtbase)) & + (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); - /* This is a bit lame, but is optimized for the common execution path. - * A wait_queue on the pl022->busy could be used, but then the common - * execution path (pump_messages) would be required to call wake_up or - * friends on every SPI message. Do this instead */ - while ((!list_empty(&pl022->queue) || pl022->busy) && limit--) { - spin_unlock_irqrestore(&pl022->queue_lock, flags); - msleep(10); - spin_lock_irqsave(&pl022->queue_lock, flags); + if (pl022->master_info->autosuspend_delay > 0) { + pm_runtime_mark_last_busy(&pl022->adev->dev); + pm_runtime_put_autosuspend(&pl022->adev->dev); + } else { + pm_runtime_put(&pl022->adev->dev); } - if (!list_empty(&pl022->queue) || pl022->busy) - status = -EBUSY; - else - pl022->running = false; - - spin_unlock_irqrestore(&pl022->queue_lock, flags); - - return status; -} - -static int destroy_queue(struct pl022 *pl022) -{ - int status; - - status = stop_queue(pl022); - - /* - * We are unloading the module or failing to load (only two calls - * to this routine), and neither call can handle a return value. - * However, flush_kthread_worker will block until all work is done. - * If the reason that stop_queue timed out is that the work will never - * finish, then it does no good to call flush/stop thread, so - * return anyway. - */ - if (status != 0) - return status; - - flush_kthread_worker(&pl022->kworker); - kthread_stop(pl022->kworker_task); - return 0; } @@ -1803,38 +1646,6 @@ static int verify_controller_parameters(struct pl022 *pl022, return 0; } -/** - * pl022_transfer - transfer function registered to SPI master framework - * @spi: spi device which is requesting transfer - * @msg: spi message which is to handled is queued to driver queue - * - * This function is registered to the SPI framework for this SPI master - * controller. It will queue the spi_message in the queue of driver if - * the queue is not stopped and return. - */ -static int pl022_transfer(struct spi_device *spi, struct spi_message *msg) -{ - struct pl022 *pl022 = spi_master_get_devdata(spi->master); - unsigned long flags; - - spin_lock_irqsave(&pl022->queue_lock, flags); - - if (!pl022->running) { - spin_unlock_irqrestore(&pl022->queue_lock, flags); - return -ESHUTDOWN; - } - msg->actual_length = 0; - msg->status = -EINPROGRESS; - msg->state = STATE_START; - - list_add_tail(&msg->queue, &pl022->queue); - if (pl022->running && !pl022->busy) - queue_kthread_work(&pl022->kworker, &pl022->pump_messages); - - spin_unlock_irqrestore(&pl022->queue_lock, flags); - return 0; -} - static inline u32 spi_rate(u32 rate, u16 cpsdvsr, u16 scr) { return rate / (cpsdvsr * (1 + scr)); @@ -2197,7 +2008,10 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) master->num_chipselect = platform_info->num_chipselect; master->cleanup = pl022_cleanup; master->setup = pl022_setup; - master->transfer = pl022_transfer; + master->prepare_transfer_hardware = pl022_prepare_transfer_hardware; + master->transfer_one_message = pl022_transfer_one_message; + master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware; + master->rt = platform_info->rt; /* * Supports mode 0-3, loopback, and active low CS. Transfers are @@ -2241,6 +2055,10 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) goto err_no_clk_en; } + /* Initialize transfer pump */ + tasklet_init(&pl022->pump_transfers, pump_transfers, + (unsigned long)pl022); + /* Disable SSP */ writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); @@ -2260,17 +2078,6 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) platform_info->enable_dma = 0; } - /* Initialize and start queue */ - status = init_queue(pl022); - if (status != 0) { - dev_err(&adev->dev, "probe - problem initializing queue\n"); - goto err_init_queue; - } - status = start_queue(pl022); - if (status != 0) { - dev_err(&adev->dev, "probe - problem starting queue\n"); - goto err_start_queue; - } /* Register with the SPI framework */ amba_set_drvdata(adev, pl022); status = spi_register_master(master); @@ -2296,9 +2103,6 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) return 0; err_spi_register: - err_start_queue: - err_init_queue: - destroy_queue(pl022); if (platform_info->enable_dma) pl022_dma_remove(pl022); @@ -2334,9 +2138,6 @@ pl022_remove(struct amba_device *adev) */ pm_runtime_get_noresume(&adev->dev); - /* Remove the queue */ - if (destroy_queue(pl022) != 0) - dev_err(&adev->dev, "queue remove failed\n"); load_ssp_default_config(pl022); if (pl022->master_info->enable_dma) pl022_dma_remove(pl022); @@ -2358,12 +2159,12 @@ pl022_remove(struct amba_device *adev) static int pl022_suspend(struct device *dev) { struct pl022 *pl022 = dev_get_drvdata(dev); - int status = 0; + int ret; - status = stop_queue(pl022); - if (status) { - dev_warn(dev, "suspend cannot stop queue\n"); - return status; + ret = spi_master_suspend(pl022->master); + if (ret) { + dev_warn(dev, "cannot suspend master\n"); + return ret; } dev_dbg(dev, "suspended\n"); @@ -2373,16 +2174,16 @@ static int pl022_suspend(struct device *dev) static int pl022_resume(struct device *dev) { struct pl022 *pl022 = dev_get_drvdata(dev); - int status = 0; + int ret; /* Start the queue running */ - status = start_queue(pl022); - if (status) - dev_err(dev, "problem starting queue (%d)\n", status); + ret = spi_master_resume(pl022->master); + if (ret) + dev_err(dev, "problem starting queue (%d)\n", ret); else dev_dbg(dev, "resumed\n"); - return status; + return ret; } #endif /* CONFIG_PM */ diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index b2ccdea30cb9..5ae1e84d9037 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include static void spidev_release(struct device *dev) { @@ -507,6 +510,293 @@ spi_register_board_info(struct spi_board_info const *info, unsigned n) /*-------------------------------------------------------------------------*/ +/** + * spi_pump_messages - kthread work function which processes spi message queue + * @work: pointer to kthread work struct contained in the master struct + * + * This function checks if there is any spi message in the queue that + * needs processing and if so call out to the driver to initialize hardware + * and transfer each message. + * + */ +static void spi_pump_messages(struct kthread_work *work) +{ + struct spi_master *master = + container_of(work, struct spi_master, pump_messages); + unsigned long flags; + bool was_busy = false; + int ret; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&master->queue_lock, flags); + if (list_empty(&master->queue) || !master->running) { + if (master->busy) { + ret = master->unprepare_transfer_hardware(master); + if (ret) { + dev_err(&master->dev, + "failed to unprepare transfer hardware\n"); + return; + } + } + master->busy = false; + spin_unlock_irqrestore(&master->queue_lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (master->cur_msg) { + spin_unlock_irqrestore(&master->queue_lock, flags); + return; + } + /* Extract head of queue */ + master->cur_msg = + list_entry(master->queue.next, struct spi_message, queue); + + list_del_init(&master->cur_msg->queue); + if (master->busy) + was_busy = true; + else + master->busy = true; + spin_unlock_irqrestore(&master->queue_lock, flags); + + if (!was_busy) { + ret = master->prepare_transfer_hardware(master); + if (ret) { + dev_err(&master->dev, + "failed to prepare transfer hardware\n"); + return; + } + } + + ret = master->transfer_one_message(master, master->cur_msg); + if (ret) { + dev_err(&master->dev, + "failed to transfer one message from queue\n"); + return; + } +} + +static int spi_init_queue(struct spi_master *master) +{ + struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; + + INIT_LIST_HEAD(&master->queue); + spin_lock_init(&master->queue_lock); + + master->running = false; + master->busy = false; + + init_kthread_worker(&master->kworker); + master->kworker_task = kthread_run(kthread_worker_fn, + &master->kworker, + dev_name(&master->dev)); + if (IS_ERR(master->kworker_task)) { + dev_err(&master->dev, "failed to create message pump task\n"); + return -ENOMEM; + } + init_kthread_work(&master->pump_messages, spi_pump_messages); + + /* + * Master config will indicate if this controller should run the + * message pump with high (realtime) priority to reduce the transfer + * latency on the bus by minimising the delay between a transfer + * request and the scheduling of the message pump thread. Without this + * setting the message pump thread will remain at default priority. + */ + if (master->rt) { + dev_info(&master->dev, + "will run message pump with realtime priority\n"); + sched_setscheduler(master->kworker_task, SCHED_FIFO, ¶m); + } + + return 0; +} + +/** + * spi_get_next_queued_message() - called by driver to check for queued + * messages + * @master: the master to check for queued messages + * + * If there are more messages in the queue, the next message is returned from + * this call. + */ +struct spi_message *spi_get_next_queued_message(struct spi_master *master) +{ + struct spi_message *next; + unsigned long flags; + + /* get a pointer to the next message, if any */ + spin_lock_irqsa