mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-17 00:33:06 +00:00
b725dc458f
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>
147 lines
3.3 KiB
C
147 lines
3.3 KiB
C
/*
|
|
* 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),
|
|
};
|