From 14af60b6fb3b76634278364b697dae2f9f360abf Mon Sep 17 00:00:00 2001 From: Chris Blair Date: Thu, 2 Feb 2012 13:59:34 +0100 Subject: [PATCH] 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; }; /** -- 2.20.1