On Sun, Nov 08, 2015 at 04:18:46PM -0800, Andrey Smirnov wrote: > Add a driver for DesignWare I2C controller IP block found on several > SoCs including Altera SoC products > > Tested using Terrasic SoCKit board and GPIO expander board with I2C > EEPROM on it > > Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> Applied, thanks Sascha > --- > > New in version 2: > > - kzalloc() replaced with xzalloc() > - removed redundant logging > > > drivers/i2c/busses/Kconfig | 6 + > drivers/i2c/busses/Makefile | 1 + > drivers/i2c/busses/i2c-designware.c | 574 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 581 insertions(+) > create mode 100644 drivers/i2c/busses/i2c-designware.c > > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig > index 181321b..a25a871 100644 > --- a/drivers/i2c/busses/Kconfig > +++ b/drivers/i2c/busses/Kconfig > @@ -25,6 +25,12 @@ config I2C_IMX > for many i.MX ARM based SoCs, for MPC85xx and MPC5200 PowerPC based > SoCs. > > +config I2C_DESIGNWARE > + bool "Synopsys DesignWare I2C Master driver" > + help > + If you say yes to this option, support will be included for the > + Synopsys DesignWare I2C adapter. Only master mode is supported. > + > config I2C_MV64XXX > bool "Marvell mv64xxx I2C Controller" > depends on HAVE_CLK && OFDEVICE > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile > index 1dbfbdf..8dccc38 100644 > --- a/drivers/i2c/busses/Makefile > +++ b/drivers/i2c/busses/Makefile > @@ -5,3 +5,4 @@ obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o > obj-$(CONFIG_I2C_OMAP) += i2c-omap.o > obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o > obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o > +obj-$(CONFIG_I2C_DESIGNWARE) += i2c-designware.o > diff --git a/drivers/i2c/busses/i2c-designware.c b/drivers/i2c/busses/i2c-designware.c > new file mode 100644 > index 0000000..1b3462b > --- /dev/null > +++ b/drivers/i2c/busses/i2c-designware.c > @@ -0,0 +1,574 @@ > +/* > + * Synopsys DesignWare I2C adapter driver (master only). > + * > + * Partly based on code of similar driver from U-Boot: > + * Copyright (C) 2009 ST Micoelectronics > + * > + * and corresponding code from Linux Kernel > + * Copyright (C) 2006 Texas Instruments. > + * Copyright (C) 2007 MontaVista Software Inc. > + * Copyright (C) 2009 Provigent Ltd. > + * > + * Copyright (C) 2015 Andrey Smirnov <andrew.smirnov@xxxxxxxxx> > + * > + * This software is licensed under the terms of the GNU General Public > + * License version 2, as published by the Free Software Foundation, and > + * may be copied, distributed, and modified under those terms. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include <clock.h> > +#include <common.h> > +#include <driver.h> > +#include <init.h> > +#include <of.h> > +#include <malloc.h> > +#include <types.h> > +#include <xfuncs.h> > +#include <linux/clk.h> > +#include <linux/err.h> > + > +#include <io.h> > +#include <i2c/i2c.h> > + > +#define DW_I2C_BIT_RATE 100000 > + > +#define DW_IC_CON 0x0 > +#define DW_IC_CON_MASTER (1 << 0) > +#define DW_IC_CON_SPEED_STD (1 << 1) > +#define DW_IC_CON_SPEED_FAST (1 << 2) > +#define DW_IC_CON_SLAVE_DISABLE (1 << 6) > + > +#define DW_IC_TAR 0x4 > + > +#define DW_IC_DATA_CMD 0x10 > +#define DW_IC_DATA_CMD_CMD (1 << 8) > +#define DW_IC_DATA_CMD_STOP (1 << 9) > + > +#define DW_IC_SS_SCL_HCNT 0x14 > +#define DW_IC_SS_SCL_LCNT 0x18 > +#define DW_IC_FS_SCL_HCNT 0x1c > +#define DW_IC_FS_SCL_LCNT 0x20 > + > +#define DW_IC_INTR_MASK 0x30 > + > +#define DW_IC_RAW_INTR_STAT 0x34 > +#define DW_IC_INTR_RX_UNDER (1 << 0) > +#define DW_IC_INTR_RX_OVER (1 << 1) > +#define DW_IC_INTR_RX_FULL (1 << 2) > +#define DW_IC_INTR_TX_OVER (1 << 3) > +#define DW_IC_INTR_TX_EMPTY (1 << 4) > +#define DW_IC_INTR_RD_REQ (1 << 5) > +#define DW_IC_INTR_TX_ABRT (1 << 6) > +#define DW_IC_INTR_RX_DONE (1 << 7) > +#define DW_IC_INTR_ACTIVITY (1 << 8) > +#define DW_IC_INTR_STOP_DET (1 << 9) > +#define DW_IC_INTR_START_DET (1 << 10) > +#define DW_IC_INTR_GEN_CALL (1 << 11) > + > +#define DW_IC_RX_TL 0x38 > +#define DW_IC_TX_TL 0x3c > +#define DW_IC_CLR_INTR 0x40 > +#define DW_IC_CLR_TX_ABRT 0x54 > + > +#define DW_IC_ENABLE 0x6c > +#define DW_IC_ENABLE_ENABLE (1 << 0) > + > +#define DW_IC_STATUS 0x70 > +#define DW_IC_STATUS_TFNF (1 << 1) > +#define DW_IC_STATUS_TFE (1 << 2) > +#define DW_IC_STATUS_RFNE (1 << 3) > +#define DW_IC_STATUS_MST_ACTIVITY (1 << 5) > + > +#define DW_IC_TX_ABRT_SOURCE 0x80 > + > +#define DW_IC_ENABLE_STATUS 0x9c > +#define DW_IC_ENABLE_STATUS_IC_EN (1 << 0) > + > +#define DW_IC_COMP_TYPE 0xfc > +#define DW_IC_COMP_TYPE_VALUE 0x44570140 > + > +#define MAX_T_POLL_COUNT 100 > + > +#define DW_TIMEOUT_IDLE (40 * MSECOND) > +#define DW_TIMEOUT_TX (2 * MSECOND) > +#define DW_TIMEOUT_RX (2 * MSECOND) > + > +struct dw_i2c_dev { > + void __iomem *base; > + struct clk *clk; > + struct i2c_adapter adapter; > +}; > + > +static inline struct dw_i2c_dev *to_dw_i2c_dev(struct i2c_adapter *a) > +{ > + return container_of(a, struct dw_i2c_dev, adapter); > +} > + > +static void i2c_dw_enable(struct dw_i2c_dev *dw, bool enable) > +{ > + /* > + * This subrotine is an implementation of an algorithm > + * described in "Cyclone V Hard Processor System Technical > + * Reference * Manual" p. 20-19, "Disabling the I2C Controller" > + */ > + int timeout = MAX_T_POLL_COUNT; > + > + enable = enable ? DW_IC_ENABLE_ENABLE : 0; > + > + do { > + uint32_t ic_enable_status; > + > + writel(enable, dw->base + DW_IC_ENABLE); > + > + ic_enable_status = readl(dw->base + DW_IC_ENABLE_STATUS); > + if ((ic_enable_status & DW_IC_ENABLE_STATUS_IC_EN) == enable) > + return; > + > + udelay(250); > + } while (timeout--); > + > + dev_warn(&dw->adapter.dev, "timeout in %sabling adapter\n", > + enable ? "en" : "dis"); > +} > + > +/* > + * All of the code pertaining to tming calculation is taken from > + * analogous driver in Linux kernel > + */ > +static uint32_t > +i2c_dw_scl_hcnt(uint32_t ic_clk, uint32_t tSYMBOL, uint32_t tf, int cond, > + int offset) > +{ > + /* > + * DesignWare I2C core doesn't seem to have solid strategy to meet > + * the tHD;STA timing spec. Configuring _HCNT based on tHIGH spec > + * will result in violation of the tHD;STA spec. > + */ > + if (cond) > + /* > + * Conditional expression: > + * > + * IC_[FS]S_SCL_HCNT + (1+4+3) >= IC_CLK * tHIGH > + * > + * This is based on the DW manuals, and represents an ideal > + * configuration. The resulting I2C bus speed will be > + * faster than any of the others. > + * > + * If your hardware is free from tHD;STA issue, try this one. > + */ > + return (ic_clk * tSYMBOL + 500000) / 1000000 - 8 + offset; > + else > + /* > + * Conditional expression: > + * > + * IC_[FS]S_SCL_HCNT + 3 >= IC_CLK * (tHD;STA + tf) > + * > + * This is just experimental rule; the tHD;STA period turned > + * out to be proportinal to (_HCNT + 3). With this setting, > + * we could meet both tHIGH and tHD;STA timing specs. > + * > + * If unsure, you'd better to take this alternative. > + * > + * The reason why we need to take into account "tf" here, > + * is the same as described in i2c_dw_scl_lcnt(). > + */ > + return (ic_clk * (tSYMBOL + tf) + 500000) / 1000000 > + - 3 + offset; > +} > + > +static uint32_t > +i2c_dw_scl_lcnt(uint32_t ic_clk, uint32_t tLOW, uint32_t tf, int offset) > +{ > + /* > + * Conditional expression: > + * > + * IC_[FS]S_SCL_LCNT + 1 >= IC_CLK * (tLOW + tf) > + * > + * DW I2C core starts counting the SCL CNTs for the LOW period > + * of the SCL clock (tLOW) as soon as it pulls the SCL line. > + * In order to meet the tLOW timing spec, we need to take into > + * account the fall time of SCL signal (tf). Default tf value > + * should be 0.3 us, for safety. > + */ > + return ((ic_clk * (tLOW + tf) + 500000) / 1000000) - 1 + offset; > +} > + > +static void i2c_dw_setup_timings(struct dw_i2c_dev *dw) > +{ > + uint32_t hcnt, lcnt; > + > + const uint32_t sda_falling_time = 300; /* ns */ > + const uint32_t scl_falling_time = 300; /* ns */ > + > + const unsigned int input_clock_khz = clk_get_rate(dw->clk) / 1000; > + > + /* Set SCL timing parameters for standard-mode */ > + hcnt = i2c_dw_scl_hcnt(input_clock_khz, > + 4000, /* tHD;STA = tHIGH = 4.0 us */ > + sda_falling_time, > + 0, /* 0: DW default, 1: Ideal */ > + 0); /* No offset */ > + lcnt = i2c_dw_scl_lcnt(input_clock_khz, > + 4700, /* tLOW = 4.7 us */ > + scl_falling_time, > + 0); /* No offset */ > + > + writel(hcnt, dw->base + DW_IC_SS_SCL_HCNT); > + writel(lcnt, dw->base + DW_IC_SS_SCL_LCNT); > + > + hcnt = i2c_dw_scl_hcnt(input_clock_khz, > + 600, /* tHD;STA = tHIGH = 0.6 us */ > + sda_falling_time, > + 0, /* 0: DW default, 1: Ideal */ > + 0); /* No offset */ > + lcnt = i2c_dw_scl_lcnt(input_clock_khz, > + 1300, /* tLOW = 1.3 us */ > + scl_falling_time, > + 0); /* No offset */ > + > + writel(hcnt, dw->base + DW_IC_FS_SCL_HCNT); > + writel(lcnt, dw->base + DW_IC_FS_SCL_LCNT); > +} > + > +static int i2c_dw_wait_for_bits(struct dw_i2c_dev *dw, uint32_t offset, > + uint32_t mask, uint32_t value, uint64_t timeout) > +{ > + const uint64_t start = get_time_ns(); > + > + do { > + const uint32_t reg = readl(dw->base + offset); > + > + if ((reg & mask) == value) > + return 0; > + > + } while (!is_timeout(start, timeout)); > + > + return -ETIMEDOUT; > +} > + > +static int i2c_dw_wait_for_idle(struct dw_i2c_dev *dw) > +{ > + const uint32_t mask = DW_IC_STATUS_MST_ACTIVITY | DW_IC_STATUS_TFE; > + const uint32_t value = DW_IC_STATUS_TFE; > + > + return i2c_dw_wait_for_bits(dw, DW_IC_STATUS, mask, value, > + DW_TIMEOUT_IDLE); > +} > + > +static int i2c_dw_wait_for_tx_fifo_not_full(struct dw_i2c_dev *dw) > +{ > + const uint32_t mask = DW_IC_STATUS_TFNF; > + const uint32_t value = DW_IC_STATUS_TFNF; > + > + return i2c_dw_wait_for_bits(dw, DW_IC_STATUS, mask, value, > + DW_TIMEOUT_TX); > +} > + > +static int i2c_dw_wait_for_rx_fifo_not_empty(struct dw_i2c_dev *dw) > +{ > + const uint32_t mask = DW_IC_STATUS_RFNE; > + const uint32_t value = DW_IC_STATUS_RFNE; > + > + return i2c_dw_wait_for_bits(dw, DW_IC_STATUS, mask, value, > + DW_TIMEOUT_RX); > +} > + > +static void i2c_dw_reset(struct dw_i2c_dev *dw) > +{ > + i2c_dw_enable(dw, false); > + i2c_dw_enable(dw, true); > +} > + > +static void i2c_dw_abort_tx(struct dw_i2c_dev *dw) > +{ > + i2c_dw_reset(dw); > +} > + > +static void i2c_dw_abort_rx(struct dw_i2c_dev *dw) > +{ > + i2c_dw_reset(dw); > +} > + > +static int i2c_dw_read(struct dw_i2c_dev *dw, > + const struct i2c_msg *msg) > +{ > + int i; > + for (i = 0; i < msg->len; i++) { > + int ret; > + const bool last_byte = i == msg->len - 1; > + uint32_t ic_cmd_data = DW_IC_DATA_CMD_CMD; > + > + if (last_byte) > + ic_cmd_data |= DW_IC_DATA_CMD_STOP; > + > + writel(ic_cmd_data, dw->base + DW_IC_DATA_CMD); > + > + ret = i2c_dw_wait_for_rx_fifo_not_empty(dw); > + if (ret < 0) { > + i2c_dw_abort_rx(dw); > + return ret; > + } > + > + msg->buf[i] = (uint8_t)readl(dw->base + DW_IC_DATA_CMD); > + } > + > + return msg->len; > +} > + > +static int i2c_dw_write(struct dw_i2c_dev *dw, > + const struct i2c_msg *msg) > +{ > + int i; > + uint32_t ic_int_stat; > + > + for (i = 0; i < msg->len; i++) { > + int ret; > + uint32_t ic_cmd_data; > + const bool last_byte = i == msg->len - 1; > + > + ic_int_stat = readl(dw->base + DW_IC_RAW_INTR_STAT); > + > + if (ic_int_stat & DW_IC_INTR_TX_ABRT) > + return -EIO; > + > + ret = i2c_dw_wait_for_tx_fifo_not_full(dw); > + if (ret < 0) { > + i2c_dw_abort_tx(dw); > + return ret; > + } > + > + ic_cmd_data = msg->buf[i]; > + > + if (last_byte) > + ic_cmd_data |= DW_IC_DATA_CMD_STOP; > + > + writel(ic_cmd_data, dw->base + DW_IC_DATA_CMD); > + } > + > + return msg->len; > +} > + > +static int i2c_dw_wait_for_stop(struct dw_i2c_dev *dw) > +{ > + const uint32_t mask = DW_IC_INTR_STOP_DET; > + const uint32_t value = DW_IC_INTR_STOP_DET; > + > + return i2c_dw_wait_for_bits(dw, DW_IC_RAW_INTR_STAT, mask, value, > + DW_TIMEOUT_IDLE); > +} > + > +static int i2c_dw_finish_xfer(struct dw_i2c_dev *dw) > +{ > + int ret; > + uint32_t ic_int_stat; > + > + /* > + * We expect the controller to signal STOP condition on the > + * bus, so we are going to wait for that first. > + */ > + ret = i2c_dw_wait_for_stop(dw); > + if (ret < 0) > + return ret; > + > + /* > + * Now that we now that the stop condition has been signaled > + * we need to wait for controller to go into IDLE state to > + * make sure all of the possible error conditions on the bus > + * have been propagated to apporpriate status > + * registers. Experiment shows that not doing so often results > + * in false positive "successful" transfers > + */ > + ret = i2c_dw_wait_for_idle(dw); > + > + if (ret >= 0) { > + ic_int_stat = readl(dw->base + DW_IC_RAW_INTR_STAT); > + > + if (ic_int_stat & DW_IC_INTR_TX_ABRT) > + return -EIO; > + } > + > + return ret; > +} > + > +static int i2c_dw_set_address(struct dw_i2c_dev *dw, uint8_t address) > +{ > + int ret; > + uint32_t ic_tar; > + /* > + * As per "Cyclone V Hard Processor System Technical Reference > + * Manual" p. 20-19, we have to wait for controller to be in > + * idle state in order to be able to set the address > + * dynamically > + */ > + ret = i2c_dw_wait_for_idle(dw); > + if (ret < 0) > + return ret; > + > + ic_tar = readl(dw->base + DW_IC_TAR); > + ic_tar &= 0xfffffc00; > + > + writel(ic_tar | address, dw->base + DW_IC_TAR); > + > + return 0; > +} > + > +static int i2c_dw_xfer(struct i2c_adapter *adapter, > + struct i2c_msg *msgs, int num) > +{ > + int i, ret = 0; > + struct dw_i2c_dev *dw = to_dw_i2c_dev(adapter); > + > + for (i = 0; i < num; i++) { > + if (msgs[i].flags & I2C_M_DATA_ONLY) > + return -ENOTSUPP; > + > + ret = i2c_dw_set_address(dw, msgs[i].addr); > + if (ret < 0) > + break; > + > + if (msgs[i].flags & I2C_M_RD) > + ret = i2c_dw_read(dw, &msgs[i]); > + else > + ret = i2c_dw_write(dw, &msgs[i]); > + > + if (ret < 0) > + break; > + > + ret = i2c_dw_finish_xfer(dw); > + if (ret < 0) > + break; > + } > + > + if (ret == -EIO) { > + /* > + * If we got -EIO it means that transfer was for some > + * reason aborted, so we should figure out the reason > + * and take steps to clear that condition > + */ > + const uint32_t ic_tx_abrt_source = > + readl(dw->base + DW_IC_TX_ABRT_SOURCE); > + dev_dbg(&dw->adapter.dev, > + "<%s> ic_tx_abrt_source: 0x%04x\n", > + __func__, ic_tx_abrt_source); > + readl(dw->base + DW_IC_CLR_TX_ABRT); > + > + return ret; > + } > + > + if (ret < 0) { > + i2c_dw_reset(dw); > + return ret; > + } > + > + return num; > +} > + > + > +static int i2c_dw_probe(struct device_d *pdev) > +{ > + struct dw_i2c_dev *dw; > + struct i2c_platform_data *pdata; > + int ret, bitrate; > + uint32_t ic_con, ic_comp_type_value; > + > + pdata = pdev->platform_data; > + > + dw = xzalloc(sizeof(*dw), GFP_KERNEL); > + > + if (IS_ENABLED(CONFIG_COMMON_CLK)) { > + dw->clk = clk_get(pdev, NULL); > + if (IS_ERR(dw->clk)) { > + ret = PTR_ERR(dw->clk); > + goto fail; > + } > + } > + > + dw->adapter.master_xfer = i2c_dw_xfer; > + dw->adapter.nr = pdev->id; > + dw->adapter.dev.parent = pdev; > + dw->adapter.dev.device_node = pdev->device_node; > + > + dw->base = dev_request_mem_region(pdev, 0); > + if (IS_ERR(dw->base)) { > + ret = PTR_ERR(dw->base); > + goto fail; > + } > + > + ic_comp_type_value = readl(dw->base + DW_IC_COMP_TYPE); > + if (ic_comp_type_value != DW_IC_COMP_TYPE_VALUE) { > + dev_err(pdev, > + "unknown DesignWare IP block 0x%08x", > + ic_comp_type_value); > + ret = -ENODEV; > + goto fail; > + } > + > + i2c_dw_enable(dw, false); > + > + if (IS_ENABLED(CONFIG_COMMON_CLK)) > + i2c_dw_setup_timings(dw); > + > + bitrate = (pdata && pdata->bitrate) ? pdata->bitrate : DW_I2C_BIT_RATE; > + > + /* > + * We have to clear 'ic_10bitaddr_master' in 'ic_tar' > + * register, otherwise 'ic_10bitaddr_master' in 'ic_con' > + * wouldn't clear. We don't care about preserving the contents > + * of that register so we set it to zero. > + */ > + writel(0, dw->base + DW_IC_TAR); > + > + switch (bitrate) { > + case 400000: > + ic_con = DW_IC_CON_SPEED_FAST; > + break; > + default: > + dev_warn(pdev, "requested bitrate (%d) is not supported." > + " Falling back to 100kHz", bitrate); > + case 100000: /* FALLTHROUGH */ > + ic_con = DW_IC_CON_SPEED_STD; > + break; > + } > + > + ic_con |= DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE; > + > + writel(ic_con, dw->base + DW_IC_CON); > + > + /* > + * Since we will be working in polling mode set both > + * thresholds to their minimum > + */ > + writel(0, dw->base + DW_IC_RX_TL); > + writel(0, dw->base + DW_IC_TX_TL); > + > + /* Disable and clear all interrrupts */ > + writel(0, dw->base + DW_IC_INTR_MASK); > + readl(dw->base + DW_IC_CLR_INTR); > + > + i2c_dw_enable(dw, true); > + > + ret = i2c_add_numbered_adapter(&dw->adapter); > +fail: > + if (ret < 0) > + kfree(dw); > + > + return ret; > +} > + > +static __maybe_unused struct of_device_id i2c_dw_dt_ids[] = { > + { .compatible = "snps,designware-i2c", }, > + { /* sentinel */ } > +}; > + > +static struct driver_d i2c_dw_driver = { > + .probe = i2c_dw_probe, > + .name = "i2c-designware", > + .of_compatible = DRV_OF_COMPAT(i2c_dw_dt_ids), > +}; > +coredevice_platform_driver(i2c_dw_driver); > -- > 2.1.4 > > _______________________________________________ > barebox mailing list > barebox@xxxxxxxxxxxxxxxxxxx > http://lists.infradead.org/mailman/listinfo/barebox > -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox