i2c: Add a mux for GPIO-based I2C bus arbitration
authorSimon Glass <sjg@chromium.org>
Mon, 3 Aug 2015 14:19:22 +0000 (08:19 -0600)
committerSimon Glass <sjg@chromium.org>
Thu, 6 Aug 2015 03:06:10 +0000 (21:06 -0600)
While I2C supports multi-master buses this is difficult to get right.
The implementation on the master side in software is quite complex.
Clock-stretching and the arbitrary time that an I2C transaction can take
make it difficult to share the bus fairly in the face of high traffic.
When one or more masters can be reset independently part-way through a
transaction it is hard to know the state of the bus.

This driver provides a scheme based on two 'claim' GPIOs, one driven by the
AP (Application Processor, meaning the main CPU) and one driven by the EC
(Embedded Controller, a small CPU aimed at handling system tasks). With
these they can communicate and reliably share the bus. This scheme has
minimal overhead and involves very little code. It is used on snow to
permit the EC and the AP to share access to the main system PMIC and
battery. The scheme can survive reboots by either side without difficulty.
This scheme has been tested in the field with millions of devices.

Since U-Boot runs on the AP, the terminology used is 'our' claim GPIO,
meaning the AP's, and 'their' claim GPIO, meaning the EC's. This terminology
is used by the device tree bindings in Linux also.

Signed-off-by: Simon Glass <sjg@chromium.org>
doc/README.i2c [new file with mode: 0644]
drivers/i2c/muxes/Kconfig
drivers/i2c/muxes/Makefile
drivers/i2c/muxes/i2c-arb-gpio-challenge.c [new file with mode: 0644]

diff --git a/doc/README.i2c b/doc/README.i2c
new file mode 100644 (file)
index 0000000..07cd8df
--- /dev/null
@@ -0,0 +1,60 @@
+I2C Bus Arbitration
+===================
+
+While I2C supports multi-master buses this is difficult to get right.
+The implementation on the master side in software is quite complex.
+Clock-stretching and the arbitrary time that an I2C transaction can take
+make it difficult to share the bus fairly in the face of high traffic.
+When one or more masters can be reset independently part-way through a
+transaction it is hard to know the state of the bus.
+
+U-Boot provides a scheme based on two 'claim' GPIOs, one driven by the
+AP (Application Processor, meaning the main CPU) and one driven by the EC
+(Embedded Controller, a small CPU aimed at handling system tasks). With
+these they can communicate and reliably share the bus. This scheme has
+minimal overhead and involves very little code. The scheme can survive
+reboots by either side without difficulty.
+
+Since U-Boot runs on the AP, the terminology used is 'our' claim GPIO,
+meaning the AP's, and 'their' claim GPIO, meaning the EC's. This terminology
+is used by the device tree bindings in Linux also.
+
+The driver is implemented as an I2C mux, as it is in Linux. See
+i2c-arb-gpio-challenge for the implementation.
+
+GPIO lines are shared between the AP and EC to manage the bus. The AP and EC
+each have a 'bus claim' line, which is an output that the other can see.
+
+- AP_CLAIM: output from AP, signalling to the EC that the AP wants the bus
+- EC_CLAIM: output from EC, signalling to the AP that the EC wants the bus
+
+The basic algorithm is to assert your line when you want the bus, then make
+sure that the other side doesn't want it also. A detailed explanation is best
+done with an example.
+
+Let's say the AP wants to claim the bus. It:
+
+1. Asserts AP_CLAIM
+2. Waits a little bit for the other side to notice (slew time)
+3. Checks EC_CLAIM. If this is not asserted, then the AP has the bus, and we
+   are done
+4. Otherwise, wait for a few milliseconds (retry time) and see if EC_CLAIM is
+   released
+5. If not, back off, release the claim and wait for a few more milliseconds
+  (retry time again)
+6. Go back to 1 if things don't look wedged (wait time has expired)
+7. Panic. The other side is hung with the CLAIM line set.
+
+The same algorithm applies on the EC.
+
+To release the bus, just de-assert the claim line.
+
+Typical delays are:
+- slew time 10 us
+- retry time 3 ms
+- wait time - 50ms
+
+In general the traffic is fairly light, and in particular the EC wants access
+to the bus quite rarely (maybe every 10s or 30s to check the battery). This
+scheme works very nicely with very low contention. There is only a 10 us
+wait for access to the bus assuming that the other side isn't using it.
index a05b32d042c6330626d3e6378c60adabeb28553c..bd3e078d629ef1f1f4d2a1f3de19354176efe4b8 100644 (file)
@@ -6,3 +6,12 @@ config I2C_MUX
          one of several buses using some sort of control mechanism. The
          bus select is handled automatically when that bus is accessed,
          using a suitable I2C MUX driver.
+
+config I2C_ARB_GPIO_CHALLENGE
+        bool "GPIO-based I2C arbitration"
+        depends on I2C_MUX
+        help
+          If you say yes to this option, support will be included for an
+          I2C multimaster arbitration scheme using GPIOs and a challenge &
+          response mechanism where masters have to claim the bus by asserting
+          a GPIO.
index 7583e3a89b008dd62b775853af3767d9848a2bd4..612cc2706b00a28ee5954f1e3d74051bc19a4769 100644 (file)
@@ -3,4 +3,5 @@
 #
 # SPDX-License-Identifier:     GPL-2.0+
 #
+obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o
 obj-$(CONFIG_I2C_MUX) += i2c-mux-uclass.o
diff --git a/drivers/i2c/muxes/i2c-arb-gpio-challenge.c b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c
new file mode 100644 (file)
index 0000000..3f072c7
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <i2c.h>
+#include <asm/gpio.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct i2c_arbitrator_priv {
+       struct gpio_desc ap_claim;
+       struct gpio_desc ec_claim;
+       uint slew_delay_us;
+       uint wait_retry_ms;
+       uint wait_free_ms;
+};
+
+int i2c_arbitrator_deselect(struct udevice *mux, struct udevice *bus,
+                           uint channel)
+{
+       struct i2c_arbitrator_priv *priv = dev_get_priv(mux);
+       int ret;
+
+       debug("%s: %s\n", __func__, mux->name);
+       ret = dm_gpio_set_value(&priv->ap_claim, 0);
+       udelay(priv->slew_delay_us);
+
+       return ret;
+}
+
+int i2c_arbitrator_select(struct udevice *mux, struct udevice *bus,
+                         uint channel)
+{
+       struct i2c_arbitrator_priv *priv = dev_get_priv(mux);
+       unsigned start;
+       int ret;
+
+       debug("%s: %s\n", __func__, mux->name);
+       /* Start a round of trying to claim the bus */
+       start = get_timer(0);
+       do {
+               unsigned start_retry;
+               int waiting = 0;
+
+               /* Indicate that we want to claim the bus */
+               ret = dm_gpio_set_value(&priv->ap_claim, 1);
+               if (ret)
+                       goto err;
+               udelay(priv->slew_delay_us);
+
+               /* Wait for the EC to release it */
+               start_retry = get_timer(0);
+               while (get_timer(start_retry) < priv->wait_retry_ms) {
+                       ret = dm_gpio_get_value(&priv->ec_claim);
+                       if (ret < 0) {
+                               goto err;
+                       } else if (!ret) {
+                               /* We got it, so return */
+                               return 0;
+                       }
+
+                       if (!waiting)
+                               waiting = 1;
+               }
+
+               /* It didn't release, so give up, wait, and try again */
+               ret = dm_gpio_set_value(&priv->ap_claim, 0);
+               if (ret)
+                       goto err;
+
+               mdelay(priv->wait_retry_ms);
+       } while (get_timer(start) < priv->wait_free_ms);
+
+       /* Give up, release our claim */
+       printf("I2C: Could not claim bus, timeout %lu\n", get_timer(start));
+       ret = -ETIMEDOUT;
+       ret = 0;
+err:
+       return ret;
+}
+
+static int i2c_arbitrator_probe(struct udevice *dev)
+{
+       struct i2c_arbitrator_priv *priv = dev_get_priv(dev);
+       const void *blob = gd->fdt_blob;
+       int node = dev->of_offset;
+       int ret;
+
+       debug("%s: %s\n", __func__, dev->name);
+       priv->slew_delay_us = fdtdec_get_int(blob, node, "slew-delay-us", 0);
+       priv->wait_retry_ms = fdtdec_get_int(blob, node, "wait-retry-us", 0) /
+               1000;
+       priv->wait_free_ms = fdtdec_get_int(blob, node, "wait-free-us", 0) /
+               1000;
+       ret = gpio_request_by_name(dev, "our-claim-gpio", 0, &priv->ap_claim,
+                                  GPIOD_IS_OUT);
+       if (ret)
+               goto err;
+       ret = gpio_request_by_name(dev, "their-claim-gpios", 0, &priv->ec_claim,
+                                  GPIOD_IS_IN);
+       if (ret)
+               goto err_ec_gpio;
+
+       return 0;
+
+err_ec_gpio:
+       dm_gpio_free(dev, &priv->ap_claim);
+err:
+       debug("%s: ret=%d\n", __func__, ret);
+       return ret;
+}
+
+static int i2c_arbitrator_remove(struct udevice *dev)
+{
+       struct i2c_arbitrator_priv *priv = dev_get_priv(dev);
+
+       dm_gpio_free(dev, &priv->ap_claim);
+       dm_gpio_free(dev, &priv->ec_claim);
+
+       return 0;
+}
+
+static const struct i2c_mux_ops i2c_arbitrator_ops = {
+       .select         = i2c_arbitrator_select,
+       .deselect       = i2c_arbitrator_deselect,
+};
+
+static const struct udevice_id i2c_arbitrator_ids[] = {
+       { .compatible = "i2c-arb-gpio-challenge" },
+       { }
+};
+
+U_BOOT_DRIVER(i2c_arbitrator) = {
+       .name = "i2c_arbitrator",
+       .id = UCLASS_I2C_MUX,
+       .of_match = i2c_arbitrator_ids,
+       .probe = i2c_arbitrator_probe,
+       .remove = i2c_arbitrator_remove,
+       .ops = &i2c_arbitrator_ops,
+       .priv_auto_alloc_size = sizeof(struct i2c_arbitrator_priv),
+};