[U-Boot] [PATCH] 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 --- drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 270 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index ae3b501555..1ef22e6bcd 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -416,6 +416,13 @@ config SYS_I2C_UNIPHIER_F Support for UniPhier FIFO-builtin I2C controller driver. This I2C controller is used on PH1-Pro4 or newer UniPhier SoCs.
+config SYS_I2C_VERSATILE + bool "Arm Ltd Versatile I2C bus driver" + depends on DM_I2C && (TARGET_VEXPRESS_CA15_TC2 || TARGET_VEXPRESS64_JUNO) + help + Add support for the Arm Ltd Versatile Express I2C driver. The I2C host + controller is present in the development boards manufactured by Arm Ltd. + config SYS_I2C_MVTWSI bool "Marvell I2C driver" depends on DM_I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index f2cbe78c53..d3637bcd8d 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o 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_VERSATILE) += i2c-versatile.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o obj-$(CONFIG_TEGRA186_BPMP_I2C) += tegra186_bpmp_i2c.o
diff --git a/drivers/i2c/i2c-versatile.c b/drivers/i2c/i2c-versatile.c new file mode 100644 index 0000000000..5faa5c93e2 --- /dev/null +++ b/drivers/i2c/i2c-versatile.c @@ -0,1 +1,270 @@ +// 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; + + 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) { + u8 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, +};

Hello Liviu,
Am 17.09.2018 um 18:51 schrieb Liviu Dudau:
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
drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 270 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
Reviewed-by: Heiko Schocher hs@denx.de
I am unsure here, if all your patches are a patchserie, or if this i2c patch can go seperately through i2c tree ...
bye, Heiko

On Tue, Sep 18, 2018 at 06:21:17AM +0200, Heiko Schocher wrote:
Hello Liviu,
Am 17.09.2018 um 18:51 schrieb Liviu Dudau:
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
drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 270 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
Reviewed-by: Heiko Schocher hs@denx.de
I am unsure here, if all your patches are a patchserie, or if this i2c patch can go seperately through i2c tree ...
The patches adding support for Arm Versatile express hardware can indeed be considered as part of a series (adding support for Mali DP, where I also need I2C to do EDID and clock generators for pixel clocks). However, there is no dependency between patches, so they could go individually into different trees.
Best regards, Liviu
bye, Heiko -- DENX Software Engineering GmbH, Managing Director: Wolfgang Denk HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany Phone: +49-8142-66989-52 Fax: +49-8142-66989-80 Email: hs@denx.de

Hello Liviu,
Am 18.09.2018 um 06:21 schrieb Heiko Schocher:
Hello Liviu,
Am 17.09.2018 um 18:51 schrieb Liviu Dudau:
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
drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 270 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
Reviewed-by: Heiko Schocher hs@denx.de
I am unsure here, if all your patches are a patchserie, or if this i2c patch can go seperately through i2c tree ...
I got some checkpatch errors:
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #64: new file mode 100644
WARNING: Missing a blank line after declarations #105: FILE: drivers/i2c/i2c-versatile.c:37: + int v = !!(readl(priv->base + I2C_CONTROL_REG) & SDA); + udelay(priv->delay);
WARNING: Missing a blank line after declarations #118: FILE: drivers/i2c/i2c-versatile.c:50: + int v = !!(readl(priv->base + I2C_CONTROL_REG) & SCL); + udelay(priv->delay);
WARNING: line over 80 characters #187: FILE: drivers/i2c/i2c-versatile.c:119: +static int versatile_i2c_read_byte(struct versatile_i2c_priv *priv, u8 *byte, u8 ack)
WARNING: Missing a blank line after declarations #260: FILE: drivers/i2c/i2c-versatile.c:192: + u8 ack = (msg->len - i - 1) == 0 ? 1 : 0; + ret = versatile_i2c_read_byte(priv, &msg->buf[i], ack);
WARNING: line over 80 characters #274: FILE: drivers/i2c/i2c-versatile.c:206: +static int versatile_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs)
WARNING: line over 80 characters #288: FILE: drivers/i2c/i2c-versatile.c:220: +static int versatile_i2c_chip_probe(struct udevice *bus, uint chip, uint chip_flags)
WARNING: line over 80 characters #291: FILE: drivers/i2c/i2c-versatile.c:223: + struct i2c_msg msg = { .addr = chip, .flags = chip_flags, .len = 0, .buf = NULL };
WARNING: line over 80 characters #314: FILE: drivers/i2c/i2c-versatile.c:246: + /* U-Boot still doesn't assign automatically sequence numbers to devices */
Please fix them, also "git am" says:
Applying: i2c: Add support for the Arm's Versatile Express I2C controller. error: new file drivers/i2c/i2c-versatile.c depends on old contents error: could not build fake ancestor
Could you please rebase your patch on current mainline U-Boot ?
Thanks!
bye, Heiko

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 --- Changelog: - v2: Added MAINTAINERS entry and fixed checkpatch warnings.
MAINTAINERS | 1 + drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 280 ++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
diff --git a/MAINTAINERS b/MAINTAINERS index 1aa68e8135..11fc404167 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -291,6 +291,7 @@ M: Liviu Dudau liviu.dudau@foss.arm.com S: Maintained T: git git://github.com/ARM-software/u-boot.git F: drivers/clk/clk_vexpress_osc.c +F: drivers/i2c/i2c-versatile.c F: drivers/misc/vexpress_config.c N: vexpress
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index ae3b501555..1ef22e6bcd 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -416,6 +416,13 @@ config SYS_I2C_UNIPHIER_F Support for UniPhier FIFO-builtin I2C controller driver. This I2C controller is used on PH1-Pro4 or newer UniPhier SoCs.
+config SYS_I2C_VERSATILE + bool "Arm Ltd Versatile I2C bus driver" + depends on DM_I2C && (TARGET_VEXPRESS_CA15_TC2 || TARGET_VEXPRESS64_JUNO) + help + Add support for the Arm Ltd Versatile Express I2C driver. The I2C host + controller is present in the development boards manufactured by Arm Ltd. + config SYS_I2C_MVTWSI bool "Marvell I2C driver" depends on DM_I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index f2cbe78c53..d3637bcd8d 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o 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_VERSATILE) += i2c-versatile.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o obj-$(CONFIG_TEGRA186_BPMP_I2C) += tegra186_bpmp_i2c.o
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, +};

Hello Liviu,
Am 28.09.2018 um 14:46 schrieb Liviu Dudau:
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
Changelog:
- v2: Added MAINTAINERS entry and fixed checkpatch warnings.
MAINTAINERS | 1 + drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-versatile.c | 280 ++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 drivers/i2c/i2c-versatile.c
After fixing some merge conflict in MAINTAINERS, applied to u-boot-i2c.git master
Thanks!
bye, Heiko
diff --git a/MAINTAINERS b/MAINTAINERS index 1aa68e8135..11fc404167 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -291,6 +291,7 @@ M: Liviu Dudau liviu.dudau@foss.arm.com S: Maintained T: git git://github.com/ARM-software/u-boot.git F: drivers/clk/clk_vexpress_osc.c +F: drivers/i2c/i2c-versatile.c F: drivers/misc/vexpress_config.c N: vexpress
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index ae3b501555..1ef22e6bcd 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -416,6 +416,13 @@ config SYS_I2C_UNIPHIER_F Support for UniPhier FIFO-builtin I2C controller driver. This I2C controller is used on PH1-Pro4 or newer UniPhier SoCs.
+config SYS_I2C_VERSATILE
- bool "Arm Ltd Versatile I2C bus driver"
- depends on DM_I2C && (TARGET_VEXPRESS_CA15_TC2 || TARGET_VEXPRESS64_JUNO)
- help
Add support for the Arm Ltd Versatile Express I2C driver. The I2C host
controller is present in the development boards manufactured by Arm Ltd.
- config SYS_I2C_MVTWSI bool "Marvell I2C driver" depends on DM_I2C
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index f2cbe78c53..d3637bcd8d 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o 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_VERSATILE) += i2c-versatile.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o obj-$(CONFIG_TEGRA186_BPMP_I2C) += tegra186_bpmp_i2c.o
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,
+};
participants (2)
-
Heiko Schocher
-
Liviu Dudau