spi: change post transfer udelay() to usleep_range() for long delays
authorDaniel Kurtz <djkurtz@chromium.org>
Fri, 7 Oct 2016 10:55:47 +0000 (18:55 +0800)
committerMark Brown <broonie@kernel.org>
Wed, 26 Oct 2016 11:35:17 +0000 (12:35 +0100)
The spi_transfer parameter delay_usecs allows specifying a time to wait
after transferring a spi message.  This wait can be quite long - some
devices, such as some Chrome OS ECs, require as much as 2000 usecs after
a SPI transaction, before it can respond.

(cf: arch/arm64/boot/dts/nvidia/tegra132-norrin.dts:
   google,cros-ec-spi-msg-delay = <2000>
)

Blocking a CPU for 2 msecs in a busy loop like this doesn't seem very
friendly to other processes, so change the blocking delay to a sleep
to allow other things to use this CPU (or so it can sleep).

This should be safe to do, because:
 (a) A post-transaction delay like this is always specified as a minimum
     wait time
 (b) A delay here is most likely not very time sensitive, as it occurs
     after all data has been transferred
 (c) This delay occurs in a non-critical section of the spi worker thread
     so where it is safe to sleep.

Two caveats:
 1) To avoid penalizing short delays, still use udelay for delays < 10us.
 2) usleep_range() very often picks the upper bound, an upper bounds 10%
    should be plenty.

Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi.c

index 5787b723b593f79bb5e55f3b68abcb2f19d4b5cb..42f3e1cb9212e8eb924c4be0cacc9736f631f31d 100644 (file)
@@ -1034,8 +1034,14 @@ static int spi_transfer_one_message(struct spi_master *master,
                if (msg->status != -EINPROGRESS)
                        goto out;
 
-               if (xfer->delay_usecs)
-                       udelay(xfer->delay_usecs);
+               if (xfer->delay_usecs) {
+                       u16 us = xfer->delay_usecs;
+
+                       if (us <= 10)
+                               udelay(us);
+                       else
+                               usleep_range(us, us + DIV_ROUND_UP(us, 10));
+               }
 
                if (xfer->cs_change) {
                        if (list_is_last(&xfer->transfer_list,