From 8800e0fa20c89df846559d85341c55439405cab6 Mon Sep 17 00:00:00 2001 From: Songjun Wu Date: Mon, 20 Jun 2016 13:22:38 +0800 Subject: [PATCH] i2c: atmel: add i2c driver Add i2c driver. Signed-off-by: Songjun Wu Reviewed-by: Heiko Schocher Acked-by: Heiko Schocher --- 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 diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index b3e84052eb..8575a23fba 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 167424db98..c5362a5a46 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 0000000000..8e9c3ad552 --- /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 0000000000..87f02bfaf3 --- /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 -- 2.39.5