diff options
author | Liviu Dudau <Liviu.Dudau@foss.arm.com> | 2018-09-28 13:46:48 +0100 |
---|---|---|
committer | Heiko Schocher <hs@denx.de> | 2018-10-03 06:08:06 +0200 |
commit | 86c83e8400fd3d7f5393e569b2b9e164204a88fe (patch) | |
tree | 3635c85f620988c99f58b7a0a11a32627f782d86 /drivers/i2c | |
parent | 592cd5defd4f71d34ffcbd8dd3326bc10f662e20 (diff) | |
download | u-boot-86c83e8400fd3d7f5393e569b2b9e164204a88fe.tar.gz |
i2c: Add support for the Arm's Versatile Express I2C controller.
The Arm Versatile Express I2C controller is a simple register-based
controller that uses a register to control the state of the SCL and
SDA lines. Add support for it.
Signed-off-by: Liviu Dudau <liviu.dudau@foss.arm.com>
Reviewed-by: Heiko Schocher <hs@denx.de>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/i2c-versatile.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/i2c/i2c-versatile.c b/drivers/i2c/i2c-versatile.c new file mode 100644 index 0000000000..f523844204 --- /dev/null +++ b/drivers/i2c/i2c-versatile.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Arm Ltd. + * Author: Liviu Dudau <liviu.dudau@foss.arm.com> + * + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <i2c.h> +#include <asm/io.h> +#include <clk.h> +#include <linux/io.h> + +#define I2C_CONTROL_REG 0x00 +#define I2C_SET_REG 0x00 +#define I2C_CLEAR_REG 0x04 + +#define SCL BIT(0) +#define SDA BIT(1) + +struct versatile_i2c_priv { + phys_addr_t base; + u32 delay; +}; + +static inline void versatile_sda_set(struct versatile_i2c_priv *priv, u8 state) +{ + writel(SDA, priv->base + (state ? I2C_SET_REG : I2C_CLEAR_REG)); + udelay(priv->delay); +} + +static inline int versatile_sda_get(struct versatile_i2c_priv *priv) +{ + int v = !!(readl(priv->base + I2C_CONTROL_REG) & SDA); + + udelay(priv->delay); + return v; +} + +static inline void versatile_scl_set(struct versatile_i2c_priv *priv, u8 state) +{ + writel(SCL, priv->base + (state ? I2C_SET_REG : I2C_CLEAR_REG)); + udelay(priv->delay); +} + +static inline int versatile_scl_get(struct versatile_i2c_priv *priv) +{ + int v = !!(readl(priv->base + I2C_CONTROL_REG) & SCL); + + udelay(priv->delay); + return v; +} + +/* start: SDA goes from high to low while SCL is high */ +static void versatile_i2c_start(struct versatile_i2c_priv *priv) +{ + udelay(priv->delay); + versatile_sda_set(priv, 1); + versatile_scl_set(priv, 1); + versatile_sda_set(priv, 0); +} + +/* stop: SDA goes from low to high while SCL is high */ +static void versatile_i2c_stop(struct versatile_i2c_priv *priv) +{ + versatile_scl_set(priv, 0); + versatile_sda_set(priv, 0); + versatile_scl_set(priv, 1); + versatile_sda_set(priv, 1); +} + +/* read a bit from the SDA line (data or ACK/NACK) */ +static u8 versatile_i2c_read_bit(struct versatile_i2c_priv *priv) +{ + versatile_scl_set(priv, 0); + versatile_sda_set(priv, 1); + versatile_scl_set(priv, 1); + udelay(priv->delay); + return (u8)versatile_sda_get(priv); +} + +/* write a bit on the SDA line */ +static void versatile_i2c_write_bit(struct versatile_i2c_priv *priv, u8 bit) +{ + versatile_scl_set(priv, 0); + versatile_sda_set(priv, bit); + versatile_scl_set(priv, 1); + udelay(priv->delay); +} + +/* send a reset sequence of 9 clocks with SDA high */ +static void versatile_i2c_reset_bus(struct versatile_i2c_priv *priv) +{ + int i; + + for (i = 0; i < 9; i++) + versatile_i2c_write_bit(priv, 1); + + versatile_i2c_stop(priv); +} + +/* write byte without start/stop sequence */ +static int versatile_i2c_write_byte(struct versatile_i2c_priv *priv, u8 byte) +{ + u8 nak, i; + + for (i = 0; i < 8; i++) { + versatile_i2c_write_bit(priv, byte & 0x80); + byte <<= 1; + } + + /* read ACK */ + nak = versatile_i2c_read_bit(priv); + versatile_scl_set(priv, 0); + + return nak; /* not a nack is an ack */ +} + +static int versatile_i2c_read_byte(struct versatile_i2c_priv *priv, + u8 *byte, u8 ack) +{ + u8 i; + + *byte = 0; + for (i = 0; i < 8; i++) { + *byte <<= 1; + *byte |= versatile_i2c_read_bit(priv); + } + /* write the nack */ + versatile_i2c_write_bit(priv, ack); + + return 0; +} + +static int versatile_i2c_send_slave_addr(struct versatile_i2c_priv *priv, + struct i2c_msg *msg) +{ + u8 addr; + int ret; + + if (msg->flags & I2C_M_TEN) { + /* 10-bit address, send extended address code first */ + addr = 0xf0 | ((msg->addr >> 7) & 0x06); + ret = versatile_i2c_write_byte(priv, addr); + if (ret) { + versatile_i2c_stop(priv); + return -EIO; + } + + /* remaining bits */ + ret = versatile_i2c_write_byte(priv, msg->addr & 0xff); + if (ret) { + versatile_i2c_stop(priv); + return -EIO; + } + /* reads need to resend the addr */ + if (msg->flags & I2C_M_RD) { + versatile_i2c_start(priv); + addr |= 1; + ret = versatile_i2c_write_byte(priv, addr); + if (ret) { + versatile_i2c_stop(priv); + return -EIO; + } + } + } else { + /* normal 7-bit address */ + addr = msg->addr << 1; + if (msg->flags & I2C_M_RD) + addr |= 1; + ret = versatile_i2c_write_byte(priv, addr); + if (ret) { + versatile_i2c_stop(priv); + return -EIO; + } + } + + return 0; +} + +static int versatile_i2c_message_xfer(struct versatile_i2c_priv *priv, + struct i2c_msg *msg) +{ + int i, ret; + u8 ack; + + versatile_i2c_start(priv); + if (versatile_i2c_send_slave_addr(priv, msg)) + return -EIO; + + for (i = 0; i < msg->len; i++) { + if (msg->flags & I2C_M_RD) { + ack = (msg->len - i - 1) == 0 ? 1 : 0; + ret = versatile_i2c_read_byte(priv, &msg->buf[i], ack); + } else { + ret = versatile_i2c_write_byte(priv, msg->buf[i]); + } + + if (ret) + break; + } + + versatile_i2c_stop(priv); + + return ret; +} + +static int versatile_i2c_xfer(struct udevice *bus, + struct i2c_msg *msg, int nmsgs) +{ + struct versatile_i2c_priv *priv = dev_get_priv(bus); + int ret; + + for ( ; nmsgs > 0; nmsgs--, msg++) { + ret = versatile_i2c_message_xfer(priv, msg); + if (ret) + return -EREMOTEIO; + } + + return 0; +} + +static int versatile_i2c_chip_probe(struct udevice *bus, + uint chip, uint chip_flags) +{ + /* probe the presence of a slave by writing a 0-size message */ + struct i2c_msg msg = { .addr = chip, .flags = chip_flags, + .len = 0, .buf = NULL }; + struct versatile_i2c_priv *priv = dev_get_priv(bus); + + return versatile_i2c_message_xfer(priv, &msg); +} + +static int versatile_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct versatile_i2c_priv *priv = dev_get_priv(bus); + + priv->delay = 1000000 / (speed << 2); + + versatile_i2c_reset_bus(priv); + + return 0; +} + +static int versatile_i2c_probe(struct udevice *dev) +{ + struct versatile_i2c_priv *priv = dev_get_priv(dev); + + priv->base = (phys_addr_t)dev_read_addr(dev); + priv->delay = 25; /* 25us * 4 = 100kHz */ + /* + * U-Boot still doesn't assign automatically + * sequence numbers to devices + */ + dev->req_seq = 1; + + return 0; +} + +static const struct dm_i2c_ops versatile_i2c_ops = { + .xfer = versatile_i2c_xfer, + .probe_chip = versatile_i2c_chip_probe, + .set_bus_speed = versatile_i2c_set_bus_speed, +}; + +static const struct udevice_id versatile_i2c_of_match[] = { + { .compatible = "arm,versatile-i2c" }, + { } +}; + +U_BOOT_DRIVER(versatile_i2c) = { + .name = "i2c-bus-versatile", + .id = UCLASS_I2C, + .of_match = versatile_i2c_of_match, + .probe = versatile_i2c_probe, + .priv_auto_alloc_size = sizeof(struct versatile_i2c_priv), + .ops = &versatile_i2c_ops, +}; |