From 50c959fc3366693174cdf3f1c82d0c01bbee926f Mon Sep 17 00:00:00 2001 From: Lukasz Czerwinski Date: Mon, 9 Sep 2013 16:09:25 +0200 Subject: spi: spi-s3c64xx: Use module_platform_driver() subsys_init_call() initializes driver too early. It's preventing to move DMA channel allocation at the begining (driver probe). This patch reduces and simplifies initalization code by using module_platform_driver() macro. It's also efficiently delaying driver startup. Signed-off-by: Lukasz Czerwinski Signed-off-by: Kyungmin Park Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 512b8893893b..20dd71237d3b 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1633,22 +1633,13 @@ static struct platform_driver s3c64xx_spi_driver = { .pm = &s3c64xx_spi_pm, .of_match_table = of_match_ptr(s3c64xx_spi_dt_match), }, + .probe = s3c64xx_spi_probe, .remove = s3c64xx_spi_remove, .id_table = s3c64xx_spi_driver_ids, }; MODULE_ALIAS("platform:s3c64xx-spi"); -static int __init s3c64xx_spi_init(void) -{ - return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe); -} -subsys_initcall(s3c64xx_spi_init); - -static void __exit s3c64xx_spi_exit(void) -{ - platform_driver_unregister(&s3c64xx_spi_driver); -} -module_exit(s3c64xx_spi_exit); +module_platform_driver(s3c64xx_spi_driver); MODULE_AUTHOR("Jaswinder Singh "); MODULE_DESCRIPTION("S3C64XX SPI Controller Driver"); -- cgit v1.2.3 From 91800f0e90050a4db4c77e940796f501e02af8be Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 31 Aug 2013 18:55:53 +0100 Subject: spi/s3c64xx: Use managed registration Also improve the error reporting on failure and remove a duplicate put. This provides a small code saving. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 20dd71237d3b..8bed27a4108c 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1428,9 +1428,9 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) 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; + ret = devm_spi_register_master(&pdev->dev, master); + if (ret != 0) { + dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret); goto err3; } @@ -1461,16 +1461,12 @@ static int s3c64xx_spi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); - spi_unregister_master(master); - writel(0, sdd->regs + S3C64XX_SPI_INT_EN); clk_disable_unprepare(sdd->src_clk); clk_disable_unprepare(sdd->clk); - spi_master_put(master); - return 0; } -- cgit v1.2.3 From a3b924df8fa9083d2291efc2d1dca93609953718 Mon Sep 17 00:00:00 2001 From: Mateusz Krawczuk Date: Mon, 23 Sep 2013 11:45:45 +0200 Subject: spi: s3c64xx: Add missing compatibles Add compatibles for s3c6410, s5pc100 and s5pc110/s5pv210 boards. Signed-off-by: Mateusz Krawczuk Signed-off-by: Kyungmin Park Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8bed27a4108c..84cc6ac801fe 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1612,6 +1612,18 @@ static struct platform_device_id s3c64xx_spi_driver_ids[] = { }; static const struct of_device_id s3c64xx_spi_dt_match[] = { + { .compatible = "samsung,s3c2443-spi", + .data = (void *)&s3c2443_spi_port_config, + }, + { .compatible = "samsung,s3c6410-spi", + .data = (void *)&s3c6410_spi_port_config, + }, + { .compatible = "samsung,s5pc100-spi", + .data = (void *)&s5pc100_spi_port_config, + }, + { .compatible = "samsung,s5pv210-spi", + .data = (void *)&s5pv210_spi_port_config, + }, { .compatible = "samsung,exynos4210-spi", .data = (void *)&exynos4_spi_port_config, }, -- cgit v1.2.3 From 7d5f880b46f222ff83a76fd36b01f5a59bf32122 Mon Sep 17 00:00:00 2001 From: Mateusz Krawczuk Date: Tue, 24 Sep 2013 18:07:46 +0200 Subject: spi: s3c64xx: Allow build on all Samsung platforms Replace all symbols by simple dependency PLAT_SAMSUNG. Signed-off-by: Mateusz Krawczuk Signed-off-by: Kyungmin Park Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b9c53cc40e1f..d92d6690d615 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -393,7 +393,7 @@ config SPI_S3C24XX_FIQ config SPI_S3C64XX tristate "Samsung S3C64XX series type SPI" - depends on (ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5P64X0 || ARCH_EXYNOS) + depends on PLAT_SAMSUNG select S3C64XX_DMA if ARCH_S3C64XX help SPI driver for Samsung S3C64XX and newer SoCs. -- cgit v1.2.3 From 67651b29ef15e5ef9897ed8cb9072222c4cf3432 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 20:10:26 +0100 Subject: spi/s3c64xx: Flush FIFOs prior to cleaning up transfer Ensure that the FIFOs are fully drained before we deassert /CS or do any delays that have been requested in order to ensure that the behaviour visible on the bus matches that which was requested by the caller. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 229c6b994be3..2e267ce9dc19 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -960,6 +960,8 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, goto out; } + flush_fifo(sdd); + if (xfer->delay_usecs) udelay(xfer->delay_usecs); @@ -972,8 +974,6 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, } msg->actual_length += xfer->len; - - flush_fifo(sdd); } out: -- cgit v1.2.3 From 8b06d5b8576d1df4122fa9ca40578ec7f094cb3e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 18:44:53 +0100 Subject: spi/s3c64xx: Check that clock enables succeed on runtime resume Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 2e267ce9dc19..dd5ed13436e5 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -1524,9 +1524,17 @@ static int s3c64xx_spi_runtime_resume(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + int ret; - clk_prepare_enable(sdd->src_clk); - clk_prepare_enable(sdd->clk); + ret = clk_prepare_enable(sdd->src_clk); + if (ret != 0) + return ret; + + ret = clk_prepare_enable(sdd->clk); + if (ret != 0) { + clk_disable_unprepare(sdd->src_clk); + return ret; + } return 0; } -- cgit v1.2.3 From 64d930ac116db25db814f76c31bfd4a7ea428f74 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 18:45:23 +0100 Subject: spi/s3c64xx: Remove unused gpios field from driver data Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index dd5ed13436e5..7f960db66763 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -205,7 +205,6 @@ struct s3c64xx_spi_driver_data { #endif struct s3c64xx_spi_port_config *port_conf; unsigned int port_id; - unsigned long gpios[4]; bool cs_gpio; }; -- cgit v1.2.3 From dd97e26849c16f484191f056b6e080cc30ebe98b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 18:58:55 +0100 Subject: spi/s3c64xx: Use core cs_gpio field Rather than using the driver custom platform data to store the chip select GPIO use the cs_gpio field provided by the SPI core, supporting future refectoring. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 7f960db66763..43caeee8641a 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -558,22 +558,18 @@ static void enable_datapath(struct s3c64xx_spi_driver_data *sdd, static inline void enable_cs(struct s3c64xx_spi_driver_data *sdd, struct spi_device *spi) { - struct s3c64xx_spi_csinfo *cs; - if (sdd->tgl_spi != NULL) { /* If last device toggled after mssg */ if (sdd->tgl_spi != spi) { /* if last mssg on diff device */ /* Deselect the last toggled device */ - cs = sdd->tgl_spi->controller_data; - if (sdd->cs_gpio) - gpio_set_value(cs->line, + if (spi->cs_gpio >= 0) + gpio_set_value(spi->cs_gpio, spi->mode & SPI_CS_HIGH ? 0 : 1); } sdd->tgl_spi = NULL; } - cs = spi->controller_data; - if (sdd->cs_gpio) - gpio_set_value(cs->line, spi->mode & SPI_CS_HIGH ? 1 : 0); + if (spi->cs_gpio >= 0) + gpio_set_value(spi->cs_gpio, spi->mode & SPI_CS_HIGH ? 1 : 0); /* Start the signals */ writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); @@ -701,13 +697,11 @@ static int wait_for_xfer(struct s3c64xx_spi_driver_data *sdd, static inline void disable_cs(struct s3c64xx_spi_driver_data *sdd, struct spi_device *spi) { - struct s3c64xx_spi_csinfo *cs = spi->controller_data; - if (sdd->tgl_spi == spi) sdd->tgl_spi = NULL; - if (sdd->cs_gpio) - gpio_set_value(cs->line, spi->mode & SPI_CS_HIGH ? 0 : 1); + if (spi->cs_gpio >= 0) + gpio_set_value(spi->cs_gpio, spi->mode & SPI_CS_HIGH ? 0 : 1); /* Quiese the signals */ writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); @@ -1070,6 +1064,8 @@ static int s3c64xx_spi_setup(struct spi_device *spi) cs->line, err); goto err_gpio_req; } + + spi->cs_gpio = cs->line; } spi_set_ctldata(spi, cs); @@ -1139,8 +1135,8 @@ static void s3c64xx_spi_cleanup(struct spi_device *spi) struct s3c64xx_spi_driver_data *sdd; sdd = spi_master_get_devdata(spi->master); - if (cs && sdd->cs_gpio) { - gpio_free(cs->line); + if (spi->cs_gpio) { + gpio_free(spi->cs_gpio); if (spi->dev.of_node) kfree(cs); } -- cgit v1.2.3 From 8c09daa1c963e2ab58029520dd2f3d54184d7258 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 19:56:31 +0100 Subject: spi/s3c64xx: Factor transfer start out of enable/disable_cs() The hardware level /CS handling is tied to the start of the data path so is rolled into the same function as we use to manipulate GPIO /CS. In order to support factoring out the /CS handling into the core separate the two and explicitly start transfers separately to the /CS handling. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 43caeee8641a..2ab96fb28d22 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -570,9 +570,6 @@ static inline void enable_cs(struct s3c64xx_spi_driver_data *sdd, if (spi->cs_gpio >= 0) gpio_set_value(spi->cs_gpio, spi->mode & SPI_CS_HIGH ? 1 : 0); - - /* Start the signals */ - writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); } static u32 s3c64xx_spi_wait_for_timeout(struct s3c64xx_spi_driver_data *sdd, @@ -702,9 +699,6 @@ static inline void disable_cs(struct s3c64xx_spi_driver_data *sdd, if (spi->cs_gpio >= 0) gpio_set_value(spi->cs_gpio, spi->mode & SPI_CS_HIGH ? 0 : 1); - - /* Quiese the signals */ - writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); } static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) @@ -930,6 +924,9 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, /* Slave Select */ enable_cs(sdd, spi); + /* Start the signals */ + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + spin_unlock_irqrestore(&sdd->lock, flags); status = wait_for_xfer(sdd, xfer, use_dma); @@ -970,10 +967,14 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, } out: - if (!cs_toggle || status) + if (!cs_toggle || status) { + /* Quiese the signals */ + writel(S3C64XX_SPI_SLAVE_SIG_INACT, + sdd->regs + S3C64XX_SPI_SLAVE_SEL); disable_cs(sdd, spi); - else + } else { sdd->tgl_spi = spi; + } s3c64xx_spi_unmap_mssg(sdd, msg); @@ -1112,11 +1113,13 @@ static int s3c64xx_spi_setup(struct spi_device *spi) } pm_runtime_put(&sdd->pdev->dev); + writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); disable_cs(sdd, spi); return 0; setup_exit: /* setup() returns with device de-selected */ + writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); disable_cs(sdd, spi); gpio_free(cs->line); -- cgit v1.2.3 From 0f5a751ace250097d3ad7835df7d376f21f44509 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 20:38:01 +0100 Subject: spi/s3c64xx: Enable GPIO /CS prior to starting hardware To help with bisection of future refactoring to share more of the code for handling a spi_message pull the enabling of GPIO based /CS prior to all the hardware setup for starting a transfer. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 2ab96fb28d22..e6312322f66a 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -906,6 +906,9 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, s3c64xx_spi_config(sdd); } + /* Slave Select */ + enable_cs(sdd, spi); + /* Polling method for xfers not bigger than FIFO capacity */ use_dma = 0; if (!is_polling(sdd) && @@ -921,9 +924,6 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, enable_datapath(sdd, spi, xfer, use_dma); - /* Slave Select */ - enable_cs(sdd, spi); - /* Start the signals */ writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); -- cgit v1.2.3 From 56ec1978ff07380bbdc0a942c8779ec9fd9e02ee Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 7 Oct 2013 19:33:53 +0100 Subject: spi: Provide trace points for message processing Provide tracepoints for the lifecycle of a message from submission to completion and for the active time for masters to help with performance analysis of SPI I/O. Signed-off-by: Mark Brown --- drivers/spi/spi.c | 16 +++++++- include/trace/events/spi.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 include/trace/events/spi.h (limited to 'drivers/spi') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 9e039c60c068..8bef0c9a7233 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -39,6 +39,9 @@ #include #include +#define CREATE_TRACE_POINTS +#include + static void spidev_release(struct device *dev) { struct spi_device *spi = to_spi_device(dev); @@ -557,6 +560,7 @@ static void spi_pump_messages(struct kthread_work *work) pm_runtime_mark_last_busy(master->dev.parent); pm_runtime_put_autosuspend(master->dev.parent); } + trace_spi_master_idle(master); return; } @@ -585,6 +589,9 @@ static void spi_pump_messages(struct kthread_work *work) } } + if (!was_busy) + trace_spi_master_busy(master); + if (!was_busy && master->prepare_transfer_hardware) { ret = master->prepare_transfer_hardware(master); if (ret) { @@ -597,6 +604,8 @@ static void spi_pump_messages(struct kthread_work *work) } } + trace_spi_message_start(master->cur_msg); + ret = master->transfer_one_message(master, master->cur_msg); if (ret) { dev_err(&master->dev, @@ -689,6 +698,8 @@ void spi_finalize_current_message(struct spi_master *master) mesg->state = NULL; if (mesg->complete) mesg->complete(mesg->context); + + trace_spi_message_done(mesg); } EXPORT_SYMBOL_GPL(spi_finalize_current_message); @@ -1421,6 +1432,10 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message) struct spi_master *master = spi->master; struct spi_transfer *xfer; + message->spi = spi; + + trace_spi_message_submit(message); + if (list_empty(&message->transfers)) return -EINVAL; if (!message->complete) @@ -1520,7 +1535,6 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message) } } - message->spi = spi; message->status = -EINPROGRESS; return master->transfer(spi, message); } diff --git a/include/trace/events/spi.h b/include/trace/events/spi.h new file mode 100644 index 000000000000..a7b09072ce56 --- /dev/null +++ b/include/trace/events/spi.h @@ -0,0 +1,94 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM spi + +#if !defined(_TRACE_SPI_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SPI_H + +#include +#include + +DECLARE_EVENT_CLASS(spi_master, + + TP_PROTO(struct spi_master *master), + + TP_ARGS(master), + + TP_STRUCT__entry( + __field( int, bus_num ) + ), + + TP_fast_assign( + __entry->bus_num = master->bus_num; + ), + + TP_printk("spi%d", (int)__entry->bus_num) + +); + +DEFINE_EVENT(spi_master, spi_master_idle, + + TP_PROTO(struct spi_master *master), + + TP_ARGS(master) + +); + +DEFINE_EVENT(spi_master, spi_master_busy, + + TP_PROTO(struct spi_master *master), + + TP_ARGS(master) + +); + +DECLARE_EVENT_CLASS(spi_message, + + TP_PROTO(struct spi_message *msg), + + TP_ARGS(msg), + + TP_STRUCT__entry( + __field( int, bus_num ) + __field( int, chip_select ) + __field( struct spi_message *, msg ) + ), + + TP_fast_assign( + __entry->bus_num = msg->spi->master->bus_num; + __entry->chip_select = msg->spi->chip_select; + __entry->msg = msg; + ), + + TP_printk("spi%d.%d %p", (int)__entry->bus_num, + (int)__entry->chip_select, + (struct spi_message *)__entry->msg) +); + +DEFINE_EVENT(spi_message, spi_message_submit, + + TP_PROTO(struct spi_message *msg), + + TP_ARGS(msg) + +); + +DEFINE_EVENT(spi_message, spi_message_start, + + TP_PROTO(struct spi_message *msg), + + TP_ARGS(msg) + +); + +DEFINE_EVENT(spi_message, spi_message_done, + + TP_PROTO(struct spi_message *msg), + + TP_ARGS(msg) + +); + +#endif /* _TRACE_POWER_H */ + +/* This part must be outside protection */ +#include -- cgit v1.2.3 From ebd805cc14bec607e74795b7933570f240508cb4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 27 Sep 2013 19:56:31 +0100 Subject: spi/s3c64xx: Factor transfer start out of enable/disable_cs() The hardware level /CS handling is tied to the start of the data path so is rolled into the same function as we use to manipulate GPIO /CS. In order to support factoring out the /CS handling into the core separate the two and explicitly start transfers separately to the /CS handling. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index e6312322f66a..8e732a1b8a9b 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -927,6 +927,9 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, /* Start the signals */ writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + /* Start the signals */ + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + spin_unlock_irqrestore(&sdd->lock, flags); status = wait_for_xfer(sdd, xfer, use_dma); -- cgit v1.2.3 From 2841a5fc375e9c573d10b82db30fa8a4cc25301c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 5 Oct 2013 00:23:12 +0100 Subject: spi: Provide per-message prepare and unprepare operations Many SPI drivers perform setup and tear down on every message, usually doing things like DMA mapping the message. Provide hooks for them to use to provide such operations. This is of limited value for drivers that implement transfer_one_message() but will be of much greater utility with future factoring out of standard implementations of that function. Signed-off-by: Mark Brown --- drivers/spi/spi.c | 22 ++++++++++++++++++++++ include/linux/spi/spi.h | 11 +++++++++++ 2 files changed, 33 insertions(+) (limited to 'drivers/spi') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 8bef0c9a7233..8a30c6b66a64 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -606,6 +606,18 @@ static void spi_pump_messages(struct kthread_work *work) trace_spi_message_start(master->cur_msg); + if (master->prepare_message) { + ret = master->prepare_message(master, master->cur_msg); + if (ret) { + dev_err(&master->dev, + "failed to prepare message: %d\n", ret); + master->cur_msg->status = ret; + spi_finalize_current_message(master); + return; + } + master->cur_msg_prepared = true; + } + ret = master->transfer_one_message(master, master->cur_msg); if (ret) { dev_err(&master->dev, @@ -687,6 +699,7 @@ void spi_finalize_current_message(struct spi_master *master) { struct spi_message *mesg; unsigned long flags; + int ret; spin_lock_irqsave(&master->queue_lock, flags); mesg = master->cur_msg; @@ -695,6 +708,15 @@ void spi_finalize_current_message(struct spi_master *master) queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); + if (master->cur_msg_prepared && master->unprepare_message) { + ret = master->unprepare_message(master, mesg); + if (ret) { + dev_err(&master->dev, + "failed to unprepare message: %d\n", ret); + } + } + master->cur_msg_prepared = false; + mesg->state = NULL; if (mesg->complete) mesg->complete(mesg->context); diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 887116dbce2c..000b50bee6c0 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -257,6 +257,8 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @queue_lock: spinlock to syncronise access to message queue * @queue: message queue * @cur_msg: the currently in-flight message + * @cur_msg_prepared: spi_prepare_message was called for the currently + * in-flight message * @busy: message pump is busy * @running: message pump is running * @rt: whether this queue is set to run as a realtime task @@ -274,6 +276,10 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @unprepare_transfer_hardware: there are currently no more messages on the * queue so the subsystem notifies the driver that it may relax the * hardware by issuing this call + * @prepare_message: set up the controller to transfer a single message, + * for example doing DMA mapping. Called from threaded + * context. + * @unprepare_message: undo any work done by prepare_message(). * @cs_gpios: Array of GPIOs to use as chip select lines; one per CS * number. Any individual value may be -ENOENT for CS lines that * are not GPIOs (driven by the SPI controller itself). @@ -388,11 +394,16 @@ struct spi_master { bool running; bool rt; bool auto_runtime_pm; + bool cur_msg_prepared; int (*prepare_transfer_hardware)(struct spi_master *master); int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_master *master); + int (*prepare_message)(struct spi_master *master, + struct spi_message *message); + int (*unprepare_message)(struct spi_master *master, + struct spi_message *message); /* gpio chip select */ int *cs_gpios; -- cgit v1.2.3 From 6bb9c0e34185717f5b0df4ad468476f64f0d73fb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 5 Oct 2013 00:42:58 +0100 Subject: spi/s3c64xx: Use prepare_message() and unprepare_message() This is of very little value in itself but will be useful once the loop iterating over the transfers is also factored out into the core. Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 512b8893893b..27b744be982e 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -862,16 +862,12 @@ static void s3c64xx_spi_unmap_mssg(struct s3c64xx_spi_driver_data *sdd, } } -static int s3c64xx_spi_transfer_one_message(struct spi_master *master, - struct spi_message *msg) +static int s3c64xx_spi_prepare_message(struct spi_master *master, + struct spi_message *msg) { struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); struct spi_device *spi = msg->spi; struct s3c64xx_spi_csinfo *cs = spi->controller_data; - struct spi_transfer *xfer; - int status = 0, cs_toggle = 0; - u32 speed; - u8 bpw; /* If Master's(controller) state differs from that needed by Slave */ if (sdd->cur_speed != spi->max_speed_hz @@ -887,13 +883,25 @@ static int s3c64xx_spi_transfer_one_message(struct spi_master *master, if (s3c64xx_spi_map_mssg(sdd, msg)) { dev_err(&spi->dev, "Xfer: Unable to map message buffers!\n"); - status = -ENOMEM; - goto out; + return -ENOMEM; } /* Configure feedback delay */ writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK); + return 0; +} + +static int s3c64xx_spi_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + struct spi_device *spi = msg->spi; + struct spi_transfer *xfer; + int status = 0, cs_toggle = 0; + u32 speed; + u8 bpw; + list_for_each_entry(xfer, &msg->transfers, transfer_list) { unsigned long flags; @@ -991,6 +999,16 @@ out: return 0; } +static int s3c64xx_spi_unprepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + + s3c64xx_spi_unmap_mssg(sdd, msg); + + return 0; +} + static struct s3c64xx_spi_csinfo *s3c64xx_get_slave_ctrldata( struct spi_device *spi) { @@ -1359,7 +1377,9 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) master->setup = s3c64xx_spi_setup; master->cleanup = s3c64xx_spi_cleanup; master->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer; + master->prepare_message = s3c64xx_spi_prepare_message; master->transfer_one_message = s3c64xx_spi_transfer_one_message; + master->unprepare_message = s3c64xx_spi_unprepare_message; master->unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer; master->num_chipselect = sci->num_cs; master->dma_alignment = 8; -- cgit v1.2.3 From b158935f70b9c156903338053216dd0adf7ce31c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 5 Oct 2013 11:50:40 +0100 Subject: spi: Provide common spi_message processing loop The loops which SPI controller drivers use to process the list of transfers in a spi_message are typically very similar and have some error prone areas such as the handling of /CS. Help simplify drivers by factoring this code out into the core - if drivers provide a transfer_one() function instead of a transfer_one_message() function the core will handle processing at the message level. /CS can be controlled by either setting cs_gpio or providing a set_cs function. If this is not possible for hardware reasons then both can be omitted and the driver should continue to implement manual /CS handling. This is a first step in refactoring and it is expected that there will be further enhancements, for example factoring out of the mapping of transfers for DMA and the initiation and completion of interrupt driven transfers. Signed-off-by: Mark Brown --- drivers/spi/spi.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/spi.h | 21 ++++++++++- include/trace/events/spi.h | 42 +++++++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 8a30c6b66a64..85c18d8a86b3 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -526,6 +526,95 @@ int spi_register_board_info(struct spi_board_info const *info, unsigned n) /*-------------------------------------------------------------------------*/ +static void spi_set_cs(struct spi_device *spi, bool enable) +{ + if (spi->mode & SPI_CS_HIGH) + enable = !enable; + + if (spi->cs_gpio >= 0) + gpio_set_value(spi->cs_gpio, !enable); + else if (spi->master->set_cs) + spi->master->set_cs(spi, !enable); +} + +/* + * spi_transfer_one_message - Default implementation of transfer_one_message() + * + * This is a standard implementation of transfer_one_message() for + * drivers which impelment a transfer_one() operation. It provides + * standard handling of delays and chip select management. + */ +static int spi_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_transfer *xfer; + bool cur_cs = true; + bool keep_cs = false; + int ret = 0; + + spi_set_cs(msg->spi, true); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + trace_spi_transfer_start(msg, xfer); + + INIT_COMPLETION(master->xfer_completion); + + ret = master->transfer_one(master, msg->spi, xfer); + if (ret < 0) { + dev_err(&msg->spi->dev, + "SPI transfer failed: %d\n", ret); + goto out; + } + + if (ret > 0) + wait_for_completion(&master->xfer_completion); + + trace_spi_transfer_stop(msg, xfer); + + if (msg->status != -EINPROGRESS) + goto out; + + if (xfer->delay_usecs) + udelay(xfer->delay_usecs); + + if (xfer->cs_change) { + if (list_is_last(&xfer->transfer_list, + &msg->transfers)) { + keep_cs = true; + } else { + cur_cs = !cur_cs; + spi_set_cs(msg->spi, cur_cs); + } + } + + msg->actual_length += xfer->len; + } + +out: + if (ret != 0 || !keep_cs) + spi_set_cs(msg->spi, false); + + if (msg->status == -EINPROGRESS) + msg->status = ret; + + spi_finalize_current_message(master); + + return ret; +} + +/** + * spi_finalize_current_transfer - report completion of a transfer + * + * Called by SPI drivers using the core transfer_one_message() + * implementation to notify it that the current interrupt driven + * transfer has finised and the next one may be scheduled. + */ +void spi_finalize_current_transfer(struct spi_master *master) +{ + complete(&master->xfer_completion); +} +EXPORT_SYMBOL_GPL(spi_finalize_current_transfer); + /** * spi_pump_messages - kthread work function which processes spi message queue * @work: pointer to kthread work struct contained in the master struct @@ -836,6 +925,8 @@ static int spi_master_initialize_queue(struct spi_master *master) master->queued = true; master->transfer = spi_queued_transfer; + if (!master->transfer_one_message) + master->transfer_one_message = spi_transfer_one_message; /* Initialize and start queue */ ret = spi_init_queue(master); @@ -1242,6 +1333,7 @@ int spi_register_master(struct spi_master *master) spin_lock_init(&master->bus_lock_spinlock); mutex_init(&master->bus_lock_mutex); master->bus_lock_flag = 0; + init_completion(&master->xfer_completion); /* register the device, then userspace will see it. * registration fails if the bus ID is in use. diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 000b50bee6c0..da371ab5ebeb 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -23,6 +23,7 @@ #include #include #include +#include /* * INTERFACES between SPI master-side drivers and SPI infrastructure. @@ -150,8 +151,7 @@ static inline void *spi_get_drvdata(struct spi_device *spi) } struct spi_message; - - +struct spi_transfer; /** * struct spi_driver - Host side "protocol" driver @@ -259,6 +259,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @cur_msg: the currently in-flight message * @cur_msg_prepared: spi_prepare_message was called for the currently * in-flight message + * @xfer_completion: used by core tranfer_one_message() * @busy: message pump is busy * @running: message pump is running * @rt: whether this queue is set to run as a realtime task @@ -276,9 +277,15 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @unprepare_transfer_hardware: there are currently no more messages on the * queue so the subsystem notifies the driver that it may relax the * hardware by issuing this call + * @set_cs: assert or deassert chip select, true to assert. May be called + * from interrupt context. * @prepare_message: set up the controller to transfer a single message, * for example doing DMA mapping. Called from threaded * context. + * @transfer_one: transfer a single spi_transfer. When the + * driver is finished with this transfer it must call + * spi_finalize_current_transfer() so the subsystem can issue + * the next transfer * @unprepare_message: undo any work done by prepare_message(). * @cs_gpios: Array of GPIOs to use as chip select lines; one per CS * number. Any individual value may be -ENOENT for CS lines that @@ -395,6 +402,7 @@ struct spi_master { bool rt; bool auto_runtime_pm; bool cur_msg_prepared; + struct completion xfer_completion; int (*prepare_transfer_hardware)(struct spi_master *master); int (*transfer_one_message)(struct spi_master *master, @@ -405,6 +413,14 @@ struct spi_master { int (*unprepare_message)(struct spi_master *master, struct spi_message *message); + /* + * These hooks are for drivers that use a generic implementation + * of transfer_one_message() provied by the core. + */ + void (*set_cs)(struct spi_device *spi, bool enable); + int (*transfer_one)(struct spi_master *master, struct spi_device *spi, + struct spi_transfer *transfer); + /* gpio chip select */ int *cs_gpios; }; @@ -439,6 +455,7 @@ extern int spi_master_resume(struct spi_master *master); /* Calls the driver make to interact with the message queue */ extern struct spi_message *spi_get_next_queued_message(struct spi_master *master); extern void spi_finalize_current_message(struct spi_master *master); +extern void spi_finalize_current_transfer(struct spi_master *master); /* the spi driver core manages memory for the spi_master classdev */ extern struct spi_master * diff --git a/include/trace/events/spi.h b/include/trace/events/spi.h index 5e77e21f885a..7e02c983bbe2 100644 --- a/include/trace/events/spi.h +++ b/include/trace/events/spi.h @@ -108,6 +108,48 @@ TRACE_EVENT(spi_message_done, (unsigned)__entry->actual, (unsigned)__entry->frame) ); +DECLARE_EVENT_CLASS(spi_transfer, + + TP_PROTO(struct spi_message *msg, struct spi_transfer *xfer), + + TP_ARGS(msg, xfer), + + TP_STRUCT__entry( + __field( int, bus_num ) + __field( int, chip_select ) + __field( struct spi_transfer *, xfer ) + __field( int, len ) + ), + + TP_fast_assign( + __entry->bus_num = msg->spi->master->bus_num; + __entry->chip_select = msg->spi->chip_select; + __entry->xfer = xfer; + __entry->len = xfer->len; + ), + + TP_printk("spi%d.%d %p len=%d", (int)__entry->bus_num, + (int)__entry->chip_select, + (struct spi_message *)__entry->xfer, + (int)__entry->len) +); + +DEFINE_EVENT(spi_transfer, spi_transfer_start, + + TP_PROTO(struct spi_message *msg, struct spi_transfer *xfer), + + TP_ARGS(msg, xfer) + +); + +DEFINE_EVENT(spi_transfer, spi_transfer_stop, + + TP_PROTO(struct spi_message *msg, struct spi_transfer *xfer), + + TP_ARGS(msg, xfer) + +); + #endif /* _TRACE_POWER_H */ /* This part must be outside protection */ -- cgit v1.2.3 From 0732a9d2a1551e3be5fd8a3ea680deb3d9e56193 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 5 Oct 2013 11:51:14 +0100 Subject: spi/s3c64xx: Use core message handling Signed-off-by: Mark Brown --- drivers/spi/spi-s3c64xx.c | 152 +++++++++++++++++----------------------------- 1 file changed, 56 insertions(+), 96 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index c7b36c06ef40..25eb352d6a42 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -879,121 +879,81 @@ static int s3c64xx_spi_prepare_message(struct spi_master *master, return 0; } -static int s3c64xx_spi_transfer_one_message(struct spi_master *master, - struct spi_message *msg) +static int s3c64xx_spi_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) { struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); - struct spi_device *spi = msg->spi; - struct spi_transfer *xfer; - int status = 0, cs_toggle = 0; + int status; u32 speed; u8 bpw; + unsigned long flags; + int use_dma; - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - - unsigned long flags; - int use_dma; - - INIT_COMPLETION(sdd->xfer_completion); - - /* Only BPW and Speed may change across transfers */ - bpw = xfer->bits_per_word; - speed = xfer->speed_hz ? : spi->max_speed_hz; - - if (xfer->len % (bpw / 8)) { - dev_err(&spi->dev, - "Xfer length(%u) not a multiple of word size(%u)\n", - xfer->len, bpw / 8); - status = -EIO; - goto out; - } - - if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) { - sdd->cur_bpw = bpw; - sdd->cur_speed = speed; - s3c64xx_spi_config(sdd); - } - - /* Slave Select */ - enable_cs(sdd, spi); - - /* Polling method for xfers not bigger than FIFO capacity */ - use_dma = 0; - if (!is_polling(sdd) && - (sdd->rx_dma.ch && sdd->tx_dma.ch && - (xfer->len > ((FIFO_LVL_MASK(sdd) >> 1) + 1)))) - use_dma = 1; - - spin_lock_irqsave(&sdd->lock, flags); + INIT_COMPLETION(sdd->xfer_completion); - /* Pending only which is to be done */ - sdd->state &= ~RXBUSY; - sdd->state &= ~TXBUSY; + /* Only BPW and Speed may change across transfers */ + bpw = xfer->bits_per_word; + speed = xfer->speed_hz ? : spi->max_speed_hz; - enable_datapath(sdd, spi, xfer, use_dma); - - /* Start the signals */ - writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + if (xfer->len % (bpw / 8)) { + dev_err(&spi->dev, + "Xfer length(%u) not a multiple of word size(%u)\n", + xfer->len, bpw / 8); + return -EIO; + } - /* Start the signals */ - writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); + if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) { + sdd->cur_bpw = bpw; + sdd->cur_speed = speed; + s3c64xx_spi_config(sdd); + } - spin_unlock_irqrestore(&sdd->lock, flags); + /* Polling method for xfers not bigger than FIFO capacity */ + use_dma = 0; + if (!is_polling(sdd) && + (sdd->rx_dma.ch && sdd->tx_dma.ch && + (xfer->len > ((FIFO_LVL_MASK(sdd) >> 1) + 1)))) + use_dma = 1; - status = wait_for_xfer(sdd, xfer, use_dma); + spin_lock_irqsave(&sdd->lock, flags); - if (status) { - dev_err(&spi->dev, "I/O Error: rx-%d tx-%d res:rx-%c tx-%c len-%d\n", - xfer->rx_buf ? 1 : 0, xfer->tx_buf ? 1 : 0, - (sdd->state & RXBUSY) ? 'f' : 'p', - (sdd->state & TXBUSY) ? 'f' : 'p', - xfer->len); + /* Pending only which is to be done */ + sdd->state &= ~RXBUSY; + sdd->state &= ~TXBUSY; - if (use_dma) { - if (xfer->tx_buf != NULL - && (sdd->state & TXBUSY)) - s3c64xx_spi_dma_stop(sdd, &sdd->tx_dma); - if (xfer->rx_buf != NULL - && (sdd->state & RXBUSY)) - s3c64xx_spi_dma_stop(sdd, &sdd->rx_dma); - } + enable_datapath(sdd, spi, xfer, use_dma); - goto out; - } + /* Start the signals */ + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); - flush_fifo(sdd); + /* Start the signals */ + writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL); - if (xfer->delay_usecs) - udelay(xfer->delay_usecs); + spin_unlock_irqrestore(&sdd->lock, flags); - if (xfer->cs_change) { - /* Hint that the next mssg is gonna be - for the same device */ - if (list_is_last(&xfer->transfer_list, - &msg->transfers)) - cs_toggle = 1; + status = wait_for_xfer(sdd, xfer, use_dma); + + if (status) { + dev_err(&spi->dev, "I/O Error: rx-%d tx-%d res:rx-%c tx-%c len-%d\n", + xfer->rx_buf ? 1 : 0, xfer->tx_buf ? 1 : 0, + (sdd->state & RXBUSY) ? 'f' : 'p', + (sdd->state & TXBUSY) ? 'f' : 'p', + xfer->len); + + if (use_dma) { + if (xfer->tx_buf != NULL + && (sdd->state & TXBUSY)) + s3c64xx_spi_dma_stop(sdd, &sdd->tx_dma); + if (xfer->rx_buf != NULL + && (sdd->state & RXBUSY)) + s3c64xx_spi_dma_stop(sdd, &sdd->rx_dma); } - - msg->actual_length += xfer->len; - } - -out: - if (!cs_toggle || status) { - /* Quiese the signals */ - writel(S3C64XX_SPI_SLAVE_SIG_INACT, - sdd->regs + S3C64XX_SPI_SLAVE_SEL); - disable_cs(sdd, spi); } else { - sdd->tgl_spi = spi; + flush_fifo(sdd); } - s3c64xx_spi_unmap_mssg(sdd, msg); - - msg->status = status; - - spi_finalize_current_message(master); - - return 0; + return status; } static int s3c64xx_spi_unprepare_message(struct spi_master *master, @@ -1379,7 +1339,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) master->cleanup = s3c64xx_spi_cleanup; master->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer; master->prepare_message = s3c64xx_spi_prepare_message; - master->transfer_one_message = s3c64xx_spi_transfer_one_message; + master->transfer_one = s3c64xx_spi_transfer_one; master->unprepare_message = s3c64xx_spi_unprepare_message; master->unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer; master->num_chipselect = sci->num_cs; -- cgit v1.2.3