diff options
author | Songjun Wu <songjun.wu@atmel.com> | 2016-06-20 13:22:38 +0800 |
---|---|---|
committer | Andreas Bießmann <andreas@biessmann.org> | 2016-08-15 22:12:00 +0200 |
commit | 8800e0fa20c89df846559d85341c55439405cab6 (patch) | |
tree | bd01e6be0945d3d7f3e8ff0e195348849f9016ec /drivers/i2c/at91_i2c.c | |
parent | f4b0df1823921ad3bc39820466e9c5201cef6210 (diff) |
i2c: atmel: add i2c driver
Add i2c driver.
Signed-off-by: Songjun Wu <songjun.wu@atmel.com>
Reviewed-by: Heiko Schocher <hs@denx.de>
Acked-by: Heiko Schocher <hs@denx.de>
Diffstat (limited to 'drivers/i2c/at91_i2c.c')
-rw-r--r-- | drivers/i2c/at91_i2c.c | 338 |
1 files changed, 338 insertions, 0 deletions
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 <songjun.wu@atmel.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <asm/io.h> +#include <common.h> +#include <clk_client.h> +#include <dm.h> +#include <errno.h> +#include <fdtdec.h> +#include <i2c.h> +#include <linux/bitops.h> +#include <mach/clk.h> + +#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, +}; |