diff options
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 26 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 4 | ||||
-rw-r--r-- | drivers/i2c/cros_ec_ldo.c | 77 | ||||
-rw-r--r-- | drivers/i2c/cros_ec_tunnel.c | 41 | ||||
-rw-r--r-- | drivers/i2c/i2c-uclass.c | 27 | ||||
-rw-r--r-- | drivers/i2c/muxes/Kconfig | 17 | ||||
-rw-r--r-- | drivers/i2c/muxes/Makefile | 7 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-arb-gpio-challenge.c | 147 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-uclass.c | 198 | ||||
-rw-r--r-- | drivers/i2c/s3c24x0_i2c.c | 153 |
10 files changed, 654 insertions, 43 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 86fb36b5d4c..9a62ddd9ca2 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -19,6 +19,30 @@ config DM_I2C_COMPAT to convert all code for a board in a single commit. It should not be enabled for any board in an official release. +config I2C_CROS_EC_TUNNEL + tristate "Chrome OS EC tunnel I2C bus" + depends on CROS_EC + help + This provides an I2C bus that will tunnel i2c commands through to + the other side of the Chrome OS EC to the I2C bus connected there. + This will work whatever the interface used to talk to the EC (SPI, + I2C or LPC). Some Chromebooks use this when the hardware design + does not allow direct access to the main PMIC from the AP. + +config I2C_CROS_EC_LDO + bool "Provide access to LDOs on the Chrome OS EC" + depends on CROS_EC + ---help--- + On many Chromebooks the main PMIC is inaccessible to the AP. This is + often dealt with by using an I2C pass-through interface provided by + the EC. On some unfortunate models (e.g. Spring) the pass-through + is not available, and an LDO message is available instead. This + option enables a driver which provides very basic access to those + regulators, via the EC. We implement this as an I2C bus which + emulates just the TPS65090 messages we know about. This is done to + avoid duplicating the logic in the TPS65090 regulator driver for + enabling/disabling an LDO. + config DM_I2C_GPIO bool "Enable Driver Model for software emulated I2C bus driver" depends on DM_I2C && DM_GPIO @@ -73,3 +97,5 @@ config SYS_I2C_UNIPHIER_F help Support for UniPhier FIFO-builtin I2C controller driver. This I2C controller is used on PH1-Pro4 or newer UniPhier SoCs. + +source "drivers/i2c/muxes/Kconfig" diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index d9e912f7864..9b45248e2e2 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -7,6 +7,8 @@ obj-$(CONFIG_DM_I2C) += i2c-uclass.o obj-$(CONFIG_DM_I2C_COMPAT) += i2c-uclass-compat.o obj-$(CONFIG_DM_I2C_GPIO) += i2c-gpio.o +obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o +obj-$(CONFIG_I2C_CROS_EC_LDO) += cros_ec_ldo.o obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.o obj-$(CONFIG_I2C_MV) += mv_i2c.o @@ -37,3 +39,5 @@ obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o obj-$(CONFIG_SYS_I2C_UNIPHIER_F) += i2c-uniphier-f.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o + +obj-y += muxes/ diff --git a/drivers/i2c/cros_ec_ldo.c b/drivers/i2c/cros_ec_ldo.c new file mode 100644 index 00000000000..b817c61f1c5 --- /dev/null +++ b/drivers/i2c/cros_ec_ldo.c @@ -0,0 +1,77 @@ +/* + * 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 <cros_ec.h> +#include <errno.h> +#include <i2c.h> +#include <power/tps65090.h> + +static int cros_ec_ldo_set_bus_speed(struct udevice *dev, unsigned int speed) +{ + return 0; +} + +static int cros_ec_ldo_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) +{ + bool is_read = nmsgs > 1; + int fet_id, ret; + + /* + * Look for reads and writes of the LDO registers. In either case the + * first message is a write with the register number as the first byte. + */ + if (!nmsgs || !msg->len || (msg->flags & I2C_M_RD)) { + debug("%s: Invalid message\n", __func__); + goto err; + } + + fet_id = msg->buf[0] - REG_FET_BASE; + if (fet_id < 1 || fet_id > MAX_FET_NUM) { + debug("%s: Invalid FET %d\n", __func__, fet_id); + goto err; + } + + if (is_read) { + uint8_t state; + + ret = cros_ec_get_ldo(dev->parent, fet_id, &state); + if (!ret) + msg[1].buf[0] = state ? + FET_CTRL_ENFET | FET_CTRL_PGFET : 0; + } else { + bool on = msg->buf[1] & FET_CTRL_ENFET; + + ret = cros_ec_set_ldo(dev->parent, fet_id, on); + } + + return ret; + +err: + /* Indicate that the message is unimplemented */ + return -ENOSYS; +} + +static const struct dm_i2c_ops cros_ec_i2c_ops = { + .xfer = cros_ec_ldo_xfer, + .set_bus_speed = cros_ec_ldo_set_bus_speed, +}; + +static const struct udevice_id cros_ec_i2c_ids[] = { + { .compatible = "google,cros-ec-ldo-tunnel" }, + { } +}; + +U_BOOT_DRIVER(cros_ec_ldo) = { + .name = "cros_ec_ldo_tunnel", + .id = UCLASS_I2C, + .of_match = cros_ec_i2c_ids, + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), + .ops = &cros_ec_i2c_ops, +}; diff --git a/drivers/i2c/cros_ec_tunnel.c b/drivers/i2c/cros_ec_tunnel.c new file mode 100644 index 00000000000..7ab1fd898ad --- /dev/null +++ b/drivers/i2c/cros_ec_tunnel.c @@ -0,0 +1,41 @@ +/* + * 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 <cros_ec.h> +#include <errno.h> +#include <i2c.h> + +static int cros_ec_i2c_set_bus_speed(struct udevice *dev, unsigned int speed) +{ + return 0; +} + +static int cros_ec_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) +{ + return cros_ec_i2c_tunnel(dev->parent, msg, nmsgs); +} + +static const struct dm_i2c_ops cros_ec_i2c_ops = { + .xfer = cros_ec_i2c_xfer, + .set_bus_speed = cros_ec_i2c_set_bus_speed, +}; + +static const struct udevice_id cros_ec_i2c_ids[] = { + { .compatible = "google,cros-ec-i2c-tunnel" }, + { } +}; + +U_BOOT_DRIVER(cros_ec_tunnel) = { + .name = "cros_ec_tunnel", + .id = UCLASS_I2C, + .of_match = cros_ec_i2c_ids, + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), + .ops = &cros_ec_i2c_ops, +}; diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c index eaba965fa31..50b99ead3d9 100644 --- a/drivers/i2c/i2c-uclass.c +++ b/drivers/i2c/i2c-uclass.c @@ -18,6 +18,22 @@ DECLARE_GLOBAL_DATA_PTR; #define I2C_MAX_OFFSET_LEN 4 +/* Useful debugging function */ +void i2c_dump_msgs(struct i2c_msg *msg, int nmsgs) +{ + int i; + + for (i = 0; i < nmsgs; i++) { + struct i2c_msg *m = &msg[i]; + + printf(" %s %x len=%x", m->flags & I2C_M_RD ? "R" : "W", + msg->addr, msg->len); + if (!(m->flags & I2C_M_RD)) + printf(": %x", m->buf[0]); + printf("\n"); + } +} + /** * i2c_setup_offset() - Set up a new message with a chip offset * @@ -186,6 +202,17 @@ int dm_i2c_write(struct udevice *dev, uint offset, const uint8_t *buffer, } } +int dm_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs) +{ + struct udevice *bus = dev_get_parent(dev); + struct dm_i2c_ops *ops = i2c_get_ops(bus); + + if (!ops->xfer) + return -ENOSYS; + + return ops->xfer(bus, msg, nmsgs); +} + int dm_i2c_reg_read(struct udevice *dev, uint offset) { uint8_t val; diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig new file mode 100644 index 00000000000..bd3e078d629 --- /dev/null +++ b/drivers/i2c/muxes/Kconfig @@ -0,0 +1,17 @@ +config I2C_MUX + bool "Suport I2C multiplexers" + depends on DM_I2C + help + This enables I2C buses to be multiplexed, so that you can select + 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 new file mode 100644 index 00000000000..612cc2706b0 --- /dev/null +++ b/drivers/i2c/muxes/Makefile @@ -0,0 +1,7 @@ +# +# Copyright (c) 2015 Google, Inc +# +# 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 00000000000..3f072c78b81 --- /dev/null +++ b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c @@ -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), +}; diff --git a/drivers/i2c/muxes/i2c-mux-uclass.c b/drivers/i2c/muxes/i2c-mux-uclass.c new file mode 100644 index 00000000000..3f52bff2fb0 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-uclass.c @@ -0,0 +1,198 @@ +/* + * 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 <dm/lists.h> +#include <dm/root.h> + +DECLARE_GLOBAL_DATA_PTR; + +/** + * struct i2c_mux: Information the uclass stores about an I2C mux + * + * @selected: Currently selected mux, or -1 for none + * @i2c_bus: I2C bus to use for communcation + */ +struct i2c_mux { + int selected; + struct udevice *i2c_bus; +}; + +/** + * struct i2c_mux_bus: Information about each bus the mux controls + * + * @channel: Channel number used to select this bus + */ +struct i2c_mux_bus { + uint channel; +}; + +/* Find out the mux channel number */ +static int i2c_mux_child_post_bind(struct udevice *dev) +{ + struct i2c_mux_bus *plat = dev_get_parent_platdata(dev); + int channel; + + channel = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "reg", -1); + if (channel < 0) + return -EINVAL; + plat->channel = channel; + + return 0; +} + +/* Find the I2C buses selected by this mux */ +static int i2c_mux_post_bind(struct udevice *mux) +{ + const void *blob = gd->fdt_blob; + int ret; + int offset; + + debug("%s: %s\n", __func__, mux->name); + /* + * There is no compatible string in the sub-nodes, so we must manually + * bind these + */ + for (offset = fdt_first_subnode(blob, mux->of_offset); + offset > 0; + offset = fdt_next_subnode(blob, offset)) { + struct udevice *dev; + const char *name; + + name = fdt_get_name(blob, offset, NULL); + ret = device_bind_driver_to_node(mux, "i2c_mux_bus_drv", name, + offset, &dev); + debug(" - bind ret=%d, %s\n", ret, dev ? dev->name : NULL); + if (ret) + return ret; + } + + return 0; +} + +/* Set up the mux ready for use */ +static int i2c_mux_post_probe(struct udevice *mux) +{ + struct i2c_mux *priv = dev_get_uclass_priv(mux); + int ret; + + debug("%s: %s\n", __func__, mux->name); + priv->selected = -1; + + ret = uclass_get_device_by_phandle(UCLASS_I2C, mux, "i2c-parent", + &priv->i2c_bus); + if (ret) + return ret; + debug("%s: bus=%p/%s\n", __func__, priv->i2c_bus, priv->i2c_bus->name); + + return 0; +} + +int i2c_mux_select(struct udevice *dev) +{ + struct i2c_mux_bus *plat = dev_get_parent_platdata(dev); + struct udevice *mux = dev->parent; + struct i2c_mux_ops *ops = i2c_mux_get_ops(mux); + + if (!ops->select) + return -ENOSYS; + + return ops->select(mux, dev, plat->channel); +} + +int i2c_mux_deselect(struct udevice *dev) +{ + struct i2c_mux_bus *plat = dev_get_parent_platdata(dev); + struct udevice *mux = dev->parent; + struct i2c_mux_ops *ops = i2c_mux_get_ops(mux); + + if (!ops->deselect) + return -ENOSYS; + + return ops->deselect(mux, dev, plat->channel); +} + +static int i2c_mux_bus_set_bus_speed(struct udevice *dev, unsigned int speed) +{ + struct udevice *mux = dev->parent; + struct i2c_mux *priv = dev_get_uclass_priv(mux); + int ret, ret2; + + ret = i2c_mux_select(dev); + if (ret) + return ret; + ret = dm_i2c_set_bus_speed(priv->i2c_bus, speed); + ret2 = i2c_mux_deselect(dev); + + return ret ? ret : ret2; +} + +static int i2c_mux_bus_probe(struct udevice *dev, uint chip_addr, + uint chip_flags) +{ + struct udevice *mux = dev->parent; + struct i2c_mux *priv = dev_get_uclass_priv(mux); + struct dm_i2c_ops *ops = i2c_get_ops(priv->i2c_bus); + int ret, ret2; + + debug("%s: %s, bus %s\n", __func__, dev->name, priv->i2c_bus->name); + if (!ops->probe_chip) + return -ENOSYS; + ret = i2c_mux_select(dev); + if (ret) + return ret; + ret = ops->probe_chip(priv->i2c_bus, chip_addr, chip_flags); + ret2 = i2c_mux_deselect(dev); + + return ret ? ret : ret2; +} + +static int i2c_mux_bus_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) +{ + struct udevice *mux = dev->parent; + struct i2c_mux *priv = dev_get_uclass_priv(mux); + struct dm_i2c_ops *ops = i2c_get_ops(priv->i2c_bus); + int ret, ret2; + + debug("%s: %s, bus %s\n", __func__, dev->name, priv->i2c_bus->name); + if (!ops->xfer) + return -ENOSYS; + ret = i2c_mux_select(dev); + if (ret) + return ret; + ret = ops->xfer(priv->i2c_bus, msg, nmsgs); + ret2 = i2c_mux_deselect(dev); + + return ret ? ret : ret2; +} + +static const struct dm_i2c_ops i2c_mux_bus_ops = { + .xfer = i2c_mux_bus_xfer, + .probe_chip = i2c_mux_bus_probe, + .set_bus_speed = i2c_mux_bus_set_bus_speed, +}; + +U_BOOT_DRIVER(i2c_mux_bus) = { + .name = "i2c_mux_bus_drv", + .id = UCLASS_I2C, + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), + .ops = &i2c_mux_bus_ops, +}; + +UCLASS_DRIVER(i2c_mux) = { + .id = UCLASS_I2C_MUX, + .name = "i2c_mux", + .post_bind = i2c_mux_post_bind, + .post_probe = i2c_mux_post_probe, + .per_device_auto_alloc_size = sizeof(struct i2c_mux), + .per_child_platdata_auto_alloc_size = sizeof(struct i2c_mux_bus), + .child_post_bind = i2c_mux_child_post_bind, +}; diff --git a/drivers/i2c/s3c24x0_i2c.c b/drivers/i2c/s3c24x0_i2c.c index 9a04e48a0f3..c11a6be7b76 100644 --- a/drivers/i2c/s3c24x0_i2c.c +++ b/drivers/i2c/s3c24x0_i2c.c @@ -258,9 +258,9 @@ static int hsi2c_wait_for_trx(struct exynos5_hsi2c *i2c) return I2C_NOK_TOUT; } -static void ReadWriteByte(struct s3c24x0_i2c *i2c) +static void read_write_byte(struct s3c24x0_i2c *i2c) { - writel(readl(&i2c->iiccon) & ~I2CCON_IRPND, &i2c->iiccon); + clrbits_le32(&i2c->iiccon, I2CCON_IRPND); } #ifdef CONFIG_SYS_I2C @@ -794,7 +794,7 @@ static int i2c_transfer(struct s3c24x0_i2c *i2c, if (addr && addr_len) { while ((i < addr_len) && (result == I2C_OK)) { writel(addr[i++], &i2c->iicds); - ReadWriteByte(i2c); + read_write_byte(i2c); result = WaitForXfer(i2c); } i = 0; @@ -806,7 +806,7 @@ static int i2c_transfer(struct s3c24x0_i2c *i2c, case I2C_WRITE: while ((i < data_len) && (result == I2C_OK)) { writel(data[i++], &i2c->iicds); - ReadWriteByte(i2c); + read_write_byte(i2c); result = WaitForXfer(i2c); } break; @@ -822,7 +822,7 @@ static int i2c_transfer(struct s3c24x0_i2c *i2c, /* Generate a re-START. */ writel(I2C_MODE_MR | I2C_TXRX_ENA | I2C_START_STOP, &i2c->iicstat); - ReadWriteByte(i2c); + read_write_byte(i2c); result = WaitForXfer(i2c); if (result != I2C_OK) @@ -835,7 +835,7 @@ static int i2c_transfer(struct s3c24x0_i2c *i2c, writel(readl(&i2c->iiccon) & ~I2CCON_ACKGEN, &i2c->iiccon); - ReadWriteByte(i2c); + read_write_byte(i2c); result = WaitForXfer(i2c); data[i++] = readl(&i2c->iicds); } @@ -852,7 +852,7 @@ static int i2c_transfer(struct s3c24x0_i2c *i2c, bailout: /* Send STOP. */ writel(I2C_MODE_MR | I2C_TXRX_ENA, &i2c->iicstat); - ReadWriteByte(i2c); + read_write_byte(i2c); return result; } @@ -1284,62 +1284,106 @@ U_BOOT_I2C_ADAP_COMPLETE(s3c0, s3c24x0_i2c_init, s3c24x0_i2c_probe, #endif /* CONFIG_SYS_I2C */ #ifdef CONFIG_DM_I2C -static int i2c_write_data(struct s3c24x0_i2c_bus *i2c_bus, uchar chip, - uchar *buffer, int len, bool end_with_repeated_start) +static int exynos_hs_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, + int nmsgs) { + struct s3c24x0_i2c_bus *i2c_bus = dev_get_priv(dev); + struct exynos5_hsi2c *hsregs = i2c_bus->hsregs; int ret; - if (i2c_bus->is_highspeed) { - ret = hsi2c_write(i2c_bus->hsregs, chip, 0, 0, - buffer, len, true); - if (ret) + for (; nmsgs > 0; nmsgs--, msg++) { + if (msg->flags & I2C_M_RD) { + ret = hsi2c_read(hsregs, msg->addr, 0, 0, msg->buf, + msg->len); + } else { + ret = hsi2c_write(hsregs, msg->addr, 0, 0, msg->buf, + msg->len, true); + } + if (ret) { exynos5_i2c_reset(i2c_bus); - } else { - ret = i2c_transfer(i2c_bus->regs, I2C_WRITE, - chip << 1, 0, 0, buffer, len); + return -EREMOTEIO; + } } - return ret != I2C_OK; + return 0; } -static int i2c_read_data(struct s3c24x0_i2c_bus *i2c_bus, uchar chip, - uchar *buffer, int len) +static int s3c24x0_do_msg(struct s3c24x0_i2c_bus *i2c_bus, struct i2c_msg *msg, + int seq) { - int ret; + struct s3c24x0_i2c *i2c = i2c_bus->regs; + bool is_read = msg->flags & I2C_M_RD; + uint status; + uint addr; + int ret, i; - if (i2c_bus->is_highspeed) { - ret = hsi2c_read(i2c_bus->hsregs, chip, 0, 0, buffer, len); - if (ret) - exynos5_i2c_reset(i2c_bus); + if (!seq) + setbits_le32(&i2c->iiccon, I2CCON_ACKGEN); + + /* Get the slave chip address going */ + addr = msg->addr << 1; + writel(addr, &i2c->iicds); + status = I2C_TXRX_ENA | I2C_START_STOP; + if (is_read) + status |= I2C_MODE_MR; + else + status |= I2C_MODE_MT; + writel(status, &i2c->iicstat); + if (seq) + read_write_byte(i2c); + + /* Wait for chip address to transmit */ + ret = WaitForXfer(i2c); + if (ret) + goto err; + + if (is_read) { + for (i = 0; !ret && i < msg->len; i++) { + /* disable ACK for final READ */ + if (i == msg->len - 1) + clrbits_le32(&i2c->iiccon, I2CCON_ACKGEN); + read_write_byte(i2c); + ret = WaitForXfer(i2c); + msg->buf[i] = readl(&i2c->iicds); + } + if (ret == I2C_NACK) + ret = I2C_OK; /* Normal terminated read */ } else { - ret = i2c_transfer(i2c_bus->regs, I2C_READ, - chip << 1, 0, 0, buffer, len); + for (i = 0; !ret && i < msg->len; i++) { + writel(msg->buf[i], &i2c->iicds); + read_write_byte(i2c); + ret = WaitForXfer(i2c); + } } - return ret != I2C_OK; +err: + return ret; } static int s3c24x0_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs) { struct s3c24x0_i2c_bus *i2c_bus = dev_get_priv(dev); - int ret; - - for (; nmsgs > 0; nmsgs--, msg++) { - bool next_is_read = nmsgs > 1 && (msg[1].flags & I2C_M_RD); + struct s3c24x0_i2c *i2c = i2c_bus->regs; + ulong start_time; + int ret, i; - if (msg->flags & I2C_M_RD) { - ret = i2c_read_data(i2c_bus, msg->addr, msg->buf, - msg->len); - } else { - ret = i2c_write_data(i2c_bus, msg->addr, msg->buf, - msg->len, next_is_read); + start_time = get_timer(0); + while (readl(&i2c->iicstat) & I2CSTAT_BSY) { + if (get_timer(start_time) > I2C_TIMEOUT_MS) { + debug("Timeout\n"); + return -ETIMEDOUT; } - if (ret) - return -EREMOTEIO; } - return 0; + for (ret = 0, i = 0; !ret && i < nmsgs; i++) + ret = s3c24x0_do_msg(i2c_bus, &msg[i], i); + + /* Send STOP */ + writel(I2C_MODE_MR | I2C_TXRX_ENA, &i2c->iicstat); + read_write_byte(i2c); + + return ret ? -EREMOTEIO : 0; } static int s3c_i2c_ofdata_to_platdata(struct udevice *dev) @@ -1364,8 +1408,7 @@ static int s3c_i2c_ofdata_to_platdata(struct udevice *dev) i2c_bus->id = pinmux_decode_periph_id(blob, node); i2c_bus->clock_frequency = fdtdec_get_int(blob, node, - "clock-frequency", - CONFIG_SYS_I2C_S3C24X0_SPEED); + "clock-frequency", 100000); i2c_bus->node = node; i2c_bus->bus_num = dev->seq; @@ -1384,7 +1427,6 @@ static const struct dm_i2c_ops s3c_i2c_ops = { static const struct udevice_id s3c_i2c_ids[] = { { .compatible = "samsung,s3c2440-i2c", .data = EXYNOS_I2C_STD }, - { .compatible = "samsung,exynos5-hsi2c", .data = EXYNOS_I2C_HS }, { } }; @@ -1397,4 +1439,29 @@ U_BOOT_DRIVER(i2c_s3c) = { .priv_auto_alloc_size = sizeof(struct s3c24x0_i2c_bus), .ops = &s3c_i2c_ops, }; + +/* + * TODO(sjg@chromium.org): Move this to a separate file when everything uses + * driver model + */ +static const struct dm_i2c_ops exynos_hs_i2c_ops = { + .xfer = exynos_hs_i2c_xfer, + .probe_chip = s3c24x0_i2c_probe, + .set_bus_speed = s3c24x0_i2c_set_bus_speed, +}; + +static const struct udevice_id exynos_hs_i2c_ids[] = { + { .compatible = "samsung,exynos5-hsi2c", .data = EXYNOS_I2C_HS }, + { } +}; + +U_BOOT_DRIVER(hs_i2c) = { + .name = "i2c_s3c_hs", + .id = UCLASS_I2C, + .of_match = exynos_hs_i2c_ids, + .ofdata_to_platdata = s3c_i2c_ofdata_to_platdata, + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), + .priv_auto_alloc_size = sizeof(struct s3c24x0_i2c_bus), + .ops = &exynos_hs_i2c_ops, +}; #endif /* CONFIG_DM_I2C */ |