CHROMIUM: i2c-s3c2410: use exponential back off while polling for bus idle
authorDaniel Kurtz <djkurtz@chromium.org>
Thu, 16 Aug 2012 12:25:35 +0000 (20:25 +0800)
committerGerrit <chrome-bot@google.com>
Fri, 17 Aug 2012 08:22:04 +0000 (01:22 -0700)
Usually, the i2c controller has finished emitting the i2c STOP before the
driver reaches the bus idle polling loop.  Optimize for this most common
case by reading IICSTAT first and potentially skipping the loop.

If the cpu is faster than the hardware, we wait for bus idle in a polling
loop.  However, since the duration of one iteration of the loop is
dependent on cpu freq, and this i2c IP is used on many different systems,
use a time based loop timeout (5 ms).

We would like very low latencies to detect bus idle for the normal
'fast' case.  However, if a device is slow to release the bus for some
reason, it could hold off the STOP generation for up to several
milliseconds.  Rapidly polling for bus idle would seriously load the CPU
while waiting for it to release the bus.  So, use a partial exponential
backoff as a compromise between idle detection latency and cpu load.

Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
BUG=chrome-os-partner:12003
TEST=On s3c2400 system with cyapa touchpad attached to an i2c port...
TEST=Confirm no large/small EV_SYN/SYN_REPORT gaps when moving finger:
  evtest | awk 'BEGIN{P=0} /SYN_REPORT/ {print ($3-P) " " $3; P=$3}'

Change-Id: Ifbcaaef413c50ef5463238af606512e3ea32a206
Reviewed-on: https://gerrit.chromium.org/gerrit/29031
Reviewed-by: Olof Johansson <olofj@chromium.org>
Reviewed-by: Benson Leung <bleung@chromium.org>
Reviewed-by: Doug Anderson <dianders@chromium.org>
Commit-Ready: Daniel Kurtz <djkurtz@chromium.org>
Reviewed-by: Daniel Kurtz <djkurtz@chromium.org>
Tested-by: Daniel Kurtz <djkurtz@chromium.org>
drivers/i2c/busses/i2c-s3c2410.c

index a008038..2788ee3 100644 (file)
@@ -49,6 +49,9 @@
 #define QUIRK_HDMIPHY          (1 << 1)
 #define QUIRK_NO_GPIO          (1 << 2)
 
+/* Max time to wait for bus to become idle after a xfer (in us) */
+#define S3C2410_IDLE_TIMEOUT   5000
+
 /* i2c controller state */
 enum s3c24xx_i2c_state {
        STATE_IDLE,
@@ -651,6 +654,48 @@ static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)
        return -ETIMEDOUT;
 }
 
+/* s3c24xx_i2c_wait_idle
+ *
+ * wait for the i2c bus to become idle.
+*/
+
+static void s3c24xx_i2c_wait_idle(struct s3c24xx_i2c *i2c)
+{
+       unsigned long iicstat;
+       ktime_t start, now;
+       unsigned long delay;
+
+       /* ensure the stop has been through the bus */
+
+       dev_dbg(i2c->dev, "waiting for bus idle\n");
+
+       start = now = ktime_get();
+
+       /*
+        * Most of the time, the bus is already idle within a few usec of the
+        * end of a transaction.  However, really slow i2c devices can stretch
+        * the clock, delaying STOP generation.
+        *
+        * As a compromise between idle detection latency for the normal, fast
+        * case, and system load in the slow device case, use an exponential
+        * back off in the polling loop, up to 1/10th of the total timeout,
+        * then continue to poll at a constant rate up to the timeout.
+        */
+       iicstat = readl(i2c->regs + S3C2410_IICSTAT);
+       delay = 1;
+       while ((iicstat & S3C2410_IICSTAT_START) &&
+              ktime_us_delta(now, start) < S3C2410_IDLE_TIMEOUT) {
+               usleep_range(delay, 2 * delay);
+               if (delay < S3C2410_IDLE_TIMEOUT / 10)
+                       delay <<= 1;
+               now = ktime_get();
+               iicstat = readl(i2c->regs + S3C2410_IICSTAT);
+       }
+
+       if (iicstat & S3C2410_IICSTAT_START)
+               dev_warn(i2c->dev, "timeout waiting for bus idle\n");
+}
+
 /* s3c24xx_i2c_doxfer
  *
  * this starts an i2c transfer
@@ -659,8 +704,7 @@ static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)
 static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
                              struct i2c_msg *msgs, int num)
 {
-       unsigned long iicstat, timeout;
-       int spins = 20;
+       unsigned long timeout;
        int ret;
 
        if (i2c->suspended)
@@ -710,24 +754,7 @@ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
        if (i2c->quirks & QUIRK_HDMIPHY)
                goto out;
 
-       /* ensure the stop has been through the bus */
-
-       dev_dbg(i2c->dev, "waiting for bus idle\n");
-
-       /* first, try busy waiting briefly */
-       do {
-               cpu_relax();
-               iicstat = readl(i2c->regs + S3C2410_IICSTAT);
-       } while ((iicstat & S3C2410_IICSTAT_START) && --spins);
-
-       /* if that timed out sleep */
-       if (!spins) {
-               msleep(1);
-               iicstat = readl(i2c->regs + S3C2410_IICSTAT);
-       }
-
-       if (iicstat & S3C2410_IICSTAT_START)
-               dev_warn(i2c->dev, "timeout waiting for bus idle\n");
+       s3c24xx_i2c_wait_idle(i2c);
 
        s3c24xx_i2c_disable_bus(i2c);