Message ID | 20180928124648.28809-1-Liviu.Dudau@foss.arm.com |
---|---|
State | Awaiting Upstream |
Delegated to: | Heiko Schocher |
Headers | show |
Series | [U-Boot,v2] i2c: Add support for the Arm's Versatile Express I2C controller. | expand |
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, > +}; >
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, +};