kernel: backport some SPI layer improvements from 4.1 to 3.18, significantly improves...
[openwrt/openwrt.git] / target / linux / generic / patches-3.18 / 092-02-spi-Pump-transfers-inside-calling-context-for-spi_sy.patch
diff --git a/target/linux/generic/patches-3.18/092-02-spi-Pump-transfers-inside-calling-context-for-spi_sy.patch b/target/linux/generic/patches-3.18/092-02-spi-Pump-transfers-inside-calling-context-for-spi_sy.patch
new file mode 100644 (file)
index 0000000..ddfb060
--- /dev/null
@@ -0,0 +1,184 @@
+From: Mark Brown <broonie@kernel.org>
+Date: Tue, 9 Dec 2014 21:38:05 +0000
+Subject: [PATCH] spi: Pump transfers inside calling context for spi_sync()
+
+If we are using the standard SPI message pump (which all drivers should be
+transitioning over to) then special case the message enqueue and instead of
+starting the worker thread to push messages to the hardware do so in the
+context of the caller if the controller is idle. This avoids a context
+switch in the common case where the controller has a single user in a
+single thread, for short PIO transfers there may be no need to context
+switch away from the calling context to complete the transfer.
+
+The code is a bit more complex than is desirable in part due to the need
+to handle drivers not using the standard queue and in part due to handling
+the various combinations of bus locking and asynchronous submission in
+interrupt context.
+
+It is still suboptimal since it will still wake the message pump for each
+transfer in order to schedule idling of the hardware and if multiple
+contexts are using the controller simultaneously a caller may end up
+pumping a message for some random other thread rather than for itself,
+and if the thread ends up deferring due to another context idling the
+hardware then it will just busy wait.  It can, however, have the benefit
+of aggregating power up and down of the hardware when a caller performs
+a series of transfers back to back without any need for the use of
+spi_async().
+
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+
+--- a/drivers/spi/spi.c
++++ b/drivers/spi/spi.c
+@@ -882,6 +882,9 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_t
+  * needs processing and if so call out to the driver to initialize hardware
+  * and transfer each message.
+  *
++ * Note that it is called both from the kthread itself and also from
++ * inside spi_sync(); the queue extraction handling at the top of the
++ * function should deal with this safely.
+  */
+ static void spi_pump_messages(struct kthread_work *work)
+ {
+@@ -900,6 +903,13 @@ static void spi_pump_messages(struct kth
+               return;
+       }
++      /* If another context is idling the device then defer */
++      if (master->idling) {
++              queue_kthread_work(&master->kworker, &master->pump_messages);
++              spin_unlock_irqrestore(&master->queue_lock, flags);
++              return;
++      }
++
+       /* Check if the queue is idle */
+       if (list_empty(&master->queue) || !master->running) {
+               if (!master->busy) {
+@@ -907,7 +917,9 @@ static void spi_pump_messages(struct kth
+                       return;
+               }
+               master->busy = false;
++              master->idling = true;
+               spin_unlock_irqrestore(&master->queue_lock, flags);
++
+               kfree(master->dummy_rx);
+               master->dummy_rx = NULL;
+               kfree(master->dummy_tx);
+@@ -921,6 +933,10 @@ static void spi_pump_messages(struct kth
+                       pm_runtime_put_autosuspend(master->dev.parent);
+               }
+               trace_spi_master_idle(master);
++
++              spin_lock_irqsave(&master->queue_lock, flags);
++              master->idling = false;
++              spin_unlock_irqrestore(&master->queue_lock, flags);
+               return;
+       }
+@@ -1166,12 +1182,9 @@ static int spi_destroy_queue(struct spi_
+       return 0;
+ }
+-/**
+- * spi_queued_transfer - transfer function for queued transfers
+- * @spi: spi device which is requesting transfer
+- * @msg: spi message which is to handled is queued to driver queue
+- */
+-static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
++static int __spi_queued_transfer(struct spi_device *spi,
++                               struct spi_message *msg,
++                               bool need_pump)
+ {
+       struct spi_master *master = spi->master;
+       unsigned long flags;
+@@ -1186,13 +1199,23 @@ static int spi_queued_transfer(struct sp
+       msg->status = -EINPROGRESS;
+       list_add_tail(&msg->queue, &master->queue);
+-      if (!master->busy)
++      if (!master->busy && need_pump)
+               queue_kthread_work(&master->kworker, &master->pump_messages);
+       spin_unlock_irqrestore(&master->queue_lock, flags);
+       return 0;
+ }
++/**
++ * spi_queued_transfer - transfer function for queued transfers
++ * @spi: spi device which is requesting transfer
++ * @msg: spi message which is to handled is queued to driver queue
++ */
++static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
++{
++      return __spi_queued_transfer(spi, msg, true);
++}
++
+ static int spi_master_initialize_queue(struct spi_master *master)
+ {
+       int ret;
+@@ -2105,19 +2128,46 @@ static int __spi_sync(struct spi_device
+       DECLARE_COMPLETION_ONSTACK(done);
+       int status;
+       struct spi_master *master = spi->master;
++      unsigned long flags;
++
++      status = __spi_validate(spi, message);
++      if (status != 0)
++              return status;
+       message->complete = spi_complete;
+       message->context = &done;
++      message->spi = spi;
+       if (!bus_locked)
+               mutex_lock(&master->bus_lock_mutex);
+-      status = spi_async_locked(spi, message);
++      /* If we're not using the legacy transfer method then we will
++       * try to transfer in the calling context so special case.
++       * This code would be less tricky if we could remove the
++       * support for driver implemented message queues.
++       */
++      if (master->transfer == spi_queued_transfer) {
++              spin_lock_irqsave(&master->bus_lock_spinlock, flags);
++
++              trace_spi_message_submit(message);
++
++              status = __spi_queued_transfer(spi, message, false);
++
++              spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
++      } else {
++              status = spi_async_locked(spi, message);
++      }
+       if (!bus_locked)
+               mutex_unlock(&master->bus_lock_mutex);
+       if (status == 0) {
++              /* Push out the messages in the calling context if we
++               * can.
++               */
++              if (master->transfer == spi_queued_transfer)
++                      spi_pump_messages(&master->pump_messages);
++
+               wait_for_completion(&done);
+               status = message->status;
+       }
+--- a/include/linux/spi/spi.h
++++ b/include/linux/spi/spi.h
+@@ -260,6 +260,7 @@ static inline void spi_unregister_driver
+  * @pump_messages: work struct for scheduling work to the message pump
+  * @queue_lock: spinlock to syncronise access to message queue
+  * @queue: message queue
++ * @idling: the device is entering idle state
+  * @cur_msg: the currently in-flight message
+  * @cur_msg_prepared: spi_prepare_message was called for the currently
+  *                    in-flight message
+@@ -425,6 +426,7 @@ struct spi_master {
+       spinlock_t                      queue_lock;
+       struct list_head                queue;
+       struct spi_message              *cur_msg;
++      bool                            idling;
+       bool                            busy;
+       bool                            running;
+       bool                            rt;