diff --git a/doc/README.i2c b/doc/README.i2c new file mode 100644 index 0000000..07cd8df --- /dev/null +++ b/doc/README.i2c @@ -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. diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index a05b32d..bd3e078 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig @@ -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. diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index 7583e3a..612cc27 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile @@ -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 index 0000000..3f072c7 --- /dev/null +++ b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2015 Google, Inc + * Written by Simon Glass + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +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), +};