From mboxrd@z Thu Jan 1 00:00:00 1970 From: Heiko Schocher invitel Date: Wed, 29 Jun 2016 08:10:58 +0200 Subject: [U-Boot] [PATCH v3 1/2] i2c: atmel: add i2c driver In-Reply-To: <1466400159-24233-2-git-send-email-songjun.wu@atmel.com> References: <1466400159-24233-1-git-send-email-songjun.wu@atmel.com> <1466400159-24233-2-git-send-email-songjun.wu@atmel.com> Message-ID: <57736672.10801@invitel.hu> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de Hello Songjun Wu, Am 20.06.2016 um 07:22 schrieb Songjun Wu: > Add i2c driver. > > Signed-off-by: Songjun Wu > --- > > Changes in v3: > - Update the clk API. > > Changes in v2: > - Add code to get and enable clock. > > drivers/i2c/Kconfig | 10 ++ > drivers/i2c/Makefile | 1 + > drivers/i2c/at91_i2c.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++ > drivers/i2c/at91_i2c.h | 77 +++++++++++ > 4 files changed, 426 insertions(+) > create mode 100644 drivers/i2c/at91_i2c.c > create mode 100644 drivers/i2c/at91_i2c.h Thanks! Reviewed-by: Heiko Schocher Acked-by: Heiko Schocher bye, Heiko > > diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig > index 6e22bba..f6a613c 100644 > --- a/drivers/i2c/Kconfig > +++ b/drivers/i2c/Kconfig > @@ -58,6 +58,16 @@ config DM_I2C_GPIO > bindings are supported. > Binding info: doc/device-tree-bindings/i2c/i2c-gpio.txt > > +config SYS_I2C_AT91 > + bool "Atmel I2C driver" > + depends on DM_I2C && ARCH_AT91 > + help > + Add support for the Atmel I2C driver. A serious problem is that there > + is no documented way to issue repeated START conditions for more than > + two messages, as needed to support combined I2C messages. Use the > + i2c-gpio driver unless your system can cope with this limitation. > + Binding info: doc/device-tree-bindings/i2c/i2c-at91.txt > + > config SYS_I2C_FSL > bool "Freescale I2C bus driver" > depends on DM_I2C > diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile > index 167424d..c5362a5 100644 > --- a/drivers/i2c/Makefile > +++ b/drivers/i2c/Makefile > @@ -16,6 +16,7 @@ obj-$(CONFIG_PCA9564_I2C) += pca9564_i2c.o > obj-$(CONFIG_TSI108_I2C) += tsi108_i2c.o > obj-$(CONFIG_SH_SH7734_I2C) += sh_sh7734_i2c.o > obj-$(CONFIG_SYS_I2C) += i2c_core.o > +obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o > obj-$(CONFIG_SYS_I2C_CADENCE) += i2c-cdns.o > obj-$(CONFIG_SYS_I2C_DAVINCI) += davinci_i2c.o > obj-$(CONFIG_SYS_I2C_DW) += designware_i2c.o > diff --git a/drivers/i2c/at91_i2c.c b/drivers/i2c/at91_i2c.c > new file mode 100644 > index 0000000..8e9c3ad > --- /dev/null > +++ b/drivers/i2c/at91_i2c.c > @@ -0,0 +1,338 @@ > +/* > + * Atmel I2C driver. > + * > + * (C) Copyright 2016 Songjun Wu > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "at91_i2c.h" > + > +DECLARE_GLOBAL_DATA_PTR; > + > +#define I2C_TIMEOUT_MS 100 > + > +static int at91_wait_for_xfer(struct at91_i2c_bus *bus, u32 status) > +{ > + struct at91_i2c_regs *reg = bus->regs; > + ulong start_time = get_timer(0); > + u32 sr; > + > + bus->status = 0; > + > + do { > + sr = readl(®->sr); > + bus->status |= sr; > + > + if (sr & TWI_SR_NACK) > + return -EREMOTEIO; > + else if (sr & status) > + return 0; > + } while (get_timer(start_time) < I2C_TIMEOUT_MS); > + > + return -ETIMEDOUT; > +} > + > +static int at91_i2c_xfer_msg(struct at91_i2c_bus *bus, struct i2c_msg *msg) > +{ > + struct at91_i2c_regs *reg = bus->regs; > + bool is_read = msg->flags & I2C_M_RD; > + u32 i; > + int ret = 0; > + > + readl(®->sr); > + if (is_read) { > + writel(TWI_CR_START, ®->cr); > + > + for (i = 0; !ret && i < (msg->len - 1); i++) { > + ret = at91_wait_for_xfer(bus, TWI_SR_RXRDY); > + msg->buf[i] = readl(®->rhr); > + } > + > + if (ret) > + goto error; > + > + writel(TWI_CR_STOP, ®->cr); > + > + ret = at91_wait_for_xfer(bus, TWI_SR_RXRDY); > + if (ret) > + goto error; > + > + msg->buf[i] = readl(®->rhr); > + > + } else { > + writel(msg->buf[0], ®->thr); > + for (i = 1; !ret && (i < msg->len); i++) { > + writel(msg->buf[i], ®->thr); > + ret = at91_wait_for_xfer(bus, TWI_SR_TXRDY); > + } > + > + if (ret) > + goto error; > + > + writel(TWI_CR_STOP, ®->cr); > + } > + > + if (!ret) > + ret = at91_wait_for_xfer(bus, TWI_SR_TXCOMP); > + > + if (ret) > + goto error; > + > + if (bus->status & (TWI_SR_OVRE | TWI_SR_UNRE | TWI_SR_LOCK)) { > + ret = -EIO; > + goto error; > + } > + > + return 0; > + > +error: > + if (bus->status & TWI_SR_LOCK) > + writel(TWI_CR_LOCKCLR, ®->cr); > + > + return ret; > +} > + > +static int at91_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + struct at91_i2c_regs *reg = bus->regs; > + struct i2c_msg *m_start = msg; > + bool is_read; > + u32 int_addr_flag = 0; > + int ret = 0; > + > + if (nmsgs == 2) { > + int internal_address = 0; > + int i; > + > + /* 1st msg is put into the internal address, start with 2nd */ > + m_start = &msg[1]; > + > + /* the max length of internal address is 3 bytes */ > + if (msg->len > 3) > + return -EFAULT; > + > + for (i = 0; i < msg->len; ++i) { > + const unsigned addr = msg->buf[msg->len - 1 - i]; > + > + internal_address |= addr << (8 * i); > + int_addr_flag += TWI_MMR_IADRSZ_1; > + } > + > + writel(internal_address, ®->iadr); > + } > + > + is_read = m_start->flags & I2C_M_RD; > + > + writel((m_start->addr << 16) | int_addr_flag | > + (is_read ? TWI_MMR_MREAD : 0), ®->mmr); > + > + ret = at91_i2c_xfer_msg(bus, m_start); > + > + return ret; > +} > + > +/* > + * Calculate symmetric clock as stated in datasheet: > + * twi_clk = F_MAIN / (2 * (cdiv * (1 << ckdiv) + offset)) > + */ > +static void at91_calc_i2c_clock(struct udevice *dev, int i2c_clk) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + const struct at91_i2c_pdata *pdata = bus->pdata; > + int offset = pdata->clk_offset; > + int max_ckdiv = pdata->clk_max_div; > + int ckdiv, cdiv, div; > + unsigned long src_rate; > + > + src_rate = bus->bus_clk_rate; > + > + div = max(0, (int)DIV_ROUND_UP(src_rate, 2 * i2c_clk) - offset); > + ckdiv = fls(div >> 8); > + cdiv = div >> ckdiv; > + > + if (ckdiv > max_ckdiv) { > + ckdiv = max_ckdiv; > + cdiv = 255; > + } > + > + bus->speed = DIV_ROUND_UP(src_rate, > + (cdiv * (1 << ckdiv) + offset) * 2); > + > + bus->cwgr_val = (ckdiv << 16) | (cdiv << 8) | cdiv; > +} > + > +static int at91_i2c_enable_clk(struct udevice *dev) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + struct udevice *dev_clk; > + struct clk clk; > + ulong clk_rate; > + int periph; > + int ret; > + > + ret = clk_get_by_index(dev, 0, &clk); > + if (ret) > + return -EINVAL; > + > + periph = fdtdec_get_uint(gd->fdt_blob, clk.dev->of_offset, "reg", -1); > + if (periph < 0) > + return -EINVAL; > + > + dev_clk = dev_get_parent(clk.dev); > + ret = clk_request(dev_clk, &clk); > + if (ret) > + return ret; > + > + clk.id = periph; > + ret = clk_enable(&clk); > + if (ret) > + return ret; > + > + ret = clk_get_by_index(dev_clk, 0, &clk); > + if (ret) > + return ret; > + > + clk_rate = clk_get_rate(&clk); > + if (!clk_rate) > + return -ENODEV; > + > + bus->bus_clk_rate = clk_rate; > + > + clk_free(&clk); > + > + return 0; > +} > + > +static int at91_i2c_probe(struct udevice *dev, uint chip, uint chip_flags) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + struct at91_i2c_regs *reg = bus->regs; > + int ret; > + > + ret = at91_i2c_enable_clk(dev); > + if (ret) > + return ret; > + > + writel(TWI_CR_SWRST, ®->cr); > + > + at91_calc_i2c_clock(dev, bus->clock_frequency); > + > + writel(bus->cwgr_val, ®->cwgr); > + writel(TWI_CR_MSEN, ®->cr); > + writel(TWI_CR_SVDIS, ®->cr); > + > + return 0; > +} > + > +static int at91_i2c_set_bus_speed(struct udevice *dev, unsigned int speed) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + > + at91_calc_i2c_clock(dev, speed); > + > + writel(bus->cwgr_val, &bus->regs->cwgr); > + > + return 0; > +} > + > +int at91_i2c_get_bus_speed(struct udevice *dev) > +{ > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + > + return bus->speed; > +} > + > +static int at91_i2c_ofdata_to_platdata(struct udevice *dev) > +{ > + const void *blob = gd->fdt_blob; > + struct at91_i2c_bus *bus = dev_get_priv(dev); > + int node = dev->of_offset; > + > + bus->regs = (struct at91_i2c_regs *)dev_get_addr(dev); > + bus->pdata = (struct at91_i2c_pdata *)dev_get_driver_data(dev); > + bus->clock_frequency = fdtdec_get_int(blob, node, > + "clock-frequency", 100000); > + > + return 0; > +} > + > +static const struct dm_i2c_ops at91_i2c_ops = { > + .xfer = at91_i2c_xfer, > + .probe_chip = at91_i2c_probe, > + .set_bus_speed = at91_i2c_set_bus_speed, > + .get_bus_speed = at91_i2c_get_bus_speed, > +}; > + > +static const struct at91_i2c_pdata at91rm9200_config = { > + .clk_max_div = 5, > + .clk_offset = 3, > +}; > + > +static const struct at91_i2c_pdata at91sam9261_config = { > + .clk_max_div = 5, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata at91sam9260_config = { > + .clk_max_div = 7, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata at91sam9g20_config = { > + .clk_max_div = 7, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata at91sam9g10_config = { > + .clk_max_div = 7, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata at91sam9x5_config = { > + .clk_max_div = 7, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata sama5d4_config = { > + .clk_max_div = 7, > + .clk_offset = 4, > +}; > + > +static const struct at91_i2c_pdata sama5d2_config = { > + .clk_max_div = 7, > + .clk_offset = 3, > +}; > + > +static const struct udevice_id at91_i2c_ids[] = { > +{ .compatible = "atmel,at91rm9200-i2c", .data = (long)&at91rm9200_config }, > +{ .compatible = "atmel,at91sam9260-i2c", .data = (long)&at91sam9260_config }, > +{ .compatible = "atmel,at91sam9261-i2c", .data = (long)&at91sam9261_config }, > +{ .compatible = "atmel,at91sam9g20-i2c", .data = (long)&at91sam9g20_config }, > +{ .compatible = "atmel,at91sam9g10-i2c", .data = (long)&at91sam9g10_config }, > +{ .compatible = "atmel,at91sam9x5-i2c", .data = (long)&at91sam9x5_config }, > +{ .compatible = "atmel,sama5d4-i2c", .data = (long)&sama5d4_config }, > +{ .compatible = "atmel,sama5d2-i2c", .data = (long)&sama5d2_config }, > +{ } > +}; > + > +U_BOOT_DRIVER(i2c_at91) = { > + .name = "i2c_at91", > + .id = UCLASS_I2C, > + .of_match = at91_i2c_ids, > + .ofdata_to_platdata = at91_i2c_ofdata_to_platdata, > + .per_child_auto_alloc_size = sizeof(struct dm_i2c_chip), > + .priv_auto_alloc_size = sizeof(struct at91_i2c_bus), > + .ops = &at91_i2c_ops, > +}; > diff --git a/drivers/i2c/at91_i2c.h b/drivers/i2c/at91_i2c.h > new file mode 100644 > index 0000000..87f02bf > --- /dev/null > +++ b/drivers/i2c/at91_i2c.h > @@ -0,0 +1,77 @@ > +#ifndef _AT91_I2C_H > +#define _AT91_I2C_H > + > +#define TWI_CR_START BIT(0) /* Send a Start Condition */ > +#define TWI_CR_MSEN BIT(2) /* Master Transfer Enable */ > +#define TWI_CR_STOP BIT(1) /* Send a Stop Condition */ > +#define TWI_CR_SVDIS BIT(5) /* Slave Transfer Disable */ > +#define TWI_CR_SWRST BIT(7) /* Software Reset */ > +#define TWI_CR_ACMEN BIT(16) /* Alternative Command Mode Enable */ > +#define TWI_CR_ACMDIS BIT(17) /* Alternative Command Mode Disable */ > +#define TWI_CR_LOCKCLR BIT(26) /* Lock Clear */ > + > +#define TWI_MMR_MREAD BIT(12) /* Master Read Direction */ > +#define TWI_MMR_IADRSZ_1 BIT(8) /* Internal Device Address Size */ > + > +#define TWI_SR_TXCOMP BIT(0) /* Transmission Complete */ > +#define TWI_SR_RXRDY BIT(1) /* Receive Holding Register Ready */ > +#define TWI_SR_TXRDY BIT(2) /* Transmit Holding Register Ready */ > +#define TWI_SR_OVRE BIT(6) /* Overrun Error */ > +#define TWI_SR_UNRE BIT(7) /* Underrun Error */ > +#define TWI_SR_NACK BIT(8) /* Not Acknowledged */ > +#define TWI_SR_LOCK BIT(23) /* TWI Lock due to Frame Errors */ > + > +#define TWI_ACR_DATAL(len) ((len) & 0xff) > +#define TWI_ACR_DIR_READ BIT(8) > + > +#define TWI_CWGR_HOLD_MAX 0x1f > +#define TWI_CWGR_HOLD(x) (((x) & TWI_CWGR_HOLD_MAX) << 24) > + > +struct at91_i2c_regs { > + u32 cr; > + u32 mmr; > + u32 smr; > + u32 iadr; > + u32 cwgr; > + u32 rev_0[3]; > + u32 sr; > + u32 ier; > + u32 idr; > + u32 imr; > + u32 rhr; > + u32 thr; > + u32 smbtr; > + u32 rev_1; > + u32 acr; > + u32 filtr; > + u32 rev_2; > + u32 swmr; > + u32 fmr; > + u32 flr; > + u32 rev_3; > + u32 fsr; > + u32 fier; > + u32 fidr; > + u32 fimr; > + u32 rev_4[29]; > + u32 wpmr; > + u32 wpsr; > + u32 rev_5[6]; > +}; > + > +struct at91_i2c_pdata { > + unsigned clk_max_div; > + unsigned clk_offset; > +}; > + > +struct at91_i2c_bus { > + struct at91_i2c_regs *regs; > + u32 status; > + ulong bus_clk_rate; > + u32 clock_frequency; > + u32 speed; > + u32 cwgr_val; > + const struct at91_i2c_pdata *pdata; > +}; > + > +#endif > -- DENX Software Engineering GmbH, Managing Director: Wolfgang Denk HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany