* Re: [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
(?)
@ 2012-11-27 13:34 ` Thomas Abraham
-1 siblings, 0 replies; 107+ messages in thread
From: Thomas Abraham @ 2012-11-27 13:34 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ
On 27 November 2012 18:30, Naveen Krishna Chatradhi
<ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/i2c/busses/Kconfig | 6 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 758 ++++++++++++++++++++++++++++++++++++++
> drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> 4 files changed, 845 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> create mode 100644 drivers/i2c/busses/i2c-exynos5.h
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 65dd599..88e8833 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -609,6 +609,12 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 HS-I2C Driver"
> + help
> + Say Y here to include support for High Speed I2C controller in the
> + Exynos5 series SoCs from Samsung.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 2d33d62..426b4fd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..5983aa9
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,758 @@
> +/* linux/drivers/i2c/busses/i2c-exynos5.c
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series High Speed I2C controller driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_i2c.h>
> +#include <linux/of_gpio.h>
> +
> +#include <asm/irq.h>
> +#include "i2c-exynos5.h"
> +
> +#define HSI2C_POLLING 0
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +/* Max time to wait for bus to become idle after a xfer */
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +struct exynos5_i2c {
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_byte_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + struct resource *ioarea;
> + struct i2c_adapter adap;
> + unsigned int bus_number;
> + unsigned int speed_mode;
> + unsigned int fast_speed;
> + unsigned int high_speed;
> + int operation_mode;
> + int gpios[2];
> +};
> +
> +static struct platform_device_id exynos5_driver_ids[] = {
> + {
> + .name = "exynos5-hs-i2c",
> + .driver_data = 0,
> + }, { },
> +};
> +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +#endif
> +
> +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> +{
> + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + , i2c->suspended
> + , readl(i2c->regs + HSI2C_CTL)
> + , readl(i2c->regs + HSI2C_FIFO_CTL)
> + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> + , readl(i2c->regs + HSI2C_CLK_CTL)
> + , readl(i2c->regs + HSI2C_CLK_SLOT)
> + , readl(i2c->regs + HSI2C_INT_ENABLE)
> + , readl(i2c->regs + HSI2C_INT_STATUS)
> + , readl(i2c->regs + HSI2C_ERR_STATUS)
> + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> + , readl(i2c->regs + HSI2C_TX_DATA)
> + , readl(i2c->regs + HSI2C_RX_DATA)
> + , readl(i2c->regs + HSI2C_CONF)
> + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> + , readl(i2c->regs + HSI2C_TIMEOUT)
> + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> + , readl(i2c->regs + HSI2C_TIMING_HS1)
> + , readl(i2c->regs + HSI2C_TIMING_HS2)
> + , readl(i2c->regs + HSI2C_TIMING_HS3)
> + , readl(i2c->regs + HSI2C_TIMING_FS1)
> + , readl(i2c->regs + HSI2C_TIMING_FS2)
> + , readl(i2c->regs + HSI2C_TIMING_FS3)
> + , readl(i2c->regs + HSI2C_TIMING_SLA)
> + , readl(i2c->regs + HSI2C_ADDR));
> +}
> +
> +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> +{
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> +
> + complete(&i2c->msg_complete);
> +}
> +
> +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> +{
> + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> +
> + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned char byte;
> +
> + if (i2c->msg->flags & I2C_M_RD) {
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + } else {
> + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + }
> +
> + exynos5_disable_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> +
> +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl;
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl |= (1u << 31);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl &= ~(1u << 31);
If the bit mask and bit position values are used more than once, it
helps to define them as constants.
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + exynos5_i2c_init(i2c);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int num, int stop)
> +{
> + unsigned long timeout;
> + unsigned long trans_status;
> + unsigned long usi_fifo_stat;
> + unsigned long usi_ctl;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr;
> + unsigned long usi_int_en;
> + unsigned long usi_fifo_ctl;
> + unsigned char byte;
> + int ret = 0;
> + int operation_mode = i2c->operation_mode;
> +
> + i2c->msg = msgs;
> + i2c->msg_byte_ptr = 0;
> +
> + init_completion(&i2c->msg_complete);
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> + HSI2C_RXFIFO_TRIGGER_LEVEL;
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> +
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + usi_int_en = 0;
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + else
> + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> +
> +
> + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> +
> + /* Clear Slave Address for I2C Master (Auto mode only) */
> + i2c_addr &= ~(0x3ff << 10);
> + /* Clear Slave Address for I2C Slave */
> + i2c_addr &= ~(0x3ff << 0);
> + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + i2c_addr &= ~(0xff << 24);
> +
> + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* Clear and set TRANS_LEN */
> + i2c_auto_conf &= ~(0xffff);
> + i2c_auto_conf |= i2c->msg->len;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Enable appropriate interrupts */
> + if (operation_mode != HSI2C_POLLING)
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +
> + ret = -EAGAIN;
> + if (msgs->flags & I2C_M_RD) {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl
> + (i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++]
> + = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len) {
> + ret = 0;
> + break;
> + }
> + }
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> +
> + ret = 0;
> + }
> + } else {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout) &&
> + (i2c->msg_byte_ptr < i2c->msg->len)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS)
> + & 0x7f) < 64) {
> + byte = i2c->msg->buf
> + [i2c->msg_byte_ptr++];
> + writel(byte,
> + i2c->regs + HSI2C_TX_DATA);
> + }
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> +
> + timeout = jiffies + timeout;
> + }
> + while (time_before(jiffies, timeout)) {
> + usi_fifo_stat = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> + ((trans_status == 0) ||
> + ((stop == 0) &&
> + (trans_status == 0x20000)))) {
> + ret = 0;
> + break;
> + }
> + }
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> + }
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> + int stop = 0;
> + struct i2c_msg *msgs_ptr = msgs;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + if (i == num - 1)
> + stop = 1;
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + stop = 0;
> + break;
> + }
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> + return num;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return -EREMOTEIO;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + op_clk = i2c->high_speed;
> + else
> + op_clk = i2c->fast_speed;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 256 / 2 */
> + if (utemp1 < 128) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_OF
If this driver supports only device tree (as stated in the commit
message), then this #ifdef can be avoided.
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + goto free_gpio;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = gpio_request(gpio, "hsi2c-bus");
Using devm_gpio_request here can simplify the error handling path.
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + goto free_gpio;
> + }
> + }
> + return 0;
> +
> +free_gpio:
> + while (--idx >= 0)
> + gpio_free(i2c->gpios[idx]);
> + return -EINVAL;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> + unsigned int idx;
> +
> + for (idx = 0; idx < 2; idx++)
> + gpio_free(i2c->gpios[idx]);
> +}
> +#else
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + return 0;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> +}
> +#endif
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + exynos5_i2c_set_timing(i2c);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + }
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c;
> + struct resource *res;
> + int ret;
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (pdev->dev.of_node) {
> + /* i2c bus number is dynamically assigned */
> + i2c->bus_number = -1;
> +
> + if (of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-speed-mode", &i2c->speed_mode))
> + i2c->speed_mode = 1;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-hs-clk", &i2c->high_speed))
> + i2c->high_speed = 2500000;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-fs-clk", &i2c->fast_speed))
> + i2c->fast_speed = 400000;
> + } else {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5250-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> + ret = -ENOENT;
> + goto err_clk;
> + }
> +
> + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> + pdev->name);
Since this is dt-only driver, use of_iomap here.
> +
> + if (i2c->ioarea == NULL) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + i2c->regs = ioremap(res->start, resource_size(res));
> +
> + if (i2c->regs == NULL) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_ioarea;
> + }
> +
> + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> + i2c->regs, i2c->ioarea, res);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + ret = exynos5_i2c_init(i2c);
> + if (ret != 0)
> + goto err_iomap;
> +
> + i2c->irq = ret = platform_get_irq(pdev, 0);
irq_of_parse_and_map() here.
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + goto err_iomap;
> + }
> +
> + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> + dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_iomap;
> + }
> +
> + i2c->adap.nr = i2c->bus_number;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_irq;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> + clk_disable_unprepare(i2c->clk);
> + return 0;
> +
> + err_irq:
> + free_irq(i2c->irq, i2c);
> +
> + err_iomap:
> + iounmap(i2c->regs);
> +
> + err_ioarea:
> + release_resource(i2c->ioarea);
> + kfree(i2c->ioarea);
> +
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_del_adapter(&i2c->adap);
> + free_irq(i2c->irq, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + iounmap(i2c->regs);
> +
> + release_resource(i2c->ioarea);
> + exynos5_i2c_dt_gpio_free(i2c);
> + kfree(i2c->ioarea);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + i2c->suspended = 1;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .id_table = exynos5_driver_ids,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-i2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = of_match_ptr(exynos5_i2c_match),
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/i2c/busses/i2c-exynos5.h b/drivers/i2c/busses/i2c-exynos5.h
> new file mode 100644
> index 0000000..063051e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.h
> @@ -0,0 +1,80 @@
> +/*
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series HS-I2C Controller
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> +
> +/*
> + * Register Map
> + */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONFING 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +
> +/* I2C_FIFO_CTL Register */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO */
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO */
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +
> +/* I2C_CONF Register */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +#define HSI2C_FS_BPS 400000
> +#define HSI2C_HS_BPS 2500000
> +
> +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
Since these constants are only use in i2c-exynos5.c file, it is better
to move these definitions into i2c-exynos5.c file.
Thanks,
Thomas.
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-11-27 13:34 ` Thomas Abraham
0 siblings, 0 replies; 107+ messages in thread
From: Thomas Abraham @ 2012-11-27 13:34 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-arm-kernel, linux-samsung-soc, devicetree-discuss,
linux-i2c, naveenkrishna.ch, kgene.kim, grant.likely, w.sang,
linux-kernel, taeggyun.ko
On 27 November 2012 18:30, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> drivers/i2c/busses/Kconfig | 6 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 758 ++++++++++++++++++++++++++++++++++++++
> drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> 4 files changed, 845 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> create mode 100644 drivers/i2c/busses/i2c-exynos5.h
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 65dd599..88e8833 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -609,6 +609,12 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 HS-I2C Driver"
> + help
> + Say Y here to include support for High Speed I2C controller in the
> + Exynos5 series SoCs from Samsung.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 2d33d62..426b4fd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..5983aa9
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,758 @@
> +/* linux/drivers/i2c/busses/i2c-exynos5.c
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series High Speed I2C controller driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_i2c.h>
> +#include <linux/of_gpio.h>
> +
> +#include <asm/irq.h>
> +#include "i2c-exynos5.h"
> +
> +#define HSI2C_POLLING 0
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +/* Max time to wait for bus to become idle after a xfer */
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +struct exynos5_i2c {
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_byte_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + struct resource *ioarea;
> + struct i2c_adapter adap;
> + unsigned int bus_number;
> + unsigned int speed_mode;
> + unsigned int fast_speed;
> + unsigned int high_speed;
> + int operation_mode;
> + int gpios[2];
> +};
> +
> +static struct platform_device_id exynos5_driver_ids[] = {
> + {
> + .name = "exynos5-hs-i2c",
> + .driver_data = 0,
> + }, { },
> +};
> +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +#endif
> +
> +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> +{
> + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + , i2c->suspended
> + , readl(i2c->regs + HSI2C_CTL)
> + , readl(i2c->regs + HSI2C_FIFO_CTL)
> + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> + , readl(i2c->regs + HSI2C_CLK_CTL)
> + , readl(i2c->regs + HSI2C_CLK_SLOT)
> + , readl(i2c->regs + HSI2C_INT_ENABLE)
> + , readl(i2c->regs + HSI2C_INT_STATUS)
> + , readl(i2c->regs + HSI2C_ERR_STATUS)
> + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> + , readl(i2c->regs + HSI2C_TX_DATA)
> + , readl(i2c->regs + HSI2C_RX_DATA)
> + , readl(i2c->regs + HSI2C_CONF)
> + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> + , readl(i2c->regs + HSI2C_TIMEOUT)
> + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> + , readl(i2c->regs + HSI2C_TIMING_HS1)
> + , readl(i2c->regs + HSI2C_TIMING_HS2)
> + , readl(i2c->regs + HSI2C_TIMING_HS3)
> + , readl(i2c->regs + HSI2C_TIMING_FS1)
> + , readl(i2c->regs + HSI2C_TIMING_FS2)
> + , readl(i2c->regs + HSI2C_TIMING_FS3)
> + , readl(i2c->regs + HSI2C_TIMING_SLA)
> + , readl(i2c->regs + HSI2C_ADDR));
> +}
> +
> +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> +{
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> +
> + complete(&i2c->msg_complete);
> +}
> +
> +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> +{
> + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> +
> + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned char byte;
> +
> + if (i2c->msg->flags & I2C_M_RD) {
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + } else {
> + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + }
> +
> + exynos5_disable_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> +
> +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl;
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl |= (1u << 31);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl &= ~(1u << 31);
If the bit mask and bit position values are used more than once, it
helps to define them as constants.
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + exynos5_i2c_init(i2c);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int num, int stop)
> +{
> + unsigned long timeout;
> + unsigned long trans_status;
> + unsigned long usi_fifo_stat;
> + unsigned long usi_ctl;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr;
> + unsigned long usi_int_en;
> + unsigned long usi_fifo_ctl;
> + unsigned char byte;
> + int ret = 0;
> + int operation_mode = i2c->operation_mode;
> +
> + i2c->msg = msgs;
> + i2c->msg_byte_ptr = 0;
> +
> + init_completion(&i2c->msg_complete);
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> + HSI2C_RXFIFO_TRIGGER_LEVEL;
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> +
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + usi_int_en = 0;
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + else
> + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> +
> +
> + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> +
> + /* Clear Slave Address for I2C Master (Auto mode only) */
> + i2c_addr &= ~(0x3ff << 10);
> + /* Clear Slave Address for I2C Slave */
> + i2c_addr &= ~(0x3ff << 0);
> + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + i2c_addr &= ~(0xff << 24);
> +
> + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* Clear and set TRANS_LEN */
> + i2c_auto_conf &= ~(0xffff);
> + i2c_auto_conf |= i2c->msg->len;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Enable appropriate interrupts */
> + if (operation_mode != HSI2C_POLLING)
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +
> + ret = -EAGAIN;
> + if (msgs->flags & I2C_M_RD) {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl
> + (i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++]
> + = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len) {
> + ret = 0;
> + break;
> + }
> + }
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> +
> + ret = 0;
> + }
> + } else {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout) &&
> + (i2c->msg_byte_ptr < i2c->msg->len)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS)
> + & 0x7f) < 64) {
> + byte = i2c->msg->buf
> + [i2c->msg_byte_ptr++];
> + writel(byte,
> + i2c->regs + HSI2C_TX_DATA);
> + }
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> +
> + timeout = jiffies + timeout;
> + }
> + while (time_before(jiffies, timeout)) {
> + usi_fifo_stat = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> + ((trans_status == 0) ||
> + ((stop == 0) &&
> + (trans_status == 0x20000)))) {
> + ret = 0;
> + break;
> + }
> + }
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> + }
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> + int stop = 0;
> + struct i2c_msg *msgs_ptr = msgs;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + if (i == num - 1)
> + stop = 1;
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + stop = 0;
> + break;
> + }
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> + return num;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return -EREMOTEIO;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + op_clk = i2c->high_speed;
> + else
> + op_clk = i2c->fast_speed;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 256 / 2 */
> + if (utemp1 < 128) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_OF
If this driver supports only device tree (as stated in the commit
message), then this #ifdef can be avoided.
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + goto free_gpio;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = gpio_request(gpio, "hsi2c-bus");
Using devm_gpio_request here can simplify the error handling path.
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + goto free_gpio;
> + }
> + }
> + return 0;
> +
> +free_gpio:
> + while (--idx >= 0)
> + gpio_free(i2c->gpios[idx]);
> + return -EINVAL;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> + unsigned int idx;
> +
> + for (idx = 0; idx < 2; idx++)
> + gpio_free(i2c->gpios[idx]);
> +}
> +#else
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + return 0;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> +}
> +#endif
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + exynos5_i2c_set_timing(i2c);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + }
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c;
> + struct resource *res;
> + int ret;
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (pdev->dev.of_node) {
> + /* i2c bus number is dynamically assigned */
> + i2c->bus_number = -1;
> +
> + if (of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-speed-mode", &i2c->speed_mode))
> + i2c->speed_mode = 1;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-hs-clk", &i2c->high_speed))
> + i2c->high_speed = 2500000;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-fs-clk", &i2c->fast_speed))
> + i2c->fast_speed = 400000;
> + } else {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5250-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> + ret = -ENOENT;
> + goto err_clk;
> + }
> +
> + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> + pdev->name);
Since this is dt-only driver, use of_iomap here.
> +
> + if (i2c->ioarea == NULL) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + i2c->regs = ioremap(res->start, resource_size(res));
> +
> + if (i2c->regs == NULL) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_ioarea;
> + }
> +
> + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> + i2c->regs, i2c->ioarea, res);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + ret = exynos5_i2c_init(i2c);
> + if (ret != 0)
> + goto err_iomap;
> +
> + i2c->irq = ret = platform_get_irq(pdev, 0);
irq_of_parse_and_map() here.
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + goto err_iomap;
> + }
> +
> + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> + dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_iomap;
> + }
> +
> + i2c->adap.nr = i2c->bus_number;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_irq;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> + clk_disable_unprepare(i2c->clk);
> + return 0;
> +
> + err_irq:
> + free_irq(i2c->irq, i2c);
> +
> + err_iomap:
> + iounmap(i2c->regs);
> +
> + err_ioarea:
> + release_resource(i2c->ioarea);
> + kfree(i2c->ioarea);
> +
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_del_adapter(&i2c->adap);
> + free_irq(i2c->irq, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + iounmap(i2c->regs);
> +
> + release_resource(i2c->ioarea);
> + exynos5_i2c_dt_gpio_free(i2c);
> + kfree(i2c->ioarea);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + i2c->suspended = 1;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .id_table = exynos5_driver_ids,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-i2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = of_match_ptr(exynos5_i2c_match),
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/i2c/busses/i2c-exynos5.h b/drivers/i2c/busses/i2c-exynos5.h
> new file mode 100644
> index 0000000..063051e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.h
> @@ -0,0 +1,80 @@
> +/*
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series HS-I2C Controller
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> +
> +/*
> + * Register Map
> + */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONFING 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +
> +/* I2C_FIFO_CTL Register */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO */
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO */
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +
> +/* I2C_CONF Register */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +#define HSI2C_FS_BPS 400000
> +#define HSI2C_HS_BPS 2500000
> +
> +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
Since these constants are only use in i2c-exynos5.c file, it is better
to move these definitions into i2c-exynos5.c file.
Thanks,
Thomas.
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-11-27 13:34 ` Thomas Abraham
0 siblings, 0 replies; 107+ messages in thread
From: Thomas Abraham @ 2012-11-27 13:34 UTC (permalink / raw)
To: linux-arm-kernel
On 27 November 2012 18:30, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> drivers/i2c/busses/Kconfig | 6 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 758 ++++++++++++++++++++++++++++++++++++++
> drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> 4 files changed, 845 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> create mode 100644 drivers/i2c/busses/i2c-exynos5.h
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 65dd599..88e8833 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -609,6 +609,12 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 HS-I2C Driver"
> + help
> + Say Y here to include support for High Speed I2C controller in the
> + Exynos5 series SoCs from Samsung.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 2d33d62..426b4fd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..5983aa9
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,758 @@
> +/* linux/drivers/i2c/busses/i2c-exynos5.c
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series High Speed I2C controller driver
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_i2c.h>
> +#include <linux/of_gpio.h>
> +
> +#include <asm/irq.h>
> +#include "i2c-exynos5.h"
> +
> +#define HSI2C_POLLING 0
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +/* Max time to wait for bus to become idle after a xfer */
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +struct exynos5_i2c {
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_byte_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + struct resource *ioarea;
> + struct i2c_adapter adap;
> + unsigned int bus_number;
> + unsigned int speed_mode;
> + unsigned int fast_speed;
> + unsigned int high_speed;
> + int operation_mode;
> + int gpios[2];
> +};
> +
> +static struct platform_device_id exynos5_driver_ids[] = {
> + {
> + .name = "exynos5-hs-i2c",
> + .driver_data = 0,
> + }, { },
> +};
> +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +#endif
> +
> +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> +{
> + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + " %x\n %x\n %x\n %x\n %x\n"
> + , i2c->suspended
> + , readl(i2c->regs + HSI2C_CTL)
> + , readl(i2c->regs + HSI2C_FIFO_CTL)
> + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> + , readl(i2c->regs + HSI2C_CLK_CTL)
> + , readl(i2c->regs + HSI2C_CLK_SLOT)
> + , readl(i2c->regs + HSI2C_INT_ENABLE)
> + , readl(i2c->regs + HSI2C_INT_STATUS)
> + , readl(i2c->regs + HSI2C_ERR_STATUS)
> + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> + , readl(i2c->regs + HSI2C_TX_DATA)
> + , readl(i2c->regs + HSI2C_RX_DATA)
> + , readl(i2c->regs + HSI2C_CONF)
> + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> + , readl(i2c->regs + HSI2C_TIMEOUT)
> + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> + , readl(i2c->regs + HSI2C_TIMING_HS1)
> + , readl(i2c->regs + HSI2C_TIMING_HS2)
> + , readl(i2c->regs + HSI2C_TIMING_HS3)
> + , readl(i2c->regs + HSI2C_TIMING_FS1)
> + , readl(i2c->regs + HSI2C_TIMING_FS2)
> + , readl(i2c->regs + HSI2C_TIMING_FS3)
> + , readl(i2c->regs + HSI2C_TIMING_SLA)
> + , readl(i2c->regs + HSI2C_ADDR));
> +}
> +
> +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> +{
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> +
> + complete(&i2c->msg_complete);
> +}
> +
> +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> +{
> + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> +
> + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned char byte;
> +
> + if (i2c->msg->flags & I2C_M_RD) {
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + } else {
> + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c);
> + }
> +
> + exynos5_disable_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> +
> +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl;
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl |= (1u << 31);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + usi_ctl &= ~(1u << 31);
If the bit mask and bit position values are used more than once, it
helps to define them as constants.
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> + exynos5_i2c_init(i2c);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int num, int stop)
> +{
> + unsigned long timeout;
> + unsigned long trans_status;
> + unsigned long usi_fifo_stat;
> + unsigned long usi_ctl;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr;
> + unsigned long usi_int_en;
> + unsigned long usi_fifo_ctl;
> + unsigned char byte;
> + int ret = 0;
> + int operation_mode = i2c->operation_mode;
> +
> + i2c->msg = msgs;
> + i2c->msg_byte_ptr = 0;
> +
> + init_completion(&i2c->msg_complete);
> +
> + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> + HSI2C_RXFIFO_TRIGGER_LEVEL;
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> +
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + usi_int_en = 0;
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + else
> + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> +
> +
> + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> +
> + /* Clear Slave Address for I2C Master (Auto mode only) */
> + i2c_addr &= ~(0x3ff << 10);
> + /* Clear Slave Address for I2C Slave */
> + i2c_addr &= ~(0x3ff << 0);
> + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + i2c_addr &= ~(0xff << 24);
> +
> + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* Clear and set TRANS_LEN */
> + i2c_auto_conf &= ~(0xffff);
> + i2c_auto_conf |= i2c->msg->len;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> +
> + /* Enable appropriate interrupts */
> + if (operation_mode != HSI2C_POLLING)
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +
> + ret = -EAGAIN;
> + if (msgs->flags & I2C_M_RD) {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + 0x1000000) == 0) {
> + byte = (unsigned char)readl
> + (i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_byte_ptr++]
> + = byte;
> + }
> +
> + if (i2c->msg_byte_ptr >= i2c->msg->len) {
> + ret = 0;
> + break;
> + }
> + }
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "rx timeout\n");
> + return ret;
> + }
> +
> + ret = 0;
> + }
> + } else {
> + if (operation_mode == HSI2C_POLLING) {
> + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> + while (time_before(jiffies, timeout) &&
> + (i2c->msg_byte_ptr < i2c->msg->len)) {
> + if ((readl(i2c->regs + HSI2C_FIFO_STATUS)
> + & 0x7f) < 64) {
> + byte = i2c->msg->buf
> + [i2c->msg_byte_ptr++];
> + writel(byte,
> + i2c->regs + HSI2C_TX_DATA);
> + }
> + }
> + } else {
> + timeout = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> +
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> +
> + timeout = jiffies + timeout;
> + }
> + while (time_before(jiffies, timeout)) {
> + usi_fifo_stat = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> + ((trans_status == 0) ||
> + ((stop == 0) &&
> + (trans_status == 0x20000)))) {
> + ret = 0;
> + break;
> + }
> + }
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "tx timeout\n");
> + return ret;
> + }
> + }
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> + int stop = 0;
> + struct i2c_msg *msgs_ptr = msgs;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + if (i == num - 1)
> + stop = 1;
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + stop = 0;
> + break;
> + }
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> + return num;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return -EREMOTEIO;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> + op_clk = i2c->high_speed;
> + else
> + op_clk = i2c->fast_speed;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 256 / 2 */
> + if (utemp1 < 128) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_OF
If this driver supports only device tree (as stated in the commit
message), then this #ifdef can be avoided.
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + goto free_gpio;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = gpio_request(gpio, "hsi2c-bus");
Using devm_gpio_request here can simplify the error handling path.
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + goto free_gpio;
> + }
> + }
> + return 0;
> +
> +free_gpio:
> + while (--idx >= 0)
> + gpio_free(i2c->gpios[idx]);
> + return -EINVAL;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> + unsigned int idx;
> +
> + for (idx = 0; idx < 2; idx++)
> + gpio_free(i2c->gpios[idx]);
> +}
> +#else
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + return 0;
> +}
> +
> +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> +{
> +}
> +#endif
> +
> +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + exynos5_i2c_set_timing(i2c);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + }
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c;
> + struct resource *res;
> + int ret;
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (pdev->dev.of_node) {
> + /* i2c bus number is dynamically assigned */
> + i2c->bus_number = -1;
> +
> + if (of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-speed-mode", &i2c->speed_mode))
> + i2c->speed_mode = 1;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-hs-clk", &i2c->high_speed))
> + i2c->high_speed = 2500000;
> + if (!of_property_read_u32(pdev->dev.of_node,
> + "samsung,hsi2c-fs-clk", &i2c->fast_speed))
> + i2c->fast_speed = 400000;
> + } else {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5250-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> + ret = -ENOENT;
> + goto err_clk;
> + }
> +
> + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> + pdev->name);
Since this is dt-only driver, use of_iomap here.
> +
> + if (i2c->ioarea == NULL) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + i2c->regs = ioremap(res->start, resource_size(res));
> +
> + if (i2c->regs == NULL) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_ioarea;
> + }
> +
> + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> + i2c->regs, i2c->ioarea, res);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + ret = exynos5_i2c_init(i2c);
> + if (ret != 0)
> + goto err_iomap;
> +
> + i2c->irq = ret = platform_get_irq(pdev, 0);
irq_of_parse_and_map() here.
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + goto err_iomap;
> + }
> +
> + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> + dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_iomap;
> + }
> +
> + i2c->adap.nr = i2c->bus_number;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_irq;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> + clk_disable_unprepare(i2c->clk);
> + return 0;
> +
> + err_irq:
> + free_irq(i2c->irq, i2c);
> +
> + err_iomap:
> + iounmap(i2c->regs);
> +
> + err_ioarea:
> + release_resource(i2c->ioarea);
> + kfree(i2c->ioarea);
> +
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_del_adapter(&i2c->adap);
> + free_irq(i2c->irq, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + clk_put(i2c->clk);
> +
> + iounmap(i2c->regs);
> +
> + release_resource(i2c->ioarea);
> + exynos5_i2c_dt_gpio_free(i2c);
> + kfree(i2c->ioarea);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + i2c->suspended = 1;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .id_table = exynos5_driver_ids,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-i2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = of_match_ptr(exynos5_i2c_match),
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/i2c/busses/i2c-exynos5.h b/drivers/i2c/busses/i2c-exynos5.h
> new file mode 100644
> index 0000000..063051e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.h
> @@ -0,0 +1,80 @@
> +/*
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * Exynos5 series HS-I2C Controller
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> +
> +/*
> + * Register Map
> + */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONFING 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +
> +/* I2C_FIFO_CTL Register */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO */
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO */
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +
> +/* I2C_CONF Register */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +#define HSI2C_FS_BPS 400000
> +#define HSI2C_HS_BPS 2500000
> +
> +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
Since these constants are only use in i2c-exynos5.c file, it is better
to move these definitions into i2c-exynos5.c file.
Thanks,
Thomas.
^ permalink raw reply [flat|nested] 107+ messages in thread[parent not found: <CAJuYYwQ5-O1931dBMSVR7Y7vMPeAeQRmJvtO99M5uCMg77Tocg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:34 ` Thomas Abraham
(?)
@ 2012-11-28 4:23 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-11-28 4:23 UTC (permalink / raw)
To: Thomas Abraham
Cc: Naveen Krishna Chatradhi,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ
Hello Felipe, Thomas,
Thanks for your time and valuable comments,
I will post the next version with you comments addressed.
On 27 November 2012 19:04, Thomas Abraham <thomas.abraham-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org> wrote:
>
> On 27 November 2012 18:30, Naveen Krishna Chatradhi
> <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
> > Adds support for High Speed I2C driver found in Exynos5 and later
> > SoCs from Samsung. This driver currently supports Auto mode.
> >
> > Driver only supports Device Tree method.
> >
> > Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> > Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> > ---
> > drivers/i2c/busses/Kconfig | 6 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-exynos5.c | 758
> > ++++++++++++++++++++++++++++++++++++++
> > drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> > 4 files changed, 845 insertions(+)
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.h
> >
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 65dd599..88e8833 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -609,6 +609,12 @@ config I2C_S3C2410
> > Say Y here to include support for I2C controller in the
> > Samsung SoCs.
> >
> > +config I2C_EXYNOS5
> > + tristate "Exynos5 HS-I2C Driver"
> > + help
> > + Say Y here to include support for High Speed I2C controller in
> > the
> > + Exynos5 series SoCs from Samsung.
> > +
> > config I2C_S6000
> > tristate "S6000 I2C support"
> > depends on XTENSA_VARIANT_S6000
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index 2d33d62..426b4fd 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> > obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> > obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> > obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> > +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> > obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> > obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> > obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> > diff --git a/drivers/i2c/busses/i2c-exynos5.c
> > b/drivers/i2c/busses/i2c-exynos5.c
> > new file mode 100644
> > index 0000000..5983aa9
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.c
> > @@ -0,0 +1,758 @@
> > +/* linux/drivers/i2c/busses/i2c-exynos5.c
> > + *
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series High Speed I2C controller driver
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/time.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/err.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/of_i2c.h>
> > +#include <linux/of_gpio.h>
> > +
> > +#include <asm/irq.h>
> > +#include "i2c-exynos5.h"
> > +
> > +#define HSI2C_POLLING 0
> > +#define HSI2C_FAST_SPD 0
> > +#define HSI2C_HIGH_SPD 1
> > +
> > +/* Max time to wait for bus to become idle after a xfer */
> > +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> > +
> > +struct exynos5_i2c {
> > + unsigned int suspended:1;
> > +
> > + struct i2c_msg *msg;
> > + struct completion msg_complete;
> > + unsigned int msg_byte_ptr;
> > +
> > + unsigned int irq;
> > +
> > + void __iomem *regs;
> > + struct clk *clk;
> > + struct device *dev;
> > + struct resource *ioarea;
> > + struct i2c_adapter adap;
> > + unsigned int bus_number;
> > + unsigned int speed_mode;
> > + unsigned int fast_speed;
> > + unsigned int high_speed;
> > + int operation_mode;
> > + int gpios[2];
> > +};
> > +
> > +static struct platform_device_id exynos5_driver_ids[] = {
> > + {
> > + .name = "exynos5-hs-i2c",
> > + .driver_data = 0,
> > + }, { },
> > +};
> > +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> > +
> > +#ifdef CONFIG_OF
> > +static const struct of_device_id exynos5_i2c_match[] = {
> > + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> > +#endif
> > +
> > +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> > +{
> > + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + , i2c->suspended
> > + , readl(i2c->regs + HSI2C_CTL)
> > + , readl(i2c->regs + HSI2C_FIFO_CTL)
> > + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_SLOT)
> > + , readl(i2c->regs + HSI2C_INT_ENABLE)
> > + , readl(i2c->regs + HSI2C_INT_STATUS)
> > + , readl(i2c->regs + HSI2C_ERR_STATUS)
> > + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> > + , readl(i2c->regs + HSI2C_TX_DATA)
> > + , readl(i2c->regs + HSI2C_RX_DATA)
> > + , readl(i2c->regs + HSI2C_CONF)
> > + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> > + , readl(i2c->regs + HSI2C_TIMEOUT)
> > + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> > + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> > + , readl(i2c->regs + HSI2C_TIMING_HS1)
> > + , readl(i2c->regs + HSI2C_TIMING_HS2)
> > + , readl(i2c->regs + HSI2C_TIMING_HS3)
> > + , readl(i2c->regs + HSI2C_TIMING_FS1)
> > + , readl(i2c->regs + HSI2C_TIMING_FS2)
> > + , readl(i2c->regs + HSI2C_TIMING_FS3)
> > + , readl(i2c->regs + HSI2C_TIMING_SLA)
> > + , readl(i2c->regs + HSI2C_ADDR));
> > +}
> > +
> > +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> > +{
> > + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + complete(&i2c->msg_complete);
> > +}
> > +
> > +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> > +
> > + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> > +}
> > +
> > +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> > +
> > + /* Clear to enable Timeout */
> > + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> > + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> > +}
> > +
> > +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> > +{
> > + struct exynos5_i2c *i2c = dev_id;
> > + unsigned char byte;
> > +
> > + if (i2c->msg->flags & I2C_M_RD) {
> > + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl(i2c->regs +
> > HSI2C_RX_DATA);
> > + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + } else {
> > + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> > + writel(byte, i2c->regs + HSI2C_TX_DATA);
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + }
> > +
> > + exynos5_disable_irq(i2c);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> > +
> > +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl;
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl |= (1u << 31);
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl &= ~(1u << 31);
>
> If the bit mask and bit position values are used more than once, it
> helps to define them as constants.
>
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + exynos5_i2c_init(i2c);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> > + struct i2c_msg *msgs, int num, int stop)
> > +{
> > + unsigned long timeout;
> > + unsigned long trans_status;
> > + unsigned long usi_fifo_stat;
> > + unsigned long usi_ctl;
> > + unsigned long i2c_auto_conf;
> > + unsigned long i2c_addr;
> > + unsigned long usi_int_en;
> > + unsigned long usi_fifo_ctl;
> > + unsigned char byte;
> > + int ret = 0;
> > + int operation_mode = i2c->operation_mode;
> > +
> > + i2c->msg = msgs;
> > + i2c->msg_byte_ptr = 0;
> > +
> > + init_completion(&i2c->msg_complete);
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + exynos5_i2c_en_timeout(i2c);
> > +
> > + /* Set default trigger level for TXFIFO and RXFIFO */
> > + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> > + HSI2C_RXFIFO_TRIGGER_LEVEL;
> > + /* Enable RXFIFO and TXFIFO */
> > + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> > +
> > + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> > +
> > + usi_int_en = 0;
> > + if (msgs->flags & I2C_M_RD) {
> > + usi_ctl &= ~HSI2C_TXCHON;
> > + usi_ctl |= HSI2C_RXCHON;
> > +
> > + i2c_auto_conf |= HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> > + HSI2C_INT_TRAILING_EN);
> > + } else {
> > + usi_ctl &= ~HSI2C_RXCHON;
> > + usi_ctl |= HSI2C_TXCHON;
> > +
> > + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> > + }
> > +
> > + if (stop == 1)
> > + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> > + else
> > + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> > +
> > +
> > + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> > +
> > + /* Clear Slave Address for I2C Master (Auto mode only) */
> > + i2c_addr &= ~(0x3ff << 10);
> > + /* Clear Slave Address for I2C Slave */
> > + i2c_addr &= ~(0x3ff << 0);
> > + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + i2c_addr &= ~(0xff << 24);
> > +
> > + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> > + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + /* Clear and set TRANS_LEN */
> > + i2c_auto_conf &= ~(0xffff);
> > + i2c_auto_conf |= i2c->msg->len;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Start data transfer in Master mode */
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > + i2c_auto_conf |= HSI2C_MASTER_RUN;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Enable appropriate interrupts */
> > + if (operation_mode != HSI2C_POLLING)
> > + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + ret = -EAGAIN;
> > + if (msgs->flags & I2C_M_RD) {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl
> > + (i2c->regs +
> > HSI2C_RX_DATA);
> > +
> > i2c->msg->buf[i2c->msg_byte_ptr++]
> > + = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > +
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > +
> > + ret = 0;
> > + }
> > + } else {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout) &&
> > + (i2c->msg_byte_ptr < i2c->msg->len)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS)
> > + & 0x7f) < 64) {
> > + byte = i2c->msg->buf
> > + [i2c->msg_byte_ptr++];
> > + writel(byte,
> > + i2c->regs +
> > HSI2C_TX_DATA);
> > + }
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > +
> > + timeout = jiffies + timeout;
> > + }
> > + while (time_before(jiffies, timeout)) {
> > + usi_fifo_stat = readl(i2c->regs +
> > HSI2C_FIFO_STATUS);
> > + trans_status = readl(i2c->regs +
> > HSI2C_TRANS_STATUS);
> > + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> > + ((trans_status == 0) ||
> > + ((stop == 0) &&
> > + (trans_status == 0x20000)))) {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> > + struct i2c_msg *msgs, int num)
> > +{
> > + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> > + int retry, i;
> > + int ret;
> > + int stop = 0;
> > + struct i2c_msg *msgs_ptr = msgs;
> > +
> > + if (i2c->suspended) {
> > + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> > + return -EIO;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + for (retry = 0; retry < adap->retries; retry++) {
> > + for (i = 0; i < num; i++) {
> > + if (i == num - 1)
> > + stop = 1;
> > + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1,
> > stop);
> > + msgs_ptr++;
> > +
> > + if (ret == -EAGAIN) {
> > + msgs_ptr = msgs;
> > + stop = 0;
> > + break;
> > + }
> > + }
> > + if (i == num) {
> > + clk_disable_unprepare(i2c->clk);
> > + return num;
> > + }
> > +
> > + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> > +
> > + udelay(100);
> > + }
> > +
> > + clk_disable_unprepare(i2c->clk);
> > +
> > + return -EREMOTEIO;
> > +}
> > +
> > +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> > +{
> > + return I2C_FUNC_I2C;
> > +}
> > +
> > +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> > + .master_xfer = exynos5_i2c_xfer,
> > + .functionality = exynos5_i2c_func,
> > +};
> > +
> > +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timing_s1;
> > + unsigned long i2c_timing_s2;
> > + unsigned long i2c_timing_s3;
> > + unsigned long i2c_timing_sla;
> > + unsigned int op_clk;
> > + unsigned int clkin = clk_get_rate(i2c->clk);
> > + unsigned int n_clkdiv;
> > + unsigned int t_start_su, t_start_hd;
> > + unsigned int t_stop_su;
> > + unsigned int t_data_su, t_data_hd;
> > + unsigned int t_scl_l, t_scl_h;
> > + unsigned int t_sr_release;
> > + unsigned int t_ftl_cycle;
> > + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + op_clk = i2c->high_speed;
> > + else
> > + op_clk = i2c->fast_speed;
> > +
> > + /* FPCLK / FI2C =
> > + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> > + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> > + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> > + * uTemp2 = TSCLK_L + TSCLK_H
> > + */
> > + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> > + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> > +
> > + /* CLK_DIV max is 256 */
> > + for (i = 0; i < 256; i++) {
> > + utemp1 = utemp0 / (i + 1);
> > + /* SCLK_L/H max is 256 / 2 */
> > + if (utemp1 < 128) {
> > + utemp2 = utemp1 - 2;
> > + break;
> > + }
> > + }
> > +
> > + n_clkdiv = i;
> > + t_scl_l = utemp2 / 2;
> > + t_scl_h = utemp2 / 2;
> > + t_start_su = t_scl_l;
> > + t_start_hd = t_scl_l;
> > + t_stop_su = t_scl_l;
> > + t_data_su = t_scl_l / 2;
> > + t_data_hd = t_scl_l / 2;
> > + t_sr_release = utemp2;
> > +
> > + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su
> > << 8;
> > + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> > + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> > + i2c_timing_sla = t_data_hd << 0;
> > +
> > + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU:
> > %X\n",
> > + t_start_su, t_start_hd, t_stop_su);
> > + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> > + t_data_su, t_scl_l, t_scl_h);
> > + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> > + n_clkdiv, t_sr_release);
> > + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> > + } else {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> > + }
> > + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_OF
>
> If this driver supports only device tree (as stated in the commit
> message), then this #ifdef can be avoided.
>
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + int idx, gpio, ret;
> > +
> > + for (idx = 0; idx < 2; idx++) {
> > + gpio = of_get_gpio(i2c->dev->of_node, idx);
> > + if (!gpio_is_valid(gpio)) {
> > + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx,
> > gpio);
> > + goto free_gpio;
> > + }
> > + i2c->gpios[idx] = gpio;
> > +
> > + ret = gpio_request(gpio, "hsi2c-bus");
>
> Using devm_gpio_request here can simplify the error handling path.
>
> > + if (ret) {
> > + dev_err(i2c->dev, "gpio [%d] request failed\n",
> > gpio);
> > + goto free_gpio;
> > + }
> > + }
> > + return 0;
> > +
> > +free_gpio:
> > + while (--idx >= 0)
> > + gpio_free(i2c->gpios[idx]);
> > + return -EINVAL;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > + unsigned int idx;
> > +
> > + for (idx = 0; idx < 2; idx++)
> > + gpio_free(i2c->gpios[idx]);
> > +}
> > +#else
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + return 0;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > +}
> > +#endif
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> > + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> > + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> > +
> > + if (exynos5_i2c_parse_dt_gpio(i2c))
> > + return -EINVAL;
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> > +
> > + exynos5_i2c_set_timing(i2c);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + i2c_conf |= HSI2C_HS_MODE;
> > + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_probe(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c;
> > + struct resource *res;
> > + int ret;
> > +
> > + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c),
> > GFP_KERNEL);
> > + if (!i2c) {
> > + dev_err(&pdev->dev, "no memory for state\n");
> > + return -ENOMEM;
> > + }
> > +
> > + if (pdev->dev.of_node) {
> > + /* i2c bus number is dynamically assigned */
> > + i2c->bus_number = -1;
> > +
> > + if (of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-speed-mode",
> > &i2c->speed_mode))
> > + i2c->speed_mode = 1;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-hs-clk",
> > &i2c->high_speed))
> > + i2c->high_speed = 2500000;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-fs-clk",
> > &i2c->fast_speed))
> > + i2c->fast_speed = 400000;
> > + } else {
> > + dev_err(&pdev->dev, "no device node\n");
> > + return -ENOENT;
> > + }
> > +
> > + strlcpy(i2c->adap.name, "exynos5250-i2c",
> > sizeof(i2c->adap.name));
> > + i2c->adap.owner = THIS_MODULE;
> > + i2c->adap.algo = &exynos5_i2c_algorithm;
> > + i2c->adap.retries = 2;
> > + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> > +
> > + i2c->dev = &pdev->dev;
> > + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> > + if (IS_ERR(i2c->clk)) {
> > + dev_err(&pdev->dev, "cannot get clock\n");
> > + ret = -ENOENT;
> > + goto err_noclk;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> > + ret = -ENOENT;
> > + goto err_clk;
> > + }
> > +
> > + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> > + pdev->name);
>
> Since this is dt-only driver, use of_iomap here.
>
> > +
> > + if (i2c->ioarea == NULL) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_clk;
> > + }
> > +
> > + i2c->regs = ioremap(res->start, resource_size(res));
> > +
> > + if (i2c->regs == NULL) {
> > + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_ioarea;
> > + }
> > +
> > + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> > + i2c->regs, i2c->ioarea, res);
> > +
> > + i2c->adap.algo_data = i2c;
> > + i2c->adap.dev.parent = &pdev->dev;
> > +
> > + ret = exynos5_i2c_init(i2c);
> > + if (ret != 0)
> > + goto err_iomap;
> > +
> > + i2c->irq = ret = platform_get_irq(pdev, 0);
>
> irq_of_parse_and_map() here.
>
> > + if (ret <= 0) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> > + goto err_iomap;
> > + }
> > +
> > + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> > + dev_name(&pdev->dev), i2c);
> > +
> > + if (ret != 0) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> > i2c->irq);
> > + goto err_iomap;
> > + }
> > +
> > + i2c->adap.nr = i2c->bus_number;
> > + i2c->adap.dev.of_node = pdev->dev.of_node;
> > +
> > + ret = i2c_add_numbered_adapter(&i2c->adap);
> > + if (ret < 0) {
> > + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> > + goto err_irq;
> > + }
> > +
> > + of_i2c_register_devices(&i2c->adap);
> > + platform_set_drvdata(pdev, i2c);
> > +
> > + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> > + dev_name(&i2c->adap.dev));
> > + clk_disable_unprepare(i2c->clk);
> > + return 0;
> > +
> > + err_irq:
> > + free_irq(i2c->irq, i2c);
> > +
> > + err_iomap:
> > + iounmap(i2c->regs);
> > +
> > + err_ioarea:
> > + release_resource(i2c->ioarea);
> > + kfree(i2c->ioarea);
> > +
> > + err_clk:
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + err_noclk:
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_remove(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_del_adapter(&i2c->adap);
> > + free_irq(i2c->irq, i2c);
> > +
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + iounmap(i2c->regs);
> > +
> > + release_resource(i2c->ioarea);
> > + exynos5_i2c_dt_gpio_free(i2c);
> > + kfree(i2c->ioarea);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int exynos5_i2c_suspend_noirq(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + i2c->suspended = 1;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_resume(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + clk_prepare_enable(i2c->clk);
> > + exynos5_i2c_init(i2c);
> > + clk_disable_unprepare(i2c->clk);
> > + i2c->suspended = 0;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> > + .suspend_noirq = exynos5_i2c_suspend_noirq,
> > + .resume_noirq = exynos5_i2c_resume,
> > +};
> > +
> > +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> > +#else
> > +#define EXYNOS5_DEV_PM_OPS NULL
> > +#endif
> > +
> > +static struct platform_driver exynos5_i2c_driver = {
> > + .probe = exynos5_i2c_probe,
> > + .remove = exynos5_i2c_remove,
> > + .id_table = exynos5_driver_ids,
> > + .driver = {
> > + .owner = THIS_MODULE,
> > + .name = "exynos5-i2c",
> > + .pm = EXYNOS5_DEV_PM_OPS,
> > + .of_match_table = of_match_ptr(exynos5_i2c_match),
> > + },
> > +};
> > +
> > +static int __init i2c_adap_exynos5_init(void)
> > +{
> > + return platform_driver_register(&exynos5_i2c_driver);
> > +}
> > +subsys_initcall(i2c_adap_exynos5_init);
> > +
> > +static void __exit i2c_adap_exynos5_exit(void)
> > +{
> > + platform_driver_unregister(&exynos5_i2c_driver);
> > +}
> > +module_exit(i2c_adap_exynos5_exit);
> > +
> > +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> > +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/i2c/busses/i2c-exynos5.h
> > b/drivers/i2c/busses/i2c-exynos5.h
> > new file mode 100644
> > index 0000000..063051e
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.h
> > @@ -0,0 +1,80 @@
> > +/*
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series HS-I2C Controller
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> > +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> > +
> > +/*
> > + * Register Map
> > + */
> > +#define HSI2C_CTL 0x00
> > +#define HSI2C_FIFO_CTL 0x04
> > +#define HSI2C_TRAILIG_CTL 0x08
> > +#define HSI2C_CLK_CTL 0x0C
> > +#define HSI2C_CLK_SLOT 0x10
> > +#define HSI2C_INT_ENABLE 0x20
> > +#define HSI2C_INT_STATUS 0x24
> > +#define HSI2C_ERR_STATUS 0x2C
> > +#define HSI2C_FIFO_STATUS 0x30
> > +#define HSI2C_TX_DATA 0x34
> > +#define HSI2C_RX_DATA 0x38
> > +#define HSI2C_CONF 0x40
> > +#define HSI2C_AUTO_CONFING 0x44
> > +#define HSI2C_TIMEOUT 0x48
> > +#define HSI2C_MANUAL_CMD 0x4C
> > +#define HSI2C_TRANS_STATUS 0x50
> > +#define HSI2C_TIMING_HS1 0x54
> > +#define HSI2C_TIMING_HS2 0x58
> > +#define HSI2C_TIMING_HS3 0x5C
> > +#define HSI2C_TIMING_FS1 0x60
> > +#define HSI2C_TIMING_FS2 0x64
> > +#define HSI2C_TIMING_FS3 0x68
> > +#define HSI2C_TIMING_SLA 0x6C
> > +#define HSI2C_ADDR 0x70
> > +
> > +/* I2C_CTL Register */
> > +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> > +#define HSI2C_MASTER (1u << 3)
> > +#define HSI2C_RXCHON (1u << 6)
> > +#define HSI2C_TXCHON (1u << 7)
> > +
> > +/* I2C_FIFO_CTL Register */
> > +#define HSI2C_RXFIFO_EN (1u << 0)
> > +#define HSI2C_TXFIFO_EN (1u << 1)
> > +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> > +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> > +
> > +/* I2C_TRAILING_CTL Register */
> > +#define HSI2C_TRAILING_COUNT (0xf)
> > +
> > +/* I2C_INT_EN Register */
> > +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO
> > */
> > +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO
> > */
> > +#define HSI2C_INT_TRAILING_EN (1u << 6)
> > +
> > +/* I2C_CONF Register */
> > +#define HSI2C_AUTO_MODE (1u << 31)
> > +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> > +#define HSI2C_HS_MODE (1u << 29)
> > +
> > +/* I2C_AUTO_CONF Register */
> > +#define HSI2C_READ_WRITE (1u << 16)
> > +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> > +#define HSI2C_MASTER_RUN (1u << 31)
> > +
> > +/* I2C_TIMEOUT Register */
> > +#define HSI2C_TIMEOUT_EN (1u << 31)
> > +
> > +#define HSI2C_FIFO_EMPTY (0x1000100)
> > +
> > +#define HSI2C_FS_BPS 400000
> > +#define HSI2C_HS_BPS 2500000
> > +
> > +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
>
> Since these constants are only use in i2c-exynos5.c file, it is better
> to move these definitions into i2c-exynos5.c file.
>
> Thanks,
> Thomas.
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-11-28 4:23 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-11-28 4:23 UTC (permalink / raw)
To: Thomas Abraham
Cc: Naveen Krishna Chatradhi, linux-arm-kernel, linux-samsung-soc,
devicetree-discuss, linux-i2c, kgene.kim, grant.likely, w.sang,
linux-kernel, taeggyun.ko
Hello Felipe, Thomas,
Thanks for your time and valuable comments,
I will post the next version with you comments addressed.
On 27 November 2012 19:04, Thomas Abraham <thomas.abraham@linaro.org> wrote:
>
> On 27 November 2012 18:30, Naveen Krishna Chatradhi
> <ch.naveen@samsung.com> wrote:
> > Adds support for High Speed I2C driver found in Exynos5 and later
> > SoCs from Samsung. This driver currently supports Auto mode.
> >
> > Driver only supports Device Tree method.
> >
> > Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> > Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> > ---
> > drivers/i2c/busses/Kconfig | 6 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-exynos5.c | 758
> > ++++++++++++++++++++++++++++++++++++++
> > drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> > 4 files changed, 845 insertions(+)
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.h
> >
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 65dd599..88e8833 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -609,6 +609,12 @@ config I2C_S3C2410
> > Say Y here to include support for I2C controller in the
> > Samsung SoCs.
> >
> > +config I2C_EXYNOS5
> > + tristate "Exynos5 HS-I2C Driver"
> > + help
> > + Say Y here to include support for High Speed I2C controller in
> > the
> > + Exynos5 series SoCs from Samsung.
> > +
> > config I2C_S6000
> > tristate "S6000 I2C support"
> > depends on XTENSA_VARIANT_S6000
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index 2d33d62..426b4fd 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> > obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> > obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> > obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> > +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> > obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> > obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> > obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> > diff --git a/drivers/i2c/busses/i2c-exynos5.c
> > b/drivers/i2c/busses/i2c-exynos5.c
> > new file mode 100644
> > index 0000000..5983aa9
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.c
> > @@ -0,0 +1,758 @@
> > +/* linux/drivers/i2c/busses/i2c-exynos5.c
> > + *
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series High Speed I2C controller driver
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/time.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/err.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/of_i2c.h>
> > +#include <linux/of_gpio.h>
> > +
> > +#include <asm/irq.h>
> > +#include "i2c-exynos5.h"
> > +
> > +#define HSI2C_POLLING 0
> > +#define HSI2C_FAST_SPD 0
> > +#define HSI2C_HIGH_SPD 1
> > +
> > +/* Max time to wait for bus to become idle after a xfer */
> > +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> > +
> > +struct exynos5_i2c {
> > + unsigned int suspended:1;
> > +
> > + struct i2c_msg *msg;
> > + struct completion msg_complete;
> > + unsigned int msg_byte_ptr;
> > +
> > + unsigned int irq;
> > +
> > + void __iomem *regs;
> > + struct clk *clk;
> > + struct device *dev;
> > + struct resource *ioarea;
> > + struct i2c_adapter adap;
> > + unsigned int bus_number;
> > + unsigned int speed_mode;
> > + unsigned int fast_speed;
> > + unsigned int high_speed;
> > + int operation_mode;
> > + int gpios[2];
> > +};
> > +
> > +static struct platform_device_id exynos5_driver_ids[] = {
> > + {
> > + .name = "exynos5-hs-i2c",
> > + .driver_data = 0,
> > + }, { },
> > +};
> > +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> > +
> > +#ifdef CONFIG_OF
> > +static const struct of_device_id exynos5_i2c_match[] = {
> > + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> > +#endif
> > +
> > +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> > +{
> > + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + , i2c->suspended
> > + , readl(i2c->regs + HSI2C_CTL)
> > + , readl(i2c->regs + HSI2C_FIFO_CTL)
> > + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_SLOT)
> > + , readl(i2c->regs + HSI2C_INT_ENABLE)
> > + , readl(i2c->regs + HSI2C_INT_STATUS)
> > + , readl(i2c->regs + HSI2C_ERR_STATUS)
> > + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> > + , readl(i2c->regs + HSI2C_TX_DATA)
> > + , readl(i2c->regs + HSI2C_RX_DATA)
> > + , readl(i2c->regs + HSI2C_CONF)
> > + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> > + , readl(i2c->regs + HSI2C_TIMEOUT)
> > + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> > + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> > + , readl(i2c->regs + HSI2C_TIMING_HS1)
> > + , readl(i2c->regs + HSI2C_TIMING_HS2)
> > + , readl(i2c->regs + HSI2C_TIMING_HS3)
> > + , readl(i2c->regs + HSI2C_TIMING_FS1)
> > + , readl(i2c->regs + HSI2C_TIMING_FS2)
> > + , readl(i2c->regs + HSI2C_TIMING_FS3)
> > + , readl(i2c->regs + HSI2C_TIMING_SLA)
> > + , readl(i2c->regs + HSI2C_ADDR));
> > +}
> > +
> > +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> > +{
> > + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + complete(&i2c->msg_complete);
> > +}
> > +
> > +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> > +
> > + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> > +}
> > +
> > +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> > +
> > + /* Clear to enable Timeout */
> > + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> > + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> > +}
> > +
> > +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> > +{
> > + struct exynos5_i2c *i2c = dev_id;
> > + unsigned char byte;
> > +
> > + if (i2c->msg->flags & I2C_M_RD) {
> > + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl(i2c->regs +
> > HSI2C_RX_DATA);
> > + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + } else {
> > + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> > + writel(byte, i2c->regs + HSI2C_TX_DATA);
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + }
> > +
> > + exynos5_disable_irq(i2c);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> > +
> > +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl;
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl |= (1u << 31);
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl &= ~(1u << 31);
>
> If the bit mask and bit position values are used more than once, it
> helps to define them as constants.
>
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + exynos5_i2c_init(i2c);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> > + struct i2c_msg *msgs, int num, int stop)
> > +{
> > + unsigned long timeout;
> > + unsigned long trans_status;
> > + unsigned long usi_fifo_stat;
> > + unsigned long usi_ctl;
> > + unsigned long i2c_auto_conf;
> > + unsigned long i2c_addr;
> > + unsigned long usi_int_en;
> > + unsigned long usi_fifo_ctl;
> > + unsigned char byte;
> > + int ret = 0;
> > + int operation_mode = i2c->operation_mode;
> > +
> > + i2c->msg = msgs;
> > + i2c->msg_byte_ptr = 0;
> > +
> > + init_completion(&i2c->msg_complete);
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + exynos5_i2c_en_timeout(i2c);
> > +
> > + /* Set default trigger level for TXFIFO and RXFIFO */
> > + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> > + HSI2C_RXFIFO_TRIGGER_LEVEL;
> > + /* Enable RXFIFO and TXFIFO */
> > + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> > +
> > + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> > +
> > + usi_int_en = 0;
> > + if (msgs->flags & I2C_M_RD) {
> > + usi_ctl &= ~HSI2C_TXCHON;
> > + usi_ctl |= HSI2C_RXCHON;
> > +
> > + i2c_auto_conf |= HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> > + HSI2C_INT_TRAILING_EN);
> > + } else {
> > + usi_ctl &= ~HSI2C_RXCHON;
> > + usi_ctl |= HSI2C_TXCHON;
> > +
> > + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> > + }
> > +
> > + if (stop == 1)
> > + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> > + else
> > + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> > +
> > +
> > + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> > +
> > + /* Clear Slave Address for I2C Master (Auto mode only) */
> > + i2c_addr &= ~(0x3ff << 10);
> > + /* Clear Slave Address for I2C Slave */
> > + i2c_addr &= ~(0x3ff << 0);
> > + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + i2c_addr &= ~(0xff << 24);
> > +
> > + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> > + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + /* Clear and set TRANS_LEN */
> > + i2c_auto_conf &= ~(0xffff);
> > + i2c_auto_conf |= i2c->msg->len;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Start data transfer in Master mode */
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > + i2c_auto_conf |= HSI2C_MASTER_RUN;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Enable appropriate interrupts */
> > + if (operation_mode != HSI2C_POLLING)
> > + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + ret = -EAGAIN;
> > + if (msgs->flags & I2C_M_RD) {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl
> > + (i2c->regs +
> > HSI2C_RX_DATA);
> > +
> > i2c->msg->buf[i2c->msg_byte_ptr++]
> > + = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > +
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > +
> > + ret = 0;
> > + }
> > + } else {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout) &&
> > + (i2c->msg_byte_ptr < i2c->msg->len)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS)
> > + & 0x7f) < 64) {
> > + byte = i2c->msg->buf
> > + [i2c->msg_byte_ptr++];
> > + writel(byte,
> > + i2c->regs +
> > HSI2C_TX_DATA);
> > + }
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > +
> > + timeout = jiffies + timeout;
> > + }
> > + while (time_before(jiffies, timeout)) {
> > + usi_fifo_stat = readl(i2c->regs +
> > HSI2C_FIFO_STATUS);
> > + trans_status = readl(i2c->regs +
> > HSI2C_TRANS_STATUS);
> > + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> > + ((trans_status == 0) ||
> > + ((stop == 0) &&
> > + (trans_status == 0x20000)))) {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> > + struct i2c_msg *msgs, int num)
> > +{
> > + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> > + int retry, i;
> > + int ret;
> > + int stop = 0;
> > + struct i2c_msg *msgs_ptr = msgs;
> > +
> > + if (i2c->suspended) {
> > + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> > + return -EIO;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + for (retry = 0; retry < adap->retries; retry++) {
> > + for (i = 0; i < num; i++) {
> > + if (i == num - 1)
> > + stop = 1;
> > + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1,
> > stop);
> > + msgs_ptr++;
> > +
> > + if (ret == -EAGAIN) {
> > + msgs_ptr = msgs;
> > + stop = 0;
> > + break;
> > + }
> > + }
> > + if (i == num) {
> > + clk_disable_unprepare(i2c->clk);
> > + return num;
> > + }
> > +
> > + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> > +
> > + udelay(100);
> > + }
> > +
> > + clk_disable_unprepare(i2c->clk);
> > +
> > + return -EREMOTEIO;
> > +}
> > +
> > +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> > +{
> > + return I2C_FUNC_I2C;
> > +}
> > +
> > +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> > + .master_xfer = exynos5_i2c_xfer,
> > + .functionality = exynos5_i2c_func,
> > +};
> > +
> > +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timing_s1;
> > + unsigned long i2c_timing_s2;
> > + unsigned long i2c_timing_s3;
> > + unsigned long i2c_timing_sla;
> > + unsigned int op_clk;
> > + unsigned int clkin = clk_get_rate(i2c->clk);
> > + unsigned int n_clkdiv;
> > + unsigned int t_start_su, t_start_hd;
> > + unsigned int t_stop_su;
> > + unsigned int t_data_su, t_data_hd;
> > + unsigned int t_scl_l, t_scl_h;
> > + unsigned int t_sr_release;
> > + unsigned int t_ftl_cycle;
> > + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + op_clk = i2c->high_speed;
> > + else
> > + op_clk = i2c->fast_speed;
> > +
> > + /* FPCLK / FI2C =
> > + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> > + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> > + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> > + * uTemp2 = TSCLK_L + TSCLK_H
> > + */
> > + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> > + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> > +
> > + /* CLK_DIV max is 256 */
> > + for (i = 0; i < 256; i++) {
> > + utemp1 = utemp0 / (i + 1);
> > + /* SCLK_L/H max is 256 / 2 */
> > + if (utemp1 < 128) {
> > + utemp2 = utemp1 - 2;
> > + break;
> > + }
> > + }
> > +
> > + n_clkdiv = i;
> > + t_scl_l = utemp2 / 2;
> > + t_scl_h = utemp2 / 2;
> > + t_start_su = t_scl_l;
> > + t_start_hd = t_scl_l;
> > + t_stop_su = t_scl_l;
> > + t_data_su = t_scl_l / 2;
> > + t_data_hd = t_scl_l / 2;
> > + t_sr_release = utemp2;
> > +
> > + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su
> > << 8;
> > + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> > + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> > + i2c_timing_sla = t_data_hd << 0;
> > +
> > + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU:
> > %X\n",
> > + t_start_su, t_start_hd, t_stop_su);
> > + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> > + t_data_su, t_scl_l, t_scl_h);
> > + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> > + n_clkdiv, t_sr_release);
> > + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> > + } else {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> > + }
> > + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_OF
>
> If this driver supports only device tree (as stated in the commit
> message), then this #ifdef can be avoided.
>
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + int idx, gpio, ret;
> > +
> > + for (idx = 0; idx < 2; idx++) {
> > + gpio = of_get_gpio(i2c->dev->of_node, idx);
> > + if (!gpio_is_valid(gpio)) {
> > + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx,
> > gpio);
> > + goto free_gpio;
> > + }
> > + i2c->gpios[idx] = gpio;
> > +
> > + ret = gpio_request(gpio, "hsi2c-bus");
>
> Using devm_gpio_request here can simplify the error handling path.
>
> > + if (ret) {
> > + dev_err(i2c->dev, "gpio [%d] request failed\n",
> > gpio);
> > + goto free_gpio;
> > + }
> > + }
> > + return 0;
> > +
> > +free_gpio:
> > + while (--idx >= 0)
> > + gpio_free(i2c->gpios[idx]);
> > + return -EINVAL;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > + unsigned int idx;
> > +
> > + for (idx = 0; idx < 2; idx++)
> > + gpio_free(i2c->gpios[idx]);
> > +}
> > +#else
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + return 0;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > +}
> > +#endif
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> > + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> > + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> > +
> > + if (exynos5_i2c_parse_dt_gpio(i2c))
> > + return -EINVAL;
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> > +
> > + exynos5_i2c_set_timing(i2c);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + i2c_conf |= HSI2C_HS_MODE;
> > + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_probe(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c;
> > + struct resource *res;
> > + int ret;
> > +
> > + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c),
> > GFP_KERNEL);
> > + if (!i2c) {
> > + dev_err(&pdev->dev, "no memory for state\n");
> > + return -ENOMEM;
> > + }
> > +
> > + if (pdev->dev.of_node) {
> > + /* i2c bus number is dynamically assigned */
> > + i2c->bus_number = -1;
> > +
> > + if (of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-speed-mode",
> > &i2c->speed_mode))
> > + i2c->speed_mode = 1;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-hs-clk",
> > &i2c->high_speed))
> > + i2c->high_speed = 2500000;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-fs-clk",
> > &i2c->fast_speed))
> > + i2c->fast_speed = 400000;
> > + } else {
> > + dev_err(&pdev->dev, "no device node\n");
> > + return -ENOENT;
> > + }
> > +
> > + strlcpy(i2c->adap.name, "exynos5250-i2c",
> > sizeof(i2c->adap.name));
> > + i2c->adap.owner = THIS_MODULE;
> > + i2c->adap.algo = &exynos5_i2c_algorithm;
> > + i2c->adap.retries = 2;
> > + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> > +
> > + i2c->dev = &pdev->dev;
> > + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> > + if (IS_ERR(i2c->clk)) {
> > + dev_err(&pdev->dev, "cannot get clock\n");
> > + ret = -ENOENT;
> > + goto err_noclk;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> > + ret = -ENOENT;
> > + goto err_clk;
> > + }
> > +
> > + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> > + pdev->name);
>
> Since this is dt-only driver, use of_iomap here.
>
> > +
> > + if (i2c->ioarea == NULL) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_clk;
> > + }
> > +
> > + i2c->regs = ioremap(res->start, resource_size(res));
> > +
> > + if (i2c->regs == NULL) {
> > + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_ioarea;
> > + }
> > +
> > + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> > + i2c->regs, i2c->ioarea, res);
> > +
> > + i2c->adap.algo_data = i2c;
> > + i2c->adap.dev.parent = &pdev->dev;
> > +
> > + ret = exynos5_i2c_init(i2c);
> > + if (ret != 0)
> > + goto err_iomap;
> > +
> > + i2c->irq = ret = platform_get_irq(pdev, 0);
>
> irq_of_parse_and_map() here.
>
> > + if (ret <= 0) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> > + goto err_iomap;
> > + }
> > +
> > + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> > + dev_name(&pdev->dev), i2c);
> > +
> > + if (ret != 0) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> > i2c->irq);
> > + goto err_iomap;
> > + }
> > +
> > + i2c->adap.nr = i2c->bus_number;
> > + i2c->adap.dev.of_node = pdev->dev.of_node;
> > +
> > + ret = i2c_add_numbered_adapter(&i2c->adap);
> > + if (ret < 0) {
> > + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> > + goto err_irq;
> > + }
> > +
> > + of_i2c_register_devices(&i2c->adap);
> > + platform_set_drvdata(pdev, i2c);
> > +
> > + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> > + dev_name(&i2c->adap.dev));
> > + clk_disable_unprepare(i2c->clk);
> > + return 0;
> > +
> > + err_irq:
> > + free_irq(i2c->irq, i2c);
> > +
> > + err_iomap:
> > + iounmap(i2c->regs);
> > +
> > + err_ioarea:
> > + release_resource(i2c->ioarea);
> > + kfree(i2c->ioarea);
> > +
> > + err_clk:
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + err_noclk:
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_remove(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_del_adapter(&i2c->adap);
> > + free_irq(i2c->irq, i2c);
> > +
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + iounmap(i2c->regs);
> > +
> > + release_resource(i2c->ioarea);
> > + exynos5_i2c_dt_gpio_free(i2c);
> > + kfree(i2c->ioarea);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int exynos5_i2c_suspend_noirq(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + i2c->suspended = 1;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_resume(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + clk_prepare_enable(i2c->clk);
> > + exynos5_i2c_init(i2c);
> > + clk_disable_unprepare(i2c->clk);
> > + i2c->suspended = 0;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> > + .suspend_noirq = exynos5_i2c_suspend_noirq,
> > + .resume_noirq = exynos5_i2c_resume,
> > +};
> > +
> > +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> > +#else
> > +#define EXYNOS5_DEV_PM_OPS NULL
> > +#endif
> > +
> > +static struct platform_driver exynos5_i2c_driver = {
> > + .probe = exynos5_i2c_probe,
> > + .remove = exynos5_i2c_remove,
> > + .id_table = exynos5_driver_ids,
> > + .driver = {
> > + .owner = THIS_MODULE,
> > + .name = "exynos5-i2c",
> > + .pm = EXYNOS5_DEV_PM_OPS,
> > + .of_match_table = of_match_ptr(exynos5_i2c_match),
> > + },
> > +};
> > +
> > +static int __init i2c_adap_exynos5_init(void)
> > +{
> > + return platform_driver_register(&exynos5_i2c_driver);
> > +}
> > +subsys_initcall(i2c_adap_exynos5_init);
> > +
> > +static void __exit i2c_adap_exynos5_exit(void)
> > +{
> > + platform_driver_unregister(&exynos5_i2c_driver);
> > +}
> > +module_exit(i2c_adap_exynos5_exit);
> > +
> > +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> > +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/i2c/busses/i2c-exynos5.h
> > b/drivers/i2c/busses/i2c-exynos5.h
> > new file mode 100644
> > index 0000000..063051e
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.h
> > @@ -0,0 +1,80 @@
> > +/*
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series HS-I2C Controller
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> > +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> > +
> > +/*
> > + * Register Map
> > + */
> > +#define HSI2C_CTL 0x00
> > +#define HSI2C_FIFO_CTL 0x04
> > +#define HSI2C_TRAILIG_CTL 0x08
> > +#define HSI2C_CLK_CTL 0x0C
> > +#define HSI2C_CLK_SLOT 0x10
> > +#define HSI2C_INT_ENABLE 0x20
> > +#define HSI2C_INT_STATUS 0x24
> > +#define HSI2C_ERR_STATUS 0x2C
> > +#define HSI2C_FIFO_STATUS 0x30
> > +#define HSI2C_TX_DATA 0x34
> > +#define HSI2C_RX_DATA 0x38
> > +#define HSI2C_CONF 0x40
> > +#define HSI2C_AUTO_CONFING 0x44
> > +#define HSI2C_TIMEOUT 0x48
> > +#define HSI2C_MANUAL_CMD 0x4C
> > +#define HSI2C_TRANS_STATUS 0x50
> > +#define HSI2C_TIMING_HS1 0x54
> > +#define HSI2C_TIMING_HS2 0x58
> > +#define HSI2C_TIMING_HS3 0x5C
> > +#define HSI2C_TIMING_FS1 0x60
> > +#define HSI2C_TIMING_FS2 0x64
> > +#define HSI2C_TIMING_FS3 0x68
> > +#define HSI2C_TIMING_SLA 0x6C
> > +#define HSI2C_ADDR 0x70
> > +
> > +/* I2C_CTL Register */
> > +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> > +#define HSI2C_MASTER (1u << 3)
> > +#define HSI2C_RXCHON (1u << 6)
> > +#define HSI2C_TXCHON (1u << 7)
> > +
> > +/* I2C_FIFO_CTL Register */
> > +#define HSI2C_RXFIFO_EN (1u << 0)
> > +#define HSI2C_TXFIFO_EN (1u << 1)
> > +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> > +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> > +
> > +/* I2C_TRAILING_CTL Register */
> > +#define HSI2C_TRAILING_COUNT (0xf)
> > +
> > +/* I2C_INT_EN Register */
> > +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO
> > */
> > +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO
> > */
> > +#define HSI2C_INT_TRAILING_EN (1u << 6)
> > +
> > +/* I2C_CONF Register */
> > +#define HSI2C_AUTO_MODE (1u << 31)
> > +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> > +#define HSI2C_HS_MODE (1u << 29)
> > +
> > +/* I2C_AUTO_CONF Register */
> > +#define HSI2C_READ_WRITE (1u << 16)
> > +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> > +#define HSI2C_MASTER_RUN (1u << 31)
> > +
> > +/* I2C_TIMEOUT Register */
> > +#define HSI2C_TIMEOUT_EN (1u << 31)
> > +
> > +#define HSI2C_FIFO_EMPTY (0x1000100)
> > +
> > +#define HSI2C_FS_BPS 400000
> > +#define HSI2C_HS_BPS 2500000
> > +
> > +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
>
> Since these constants are only use in i2c-exynos5.c file, it is better
> to move these definitions into i2c-exynos5.c file.
>
> Thanks,
> Thomas.
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH 1/3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-11-28 4:23 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-11-28 4:23 UTC (permalink / raw)
To: linux-arm-kernel
Hello Felipe, Thomas,
Thanks for your time and valuable comments,
I will post the next version with you comments addressed.
On 27 November 2012 19:04, Thomas Abraham <thomas.abraham@linaro.org> wrote:
>
> On 27 November 2012 18:30, Naveen Krishna Chatradhi
> <ch.naveen@samsung.com> wrote:
> > Adds support for High Speed I2C driver found in Exynos5 and later
> > SoCs from Samsung. This driver currently supports Auto mode.
> >
> > Driver only supports Device Tree method.
> >
> > Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> > Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> > ---
> > drivers/i2c/busses/Kconfig | 6 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-exynos5.c | 758
> > ++++++++++++++++++++++++++++++++++++++
> > drivers/i2c/busses/i2c-exynos5.h | 80 ++++
> > 4 files changed, 845 insertions(+)
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.c
> > create mode 100644 drivers/i2c/busses/i2c-exynos5.h
> >
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 65dd599..88e8833 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -609,6 +609,12 @@ config I2C_S3C2410
> > Say Y here to include support for I2C controller in the
> > Samsung SoCs.
> >
> > +config I2C_EXYNOS5
> > + tristate "Exynos5 HS-I2C Driver"
> > + help
> > + Say Y here to include support for High Speed I2C controller in
> > the
> > + Exynos5 series SoCs from Samsung.
> > +
> > config I2C_S6000
> > tristate "S6000 I2C support"
> > depends on XTENSA_VARIANT_S6000
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index 2d33d62..426b4fd 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> > obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> > obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> > obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> > +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> > obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> > obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> > obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> > diff --git a/drivers/i2c/busses/i2c-exynos5.c
> > b/drivers/i2c/busses/i2c-exynos5.c
> > new file mode 100644
> > index 0000000..5983aa9
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.c
> > @@ -0,0 +1,758 @@
> > +/* linux/drivers/i2c/busses/i2c-exynos5.c
> > + *
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series High Speed I2C controller driver
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/time.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/err.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/clk.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/of_i2c.h>
> > +#include <linux/of_gpio.h>
> > +
> > +#include <asm/irq.h>
> > +#include "i2c-exynos5.h"
> > +
> > +#define HSI2C_POLLING 0
> > +#define HSI2C_FAST_SPD 0
> > +#define HSI2C_HIGH_SPD 1
> > +
> > +/* Max time to wait for bus to become idle after a xfer */
> > +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> > +
> > +struct exynos5_i2c {
> > + unsigned int suspended:1;
> > +
> > + struct i2c_msg *msg;
> > + struct completion msg_complete;
> > + unsigned int msg_byte_ptr;
> > +
> > + unsigned int irq;
> > +
> > + void __iomem *regs;
> > + struct clk *clk;
> > + struct device *dev;
> > + struct resource *ioarea;
> > + struct i2c_adapter adap;
> > + unsigned int bus_number;
> > + unsigned int speed_mode;
> > + unsigned int fast_speed;
> > + unsigned int high_speed;
> > + int operation_mode;
> > + int gpios[2];
> > +};
> > +
> > +static struct platform_device_id exynos5_driver_ids[] = {
> > + {
> > + .name = "exynos5-hs-i2c",
> > + .driver_data = 0,
> > + }, { },
> > +};
> > +MODULE_DEVICE_TABLE(platform, exynos5_driver_ids);
> > +
> > +#ifdef CONFIG_OF
> > +static const struct of_device_id exynos5_i2c_match[] = {
> > + { .compatible = "samsung,exynos5-hs-i2c", .data = (void *)0 },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> > +#endif
> > +
> > +static inline void dump_i2c_register(struct exynos5_i2c *i2c)
> > +{
> > + dev_dbg(i2c->dev, "Register dump(%d) :\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + " %x\n %x\n %x\n %x\n %x\n"
> > + , i2c->suspended
> > + , readl(i2c->regs + HSI2C_CTL)
> > + , readl(i2c->regs + HSI2C_FIFO_CTL)
> > + , readl(i2c->regs + HSI2C_TRAILIG_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_CTL)
> > + , readl(i2c->regs + HSI2C_CLK_SLOT)
> > + , readl(i2c->regs + HSI2C_INT_ENABLE)
> > + , readl(i2c->regs + HSI2C_INT_STATUS)
> > + , readl(i2c->regs + HSI2C_ERR_STATUS)
> > + , readl(i2c->regs + HSI2C_FIFO_STATUS)
> > + , readl(i2c->regs + HSI2C_TX_DATA)
> > + , readl(i2c->regs + HSI2C_RX_DATA)
> > + , readl(i2c->regs + HSI2C_CONF)
> > + , readl(i2c->regs + HSI2C_AUTO_CONFING)
> > + , readl(i2c->regs + HSI2C_TIMEOUT)
> > + , readl(i2c->regs + HSI2C_MANUAL_CMD)
> > + , readl(i2c->regs + HSI2C_TRANS_STATUS)
> > + , readl(i2c->regs + HSI2C_TIMING_HS1)
> > + , readl(i2c->regs + HSI2C_TIMING_HS2)
> > + , readl(i2c->regs + HSI2C_TIMING_HS3)
> > + , readl(i2c->regs + HSI2C_TIMING_FS1)
> > + , readl(i2c->regs + HSI2C_TIMING_FS2)
> > + , readl(i2c->regs + HSI2C_TIMING_FS3)
> > + , readl(i2c->regs + HSI2C_TIMING_SLA)
> > + , readl(i2c->regs + HSI2C_ADDR));
> > +}
> > +
> > +static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
> > +{
> > + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + complete(&i2c->msg_complete);
> > +}
> > +
> > +static inline void exynos5_disable_irq(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long tmp = readl(i2c->regs + HSI2C_INT_STATUS);
> > +
> > + writel(tmp, i2c->regs + HSI2C_INT_STATUS);
> > +}
> > +
> > +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> > +
> > + /* Clear to enable Timeout */
> > + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> > + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> > +}
> > +
> > +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> > +{
> > + struct exynos5_i2c *i2c = dev_id;
> > + unsigned char byte;
> > +
> > + if (i2c->msg->flags & I2C_M_RD) {
> > + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl(i2c->regs +
> > HSI2C_RX_DATA);
> > + i2c->msg->buf[i2c->msg_byte_ptr++] = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + } else {
> > + byte = i2c->msg->buf[i2c->msg_byte_ptr++];
> > + writel(byte, i2c->regs + HSI2C_TX_DATA);
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > + exynos5_i2c_stop(i2c);
> > + }
> > +
> > + exynos5_disable_irq(i2c);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c);
> > +
> > +static int exynos5_i2c_reset(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl;
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl |= (1u << 31);
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + usi_ctl &= ~(1u << 31);
>
> If the bit mask and bit position values are used more than once, it
> helps to define them as constants.
>
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > + exynos5_i2c_init(i2c);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> > + struct i2c_msg *msgs, int num, int stop)
> > +{
> > + unsigned long timeout;
> > + unsigned long trans_status;
> > + unsigned long usi_fifo_stat;
> > + unsigned long usi_ctl;
> > + unsigned long i2c_auto_conf;
> > + unsigned long i2c_addr;
> > + unsigned long usi_int_en;
> > + unsigned long usi_fifo_ctl;
> > + unsigned char byte;
> > + int ret = 0;
> > + int operation_mode = i2c->operation_mode;
> > +
> > + i2c->msg = msgs;
> > + i2c->msg_byte_ptr = 0;
> > +
> > + init_completion(&i2c->msg_complete);
> > +
> > + usi_ctl = readl(i2c->regs + HSI2C_CTL);
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + exynos5_i2c_en_timeout(i2c);
> > +
> > + /* Set default trigger level for TXFIFO and RXFIFO */
> > + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL |
> > + HSI2C_RXFIFO_TRIGGER_LEVEL;
> > + /* Enable RXFIFO and TXFIFO */
> > + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> > +
> > + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> > +
> > + usi_int_en = 0;
> > + if (msgs->flags & I2C_M_RD) {
> > + usi_ctl &= ~HSI2C_TXCHON;
> > + usi_ctl |= HSI2C_RXCHON;
> > +
> > + i2c_auto_conf |= HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> > + HSI2C_INT_TRAILING_EN);
> > + } else {
> > + usi_ctl &= ~HSI2C_RXCHON;
> > + usi_ctl |= HSI2C_TXCHON;
> > +
> > + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> > +
> > + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> > + }
> > +
> > + if (stop == 1)
> > + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> > + else
> > + i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
> > +
> > +
> > + i2c_addr = readl(i2c->regs + HSI2C_ADDR);
> > +
> > + /* Clear Slave Address for I2C Master (Auto mode only) */
> > + i2c_addr &= ~(0x3ff << 10);
> > + /* Clear Slave Address for I2C Slave */
> > + i2c_addr &= ~(0x3ff << 0);
> > + /* Clear Master ID for I2C Master (Auto and HS mode only) */
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + i2c_addr &= ~(0xff << 24);
> > +
> > + i2c_addr |= ((msgs->addr & 0x7f) << 10);
> > + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + /* Clear and set TRANS_LEN */
> > + i2c_auto_conf &= ~(0xffff);
> > + i2c_auto_conf |= i2c->msg->len;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Start data transfer in Master mode */
> > + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONFING);
> > + i2c_auto_conf |= HSI2C_MASTER_RUN;
> > + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONFING);
> > +
> > + /* Enable appropriate interrupts */
> > + if (operation_mode != HSI2C_POLLING)
> > + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> > +
> > + ret = -EAGAIN;
> > + if (msgs->flags & I2C_M_RD) {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS) &
> > + 0x1000000) == 0) {
> > + byte = (unsigned char)readl
> > + (i2c->regs +
> > HSI2C_RX_DATA);
> > +
> > i2c->msg->buf[i2c->msg_byte_ptr++]
> > + = byte;
> > + }
> > +
> > + if (i2c->msg_byte_ptr >= i2c->msg->len)
> > {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > +
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "rx timeout\n");
> > + return ret;
> > + }
> > +
> > + ret = 0;
> > + }
> > + } else {
> > + if (operation_mode == HSI2C_POLLING) {
> > + timeout = jiffies + EXYNOS5_I2C_TIMEOUT;
> > + while (time_before(jiffies, timeout) &&
> > + (i2c->msg_byte_ptr < i2c->msg->len)) {
> > + if ((readl(i2c->regs +
> > HSI2C_FIFO_STATUS)
> > + & 0x7f) < 64) {
> > + byte = i2c->msg->buf
> > + [i2c->msg_byte_ptr++];
> > + writel(byte,
> > + i2c->regs +
> > HSI2C_TX_DATA);
> > + }
> > + }
> > + } else {
> > + timeout =
> > wait_for_completion_interruptible_timeout
> > + (&i2c->msg_complete,
> > EXYNOS5_I2C_TIMEOUT);
> > +
> > + if (timeout == 0) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > +
> > + timeout = jiffies + timeout;
> > + }
> > + while (time_before(jiffies, timeout)) {
> > + usi_fifo_stat = readl(i2c->regs +
> > HSI2C_FIFO_STATUS);
> > + trans_status = readl(i2c->regs +
> > HSI2C_TRANS_STATUS);
> > + if ((usi_fifo_stat == HSI2C_FIFO_EMPTY) &&
> > + ((trans_status == 0) ||
> > + ((stop == 0) &&
> > + (trans_status == 0x20000)))) {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > + if (ret == -EAGAIN) {
> > + exynos5_i2c_reset(i2c);
> > + dev_warn(i2c->dev, "tx timeout\n");
> > + return ret;
> > + }
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> > + struct i2c_msg *msgs, int num)
> > +{
> > + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> > + int retry, i;
> > + int ret;
> > + int stop = 0;
> > + struct i2c_msg *msgs_ptr = msgs;
> > +
> > + if (i2c->suspended) {
> > + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> > + return -EIO;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + for (retry = 0; retry < adap->retries; retry++) {
> > + for (i = 0; i < num; i++) {
> > + if (i == num - 1)
> > + stop = 1;
> > + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, 1,
> > stop);
> > + msgs_ptr++;
> > +
> > + if (ret == -EAGAIN) {
> > + msgs_ptr = msgs;
> > + stop = 0;
> > + break;
> > + }
> > + }
> > + if (i == num) {
> > + clk_disable_unprepare(i2c->clk);
> > + return num;
> > + }
> > +
> > + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> > +
> > + udelay(100);
> > + }
> > +
> > + clk_disable_unprepare(i2c->clk);
> > +
> > + return -EREMOTEIO;
> > +}
> > +
> > +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> > +{
> > + return I2C_FUNC_I2C;
> > +}
> > +
> > +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> > + .master_xfer = exynos5_i2c_xfer,
> > + .functionality = exynos5_i2c_func,
> > +};
> > +
> > +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long i2c_timing_s1;
> > + unsigned long i2c_timing_s2;
> > + unsigned long i2c_timing_s3;
> > + unsigned long i2c_timing_sla;
> > + unsigned int op_clk;
> > + unsigned int clkin = clk_get_rate(i2c->clk);
> > + unsigned int n_clkdiv;
> > + unsigned int t_start_su, t_start_hd;
> > + unsigned int t_stop_su;
> > + unsigned int t_data_su, t_data_hd;
> > + unsigned int t_scl_l, t_scl_h;
> > + unsigned int t_sr_release;
> > + unsigned int t_ftl_cycle;
> > + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD)
> > + op_clk = i2c->high_speed;
> > + else
> > + op_clk = i2c->fast_speed;
> > +
> > + /* FPCLK / FI2C =
> > + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> > + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> > + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> > + * uTemp2 = TSCLK_L + TSCLK_H
> > + */
> > + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> > + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> > +
> > + /* CLK_DIV max is 256 */
> > + for (i = 0; i < 256; i++) {
> > + utemp1 = utemp0 / (i + 1);
> > + /* SCLK_L/H max is 256 / 2 */
> > + if (utemp1 < 128) {
> > + utemp2 = utemp1 - 2;
> > + break;
> > + }
> > + }
> > +
> > + n_clkdiv = i;
> > + t_scl_l = utemp2 / 2;
> > + t_scl_h = utemp2 / 2;
> > + t_start_su = t_scl_l;
> > + t_start_hd = t_scl_l;
> > + t_stop_su = t_scl_l;
> > + t_data_su = t_scl_l / 2;
> > + t_data_hd = t_scl_l / 2;
> > + t_sr_release = utemp2;
> > +
> > + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su
> > << 8;
> > + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> > + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> > + i2c_timing_sla = t_data_hd << 0;
> > +
> > + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU:
> > %X\n",
> > + t_start_su, t_start_hd, t_stop_su);
> > + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> > + t_data_su, t_scl_l, t_scl_h);
> > + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> > + n_clkdiv, t_sr_release);
> > + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> > + } else {
> > + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> > + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> > + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> > + }
> > + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_OF
>
> If this driver supports only device tree (as stated in the commit
> message), then this #ifdef can be avoided.
>
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + int idx, gpio, ret;
> > +
> > + for (idx = 0; idx < 2; idx++) {
> > + gpio = of_get_gpio(i2c->dev->of_node, idx);
> > + if (!gpio_is_valid(gpio)) {
> > + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx,
> > gpio);
> > + goto free_gpio;
> > + }
> > + i2c->gpios[idx] = gpio;
> > +
> > + ret = gpio_request(gpio, "hsi2c-bus");
>
> Using devm_gpio_request here can simplify the error handling path.
>
> > + if (ret) {
> > + dev_err(i2c->dev, "gpio [%d] request failed\n",
> > gpio);
> > + goto free_gpio;
> > + }
> > + }
> > + return 0;
> > +
> > +free_gpio:
> > + while (--idx >= 0)
> > + gpio_free(i2c->gpios[idx]);
> > + return -EINVAL;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > + unsigned int idx;
> > +
> > + for (idx = 0; idx < 2; idx++)
> > + gpio_free(i2c->gpios[idx]);
> > +}
> > +#else
> > +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> > +{
> > + return 0;
> > +}
> > +
> > +static void exynos5_i2c_dt_gpio_free(struct exynos5_i2c *i2c)
> > +{
> > +}
> > +#endif
> > +
> > +static int exynos5_i2c_init(struct exynos5_i2c *i2c)
> > +{
> > + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> > + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> > + unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
> > +
> > + if (exynos5_i2c_parse_dt_gpio(i2c))
> > + return -EINVAL;
> > +
> > + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> > +
> > + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> > +
> > + exynos5_i2c_set_timing(i2c);
> > +
> > + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> > + i2c_conf |= HSI2C_HS_MODE;
> > + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_probe(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c;
> > + struct resource *res;
> > + int ret;
> > +
> > + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c),
> > GFP_KERNEL);
> > + if (!i2c) {
> > + dev_err(&pdev->dev, "no memory for state\n");
> > + return -ENOMEM;
> > + }
> > +
> > + if (pdev->dev.of_node) {
> > + /* i2c bus number is dynamically assigned */
> > + i2c->bus_number = -1;
> > +
> > + if (of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-speed-mode",
> > &i2c->speed_mode))
> > + i2c->speed_mode = 1;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-hs-clk",
> > &i2c->high_speed))
> > + i2c->high_speed = 2500000;
> > + if (!of_property_read_u32(pdev->dev.of_node,
> > + "samsung,hsi2c-fs-clk",
> > &i2c->fast_speed))
> > + i2c->fast_speed = 400000;
> > + } else {
> > + dev_err(&pdev->dev, "no device node\n");
> > + return -ENOENT;
> > + }
> > +
> > + strlcpy(i2c->adap.name, "exynos5250-i2c",
> > sizeof(i2c->adap.name));
> > + i2c->adap.owner = THIS_MODULE;
> > + i2c->adap.algo = &exynos5_i2c_algorithm;
> > + i2c->adap.retries = 2;
> > + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> > +
> > + i2c->dev = &pdev->dev;
> > + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> > + if (IS_ERR(i2c->clk)) {
> > + dev_err(&pdev->dev, "cannot get clock\n");
> > + ret = -ENOENT;
> > + goto err_noclk;
> > + }
> > +
> > + clk_prepare_enable(i2c->clk);
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IO resource\n");
> > + ret = -ENOENT;
> > + goto err_clk;
> > + }
> > +
> > + i2c->ioarea = request_mem_region(res->start, resource_size(res),
> > + pdev->name);
>
> Since this is dt-only driver, use of_iomap here.
>
> > +
> > + if (i2c->ioarea == NULL) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_clk;
> > + }
> > +
> > + i2c->regs = ioremap(res->start, resource_size(res));
> > +
> > + if (i2c->regs == NULL) {
> > + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> > + ret = -ENXIO;
> > + goto err_ioarea;
> > + }
> > +
> > + dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
> > + i2c->regs, i2c->ioarea, res);
> > +
> > + i2c->adap.algo_data = i2c;
> > + i2c->adap.dev.parent = &pdev->dev;
> > +
> > + ret = exynos5_i2c_init(i2c);
> > + if (ret != 0)
> > + goto err_iomap;
> > +
> > + i2c->irq = ret = platform_get_irq(pdev, 0);
>
> irq_of_parse_and_map() here.
>
> > + if (ret <= 0) {
> > + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> > + goto err_iomap;
> > + }
> > +
> > + ret = request_irq(i2c->irq, exynos5_i2c_irq, IRQF_DISABLED,
> > + dev_name(&pdev->dev), i2c);
> > +
> > + if (ret != 0) {
> > + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> > i2c->irq);
> > + goto err_iomap;
> > + }
> > +
> > + i2c->adap.nr = i2c->bus_number;
> > + i2c->adap.dev.of_node = pdev->dev.of_node;
> > +
> > + ret = i2c_add_numbered_adapter(&i2c->adap);
> > + if (ret < 0) {
> > + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> > + goto err_irq;
> > + }
> > +
> > + of_i2c_register_devices(&i2c->adap);
> > + platform_set_drvdata(pdev, i2c);
> > +
> > + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> > + dev_name(&i2c->adap.dev));
> > + clk_disable_unprepare(i2c->clk);
> > + return 0;
> > +
> > + err_irq:
> > + free_irq(i2c->irq, i2c);
> > +
> > + err_iomap:
> > + iounmap(i2c->regs);
> > +
> > + err_ioarea:
> > + release_resource(i2c->ioarea);
> > + kfree(i2c->ioarea);
> > +
> > + err_clk:
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + err_noclk:
> > + return ret;
> > +}
> > +
> > +static int exynos5_i2c_remove(struct platform_device *pdev)
> > +{
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_del_adapter(&i2c->adap);
> > + free_irq(i2c->irq, i2c);
> > +
> > + clk_disable_unprepare(i2c->clk);
> > + clk_put(i2c->clk);
> > +
> > + iounmap(i2c->regs);
> > +
> > + release_resource(i2c->ioarea);
> > + exynos5_i2c_dt_gpio_free(i2c);
> > + kfree(i2c->ioarea);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int exynos5_i2c_suspend_noirq(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + i2c->suspended = 1;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static int exynos5_i2c_resume(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> > +
> > + i2c_lock_adapter(&i2c->adap);
> > + clk_prepare_enable(i2c->clk);
> > + exynos5_i2c_init(i2c);
> > + clk_disable_unprepare(i2c->clk);
> > + i2c->suspended = 0;
> > + i2c_unlock_adapter(&i2c->adap);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> > + .suspend_noirq = exynos5_i2c_suspend_noirq,
> > + .resume_noirq = exynos5_i2c_resume,
> > +};
> > +
> > +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> > +#else
> > +#define EXYNOS5_DEV_PM_OPS NULL
> > +#endif
> > +
> > +static struct platform_driver exynos5_i2c_driver = {
> > + .probe = exynos5_i2c_probe,
> > + .remove = exynos5_i2c_remove,
> > + .id_table = exynos5_driver_ids,
> > + .driver = {
> > + .owner = THIS_MODULE,
> > + .name = "exynos5-i2c",
> > + .pm = EXYNOS5_DEV_PM_OPS,
> > + .of_match_table = of_match_ptr(exynos5_i2c_match),
> > + },
> > +};
> > +
> > +static int __init i2c_adap_exynos5_init(void)
> > +{
> > + return platform_driver_register(&exynos5_i2c_driver);
> > +}
> > +subsys_initcall(i2c_adap_exynos5_init);
> > +
> > +static void __exit i2c_adap_exynos5_exit(void)
> > +{
> > + platform_driver_unregister(&exynos5_i2c_driver);
> > +}
> > +module_exit(i2c_adap_exynos5_exit);
> > +
> > +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> > +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/i2c/busses/i2c-exynos5.h
> > b/drivers/i2c/busses/i2c-exynos5.h
> > new file mode 100644
> > index 0000000..063051e
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-exynos5.h
> > @@ -0,0 +1,80 @@
> > +/*
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> > + *
> > + * Exynos5 series HS-I2C Controller
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > +*/
> > +
> > +#ifndef __ASM_ARCH_REGS_HS_IIC_H
> > +#define __ASM_ARCH_REGS_HS_IIC_H __FILE__
> > +
> > +/*
> > + * Register Map
> > + */
> > +#define HSI2C_CTL 0x00
> > +#define HSI2C_FIFO_CTL 0x04
> > +#define HSI2C_TRAILIG_CTL 0x08
> > +#define HSI2C_CLK_CTL 0x0C
> > +#define HSI2C_CLK_SLOT 0x10
> > +#define HSI2C_INT_ENABLE 0x20
> > +#define HSI2C_INT_STATUS 0x24
> > +#define HSI2C_ERR_STATUS 0x2C
> > +#define HSI2C_FIFO_STATUS 0x30
> > +#define HSI2C_TX_DATA 0x34
> > +#define HSI2C_RX_DATA 0x38
> > +#define HSI2C_CONF 0x40
> > +#define HSI2C_AUTO_CONFING 0x44
> > +#define HSI2C_TIMEOUT 0x48
> > +#define HSI2C_MANUAL_CMD 0x4C
> > +#define HSI2C_TRANS_STATUS 0x50
> > +#define HSI2C_TIMING_HS1 0x54
> > +#define HSI2C_TIMING_HS2 0x58
> > +#define HSI2C_TIMING_HS3 0x5C
> > +#define HSI2C_TIMING_FS1 0x60
> > +#define HSI2C_TIMING_FS2 0x64
> > +#define HSI2C_TIMING_FS3 0x68
> > +#define HSI2C_TIMING_SLA 0x6C
> > +#define HSI2C_ADDR 0x70
> > +
> > +/* I2C_CTL Register */
> > +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> > +#define HSI2C_MASTER (1u << 3)
> > +#define HSI2C_RXCHON (1u << 6)
> > +#define HSI2C_TXCHON (1u << 7)
> > +
> > +/* I2C_FIFO_CTL Register */
> > +#define HSI2C_RXFIFO_EN (1u << 0)
> > +#define HSI2C_TXFIFO_EN (1u << 1)
> > +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> > +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> > +
> > +/* I2C_TRAILING_CTL Register */
> > +#define HSI2C_TRAILING_COUNT (0xf)
> > +
> > +/* I2C_INT_EN Register */
> > +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0) /* For TX FIFO
> > */
> > +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1) /* For RX FIFO
> > */
> > +#define HSI2C_INT_TRAILING_EN (1u << 6)
> > +
> > +/* I2C_CONF Register */
> > +#define HSI2C_AUTO_MODE (1u << 31)
> > +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> > +#define HSI2C_HS_MODE (1u << 29)
> > +
> > +/* I2C_AUTO_CONF Register */
> > +#define HSI2C_READ_WRITE (1u << 16)
> > +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> > +#define HSI2C_MASTER_RUN (1u << 31)
> > +
> > +/* I2C_TIMEOUT Register */
> > +#define HSI2C_TIMEOUT_EN (1u << 31)
> > +
> > +#define HSI2C_FIFO_EMPTY (0x1000100)
> > +
> > +#define HSI2C_FS_BPS 400000
> > +#define HSI2C_HS_BPS 2500000
> > +
> > +#endif /* __ASM_ARCH_REGS_HS_IIC_H */
>
> Since these constants are only use in i2c-exynos5.c file, it is better
> to move these definitions into i2c-exynos5.c file.
>
> Thanks,
> Thomas.
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
(?)
@ 2012-12-28 11:27 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2012-12-28 11:27 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ, balbi-l0cyMroinI0,
thomas.abraham-QSEj5FYQhm4dnm+yROfE0A
Adds support for High Speed I2C driver found in Exynos5 and later
SoCs from Samsung. This driver currently supports Auto mode.
Driver only supports Device Tree method of passing platform data.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
---
Changes since v2: fixed comments from Felipe Balbi.
And minor fixes for the return values in exynos5_i2c_doxfer()
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
3 files changed, 744 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bdca511..4caea76 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -618,6 +618,13 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung SoCs.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_S6000
tristate "S6000 I2C support"
depends on XTENSA_VARIANT_S6000
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 6181f3f..4b1548c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..a5eb959
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,736 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
+#define HSI2C_FIFO_EMPTY (0x1000100)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/**
+ * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
+ * but currently we are facing booting issues beyond 1Mhz
+ * So limiting HS-IIC bus speed to 1Mhz
+*/
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 400000
+
+#define HSI2C_FAST_SPD 0
+#define HSI2C_HIGH_SPD 1
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ unsigned int msg_idx;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int gpios[2];
+
+ int bus_num;
+ int speed_mode;
+ struct dentry *debugfs_root;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
+{
+ dev_vdbg(i2c->dev, "STOP\n");
+
+ i2c->msg_idx++;
+ if (err)
+ i2c->msg_idx = err;
+
+ /* Disable interrrupts */
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/**
+ * exynos5_i2c_set_bus: get the i2c bus for a master transaction
+*/
+static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
+{
+ unsigned long t_status;
+ int timeout = 400;
+
+ while (timeout-- > 0) {
+ t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (!(t_status & HSI2C_MASTER_BUSY))
+ return 0;
+
+ msleep(20);
+ }
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * exynos5_i2c_irq: top level IRQ servicing routine
+*/
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ unsigned long t_stat;
+ unsigned char byte;
+
+ t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (t_stat & HSI2C_TRANS_ABORT) {
+ /* deal with arbitration loss */
+ dev_err(i2c->dev, "deal with arbitration loss\n");
+ goto out;
+ }
+ if (i2c->msg->flags & I2C_M_RD) {
+ if (t_stat & HSI2C_TRANS_DONE) {
+ dev_dbg(i2c->dev, "Device found.");
+ while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
+ HSI2C_RX_FIFO_EMPTY) == 0) {
+ byte = readl(i2c->regs + HSI2C_RX_DATA);
+ dev_dbg(i2c->dev, "read rx_data = %x", byte);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ }
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+
+ } else if (t_stat & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device found.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ } else if (t_stat & HSI2C_NO_DEV_ACK &&
+ !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "No device Ack.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ }
+ } else {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ dev_dbg(i2c->dev, "write tx_data = %x ", byte);
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+ }
+
+ out:
+ /* Set those bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs)
+{
+ unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
+ unsigned long i2c_auto_conf;
+ unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
+ unsigned long usi_int_en = 0;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ if (msgs->flags & I2C_M_RD) {
+ usi_ctl &= ~HSI2C_TXCHON;
+ usi_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ usi_ctl &= ~HSI2C_RXCHON;
+ usi_ctl |= HSI2C_TXCHON;
+
+ i2c_auto_conf &= ~HSI2C_READ_WRITE;
+
+ usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ writel(i2c_addr, i2c->regs + HSI2C_ADDR);
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_auto_conf |= i2c->msg->len;
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ /* Enable appropriate interrupts */
+ writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
+{
+ unsigned long timeout;
+ int ret;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ if (exynos5_i2c_set_master(i2c)) {
+ dev_err(i2c->dev, "cannot get bus, Master busy.\n");
+ return -EAGAIN;
+ }
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_idx = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, msgs);
+
+ timeout = wait_for_completion_timeout(&i2c->msg_complete,
+ EXYNOS5_I2C_TIMEOUT);
+
+ ret = i2c->msg_idx;
+
+ if (timeout == 0)
+ dev_dbg(i2c->dev, "timeout\n");
+ else if ((ret != msgs->len) && (ret < 0))
+ dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int retry, i;
+ int ret;
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto out;
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ ret = exynos5_i2c_doxfer(i2c, msgs);
+ msgs++;
+
+ if (ret == -EAGAIN)
+ break;
+ }
+ if (i == num) {
+ clk_disable_unprepare(i2c->clk);
+
+ if (i2c->msg_idx == -ENXIO)
+ ret = i2c->msg_idx;
+ else
+ ret = num;
+ goto out;
+ }
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+ clk_disable_unprepare(i2c->clk);
+ out:
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
+{
+ unsigned long i2c_timing_s1;
+ unsigned long i2c_timing_s2;
+ unsigned long i2c_timing_s3;
+ unsigned long i2c_timing_sla;
+ unsigned int op_clk;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
+
+ if (speed_mode == HSI2C_HIGH_SPD)
+ op_clk = HSI2C_HS_TX_CLOCK;
+ else
+ op_clk = HSI2C_FS_TX_CLOCK;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * uTemp1 = (TSCLK_L + TSCLK_H + 2)
+ * uTemp2 = TSCLK_L + TSCLK_H
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+ /* SCLK_L/H max is 255
+ * so sclk_l + sclk_h has max value of 510
+ */
+ if (utemp1 < 511) {
+ utemp2 = utemp1 - 2;
+ break;
+ }
+ }
+
+ n_clkdiv = i;
+ t_scl_l = utemp2 / 2;
+ t_scl_h = utemp2 / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = utemp2;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
+ unsigned long i2c_conf = HSI2C_AUTO_MODE;
+ unsigned long usi_fifo_ctl;
+
+ writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ /* Set default trigger level for TXFIFO and RXFIFO */
+ usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+
+ /* Enable RXFIFO and TXFIFO */
+ usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
+ /* Configure I2C controller in High speed mode */
+ i2c_conf |= HSI2C_HS_MODE;
+ writel(i2c_conf, i2c->regs + HSI2C_CONF);
+ } else {
+ /* Configure I2C controller in Fast speed mode */
+ exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
+ }
+}
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ i2c->bus_num = -1;
+ /* Mode of operation High/Fast Speed mode */
+ of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
+
+ strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ ret = -ENOENT;
+ goto err_noclk;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ i2c->irq = irq_of_parse_and_map(np, 0);
+ if (i2c->irq) {
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
+ i2c->irq);
+ goto err_iomap;
+ }
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.nr = i2c->bus_num;
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ init_completion(&i2c->msg_complete);
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
+ dev_name(&i2c->adap.dev));
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ err_noclk:
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-12-28 11:27 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2012-12-28 11:27 UTC (permalink / raw)
To: linux-i2c
Cc: linux-arm-kernel, linux-samsung-soc, devicetree-discuss,
naveenkrishna.ch, kgene.kim, grant.likely, w.sang, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
Adds support for High Speed I2C driver found in Exynos5 and later
SoCs from Samsung. This driver currently supports Auto mode.
Driver only supports Device Tree method of passing platform data.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v2: fixed comments from Felipe Balbi.
And minor fixes for the return values in exynos5_i2c_doxfer()
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
3 files changed, 744 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bdca511..4caea76 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -618,6 +618,13 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung SoCs.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_S6000
tristate "S6000 I2C support"
depends on XTENSA_VARIANT_S6000
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 6181f3f..4b1548c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..a5eb959
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,736 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
+#define HSI2C_FIFO_EMPTY (0x1000100)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/**
+ * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
+ * but currently we are facing booting issues beyond 1Mhz
+ * So limiting HS-IIC bus speed to 1Mhz
+*/
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 400000
+
+#define HSI2C_FAST_SPD 0
+#define HSI2C_HIGH_SPD 1
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ unsigned int msg_idx;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int gpios[2];
+
+ int bus_num;
+ int speed_mode;
+ struct dentry *debugfs_root;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
+{
+ dev_vdbg(i2c->dev, "STOP\n");
+
+ i2c->msg_idx++;
+ if (err)
+ i2c->msg_idx = err;
+
+ /* Disable interrrupts */
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/**
+ * exynos5_i2c_set_bus: get the i2c bus for a master transaction
+*/
+static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
+{
+ unsigned long t_status;
+ int timeout = 400;
+
+ while (timeout-- > 0) {
+ t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (!(t_status & HSI2C_MASTER_BUSY))
+ return 0;
+
+ msleep(20);
+ }
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * exynos5_i2c_irq: top level IRQ servicing routine
+*/
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ unsigned long t_stat;
+ unsigned char byte;
+
+ t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (t_stat & HSI2C_TRANS_ABORT) {
+ /* deal with arbitration loss */
+ dev_err(i2c->dev, "deal with arbitration loss\n");
+ goto out;
+ }
+ if (i2c->msg->flags & I2C_M_RD) {
+ if (t_stat & HSI2C_TRANS_DONE) {
+ dev_dbg(i2c->dev, "Device found.");
+ while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
+ HSI2C_RX_FIFO_EMPTY) == 0) {
+ byte = readl(i2c->regs + HSI2C_RX_DATA);
+ dev_dbg(i2c->dev, "read rx_data = %x", byte);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ }
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+
+ } else if (t_stat & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device found.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ } else if (t_stat & HSI2C_NO_DEV_ACK &&
+ !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "No device Ack.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ }
+ } else {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ dev_dbg(i2c->dev, "write tx_data = %x ", byte);
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+ }
+
+ out:
+ /* Set those bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs)
+{
+ unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
+ unsigned long i2c_auto_conf;
+ unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
+ unsigned long usi_int_en = 0;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ if (msgs->flags & I2C_M_RD) {
+ usi_ctl &= ~HSI2C_TXCHON;
+ usi_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ usi_ctl &= ~HSI2C_RXCHON;
+ usi_ctl |= HSI2C_TXCHON;
+
+ i2c_auto_conf &= ~HSI2C_READ_WRITE;
+
+ usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ writel(i2c_addr, i2c->regs + HSI2C_ADDR);
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_auto_conf |= i2c->msg->len;
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ /* Enable appropriate interrupts */
+ writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
+{
+ unsigned long timeout;
+ int ret;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ if (exynos5_i2c_set_master(i2c)) {
+ dev_err(i2c->dev, "cannot get bus, Master busy.\n");
+ return -EAGAIN;
+ }
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_idx = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, msgs);
+
+ timeout = wait_for_completion_timeout(&i2c->msg_complete,
+ EXYNOS5_I2C_TIMEOUT);
+
+ ret = i2c->msg_idx;
+
+ if (timeout == 0)
+ dev_dbg(i2c->dev, "timeout\n");
+ else if ((ret != msgs->len) && (ret < 0))
+ dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int retry, i;
+ int ret;
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto out;
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ ret = exynos5_i2c_doxfer(i2c, msgs);
+ msgs++;
+
+ if (ret == -EAGAIN)
+ break;
+ }
+ if (i == num) {
+ clk_disable_unprepare(i2c->clk);
+
+ if (i2c->msg_idx == -ENXIO)
+ ret = i2c->msg_idx;
+ else
+ ret = num;
+ goto out;
+ }
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+ clk_disable_unprepare(i2c->clk);
+ out:
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
+{
+ unsigned long i2c_timing_s1;
+ unsigned long i2c_timing_s2;
+ unsigned long i2c_timing_s3;
+ unsigned long i2c_timing_sla;
+ unsigned int op_clk;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
+
+ if (speed_mode == HSI2C_HIGH_SPD)
+ op_clk = HSI2C_HS_TX_CLOCK;
+ else
+ op_clk = HSI2C_FS_TX_CLOCK;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * uTemp1 = (TSCLK_L + TSCLK_H + 2)
+ * uTemp2 = TSCLK_L + TSCLK_H
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+ /* SCLK_L/H max is 255
+ * so sclk_l + sclk_h has max value of 510
+ */
+ if (utemp1 < 511) {
+ utemp2 = utemp1 - 2;
+ break;
+ }
+ }
+
+ n_clkdiv = i;
+ t_scl_l = utemp2 / 2;
+ t_scl_h = utemp2 / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = utemp2;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
+ unsigned long i2c_conf = HSI2C_AUTO_MODE;
+ unsigned long usi_fifo_ctl;
+
+ writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ /* Set default trigger level for TXFIFO and RXFIFO */
+ usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+
+ /* Enable RXFIFO and TXFIFO */
+ usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
+ /* Configure I2C controller in High speed mode */
+ i2c_conf |= HSI2C_HS_MODE;
+ writel(i2c_conf, i2c->regs + HSI2C_CONF);
+ } else {
+ /* Configure I2C controller in Fast speed mode */
+ exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
+ }
+}
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ i2c->bus_num = -1;
+ /* Mode of operation High/Fast Speed mode */
+ of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
+
+ strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ ret = -ENOENT;
+ goto err_noclk;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ i2c->irq = irq_of_parse_and_map(np, 0);
+ if (i2c->irq) {
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
+ i2c->irq);
+ goto err_iomap;
+ }
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.nr = i2c->bus_num;
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ init_completion(&i2c->msg_complete);
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
+ dev_name(&i2c->adap.dev));
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ err_noclk:
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-12-28 11:27 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2012-12-28 11:27 UTC (permalink / raw)
To: linux-arm-kernel
Adds support for High Speed I2C driver found in Exynos5 and later
SoCs from Samsung. This driver currently supports Auto mode.
Driver only supports Device Tree method of passing platform data.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v2: fixed comments from Felipe Balbi.
And minor fixes for the return values in exynos5_i2c_doxfer()
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
3 files changed, 744 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bdca511..4caea76 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -618,6 +618,13 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung SoCs.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_S6000
tristate "S6000 I2C support"
depends on XTENSA_VARIANT_S6000
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 6181f3f..4b1548c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..a5eb959
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,736 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
+#define HSI2C_FIFO_EMPTY (0x1000100)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/**
+ * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
+ * but currently we are facing booting issues beyond 1Mhz
+ * So limiting HS-IIC bus speed to 1Mhz
+*/
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 400000
+
+#define HSI2C_FAST_SPD 0
+#define HSI2C_HIGH_SPD 1
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ unsigned int msg_idx;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int gpios[2];
+
+ int bus_num;
+ int speed_mode;
+ struct dentry *debugfs_root;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
+{
+ dev_vdbg(i2c->dev, "STOP\n");
+
+ i2c->msg_idx++;
+ if (err)
+ i2c->msg_idx = err;
+
+ /* Disable interrrupts */
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/**
+ * exynos5_i2c_set_bus: get the i2c bus for a master transaction
+*/
+static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
+{
+ unsigned long t_status;
+ int timeout = 400;
+
+ while (timeout-- > 0) {
+ t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (!(t_status & HSI2C_MASTER_BUSY))
+ return 0;
+
+ msleep(20);
+ }
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * exynos5_i2c_irq: top level IRQ servicing routine
+*/
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ unsigned long t_stat;
+ unsigned char byte;
+
+ t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ if (t_stat & HSI2C_TRANS_ABORT) {
+ /* deal with arbitration loss */
+ dev_err(i2c->dev, "deal with arbitration loss\n");
+ goto out;
+ }
+ if (i2c->msg->flags & I2C_M_RD) {
+ if (t_stat & HSI2C_TRANS_DONE) {
+ dev_dbg(i2c->dev, "Device found.");
+ while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
+ HSI2C_RX_FIFO_EMPTY) == 0) {
+ byte = readl(i2c->regs + HSI2C_RX_DATA);
+ dev_dbg(i2c->dev, "read rx_data = %x", byte);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ }
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+
+ } else if (t_stat & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device found.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ } else if (t_stat & HSI2C_NO_DEV_ACK &&
+ !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "No device Ack.");
+ exynos5_i2c_stop(i2c, -ENXIO);
+ }
+ } else {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ dev_dbg(i2c->dev, "write tx_data = %x ", byte);
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c, 0);
+ }
+
+ out:
+ /* Set those bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs)
+{
+ unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
+ unsigned long i2c_auto_conf;
+ unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
+ unsigned long usi_int_en = 0;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ if (msgs->flags & I2C_M_RD) {
+ usi_ctl &= ~HSI2C_TXCHON;
+ usi_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ usi_ctl &= ~HSI2C_RXCHON;
+ usi_ctl |= HSI2C_TXCHON;
+
+ i2c_auto_conf &= ~HSI2C_READ_WRITE;
+
+ usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ writel(i2c_addr, i2c->regs + HSI2C_ADDR);
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_auto_conf |= i2c->msg->len;
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ /* Enable appropriate interrupts */
+ writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
+{
+ unsigned long timeout;
+ int ret;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ if (exynos5_i2c_set_master(i2c)) {
+ dev_err(i2c->dev, "cannot get bus, Master busy.\n");
+ return -EAGAIN;
+ }
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_idx = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, msgs);
+
+ timeout = wait_for_completion_timeout(&i2c->msg_complete,
+ EXYNOS5_I2C_TIMEOUT);
+
+ ret = i2c->msg_idx;
+
+ if (timeout == 0)
+ dev_dbg(i2c->dev, "timeout\n");
+ else if ((ret != msgs->len) && (ret < 0))
+ dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int retry, i;
+ int ret;
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto out;
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ ret = exynos5_i2c_doxfer(i2c, msgs);
+ msgs++;
+
+ if (ret == -EAGAIN)
+ break;
+ }
+ if (i == num) {
+ clk_disable_unprepare(i2c->clk);
+
+ if (i2c->msg_idx == -ENXIO)
+ ret = i2c->msg_idx;
+ else
+ ret = num;
+ goto out;
+ }
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+ clk_disable_unprepare(i2c->clk);
+ out:
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
+{
+ unsigned long i2c_timing_s1;
+ unsigned long i2c_timing_s2;
+ unsigned long i2c_timing_s3;
+ unsigned long i2c_timing_sla;
+ unsigned int op_clk;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
+
+ if (speed_mode == HSI2C_HIGH_SPD)
+ op_clk = HSI2C_HS_TX_CLOCK;
+ else
+ op_clk = HSI2C_FS_TX_CLOCK;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * uTemp1 = (TSCLK_L + TSCLK_H + 2)
+ * uTemp2 = TSCLK_L + TSCLK_H
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+ /* SCLK_L/H max is 255
+ * so sclk_l + sclk_h has max value of 510
+ */
+ if (utemp1 < 511) {
+ utemp2 = utemp1 - 2;
+ break;
+ }
+ }
+
+ n_clkdiv = i;
+ t_scl_l = utemp2 / 2;
+ t_scl_h = utemp2 / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = utemp2;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
+ unsigned long i2c_conf = HSI2C_AUTO_MODE;
+ unsigned long usi_fifo_ctl;
+
+ writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ /* Set default trigger level for TXFIFO and RXFIFO */
+ usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+
+ /* Enable RXFIFO and TXFIFO */
+ usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
+ /* Configure I2C controller in High speed mode */
+ i2c_conf |= HSI2C_HS_MODE;
+ writel(i2c_conf, i2c->regs + HSI2C_CONF);
+ } else {
+ /* Configure I2C controller in Fast speed mode */
+ exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
+ }
+}
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ i2c->bus_num = -1;
+ /* Mode of operation High/Fast Speed mode */
+ of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
+
+ strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ ret = -ENOENT;
+ goto err_noclk;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ i2c->irq = irq_of_parse_and_map(np, 0);
+ if (i2c->irq) {
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
+ i2c->irq);
+ goto err_iomap;
+ }
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.nr = i2c->bus_num;
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ init_completion(&i2c->msg_complete);
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
+ dev_name(&i2c->adap.dev));
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ err_noclk:
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread[parent not found: <1356694023-32470-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
2012-12-28 11:27 ` Naveen Krishna Chatradhi
(?)
@ 2012-12-28 16:36 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-12-28 16:36 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ, balbi-l0cyMroinI0,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
I took the reference of this call from MFD driver, which is the only
driver using this (i guess)
usb/dwc3.c driver had a generic implementation. I wasn't sure of the
implementation.
if someone can help, i willing to do that.
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-12-28 16:36 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-12-28 16:36 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-arm-kernel, linux-samsung-soc,
devicetree-discuss, kgene.kim, grant.likely, w.sang, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
I took the reference of this call from MFD driver, which is the only
driver using this (i guess)
usb/dwc3.c driver had a generic implementation. I wasn't sure of the
implementation.
if someone can help, i willing to do that.
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2012-12-28 16:36 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2012-12-28 16:36 UTC (permalink / raw)
To: linux-arm-kernel
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
I took the reference of this call from MFD driver, which is the only
driver using this (i guess)
usb/dwc3.c driver had a generic implementation. I wasn't sure of the
implementation.
if someone can help, i willing to do that.
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
2012-12-28 16:36 ` Naveen Krishna Ch
@ 2013-01-15 6:23 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-01-15 6:23 UTC (permalink / raw)
To: linux-i2c
Cc: linux-arm-kernel, linux-samsung-soc, kgene.kim, grant.likely,
w.sang, linux-kernel, taeggyun.ko, balbi, thomas.abraham
On 28 December 2012 22:06, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
> On 28 December 2012 16:57, Naveen Krishna Chatradhi
> <ch.naveen@samsung.com> wrote:
>> Adds support for High Speed I2C driver found in Exynos5 and later
>> SoCs from Samsung. This driver currently supports Auto mode.
>>
>> Driver only supports Device Tree method of passing platform data.
>> Note: Added debugfs support for registers view, not tested.
>>
>> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> ---
>> Changes since v2: fixed comments from Felipe Balbi.
>> And minor fixes for the return values in exynos5_i2c_doxfer()
>>
>> drivers/i2c/busses/Kconfig | 7 +
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 744 insertions(+)
>> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>>
>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> index bdca511..4caea76 100644
>> --- a/drivers/i2c/busses/Kconfig
>> +++ b/drivers/i2c/busses/Kconfig
>> @@ -618,6 +618,13 @@ config I2C_S3C2410
>> Say Y here to include support for I2C controller in the
>> Samsung SoCs.
>>
>> +config I2C_EXYNOS5
>> + tristate "Exynos5 high-speed I2C driver"
>> + depends on ARCH_EXYNOS5
>> + help
>> + Say Y here to include support for High-speed I2C controller in the
>> + Exynos5 based Samsung SoCs.
>> +
>> config I2C_S6000
>> tristate "S6000 I2C support"
>> depends on XTENSA_VARIANT_S6000
>> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> index 6181f3f..4b1548c 100644
>> --- a/drivers/i2c/busses/Makefile
>> +++ b/drivers/i2c/busses/Makefile
>> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
>> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
>> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
>> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
>> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
>> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
>> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
>> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
>> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
>> new file mode 100644
>> index 0000000..a5eb959
>> --- /dev/null
>> +++ b/drivers/i2c/busses/i2c-exynos5.c
>> @@ -0,0 +1,736 @@
>> +/**
>> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
>> + *
>> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> +*/
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/debugfs.h>
>> +
>> +#include <linux/i2c.h>
>> +#include <linux/init.h>
>> +#include <linux/time.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/errno.h>
>> +#include <linux/err.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/clk.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_i2c.h>
>> +
>> +/* Register Map */
>> +#define HSI2C_CTL 0x00
>> +#define HSI2C_FIFO_CTL 0x04
>> +#define HSI2C_TRAILIG_CTL 0x08
>> +#define HSI2C_CLK_CTL 0x0C
>> +#define HSI2C_CLK_SLOT 0x10
>> +#define HSI2C_INT_ENABLE 0x20
>> +#define HSI2C_INT_STATUS 0x24
>> +#define HSI2C_ERR_STATUS 0x2C
>> +#define HSI2C_FIFO_STATUS 0x30
>> +#define HSI2C_TX_DATA 0x34
>> +#define HSI2C_RX_DATA 0x38
>> +#define HSI2C_CONF 0x40
>> +#define HSI2C_AUTO_CONF 0x44
>> +#define HSI2C_TIMEOUT 0x48
>> +#define HSI2C_MANUAL_CMD 0x4C
>> +#define HSI2C_TRANS_STATUS 0x50
>> +#define HSI2C_TIMING_HS1 0x54
>> +#define HSI2C_TIMING_HS2 0x58
>> +#define HSI2C_TIMING_HS3 0x5C
>> +#define HSI2C_TIMING_FS1 0x60
>> +#define HSI2C_TIMING_FS2 0x64
>> +#define HSI2C_TIMING_FS3 0x68
>> +#define HSI2C_TIMING_SLA 0x6C
>> +#define HSI2C_ADDR 0x70
>> +
>> +/* I2C_CTL Register bits */
>> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
>> +#define HSI2C_MASTER (1u << 3)
>> +#define HSI2C_RXCHON (1u << 6)
>> +#define HSI2C_TXCHON (1u << 7)
>> +#define HSI2C_SW_RST (1u << 31)
>> +
>> +/* I2C_FIFO_CTL Register bits */
>> +#define HSI2C_RXFIFO_EN (1u << 0)
>> +#define HSI2C_TXFIFO_EN (1u << 1)
>> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
>> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
>> +
>> +/* I2C_TRAILING_CTL Register bits */
>> +#define HSI2C_TRAILING_COUNT (0xf)
>> +
>> +/* I2C_INT_EN Register bits */
>> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
>> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
>> +#define HSI2C_INT_TRAILING_EN (1u << 6)
>> +#define HSI2C_INT_I2C_EN (1u << 9)
>> +
>> +/* I2C_FIFO_STAT Register bits */
>> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
>> +#define HSI2C_RX_FIFO_FULL (1u << 23)
>> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
>> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
>> +#define HSI2C_TX_FIFO_FULL (1u << 7)
>> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
>> +#define HSI2C_FIFO_EMPTY (0x1000100)
>> +
>> +/* I2C_CONF Register bits */
>> +#define HSI2C_AUTO_MODE (1u << 31)
>> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
>> +#define HSI2C_HS_MODE (1u << 29)
>> +
>> +/* I2C_AUTO_CONF Register bits */
>> +#define HSI2C_READ_WRITE (1u << 16)
>> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
>> +#define HSI2C_MASTER_RUN (1u << 31)
>> +
>> +/* I2C_TIMEOUT Register bits */
>> +#define HSI2C_TIMEOUT_EN (1u << 31)
>> +
>> +/* I2C_TRANS_STATUS register bits */
>> +#define HSI2C_MASTER_BUSY (1u << 17)
>> +#define HSI2C_SLAVE_BUSY (1u << 16)
>> +#define HSI2C_NO_DEV (1u << 3)
>> +#define HSI2C_NO_DEV_ACK (1u << 2)
>> +#define HSI2C_TRANS_ABORT (1u << 1)
>> +#define HSI2C_TRANS_DONE (1u << 0)
>> +
>> +/**
>> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
>> + * but currently we are facing booting issues beyond 1Mhz
>> + * So limiting HS-IIC bus speed to 1Mhz
>> +*/
>> +#define HSI2C_HS_TX_CLOCK 1000000
>> +#define HSI2C_FS_TX_CLOCK 400000
>> +
>> +#define HSI2C_FAST_SPD 0
>> +#define HSI2C_HIGH_SPD 1
>> +
>> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
>> +
>> +/* timeout for pm runtime autosuspend */
>> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
>> +
>> +struct exynos5_i2c {
>> + struct i2c_adapter adap;
>> + unsigned int suspended:1;
>> +
>> + struct i2c_msg *msg;
>> + unsigned int msg_idx;
>> + struct completion msg_complete;
>> + unsigned int msg_ptr;
>> +
>> + unsigned int irq;
>> +
>> + void __iomem *regs;
>> + struct clk *clk;
>> + struct device *dev;
>> + int gpios[2];
>> +
>> + int bus_num;
>> + int speed_mode;
>> + struct dentry *debugfs_root;
>> +};
>> +
>> +static const struct of_device_id exynos5_i2c_match[] = {
>> + { .compatible = "samsung,exynos5-hsi2c" },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
>> +
>> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
>> +{
>> + dev_vdbg(i2c->dev, "STOP\n");
>> +
>> + i2c->msg_idx++;
>> + if (err)
>> + i2c->msg_idx = err;
>> +
>> + /* Disable interrrupts */
>> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
>> + complete(&i2c->msg_complete);
>> +}
>> +
>> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
>> +
>> + /* Clear to enable Timeout */
>> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
>> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
>> +}
>> +
>> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
>> +{
>> + /* Start data transfer in Master mode */
>> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
>> + i2c_auto_conf |= HSI2C_MASTER_RUN;
>> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
>> +}
>> +
>> +/**
>> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
>> +*/
>> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long t_status;
>> + int timeout = 400;
>> +
>> + while (timeout-- > 0) {
>> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
>> +
>> + if (!(t_status & HSI2C_MASTER_BUSY))
>> + return 0;
>> +
>> + msleep(20);
>> + }
>> +
>> + return -ETIMEDOUT;
>> +}
>> +
>> +/**
>> + * exynos5_i2c_irq: top level IRQ servicing routine
>> +*/
>> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
>> +{
>> + struct exynos5_i2c *i2c = dev_id;
>> + unsigned long t_stat;
>> + unsigned char byte;
>> +
>> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
>> +
>> + if (t_stat & HSI2C_TRANS_ABORT) {
>> + /* deal with arbitration loss */
>> + dev_err(i2c->dev, "deal with arbitration loss\n");
>> + goto out;
>> + }
>> + if (i2c->msg->flags & I2C_M_RD) {
>> + if (t_stat & HSI2C_TRANS_DONE) {
>> + dev_dbg(i2c->dev, "Device found.");
>> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
>> + HSI2C_RX_FIFO_EMPTY) == 0) {
>> + byte = readl(i2c->regs + HSI2C_RX_DATA);
>> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
>> + i2c->msg->buf[i2c->msg_ptr++] = byte;
>> + }
>> +
>> + if (i2c->msg_ptr >= i2c->msg->len)
>> + exynos5_i2c_stop(i2c, 0);
>> +
>> + } else if (t_stat & HSI2C_NO_DEV) {
>> + dev_dbg(i2c->dev, "No device found.");
>> + exynos5_i2c_stop(i2c, -ENXIO);
>> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
>> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
>> + dev_dbg(i2c->dev, "No device Ack.");
>> + exynos5_i2c_stop(i2c, -ENXIO);
>> + }
>> + } else {
>> + byte = i2c->msg->buf[i2c->msg_ptr++];
>> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
>> + writel(byte, i2c->regs + HSI2C_TX_DATA);
>> +
>> + if (i2c->msg_ptr >= i2c->msg->len)
>> + exynos5_i2c_stop(i2c, 0);
>> + }
>> +
>> + out:
>> + /* Set those bits to clear them */
>> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
>> + i2c->regs + HSI2C_INT_STATUS);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
>> + struct i2c_msg *msgs)
>> +{
>> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
>> + unsigned long i2c_auto_conf;
>> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
>> + unsigned long usi_int_en = 0;
>> +
>> + exynos5_i2c_en_timeout(i2c);
>> +
>> + if (msgs->flags & I2C_M_RD) {
>> + usi_ctl &= ~HSI2C_TXCHON;
>> + usi_ctl |= HSI2C_RXCHON;
>> +
>> + i2c_auto_conf |= HSI2C_READ_WRITE;
>> +
>> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
>> + HSI2C_INT_TRAILING_EN);
>> + } else {
>> + usi_ctl &= ~HSI2C_RXCHON;
>> + usi_ctl |= HSI2C_TXCHON;
>> +
>> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
>> +
>> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
>> + }
>> +
>> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
>> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
>> +
>> + i2c_auto_conf |= i2c->msg->len;
>> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
>> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
>> +
>> + exynos5_i2c_master_run(i2c);
>> +
>> + /* Enable appropriate interrupts */
>> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
>> +}
>> +
>> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
>> +{
>> + unsigned long timeout;
>> + int ret;
>> +
>> + if (i2c->suspended) {
>> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
>> + return -EIO;
>> + }
>> +
>> + if (exynos5_i2c_set_master(i2c)) {
>> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
>> + return -EAGAIN;
>> + }
>> +
>> + i2c->msg = msgs;
>> + i2c->msg_ptr = 0;
>> + i2c->msg_idx = 0;
>> +
>> + INIT_COMPLETION(i2c->msg_complete);
>> +
>> + exynos5_i2c_message_start(i2c, msgs);
>> +
>> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
>> + EXYNOS5_I2C_TIMEOUT);
>> +
>> + ret = i2c->msg_idx;
>> +
>> + if (timeout == 0)
>> + dev_dbg(i2c->dev, "timeout\n");
>> + else if ((ret != msgs->len) && (ret < 0))
>> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
>> +
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
>> + struct i2c_msg *msgs, int num)
>> +{
>> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
>> + int retry, i;
>> + int ret;
>> +
>> + ret = pm_runtime_get_sync(i2c->dev);
>> + if (IS_ERR_VALUE(ret))
>> + goto out;
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + for (retry = 0; retry < adap->retries; retry++) {
>> + for (i = 0; i < num; i++) {
>> + ret = exynos5_i2c_doxfer(i2c, msgs);
>> + msgs++;
>> +
>> + if (ret == -EAGAIN)
>> + break;
>> + }
>> + if (i == num) {
>> + clk_disable_unprepare(i2c->clk);
>> +
>> + if (i2c->msg_idx == -ENXIO)
>> + ret = i2c->msg_idx;
>> + else
>> + ret = num;
>> + goto out;
>> + }
>> +
>> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
>> +
>> + udelay(100);
>> + }
>> +
>> + ret = -EREMOTEIO;
>> + clk_disable_unprepare(i2c->clk);
>> + out:
>> + pm_runtime_mark_last_busy(i2c->dev);
>> + pm_runtime_put_autosuspend(i2c->dev);
>> + return ret;
>> +}
>> +
>> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
>> +{
>> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
>> +}
>> +
>> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
>> + .master_xfer = exynos5_i2c_xfer,
>> + .functionality = exynos5_i2c_func,
>> +};
>> +
>> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
>> +{
>> + unsigned long i2c_timing_s1;
>> + unsigned long i2c_timing_s2;
>> + unsigned long i2c_timing_s3;
>> + unsigned long i2c_timing_sla;
>> + unsigned int op_clk;
>> + unsigned int clkin = clk_get_rate(i2c->clk);
>> + unsigned int n_clkdiv;
>> + unsigned int t_start_su, t_start_hd;
>> + unsigned int t_stop_su;
>> + unsigned int t_data_su, t_data_hd;
>> + unsigned int t_scl_l, t_scl_h;
>> + unsigned int t_sr_release;
>> + unsigned int t_ftl_cycle;
>> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
>> +
>> + if (speed_mode == HSI2C_HIGH_SPD)
>> + op_clk = HSI2C_HS_TX_CLOCK;
>> + else
>> + op_clk = HSI2C_FS_TX_CLOCK;
>> +
>> + /* FPCLK / FI2C =
>> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
>> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
>> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
>> + * uTemp2 = TSCLK_L + TSCLK_H
>> + */
>> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
>> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
>> +
>> + /* CLK_DIV max is 256 */
>> + for (i = 0; i < 256; i++) {
>> + utemp1 = utemp0 / (i + 1);
>> + /* SCLK_L/H max is 255
>> + * so sclk_l + sclk_h has max value of 510
>> + */
>> + if (utemp1 < 511) {
>> + utemp2 = utemp1 - 2;
>> + break;
>> + }
>> + }
>> +
>> + n_clkdiv = i;
>> + t_scl_l = utemp2 / 2;
>> + t_scl_h = utemp2 / 2;
>> + t_start_su = t_scl_l;
>> + t_start_hd = t_scl_l;
>> + t_stop_su = t_scl_l;
>> + t_data_su = t_scl_l / 2;
>> + t_data_hd = t_scl_l / 2;
>> + t_sr_release = utemp2;
>> +
>> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
>> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
>> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
>> + i2c_timing_sla = t_data_hd << 0;
>> +
>> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
>> + t_start_su, t_start_hd, t_stop_su);
>> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
>> + t_data_su, t_scl_l, t_scl_h);
>> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
>> + n_clkdiv, t_sr_release);
>> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
>> +
>> + if (speed_mode == HSI2C_HIGH_SPD) {
>> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
>> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
>> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
>> + } else {
>> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
>> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
>> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
>> + }
>> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * Parse a list of GPIOs from a node property and request each one
>> + *
>> + * @param i2c i2c driver data
>> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
>> +*/
>> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
>> +{
>> + int idx, gpio, ret;
>> +
>> + for (idx = 0; idx < 2; idx++) {
>> + gpio = of_get_gpio(i2c->dev->of_node, idx);
>> + if (!gpio_is_valid(gpio)) {
>> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
>> + return -EINVAL;
>> + }
>> + i2c->gpios[idx] = gpio;
>> +
>> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
>> + if (ret) {
>> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
>> + return -EINVAL;
>> + }
>> + }
>> + return 0;
>> +}
>> +
>> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
>> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
>> + unsigned long usi_fifo_ctl;
>> +
>> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
>> +
>> + /* Set default trigger level for TXFIFO and RXFIFO */
>> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
>> +
>> + /* Enable RXFIFO and TXFIFO */
>> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
>> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
>> +
>> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
>> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
>> + /* Configure I2C controller in High speed mode */
>> + i2c_conf |= HSI2C_HS_MODE;
>> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
>> + } else {
>> + /* Configure I2C controller in Fast speed mode */
>> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
>> + }
>> +}
>> +
>> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
>> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
>> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
>> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
>> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
>> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
>> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
>> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
>> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
>> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
>> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
>> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
>> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
>> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
>> +};
>> +
>> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
>> + .regs = exynos5_hsi2c_regs,
>> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
>> +};
>> +
>> +static struct dentry *exynos5_hsi2c_reg_debugfs;
>> +
>> +static int exynos5_i2c_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *np = pdev->dev.of_node;
>> + struct exynos5_i2c *i2c;
>> + int ret;
>> +
>> + if (!np) {
>> + dev_err(&pdev->dev, "no device node\n");
>> + return -ENOENT;
>> + }
>> +
>> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
>> + if (!i2c) {
>> + dev_err(&pdev->dev, "no memory for state\n");
>> + return -ENOMEM;
>> + }
>> +
>> + i2c->bus_num = -1;
>> + /* Mode of operation High/Fast Speed mode */
>> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
>> +
>> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
>> + i2c->adap.owner = THIS_MODULE;
>> + i2c->adap.algo = &exynos5_i2c_algorithm;
>> + i2c->adap.retries = 2;
>> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
>> +
>> + i2c->dev = &pdev->dev;
>> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
>> + if (IS_ERR(i2c->clk)) {
>> + dev_err(&pdev->dev, "cannot get clock\n");
>> + ret = -ENOENT;
>> + goto err_noclk;
>> + }
>> +
>> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + i2c->regs = of_iomap(np, 0);
>> + if (!i2c->regs) {
>> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
>> + ret = -ENXIO;
>> + goto err_clk;
>> + }
>> +
>> + /* inititalise the gpio */
>> + if (exynos5_i2c_parse_dt_gpio(i2c))
>> + return -EINVAL;
>> +
>> + i2c->irq = irq_of_parse_and_map(np, 0);
>> + if (i2c->irq) {
>> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
>> + 0, dev_name(&pdev->dev), i2c);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
>> + i2c->irq);
>> + goto err_iomap;
>> + }
>> + }
>> +
>> + /*
>> + * TODO: Use private lock to avoid race conditions as
>> + * mentioned in pm_runtime.txt
>> + */
>> + pm_runtime_enable(i2c->dev);
>> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
>> + pm_runtime_use_autosuspend(i2c->dev);
>> +
>> + ret = pm_runtime_get_sync(i2c->dev);
>> + if (IS_ERR_VALUE(ret))
>> + goto err_iomap;
>> +
>> + exynos5_i2c_init(i2c);
>> +
>> + i2c->adap.algo_data = i2c;
>> + i2c->adap.dev.parent = &pdev->dev;
>> + i2c->adap.nr = i2c->bus_num;
>> + i2c->adap.dev.of_node = pdev->dev.of_node;
>> +
>> + ret = i2c_add_numbered_adapter(&i2c->adap);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
>> + goto err_pm;
>> + }
>> +
>> + init_completion(&i2c->msg_complete);
>> + of_i2c_register_devices(&i2c->adap);
>> + platform_set_drvdata(pdev, i2c);
>> +
>> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
>> + dev_name(&i2c->adap.dev));
>> +
>> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
>> + S_IFREG | S_IRUGO,
>> + NULL, &exynos5_hsi2c_regset);
> I took the reference of this call from MFD driver, which is the only
> driver using this (i guess)
> usb/dwc3.c driver had a generic implementation. I wasn't sure of the
> implementation.
> if someone can help, i willing to do that.
>> + clk_disable_unprepare(i2c->clk);
>> + pm_runtime_mark_last_busy(i2c->dev);
>> + pm_runtime_put_autosuspend(i2c->dev);
>> + return 0;
>> +
>> + err_pm:
>> + pm_runtime_put(i2c->dev);
>> + pm_runtime_disable(&pdev->dev);
>> + err_iomap:
>> + iounmap(i2c->regs);
>> + err_clk:
>> + clk_disable_unprepare(i2c->clk);
>> + err_noclk:
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_remove(struct platform_device *pdev)
>> +{
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> + int ret;
>> +
>> + ret = pm_runtime_get_sync(&pdev->dev);
>> + if (IS_ERR_VALUE(ret))
>> + return ret;
>> +
>> + clk_disable_unprepare(i2c->clk);
>> + pm_runtime_put(&pdev->dev);
>> + pm_runtime_disable(&pdev->dev);
>> +
>> + i2c_del_adapter(&i2c->adap);
>> +
>> + iounmap(i2c->regs);
>> + platform_set_drvdata(pdev, NULL);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM
>> +static int exynos5_i2c_suspend_noirq(struct device *dev)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> +
>> + i2c->suspended = 1;
>> +
>> + return 0;
>> +}
>> +
>> +static int exynos5_i2c_resume_noirq(struct device *dev)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> +
>> + clk_prepare_enable(i2c->clk);
>> + exynos5_i2c_init(i2c);
>> + clk_disable_unprepare(i2c->clk);
>> + i2c->suspended = 0;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
>> + .suspend_noirq = exynos5_i2c_suspend_noirq,
>> + .resume_noirq = exynos5_i2c_resume_noirq,
>> +};
>> +
>> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
>> +#else
>> +#define EXYNOS5_DEV_PM_OPS NULL
>> +#endif
>> +
>> +static struct platform_driver exynos5_i2c_driver = {
>> + .probe = exynos5_i2c_probe,
>> + .remove = exynos5_i2c_remove,
>> + .driver = {
>> + .owner = THIS_MODULE,
>> + .name = "exynos5-hsi2c",
>> + .pm = EXYNOS5_DEV_PM_OPS,
>> + .of_match_table = exynos5_i2c_match,
>> + },
>> +};
>> +
>> +static int __init i2c_adap_exynos5_init(void)
>> +{
>> + return platform_driver_register(&exynos5_i2c_driver);
>> +}
>> +subsys_initcall(i2c_adap_exynos5_init);
>> +
>> +static void __exit i2c_adap_exynos5_exit(void)
>> +{
>> + platform_driver_unregister(&exynos5_i2c_driver);
>> +}
>> +module_exit(i2c_adap_exynos5_exit);
>> +
>> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
>> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
>> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.7.9.5
Any comments please. Thanks in advance
>>
>
>
>
> --
> Shine bright,
> (: Nav :)
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2013-01-15 6:23 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-01-15 6:23 UTC (permalink / raw)
To: linux-arm-kernel
On 28 December 2012 22:06, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
> On 28 December 2012 16:57, Naveen Krishna Chatradhi
> <ch.naveen@samsung.com> wrote:
>> Adds support for High Speed I2C driver found in Exynos5 and later
>> SoCs from Samsung. This driver currently supports Auto mode.
>>
>> Driver only supports Device Tree method of passing platform data.
>> Note: Added debugfs support for registers view, not tested.
>>
>> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> ---
>> Changes since v2: fixed comments from Felipe Balbi.
>> And minor fixes for the return values in exynos5_i2c_doxfer()
>>
>> drivers/i2c/busses/Kconfig | 7 +
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 744 insertions(+)
>> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>>
>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> index bdca511..4caea76 100644
>> --- a/drivers/i2c/busses/Kconfig
>> +++ b/drivers/i2c/busses/Kconfig
>> @@ -618,6 +618,13 @@ config I2C_S3C2410
>> Say Y here to include support for I2C controller in the
>> Samsung SoCs.
>>
>> +config I2C_EXYNOS5
>> + tristate "Exynos5 high-speed I2C driver"
>> + depends on ARCH_EXYNOS5
>> + help
>> + Say Y here to include support for High-speed I2C controller in the
>> + Exynos5 based Samsung SoCs.
>> +
>> config I2C_S6000
>> tristate "S6000 I2C support"
>> depends on XTENSA_VARIANT_S6000
>> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> index 6181f3f..4b1548c 100644
>> --- a/drivers/i2c/busses/Makefile
>> +++ b/drivers/i2c/busses/Makefile
>> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
>> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
>> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
>> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
>> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
>> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
>> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
>> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
>> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
>> new file mode 100644
>> index 0000000..a5eb959
>> --- /dev/null
>> +++ b/drivers/i2c/busses/i2c-exynos5.c
>> @@ -0,0 +1,736 @@
>> +/**
>> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
>> + *
>> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> +*/
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/debugfs.h>
>> +
>> +#include <linux/i2c.h>
>> +#include <linux/init.h>
>> +#include <linux/time.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/errno.h>
>> +#include <linux/err.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/clk.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_i2c.h>
>> +
>> +/* Register Map */
>> +#define HSI2C_CTL 0x00
>> +#define HSI2C_FIFO_CTL 0x04
>> +#define HSI2C_TRAILIG_CTL 0x08
>> +#define HSI2C_CLK_CTL 0x0C
>> +#define HSI2C_CLK_SLOT 0x10
>> +#define HSI2C_INT_ENABLE 0x20
>> +#define HSI2C_INT_STATUS 0x24
>> +#define HSI2C_ERR_STATUS 0x2C
>> +#define HSI2C_FIFO_STATUS 0x30
>> +#define HSI2C_TX_DATA 0x34
>> +#define HSI2C_RX_DATA 0x38
>> +#define HSI2C_CONF 0x40
>> +#define HSI2C_AUTO_CONF 0x44
>> +#define HSI2C_TIMEOUT 0x48
>> +#define HSI2C_MANUAL_CMD 0x4C
>> +#define HSI2C_TRANS_STATUS 0x50
>> +#define HSI2C_TIMING_HS1 0x54
>> +#define HSI2C_TIMING_HS2 0x58
>> +#define HSI2C_TIMING_HS3 0x5C
>> +#define HSI2C_TIMING_FS1 0x60
>> +#define HSI2C_TIMING_FS2 0x64
>> +#define HSI2C_TIMING_FS3 0x68
>> +#define HSI2C_TIMING_SLA 0x6C
>> +#define HSI2C_ADDR 0x70
>> +
>> +/* I2C_CTL Register bits */
>> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
>> +#define HSI2C_MASTER (1u << 3)
>> +#define HSI2C_RXCHON (1u << 6)
>> +#define HSI2C_TXCHON (1u << 7)
>> +#define HSI2C_SW_RST (1u << 31)
>> +
>> +/* I2C_FIFO_CTL Register bits */
>> +#define HSI2C_RXFIFO_EN (1u << 0)
>> +#define HSI2C_TXFIFO_EN (1u << 1)
>> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
>> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
>> +
>> +/* I2C_TRAILING_CTL Register bits */
>> +#define HSI2C_TRAILING_COUNT (0xf)
>> +
>> +/* I2C_INT_EN Register bits */
>> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
>> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
>> +#define HSI2C_INT_TRAILING_EN (1u << 6)
>> +#define HSI2C_INT_I2C_EN (1u << 9)
>> +
>> +/* I2C_FIFO_STAT Register bits */
>> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
>> +#define HSI2C_RX_FIFO_FULL (1u << 23)
>> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
>> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
>> +#define HSI2C_TX_FIFO_FULL (1u << 7)
>> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
>> +#define HSI2C_FIFO_EMPTY (0x1000100)
>> +
>> +/* I2C_CONF Register bits */
>> +#define HSI2C_AUTO_MODE (1u << 31)
>> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
>> +#define HSI2C_HS_MODE (1u << 29)
>> +
>> +/* I2C_AUTO_CONF Register bits */
>> +#define HSI2C_READ_WRITE (1u << 16)
>> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
>> +#define HSI2C_MASTER_RUN (1u << 31)
>> +
>> +/* I2C_TIMEOUT Register bits */
>> +#define HSI2C_TIMEOUT_EN (1u << 31)
>> +
>> +/* I2C_TRANS_STATUS register bits */
>> +#define HSI2C_MASTER_BUSY (1u << 17)
>> +#define HSI2C_SLAVE_BUSY (1u << 16)
>> +#define HSI2C_NO_DEV (1u << 3)
>> +#define HSI2C_NO_DEV_ACK (1u << 2)
>> +#define HSI2C_TRANS_ABORT (1u << 1)
>> +#define HSI2C_TRANS_DONE (1u << 0)
>> +
>> +/**
>> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
>> + * but currently we are facing booting issues beyond 1Mhz
>> + * So limiting HS-IIC bus speed to 1Mhz
>> +*/
>> +#define HSI2C_HS_TX_CLOCK 1000000
>> +#define HSI2C_FS_TX_CLOCK 400000
>> +
>> +#define HSI2C_FAST_SPD 0
>> +#define HSI2C_HIGH_SPD 1
>> +
>> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
>> +
>> +/* timeout for pm runtime autosuspend */
>> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
>> +
>> +struct exynos5_i2c {
>> + struct i2c_adapter adap;
>> + unsigned int suspended:1;
>> +
>> + struct i2c_msg *msg;
>> + unsigned int msg_idx;
>> + struct completion msg_complete;
>> + unsigned int msg_ptr;
>> +
>> + unsigned int irq;
>> +
>> + void __iomem *regs;
>> + struct clk *clk;
>> + struct device *dev;
>> + int gpios[2];
>> +
>> + int bus_num;
>> + int speed_mode;
>> + struct dentry *debugfs_root;
>> +};
>> +
>> +static const struct of_device_id exynos5_i2c_match[] = {
>> + { .compatible = "samsung,exynos5-hsi2c" },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
>> +
>> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
>> +{
>> + dev_vdbg(i2c->dev, "STOP\n");
>> +
>> + i2c->msg_idx++;
>> + if (err)
>> + i2c->msg_idx = err;
>> +
>> + /* Disable interrrupts */
>> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
>> + complete(&i2c->msg_complete);
>> +}
>> +
>> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
>> +
>> + /* Clear to enable Timeout */
>> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
>> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
>> +}
>> +
>> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
>> +{
>> + /* Start data transfer in Master mode */
>> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
>> + i2c_auto_conf |= HSI2C_MASTER_RUN;
>> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
>> +}
>> +
>> +/**
>> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
>> +*/
>> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long t_status;
>> + int timeout = 400;
>> +
>> + while (timeout-- > 0) {
>> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
>> +
>> + if (!(t_status & HSI2C_MASTER_BUSY))
>> + return 0;
>> +
>> + msleep(20);
>> + }
>> +
>> + return -ETIMEDOUT;
>> +}
>> +
>> +/**
>> + * exynos5_i2c_irq: top level IRQ servicing routine
>> +*/
>> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
>> +{
>> + struct exynos5_i2c *i2c = dev_id;
>> + unsigned long t_stat;
>> + unsigned char byte;
>> +
>> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
>> +
>> + if (t_stat & HSI2C_TRANS_ABORT) {
>> + /* deal with arbitration loss */
>> + dev_err(i2c->dev, "deal with arbitration loss\n");
>> + goto out;
>> + }
>> + if (i2c->msg->flags & I2C_M_RD) {
>> + if (t_stat & HSI2C_TRANS_DONE) {
>> + dev_dbg(i2c->dev, "Device found.");
>> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
>> + HSI2C_RX_FIFO_EMPTY) == 0) {
>> + byte = readl(i2c->regs + HSI2C_RX_DATA);
>> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
>> + i2c->msg->buf[i2c->msg_ptr++] = byte;
>> + }
>> +
>> + if (i2c->msg_ptr >= i2c->msg->len)
>> + exynos5_i2c_stop(i2c, 0);
>> +
>> + } else if (t_stat & HSI2C_NO_DEV) {
>> + dev_dbg(i2c->dev, "No device found.");
>> + exynos5_i2c_stop(i2c, -ENXIO);
>> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
>> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
>> + dev_dbg(i2c->dev, "No device Ack.");
>> + exynos5_i2c_stop(i2c, -ENXIO);
>> + }
>> + } else {
>> + byte = i2c->msg->buf[i2c->msg_ptr++];
>> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
>> + writel(byte, i2c->regs + HSI2C_TX_DATA);
>> +
>> + if (i2c->msg_ptr >= i2c->msg->len)
>> + exynos5_i2c_stop(i2c, 0);
>> + }
>> +
>> + out:
>> + /* Set those bits to clear them */
>> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
>> + i2c->regs + HSI2C_INT_STATUS);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
>> + struct i2c_msg *msgs)
>> +{
>> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
>> + unsigned long i2c_auto_conf;
>> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
>> + unsigned long usi_int_en = 0;
>> +
>> + exynos5_i2c_en_timeout(i2c);
>> +
>> + if (msgs->flags & I2C_M_RD) {
>> + usi_ctl &= ~HSI2C_TXCHON;
>> + usi_ctl |= HSI2C_RXCHON;
>> +
>> + i2c_auto_conf |= HSI2C_READ_WRITE;
>> +
>> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
>> + HSI2C_INT_TRAILING_EN);
>> + } else {
>> + usi_ctl &= ~HSI2C_RXCHON;
>> + usi_ctl |= HSI2C_TXCHON;
>> +
>> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
>> +
>> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
>> + }
>> +
>> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
>> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
>> +
>> + i2c_auto_conf |= i2c->msg->len;
>> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
>> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
>> +
>> + exynos5_i2c_master_run(i2c);
>> +
>> + /* Enable appropriate interrupts */
>> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
>> +}
>> +
>> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
>> +{
>> + unsigned long timeout;
>> + int ret;
>> +
>> + if (i2c->suspended) {
>> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
>> + return -EIO;
>> + }
>> +
>> + if (exynos5_i2c_set_master(i2c)) {
>> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
>> + return -EAGAIN;
>> + }
>> +
>> + i2c->msg = msgs;
>> + i2c->msg_ptr = 0;
>> + i2c->msg_idx = 0;
>> +
>> + INIT_COMPLETION(i2c->msg_complete);
>> +
>> + exynos5_i2c_message_start(i2c, msgs);
>> +
>> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
>> + EXYNOS5_I2C_TIMEOUT);
>> +
>> + ret = i2c->msg_idx;
>> +
>> + if (timeout == 0)
>> + dev_dbg(i2c->dev, "timeout\n");
>> + else if ((ret != msgs->len) && (ret < 0))
>> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
>> +
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
>> + struct i2c_msg *msgs, int num)
>> +{
>> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
>> + int retry, i;
>> + int ret;
>> +
>> + ret = pm_runtime_get_sync(i2c->dev);
>> + if (IS_ERR_VALUE(ret))
>> + goto out;
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + for (retry = 0; retry < adap->retries; retry++) {
>> + for (i = 0; i < num; i++) {
>> + ret = exynos5_i2c_doxfer(i2c, msgs);
>> + msgs++;
>> +
>> + if (ret == -EAGAIN)
>> + break;
>> + }
>> + if (i == num) {
>> + clk_disable_unprepare(i2c->clk);
>> +
>> + if (i2c->msg_idx == -ENXIO)
>> + ret = i2c->msg_idx;
>> + else
>> + ret = num;
>> + goto out;
>> + }
>> +
>> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
>> +
>> + udelay(100);
>> + }
>> +
>> + ret = -EREMOTEIO;
>> + clk_disable_unprepare(i2c->clk);
>> + out:
>> + pm_runtime_mark_last_busy(i2c->dev);
>> + pm_runtime_put_autosuspend(i2c->dev);
>> + return ret;
>> +}
>> +
>> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
>> +{
>> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
>> +}
>> +
>> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
>> + .master_xfer = exynos5_i2c_xfer,
>> + .functionality = exynos5_i2c_func,
>> +};
>> +
>> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
>> +{
>> + unsigned long i2c_timing_s1;
>> + unsigned long i2c_timing_s2;
>> + unsigned long i2c_timing_s3;
>> + unsigned long i2c_timing_sla;
>> + unsigned int op_clk;
>> + unsigned int clkin = clk_get_rate(i2c->clk);
>> + unsigned int n_clkdiv;
>> + unsigned int t_start_su, t_start_hd;
>> + unsigned int t_stop_su;
>> + unsigned int t_data_su, t_data_hd;
>> + unsigned int t_scl_l, t_scl_h;
>> + unsigned int t_sr_release;
>> + unsigned int t_ftl_cycle;
>> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
>> +
>> + if (speed_mode == HSI2C_HIGH_SPD)
>> + op_clk = HSI2C_HS_TX_CLOCK;
>> + else
>> + op_clk = HSI2C_FS_TX_CLOCK;
>> +
>> + /* FPCLK / FI2C =
>> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
>> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
>> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
>> + * uTemp2 = TSCLK_L + TSCLK_H
>> + */
>> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
>> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
>> +
>> + /* CLK_DIV max is 256 */
>> + for (i = 0; i < 256; i++) {
>> + utemp1 = utemp0 / (i + 1);
>> + /* SCLK_L/H max is 255
>> + * so sclk_l + sclk_h has max value of 510
>> + */
>> + if (utemp1 < 511) {
>> + utemp2 = utemp1 - 2;
>> + break;
>> + }
>> + }
>> +
>> + n_clkdiv = i;
>> + t_scl_l = utemp2 / 2;
>> + t_scl_h = utemp2 / 2;
>> + t_start_su = t_scl_l;
>> + t_start_hd = t_scl_l;
>> + t_stop_su = t_scl_l;
>> + t_data_su = t_scl_l / 2;
>> + t_data_hd = t_scl_l / 2;
>> + t_sr_release = utemp2;
>> +
>> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
>> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
>> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
>> + i2c_timing_sla = t_data_hd << 0;
>> +
>> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
>> + t_start_su, t_start_hd, t_stop_su);
>> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
>> + t_data_su, t_scl_l, t_scl_h);
>> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
>> + n_clkdiv, t_sr_release);
>> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
>> +
>> + if (speed_mode == HSI2C_HIGH_SPD) {
>> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
>> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
>> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
>> + } else {
>> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
>> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
>> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
>> + }
>> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * Parse a list of GPIOs from a node property and request each one
>> + *
>> + * @param i2c i2c driver data
>> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
>> +*/
>> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
>> +{
>> + int idx, gpio, ret;
>> +
>> + for (idx = 0; idx < 2; idx++) {
>> + gpio = of_get_gpio(i2c->dev->of_node, idx);
>> + if (!gpio_is_valid(gpio)) {
>> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
>> + return -EINVAL;
>> + }
>> + i2c->gpios[idx] = gpio;
>> +
>> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
>> + if (ret) {
>> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
>> + return -EINVAL;
>> + }
>> + }
>> + return 0;
>> +}
>> +
>> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
>> +{
>> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
>> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
>> + unsigned long usi_fifo_ctl;
>> +
>> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
>> +
>> + /* Set default trigger level for TXFIFO and RXFIFO */
>> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
>> +
>> + /* Enable RXFIFO and TXFIFO */
>> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
>> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
>> +
>> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
>> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
>> + /* Configure I2C controller in High speed mode */
>> + i2c_conf |= HSI2C_HS_MODE;
>> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
>> + } else {
>> + /* Configure I2C controller in Fast speed mode */
>> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
>> + }
>> +}
>> +
>> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
>> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
>> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
>> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
>> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
>> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
>> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
>> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
>> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
>> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
>> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
>> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
>> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
>> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
>> +};
>> +
>> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
>> + .regs = exynos5_hsi2c_regs,
>> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
>> +};
>> +
>> +static struct dentry *exynos5_hsi2c_reg_debugfs;
>> +
>> +static int exynos5_i2c_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *np = pdev->dev.of_node;
>> + struct exynos5_i2c *i2c;
>> + int ret;
>> +
>> + if (!np) {
>> + dev_err(&pdev->dev, "no device node\n");
>> + return -ENOENT;
>> + }
>> +
>> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
>> + if (!i2c) {
>> + dev_err(&pdev->dev, "no memory for state\n");
>> + return -ENOMEM;
>> + }
>> +
>> + i2c->bus_num = -1;
>> + /* Mode of operation High/Fast Speed mode */
>> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
>> +
>> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
>> + i2c->adap.owner = THIS_MODULE;
>> + i2c->adap.algo = &exynos5_i2c_algorithm;
>> + i2c->adap.retries = 2;
>> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
>> +
>> + i2c->dev = &pdev->dev;
>> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
>> + if (IS_ERR(i2c->clk)) {
>> + dev_err(&pdev->dev, "cannot get clock\n");
>> + ret = -ENOENT;
>> + goto err_noclk;
>> + }
>> +
>> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + i2c->regs = of_iomap(np, 0);
>> + if (!i2c->regs) {
>> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
>> + ret = -ENXIO;
>> + goto err_clk;
>> + }
>> +
>> + /* inititalise the gpio */
>> + if (exynos5_i2c_parse_dt_gpio(i2c))
>> + return -EINVAL;
>> +
>> + i2c->irq = irq_of_parse_and_map(np, 0);
>> + if (i2c->irq) {
>> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
>> + 0, dev_name(&pdev->dev), i2c);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
>> + i2c->irq);
>> + goto err_iomap;
>> + }
>> + }
>> +
>> + /*
>> + * TODO: Use private lock to avoid race conditions as
>> + * mentioned in pm_runtime.txt
>> + */
>> + pm_runtime_enable(i2c->dev);
>> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
>> + pm_runtime_use_autosuspend(i2c->dev);
>> +
>> + ret = pm_runtime_get_sync(i2c->dev);
>> + if (IS_ERR_VALUE(ret))
>> + goto err_iomap;
>> +
>> + exynos5_i2c_init(i2c);
>> +
>> + i2c->adap.algo_data = i2c;
>> + i2c->adap.dev.parent = &pdev->dev;
>> + i2c->adap.nr = i2c->bus_num;
>> + i2c->adap.dev.of_node = pdev->dev.of_node;
>> +
>> + ret = i2c_add_numbered_adapter(&i2c->adap);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
>> + goto err_pm;
>> + }
>> +
>> + init_completion(&i2c->msg_complete);
>> + of_i2c_register_devices(&i2c->adap);
>> + platform_set_drvdata(pdev, i2c);
>> +
>> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
>> + dev_name(&i2c->adap.dev));
>> +
>> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
>> + S_IFREG | S_IRUGO,
>> + NULL, &exynos5_hsi2c_regset);
> I took the reference of this call from MFD driver, which is the only
> driver using this (i guess)
> usb/dwc3.c driver had a generic implementation. I wasn't sure of the
> implementation.
> if someone can help, i willing to do that.
>> + clk_disable_unprepare(i2c->clk);
>> + pm_runtime_mark_last_busy(i2c->dev);
>> + pm_runtime_put_autosuspend(i2c->dev);
>> + return 0;
>> +
>> + err_pm:
>> + pm_runtime_put(i2c->dev);
>> + pm_runtime_disable(&pdev->dev);
>> + err_iomap:
>> + iounmap(i2c->regs);
>> + err_clk:
>> + clk_disable_unprepare(i2c->clk);
>> + err_noclk:
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_remove(struct platform_device *pdev)
>> +{
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> + int ret;
>> +
>> + ret = pm_runtime_get_sync(&pdev->dev);
>> + if (IS_ERR_VALUE(ret))
>> + return ret;
>> +
>> + clk_disable_unprepare(i2c->clk);
>> + pm_runtime_put(&pdev->dev);
>> + pm_runtime_disable(&pdev->dev);
>> +
>> + i2c_del_adapter(&i2c->adap);
>> +
>> + iounmap(i2c->regs);
>> + platform_set_drvdata(pdev, NULL);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM
>> +static int exynos5_i2c_suspend_noirq(struct device *dev)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> +
>> + i2c->suspended = 1;
>> +
>> + return 0;
>> +}
>> +
>> +static int exynos5_i2c_resume_noirq(struct device *dev)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
>> +
>> + clk_prepare_enable(i2c->clk);
>> + exynos5_i2c_init(i2c);
>> + clk_disable_unprepare(i2c->clk);
>> + i2c->suspended = 0;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
>> + .suspend_noirq = exynos5_i2c_suspend_noirq,
>> + .resume_noirq = exynos5_i2c_resume_noirq,
>> +};
>> +
>> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
>> +#else
>> +#define EXYNOS5_DEV_PM_OPS NULL
>> +#endif
>> +
>> +static struct platform_driver exynos5_i2c_driver = {
>> + .probe = exynos5_i2c_probe,
>> + .remove = exynos5_i2c_remove,
>> + .driver = {
>> + .owner = THIS_MODULE,
>> + .name = "exynos5-hsi2c",
>> + .pm = EXYNOS5_DEV_PM_OPS,
>> + .of_match_table = exynos5_i2c_match,
>> + },
>> +};
>> +
>> +static int __init i2c_adap_exynos5_init(void)
>> +{
>> + return platform_driver_register(&exynos5_i2c_driver);
>> +}
>> +subsys_initcall(i2c_adap_exynos5_init);
>> +
>> +static void __exit i2c_adap_exynos5_exit(void)
>> +{
>> + platform_driver_unregister(&exynos5_i2c_driver);
>> +}
>> +module_exit(i2c_adap_exynos5_exit);
>> +
>> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
>> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
>> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.7.9.5
Any comments please. Thanks in advance
>>
>
>
>
> --
> Shine bright,
> (: Nav :)
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
2012-12-28 11:27 ` Naveen Krishna Chatradhi
(?)
@ 2013-01-23 5:05 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-01-23 5:05 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ, balbi-l0cyMroinI0,
thomas.abraham-QSEj5FYQhm4dnm+yROfE0A
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
Any comments please.
Or shall i re base and resent
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2013-01-23 5:05 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-01-23 5:05 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-arm-kernel, linux-samsung-soc,
devicetree-discuss, kgene.kim, grant.likely, w.sang, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
Any comments please.
Or shall i re base and resent
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH v3] i2c: exynos5: add High Speed I2C controller driver
@ 2013-01-23 5:05 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-01-23 5:05 UTC (permalink / raw)
To: linux-arm-kernel
On 28 December 2012 16:57, Naveen Krishna Chatradhi
<ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method of passing platform data.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v2: fixed comments from Felipe Balbi.
> And minor fixes for the return values in exynos5_i2c_doxfer()
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 736 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 744 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bdca511..4caea76 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -618,6 +618,13 @@ config I2C_S3C2410
> Say Y here to include support for I2C controller in the
> Samsung SoCs.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5
> + help
> + Say Y here to include support for High-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_S6000
> tristate "S6000 I2C support"
> depends on XTENSA_VARIANT_S6000
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 6181f3f..4b1548c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..a5eb959
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,736 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
> +
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
> +#define HSI2C_FIFO_EMPTY (0x1000100)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/**
> + * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
> + * but currently we are facing booting issues beyond 1Mhz
> + * So limiting HS-IIC bus speed to 1Mhz
> +*/
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 400000
> +
> +#define HSI2C_FAST_SPD 0
> +#define HSI2C_HIGH_SPD 1
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + unsigned int msg_idx;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int gpios[2];
> +
> + int bus_num;
> + int speed_mode;
> + struct dentry *debugfs_root;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_stop(struct exynos5_i2c *i2c, int err)
> +{
> + dev_vdbg(i2c->dev, "STOP\n");
> +
> + i2c->msg_idx++;
> + if (err)
> + i2c->msg_idx = err;
> +
> + /* Disable interrrupts */
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> +}
> +
> +static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
> +{
> + unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Clear to enable Timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +}
> +
> +static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
> +{
> + /* Start data transfer in Master mode */
> + u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +}
> +
> +/**
> + * exynos5_i2c_set_bus: get the i2c bus for a master transaction
> +*/
> +static int exynos5_i2c_set_master(struct exynos5_i2c *i2c)
> +{
> + unsigned long t_status;
> + int timeout = 400;
> +
> + while (timeout-- > 0) {
> + t_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (!(t_status & HSI2C_MASTER_BUSY))
> + return 0;
> +
> + msleep(20);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +/**
> + * exynos5_i2c_irq: top level IRQ servicing routine
> +*/
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + unsigned long t_stat;
> + unsigned char byte;
> +
> + t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
> +
> + if (t_stat & HSI2C_TRANS_ABORT) {
> + /* deal with arbitration loss */
> + dev_err(i2c->dev, "deal with arbitration loss\n");
> + goto out;
> + }
> + if (i2c->msg->flags & I2C_M_RD) {
> + if (t_stat & HSI2C_TRANS_DONE) {
> + dev_dbg(i2c->dev, "Device found.");
> + while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
> + HSI2C_RX_FIFO_EMPTY) == 0) {
> + byte = readl(i2c->regs + HSI2C_RX_DATA);
> + dev_dbg(i2c->dev, "read rx_data = %x", byte);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + }
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> +
> + } else if (t_stat & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device found.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + } else if (t_stat & HSI2C_NO_DEV_ACK &&
> + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
> + dev_dbg(i2c->dev, "No device Ack.");
> + exynos5_i2c_stop(i2c, -ENXIO);
> + }
> + } else {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + dev_dbg(i2c->dev, "write tx_data = %x ", byte);
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> +
> + if (i2c->msg_ptr >= i2c->msg->len)
> + exynos5_i2c_stop(i2c, 0);
> + }
> +
> + out:
> + /* Set those bits to clear them */
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs)
> +{
> + unsigned long usi_ctl = HSI2C_FUNC_MODE_I2C | HSI2C_MASTER;
> + unsigned long i2c_auto_conf;
> + unsigned long i2c_addr = ((msgs->addr & 0x7f) << 10);
> + unsigned long usi_int_en = 0;
> +
> + exynos5_i2c_en_timeout(i2c);
> +
> + if (msgs->flags & I2C_M_RD) {
> + usi_ctl &= ~HSI2C_TXCHON;
> + usi_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + usi_ctl &= ~HSI2C_RXCHON;
> + usi_ctl |= HSI2C_TXCHON;
> +
> + i2c_auto_conf &= ~HSI2C_READ_WRITE;
> +
> + usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + writel(i2c_addr, i2c->regs + HSI2C_ADDR);
> + writel(usi_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_auto_conf |= i2c->msg->len;
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + exynos5_i2c_master_run(i2c);
> +
> + /* Enable appropriate interrupts */
> + writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c, struct i2c_msg *msgs)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + if (exynos5_i2c_set_master(i2c)) {
> + dev_err(i2c->dev, "cannot get bus, Master busy.\n");
> + return -EAGAIN;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_idx = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, msgs);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> +
> + ret = i2c->msg_idx;
> +
> + if (timeout == 0)
> + dev_dbg(i2c->dev, "timeout\n");
> + else if ((ret != msgs->len) && (ret < 0))
> + dev_dbg(i2c->dev, "incomplete xfer (%d)\n", i2c->msg_idx);
> +
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int retry, i;
> + int ret;
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto out;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + ret = exynos5_i2c_doxfer(i2c, msgs);
> + msgs++;
> +
> + if (ret == -EAGAIN)
> + break;
> + }
> + if (i == num) {
> + clk_disable_unprepare(i2c->clk);
> +
> + if (i2c->msg_idx == -ENXIO)
> + ret = i2c->msg_idx;
> + else
> + ret = num;
> + goto out;
> + }
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + ret = -EREMOTEIO;
> + clk_disable_unprepare(i2c->clk);
> + out:
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int speed_mode)
> +{
> + unsigned long i2c_timing_s1;
> + unsigned long i2c_timing_s2;
> + unsigned long i2c_timing_s3;
> + unsigned long i2c_timing_sla;
> + unsigned int op_clk;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int n_clkdiv;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
> +
> + if (speed_mode == HSI2C_HIGH_SPD)
> + op_clk = HSI2C_HS_TX_CLOCK;
> + else
> + op_clk = HSI2C_FS_TX_CLOCK;
> +
> + /* FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * uTemp1 = (TSCLK_L + TSCLK_H + 2)
> + * uTemp2 = TSCLK_L + TSCLK_H
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (i = 0; i < 256; i++) {
> + utemp1 = utemp0 / (i + 1);
> + /* SCLK_L/H max is 255
> + * so sclk_l + sclk_h has max value of 510
> + */
> + if (utemp1 < 511) {
> + utemp2 = utemp1 - 2;
> + break;
> + }
> + }
> +
> + n_clkdiv = i;
> + t_scl_l = utemp2 / 2;
> + t_scl_h = utemp2 / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = utemp2;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + n_clkdiv, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (speed_mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +/**
> + * Parse a list of GPIOs from a node property and request each one
> + *
> + * @param i2c i2c driver data
> + * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
> +*/
> +static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
> +{
> + int idx, gpio, ret;
> +
> + for (idx = 0; idx < 2; idx++) {
> + gpio = of_get_gpio(i2c->dev->of_node, idx);
> + if (!gpio_is_valid(gpio)) {
> + dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
> + return -EINVAL;
> + }
> + i2c->gpios[idx] = gpio;
> +
> + ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
> + if (ret) {
> + dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + unsigned long usi_trailing_ctl = HSI2C_TRAILING_COUNT;
> + unsigned long i2c_conf = HSI2C_AUTO_MODE;
> + unsigned long usi_fifo_ctl;
> +
> + writel(usi_trailing_ctl, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + /* Set default trigger level for TXFIFO and RXFIFO */
> + usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
> +
> + /* Enable RXFIFO and TXFIFO */
> + usi_fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD);
> + /* Configure I2C controller in High speed mode */
> + i2c_conf |= HSI2C_HS_MODE;
> + writel(i2c_conf, i2c->regs + HSI2C_CONF);
> + } else {
> + /* Configure I2C controller in Fast speed mode */
> + exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD);
> + }
> +}
> +
> +#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
> +static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
> + HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
> + HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
> + HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
> + HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
> + HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
> + HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
> + HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
> + HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
> + HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
> + HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
> + HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
> + HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
> +};
> +
> +static struct debugfs_regset32 exynos5_hsi2c_regset = {
> + .regs = exynos5_hsi2c_regs,
> + .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
> +};
> +
> +static struct dentry *exynos5_hsi2c_reg_debugfs;
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + i2c->bus_num = -1;
> + /* Mode of operation High/Fast Speed mode */
> + of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode);
> +
> + strlcpy(i2c->adap.name, "exynos5-hsi2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + ret = -ENOENT;
> + goto err_noclk;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
> +
> + clk_prepare_enable(i2c->clk);
> +
> + i2c->regs = of_iomap(np, 0);
> + if (!i2c->regs) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = -ENXIO;
> + goto err_clk;
> + }
> +
> + /* inititalise the gpio */
> + if (exynos5_i2c_parse_dt_gpio(i2c))
> + return -EINVAL;
> +
> + i2c->irq = irq_of_parse_and_map(np, 0);
> + if (i2c->irq) {
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> + i2c->irq);
> + goto err_iomap;
> + }
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_iomap;
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.nr = i2c->bus_num;
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> +
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + init_completion(&i2c->msg_complete);
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + dev_info(&pdev->dev, "%s: Exynos5 HS-I2C adapter\n",
> + dev_name(&i2c->adap.dev));
> +
> + exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
> + S_IFREG | S_IRUGO,
> + NULL, &exynos5_hsi2c_regset);
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_iomap:
> + iounmap(i2c->regs);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + err_noclk:
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + iounmap(i2c->regs);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + clk_prepare_enable(i2c->clk);
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
Any comments please.
Or shall i re base and resent
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v4] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
@ 2013-02-01 15:54 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-02-01 15:54 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA
Cc: w.sang-bIcnvbaLZ9MEGnE8C9+IrQ, khali-PUYAD+kWke1g9hUCZPvPmw,
ben-linux-elnMNo+KYs3YtjvyW6yDsg,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw, grundler-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E
Adds support for High Speed I2C driver found in Exynos5 and later
SoCs from Samsung. This driver currently supports Auto mode.
Driver only supports Device Tree method.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
---
Help please,
Usual reads and writes are happening. But, when i try doing
i2cdetect -y 0 0x10 0x59; It throws me continous interrupts at 0x1a
i2cdetect -y 0 0x50 0x64; It throws me continous interrupts at 0x63
There are eeproms from 0x50 till 0x58
wm8994 codec at 0x1a
Kindly, can anyone explain this behaviour.
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 754 ++++++++++++++++++++++++++++++++++++++
3 files changed, 762 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bdca511..4caea76 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -618,6 +618,13 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung SoCs.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_S6000
tristate "S6000 I2C support"
depends on XTENSA_VARIANT_S6000
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 6181f3f..4b1548c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..6bd5637
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,754 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+
+/* I2C_CONF Register bits */
+#define HSI2C_HS_MODE (1u << 29)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_AUTO_MODE (1u << 31)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_TRANS_DONE (1u << 0)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_MASTER_BUSY (1u << 17)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+
+/**
+ * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
+ * but currently we are facing booting issues beyond 2.5Mhz
+ * So limiting HS-IIC bus speed to 2.5Mhz
+*/
+#define HSI2C_HS_TX_CLOCK 2500000
+#define HSI2C_FS_TX_CLOCK 400000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int gpios[2];
+ unsigned int clock;
+
+ int status;
+ unsigned int bus_num;
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
+{
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ writel(HSI2C_FUNC_MODE_I2C, i2c->regs + HSI2C_CTL);
+
+ complete(&i2c->msg_complete);
+}
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c);
+
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ /* Configure I2C controller in High speed mode */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD)
+ i2c_conf |= HSI2C_HS_MODE;
+
+ writel(i2c_conf, i2c->regs + HSI2C_CONF);
+
+ exynos5_i2c_set_timing(i2c);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ unsigned long usi_ctl;
+
+ usi_ctl = readl(i2c->regs + HSI2C_CTL);
+ usi_ctl |= HSI2C_SW_RST;
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ usi_ctl = readl(i2c->regs + HSI2C_CTL);
+ usi_ctl &= ~HSI2C_SW_RST;
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ exynos5_i2c_init(i2c);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/**
+ * exynos5_i2c_irq: top level IRQ servicing routine
+*/
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ unsigned char byte;
+ u32 t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ i2c->status = -ENXIO;
+ if (t_stat & HSI2C_TRANS_DONE) {
+ i2c->status = 0;
+ if (i2c->msg->flags & I2C_M_RD) {
+ while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
+ HSI2C_RX_FIFO_FULL) == 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+
+ if (i2c->msg->len == 1)
+ break;
+ }
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c);
+ } else {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c);
+ }
+ } else if (t_stat & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device found. returning -ENXIO");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ } else if (t_stat & HSI2C_NO_DEV_ACK &&
+ !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "No device Ack. returning -ENXIO");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ } else if (t_stat & HSI2C_TRANS_ABORT) {
+ /* deal with arbitration loss */
+ dev_err(i2c->dev, "deal with arbitration loss\n");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ }
+
+ out:
+ /* Set these bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+ return IRQ_HANDLED;
+}
+
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long usi_ctl = (HSI2C_FUNC_MODE_I2C | HSI2C_MASTER);
+ unsigned long i2c_auto_conf;
+ unsigned long usi_int_en = HSI2C_INT_I2C_EN;
+ unsigned long usi_fifo_ctl;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ /* Set default trigger level for TXFIFO and RXFIFO */
+ usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+ /* Enable RXFIFO and TXFIFO */
+ usi_fifo_ctl |= (HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN);
+ writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ if (msgs->flags & I2C_M_RD) {
+ usi_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ usi_ctl |= HSI2C_TXCHON;
+
+ i2c_auto_conf &= ~HSI2C_READ_WRITE;
+
+ usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ else
+ i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(msgs->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_auto_conf |= i2c->msg->len;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ /* Enable appropriate interrupts */
+ writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ unsigned long trans_status;
+ unsigned long usi_fifo_stat;
+ int ret = -EAGAIN;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is suspended.\n");
+ return -EIO;
+ }
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+
+ exynos5_i2c_message_start(i2c, msgs, stop);
+
+ timeout = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+
+ if (i2c->status == -ENXIO) {
+ return i2c->status;
+
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ if (msgs->flags & I2C_M_RD) {
+ dev_warn(i2c->dev, "rx timeout\n");
+ ret = 0;
+ } else
+ dev_warn(i2c->dev, "tx timeout\n");
+
+ return ret;
+ }
+
+ timeout = jiffies + timeout;
+ while (time_after(timeout, jiffies)) {
+ usi_fifo_stat = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if ((usi_fifo_stat &
+ (HSI2C_TX_FIFO_EMPTY | HSI2C_RX_FIFO_FULL)) &&
+ ((trans_status == 0) || ((stop == 0) &&
+ (trans_status & HSI2C_MASTER_BUSY)))) {
+ ret = 0;
+ break;
+ }
+ }
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "xfer timeout\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int retry, i;
+ int ret;
+ int stop = 0;
+ struct i2c_msg *msgs_ptr = msgs;
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto out;
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ if (i == num - 1)
+ stop = 1;
+ ret = exynos5_i2c_doxfer(i2c, msgs, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ stop = 0;
+ break;
+ }
+ }
+
+ if (i == num) {
+ if (ret != -ENXIO)
+ ret = num;
+ goto out;
+
+ }
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+ out:
+ clk_disable_unprepare(i2c->clk);
+
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timing_s1;
+ unsigned long i2c_timing_s2;
+ unsigned long i2c_timing_s3;
+ unsigned long i2c_timing_sla;
+ unsigned int op_clk = i2c->clock;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * uTemp1 = (TSCLK_L + TSCLK_H + 2)
+ * uTemp2 = TSCLK_L + TSCLK_H
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+ /* SCLK_L/H max is 256 / 2 */
+ if (utemp1 < 128) {
+ utemp2 = utemp1 - 2;
+ break;
+ }
+ }
+
+ n_clkdiv = i;
+ t_scl_l = utemp2 / 2;
+ t_scl_h = utemp2 / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = utemp2;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ i2c->bus_num = -1;
+
+ /* Mode of operation High/Fast Speed mode */
+ if (!of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode)) {
+ if (of_property_read_u32(np, "samsung,hs-clock", &i2c->clock))
+ i2c->clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ if (of_property_read_u32(np, "samsung,fs-clock", &i2c->clock))
+ i2c->clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get HS-I2C clock\n");
+ ret = -ENOENT;
+ goto err_noclk;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* parse device tree and inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ i2c->irq = irq_of_parse_and_map(np, 0);
+ if (i2c->irq) {
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
+ i2c->irq);
+ goto err_iomap;
+ }
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.nr = i2c->bus_num;
+ i2c->adap.dev.of_node = np;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ init_completion(&i2c->msg_complete);
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ err_noclk:
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = of_match_ptr(exynos5_i2c_match),
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v4] i2c: exynos5: add High Speed I2C controller driver
@ 2013-02-01 15:54 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-02-01 15:54 UTC (permalink / raw)
To: linux-i2c, linux-kernel, linux-samsung-soc
Cc: w.sang, khali, ben-linux, grant.likely, devicetree-discuss, sjg,
grundler, naveenkrishna.ch, broonie
Adds support for High Speed I2C driver found in Exynos5 and later
SoCs from Samsung. This driver currently supports Auto mode.
Driver only supports Device Tree method.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Help please,
Usual reads and writes are happening. But, when i try doing
i2cdetect -y 0 0x10 0x59; It throws me continous interrupts at 0x1a
i2cdetect -y 0 0x50 0x64; It throws me continous interrupts at 0x63
There are eeproms from 0x50 till 0x58
wm8994 codec at 0x1a
Kindly, can anyone explain this behaviour.
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 754 ++++++++++++++++++++++++++++++++++++++
3 files changed, 762 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bdca511..4caea76 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -618,6 +618,13 @@ config I2C_S3C2410
Say Y here to include support for I2C controller in the
Samsung SoCs.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_S6000
tristate "S6000 I2C support"
depends on XTENSA_VARIANT_S6000
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 6181f3f..4b1548c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..6bd5637
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,754 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x20 << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x20 << 16)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_TX_FIFO_LEVEL_MASK (0x7 << 7)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_RX_FIFO_LEVEL_MASK (0x7 << 16)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+
+/* I2C_CONF Register bits */
+#define HSI2C_HS_MODE (1u << 29)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_AUTO_MODE (1u << 31)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_TRANS_DONE (1u << 0)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_MASTER_BUSY (1u << 17)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+
+/**
+ * Although exynos5 supports max HS-IIC speed of 3.4Mhz,
+ * but currently we are facing booting issues beyond 2.5Mhz
+ * So limiting HS-IIC bus speed to 2.5Mhz
+*/
+#define HSI2C_HS_TX_CLOCK 2500000
+#define HSI2C_FS_TX_CLOCK 400000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int gpios[2];
+ unsigned int clock;
+
+ int status;
+ unsigned int bus_num;
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
+{
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ writel(HSI2C_FUNC_MODE_I2C, i2c->regs + HSI2C_CTL);
+
+ complete(&i2c->msg_complete);
+}
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c);
+
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ /* Configure I2C controller in High speed mode */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD)
+ i2c_conf |= HSI2C_HS_MODE;
+
+ writel(i2c_conf, i2c->regs + HSI2C_CONF);
+
+ exynos5_i2c_set_timing(i2c);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ unsigned long usi_ctl;
+
+ usi_ctl = readl(i2c->regs + HSI2C_CTL);
+ usi_ctl |= HSI2C_SW_RST;
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ usi_ctl = readl(i2c->regs + HSI2C_CTL);
+ usi_ctl &= ~HSI2C_SW_RST;
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ exynos5_i2c_init(i2c);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/**
+ * exynos5_i2c_irq: top level IRQ servicing routine
+*/
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ unsigned char byte;
+ u32 t_stat = readl(i2c->regs + HSI2C_TRANS_STATUS);
+
+ i2c->status = -ENXIO;
+ if (t_stat & HSI2C_TRANS_DONE) {
+ i2c->status = 0;
+ if (i2c->msg->flags & I2C_M_RD) {
+ while ((readl(i2c->regs + HSI2C_FIFO_STATUS) &
+ HSI2C_RX_FIFO_FULL) == 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+
+ if (i2c->msg->len == 1)
+ break;
+ }
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c);
+ } else {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+
+ if (i2c->msg_ptr >= i2c->msg->len)
+ exynos5_i2c_stop(i2c);
+ }
+ } else if (t_stat & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device found. returning -ENXIO");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ } else if (t_stat & HSI2C_NO_DEV_ACK &&
+ !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
+ dev_dbg(i2c->dev, "No device Ack. returning -ENXIO");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ } else if (t_stat & HSI2C_TRANS_ABORT) {
+ /* deal with arbitration loss */
+ dev_err(i2c->dev, "deal with arbitration loss\n");
+ exynos5_i2c_stop(i2c);
+ goto out;
+ }
+
+ out:
+ /* Set these bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+ return IRQ_HANDLED;
+}
+
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long usi_ctl = (HSI2C_FUNC_MODE_I2C | HSI2C_MASTER);
+ unsigned long i2c_auto_conf;
+ unsigned long usi_int_en = HSI2C_INT_I2C_EN;
+ unsigned long usi_fifo_ctl;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ /* Set default trigger level for TXFIFO and RXFIFO */
+ usi_fifo_ctl = HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+ /* Enable RXFIFO and TXFIFO */
+ usi_fifo_ctl |= (HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN);
+ writel(usi_fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ if (msgs->flags & I2C_M_RD) {
+ usi_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ usi_int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ usi_ctl |= HSI2C_TXCHON;
+
+ i2c_auto_conf &= ~HSI2C_READ_WRITE;
+
+ usi_int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ else
+ i2c_auto_conf &= ~HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(msgs->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(usi_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_auto_conf |= i2c->msg->len;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ /* Enable appropriate interrupts */
+ writel(usi_int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_doxfer(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ unsigned long trans_status;
+ unsigned long usi_fifo_stat;
+ int ret = -EAGAIN;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is suspended.\n");
+ return -EIO;
+ }
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+
+ exynos5_i2c_message_start(i2c, msgs, stop);
+
+ timeout = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+
+ if (i2c->status == -ENXIO) {
+ return i2c->status;
+
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ if (msgs->flags & I2C_M_RD) {
+ dev_warn(i2c->dev, "rx timeout\n");
+ ret = 0;
+ } else
+ dev_warn(i2c->dev, "tx timeout\n");
+
+ return ret;
+ }
+
+ timeout = jiffies + timeout;
+ while (time_after(timeout, jiffies)) {
+ usi_fifo_stat = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if ((usi_fifo_stat &
+ (HSI2C_TX_FIFO_EMPTY | HSI2C_RX_FIFO_FULL)) &&
+ ((trans_status == 0) || ((stop == 0) &&
+ (trans_status & HSI2C_MASTER_BUSY)))) {
+ ret = 0;
+ break;
+ }
+ }
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "xfer timeout\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int retry, i;
+ int ret;
+ int stop = 0;
+ struct i2c_msg *msgs_ptr = msgs;
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto out;
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ if (i == num - 1)
+ stop = 1;
+ ret = exynos5_i2c_doxfer(i2c, msgs, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ stop = 0;
+ break;
+ }
+ }
+
+ if (i == num) {
+ if (ret != -ENXIO)
+ ret = num;
+ goto out;
+
+ }
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+ out:
+ clk_disable_unprepare(i2c->clk);
+
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
+{
+ unsigned long i2c_timing_s1;
+ unsigned long i2c_timing_s2;
+ unsigned long i2c_timing_s3;
+ unsigned long i2c_timing_sla;
+ unsigned int op_clk = i2c->clock;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int i = 0, utemp0 = 0, utemp1 = 0, utemp2 = 0;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * uTemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * uTemp1 = (TSCLK_L + TSCLK_H + 2)
+ * uTemp2 = TSCLK_L + TSCLK_H
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+ /* SCLK_L/H max is 256 / 2 */
+ if (utemp1 < 128) {
+ utemp2 = utemp1 - 2;
+ break;
+ }
+ }
+
+ n_clkdiv = i;
+ t_scl_l = utemp2 / 2;
+ t_scl_h = utemp2 / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = utemp2;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ i2c->bus_num = -1;
+
+ /* Mode of operation High/Fast Speed mode */
+ if (!of_property_read_u32(np, "samsung,hs-mode", &i2c->speed_mode)) {
+ if (of_property_read_u32(np, "samsung,hs-clock", &i2c->clock))
+ i2c->clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ if (of_property_read_u32(np, "samsung,fs-clock", &i2c->clock))
+ i2c->clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get HS-I2C clock\n");
+ ret = -ENOENT;
+ goto err_noclk;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* parse device tree and inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ i2c->irq = irq_of_parse_and_map(np, 0);
+ if (i2c->irq) {
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
+ i2c->irq);
+ goto err_iomap;
+ }
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.nr = i2c->bus_num;
+ i2c->adap.dev.of_node = np;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ init_completion(&i2c->msg_complete);
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ err_noclk:
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = of_match_ptr(exynos5_i2c_match),
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread[parent not found: <1359734084-10594-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v4] i2c: exynos5: add High Speed I2C controller driver
2013-02-01 15:54 ` Naveen Krishna Chatradhi
@ 2013-02-01 19:29 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-02-01 19:29 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
khali-PUYAD+kWke1g9hUCZPvPmw, ben-linux-elnMNo+KYs3YtjvyW6yDsg,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw, grundler-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E
[-- Attachment #1: Type: text/plain, Size: 1254 bytes --]
On Fri, Feb 01, 2013 at 09:24:44PM +0530, Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> ---
> Help please,
>
> Usual reads and writes are happening. But, when i try doing
> i2cdetect -y 0 0x10 0x59; It throws me continous interrupts at 0x1a
> i2cdetect -y 0 0x50 0x64; It throws me continous interrupts at 0x63
>
> There are eeproms from 0x50 till 0x58
> wm8994 codec at 0x1a
>
> Kindly, can anyone explain this behaviour.
You select I2C_FUNC_SMBUS_EMUL, but make sure your hardware really
supports I2C_FUNC_SMBUS_QUICK. That would be my first guess. Also, which
eeprom do you have? Check its datasheet, some in deed use 8 addresses.
Regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v4] i2c: exynos5: add High Speed I2C controller driver
@ 2013-02-01 19:29 ` Wolfram Sang
0 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-02-01 19:29 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-kernel, linux-samsung-soc, khali, ben-linux,
grant.likely, devicetree-discuss, sjg, grundler, naveenkrishna.ch,
broonie
[-- Attachment #1: Type: text/plain, Size: 1198 bytes --]
On Fri, Feb 01, 2013 at 09:24:44PM +0530, Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Help please,
>
> Usual reads and writes are happening. But, when i try doing
> i2cdetect -y 0 0x10 0x59; It throws me continous interrupts at 0x1a
> i2cdetect -y 0 0x50 0x64; It throws me continous interrupts at 0x63
>
> There are eeproms from 0x50 till 0x58
> wm8994 codec at 0x1a
>
> Kindly, can anyone explain this behaviour.
You select I2C_FUNC_SMBUS_EMUL, but make sure your hardware really
supports I2C_FUNC_SMBUS_QUICK. That would be my first guess. Also, which
eeprom do you have? Check its datasheet, some in deed use 8 addresses.
Regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v4] i2c: exynos5: add High Speed I2C controller driver
2013-02-01 15:54 ` Naveen Krishna Chatradhi
(?)
(?)
@ 2013-02-08 13:16 ` Grant Likely
-1 siblings, 0 replies; 107+ messages in thread
From: Grant Likely @ 2013-02-08 13:16 UTC (permalink / raw)
To: Naveen Krishna Chatradhi, linux-i2c, linux-kernel,
linux-samsung-soc
Cc: w.sang, khali, ben-linux, devicetree-discuss, sjg, grundler,
naveenkrishna.ch, broonie
On Fri, 01 Feb 2013 21:24:44 +0530, Naveen Krishna Chatradhi <ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and later
> SoCs from Samsung. This driver currently supports Auto mode.
>
> Driver only supports Device Tree method.
> Note: Added debugfs support for registers view, not tested.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Help please,
>
> Usual reads and writes are happening. But, when i try doing
> i2cdetect -y 0 0x10 0x59; It throws me continous interrupts at 0x1a
> i2cdetect -y 0 0x50 0x64; It throws me continous interrupts at 0x63
>
> There are eeproms from 0x50 till 0x58
> wm8994 codec at 0x1a
>
> Kindly, can anyone explain this behaviour.
>
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 754 ++++++++++++++++++++++++++++++++++++++
Don't forget you need to document the new compatible string in
Documentation/devicetree/bindings/i2c. That should be part of this
patch.
g.
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v6] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
@ 2013-03-28 23:40 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-03-28 23:40 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
This driver currently supports Auto mode.
Driver only supports Device Tree method.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
---
Changes since v5:
1. fixed the clock_info function pointed out by Yuvaraj
2. Clear pending interrupt register before initializing in probe
3. Implement INIT_COMPLETION call before xfer
.../devicetree/bindings/i2c/i2c-exynos5.txt | 50 ++
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 874 ++++++++++++++++++++
4 files changed, 932 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..0bc9347
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,50 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ (a) "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+
+ - Samsung GPIO variant (deprecated):
+ - gpios: The order of the gpios should be the following: <SDA, SCL>.
+ The gpio specifier depends on the gpio controller.
+ - Pinctrl variant (preferred, if available):
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - samsung,hs-clock-freq: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+ - samsung,fs-clock-freq: Desired operarting frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+ hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ samsung,fs-clock-freq = <100000>;
+ /* Samsung GPIO variant begins here */
+ gpios = <&gpd1 2 0 /* SDA */
+ &gpd1 3 0 /* SCL */>;
+ /* Samsung GPIO variant ends here */
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+ };
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a3725de..78b4936 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..a17848f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,874 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x30 << 16)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x30 << 4)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+
+/* Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /* GPIO lines for SDA/SCL*/
+ int gpios[2];
+
+ /* Controller operating frequency */
+ unsigned int clock;
+ unsigned int clk_cycle;
+ unsigned int clk_div;
+
+ /* HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
+{
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+
+ complete(&i2c->msg_complete);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+/* exynos5_i2c_clock_info: Calculates the clock divisor and
+ * the clock cycle length (SCLK High + SCLK Low)
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_clock_info(struct exynos5_i2c *i2c)
+{
+ unsigned int op_clk = i2c->clock;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int i, utemp0 = 0, utemp1 = 0;
+ unsigned int t_ftl_cycle;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+
+ /* SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ i2c->clk_cycle = utemp1 - 2;
+ i2c->clk_div = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated earlier (exynos5_i2c_clock_info)
+ *
+ * To handle the error case in calculates and to avoid the subsequent
+ * calculates the clk_div and clk_cycle are stored
+ */
+static void exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+
+ n_clkdiv = i2c->clk_div;
+ t_scl_l = i2c->clk_cycle / 2;
+ t_scl_h = i2c->clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = i2c->clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+}
+
+/* exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ *
+ * Note: Currently, supports AUTO mode of operation.
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ exynos5_i2c_set_timing(i2c);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD)
+ i2c_conf |= HSI2C_HS_MODE;
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/* exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ /* 0x30 is the default trigger level for TX FIFO */
+ len = 48 - fifo_level;
+
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0))
+ exynos5_i2c_stop(i2c);
+
+ /* Set these bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN |
+ HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+
+ INIT_COMPLETION(&i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ }
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ /* only one message, return number of bytes transfered */
+ if (num == 1)
+ ret = i2c->msg_len;
+ else
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = 1;
+ if (of_property_read_u32(np, "samsung,hs-clock", &i2c->clock))
+ i2c->clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = 0;
+ if (of_property_read_u32(np, "samsung,fs-clock", &i2c->clock))
+ i2c->clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -EADDRNOTAVAIL;
+ goto err_clk;
+ }
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* parse device tree and inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ ret = exynos5_i2c_clock_info(i2c);
+ if (ret)
+ goto err_pm;
+
+ exynos5_i2c_init(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ goto err_iomap;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_iomap;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ i2c->adap.nr = -1;
+ i2c->adap.dev.of_node = np;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v6] i2c: exynos5: add High Speed I2C controller driver
@ 2013-03-28 23:40 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-03-28 23:40 UTC (permalink / raw)
To: linux-i2c, linux-samsung-soc
Cc: linux-kernel, w.sang, grant.likely, devicetree-discuss, sjg,
naveenkrishna.ch, broonie
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
This driver currently supports Auto mode.
Driver only supports Device Tree method.
Note: Added debugfs support for registers view, not tested.
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v5:
1. fixed the clock_info function pointed out by Yuvaraj
2. Clear pending interrupt register before initializing in probe
3. Implement INIT_COMPLETION call before xfer
.../devicetree/bindings/i2c/i2c-exynos5.txt | 50 ++
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 874 ++++++++++++++++++++
4 files changed, 932 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..0bc9347
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,50 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ (a) "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+
+ - Samsung GPIO variant (deprecated):
+ - gpios: The order of the gpios should be the following: <SDA, SCL>.
+ The gpio specifier depends on the gpio controller.
+ - Pinctrl variant (preferred, if available):
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - samsung,hs-clock-freq: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+ - samsung,fs-clock-freq: Desired operarting frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+ hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ samsung,fs-clock-freq = <100000>;
+ /* Samsung GPIO variant begins here */
+ gpios = <&gpd1 2 0 /* SDA */
+ &gpd1 3 0 /* SCL */>;
+ /* Samsung GPIO variant ends here */
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+ };
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a3725de..78b4936 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for High-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..a17848f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,874 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL (0x30 << 16)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL (0x30 << 4)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+
+/* Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /* GPIO lines for SDA/SCL*/
+ int gpios[2];
+
+ /* Controller operating frequency */
+ unsigned int clock;
+ unsigned int clk_cycle;
+ unsigned int clk_div;
+
+ /* HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static inline void exynos5_i2c_stop(struct exynos5_i2c *i2c)
+{
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+
+ complete(&i2c->msg_complete);
+}
+
+static void exynos5_i2c_en_timeout(struct exynos5_i2c *i2c)
+{
+ u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Clear to enable Timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+}
+
+/* exynos5_i2c_clock_info: Calculates the clock divisor and
+ * the clock cycle length (SCLK High + SCLK Low)
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_clock_info(struct exynos5_i2c *i2c)
+{
+ unsigned int op_clk = i2c->clock;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int i, utemp0 = 0, utemp1 = 0;
+ unsigned int t_ftl_cycle;
+
+ /* FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (i = 0; i < 256; i++) {
+ utemp1 = utemp0 / (i + 1);
+
+ /* SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ i2c->clk_cycle = utemp1 - 2;
+ i2c->clk_div = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated earlier (exynos5_i2c_clock_info)
+ *
+ * To handle the error case in calculates and to avoid the subsequent
+ * calculates the clk_div and clk_cycle are stored
+ */
+static void exynos5_i2c_set_timing(struct exynos5_i2c *i2c)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int n_clkdiv;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+
+ n_clkdiv = i2c->clk_div;
+ t_scl_l = i2c->clk_cycle / 2;
+ t_scl_h = i2c->clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = i2c->clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = n_clkdiv << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ n_clkdiv, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+}
+
+/* exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ *
+ * Note: Currently, supports AUTO mode of operation.
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ exynos5_i2c_set_timing(i2c);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD)
+ i2c_conf |= HSI2C_HS_MODE;
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+static void exynos5_i2c_master_run(struct exynos5_i2c *i2c)
+{
+ /* Start data transfer in Master mode */
+ u32 i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+}
+
+/* exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ /* 0x30 is the default trigger level for TX FIFO */
+ len = 48 - fifo_level;
+
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0))
+ exynos5_i2c_stop(i2c);
+
+ /* Set these bits to clear them */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+
+ exynos5_i2c_en_timeout(i2c);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN |
+ HSI2C_TXFIFO_TRIGGER_LEVEL | HSI2C_RXFIFO_TRIGGER_LEVEL;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ exynos5_i2c_master_run(i2c);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+
+ INIT_COMPLETION(&i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ }
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ /* only one message, return number of bytes transfered */
+ if (num == 1)
+ ret = i2c->msg_len;
+ else
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+/**
+ * Parse a list of GPIOs from a node property and request each one
+ *
+ * @param i2c i2c driver data
+ * @return 0 on success, -EINVAL on error, in which case no GPIOs requested
+*/
+static int exynos5_i2c_parse_dt_gpio(struct exynos5_i2c *i2c)
+{
+ int idx, gpio, ret;
+
+ for (idx = 0; idx < 2; idx++) {
+ gpio = of_get_gpio(i2c->dev->of_node, idx);
+ if (!gpio_is_valid(gpio)) {
+ dev_err(i2c->dev, "invalid gpio[%d]: %d\n", idx, gpio);
+ return -EINVAL;
+ }
+ i2c->gpios[idx] = gpio;
+
+ ret = devm_gpio_request(i2c->dev, gpio, "i2c-bus");
+ if (ret) {
+ dev_err(i2c->dev, "gpio [%d] request failed\n", gpio);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+#define HSI2C_REG(regname) {.name = #regname, .offset = regname}
+static struct debugfs_reg32 exynos5_hsi2c_regs[] = {
+ HSI2C_REG(HSI2C_CTL), HSI2C_REG(HSI2C_FIFO_CTL),
+ HSI2C_REG(HSI2C_TRAILIG_CTL), HSI2C_REG(HSI2C_CLK_CTL),
+ HSI2C_REG(HSI2C_CLK_SLOT), HSI2C_REG(HSI2C_INT_ENABLE),
+ HSI2C_REG(HSI2C_INT_STATUS), HSI2C_REG(HSI2C_ERR_STATUS),
+ HSI2C_REG(HSI2C_FIFO_STATUS), HSI2C_REG(HSI2C_TX_DATA),
+ HSI2C_REG(HSI2C_RX_DATA), HSI2C_REG(HSI2C_CONF),
+ HSI2C_REG(HSI2C_AUTO_CONF), HSI2C_REG(HSI2C_TIMEOUT),
+ HSI2C_REG(HSI2C_MANUAL_CMD), HSI2C_REG(HSI2C_TRANS_STATUS),
+ HSI2C_REG(HSI2C_TIMING_HS1), HSI2C_REG(HSI2C_TIMING_HS2),
+ HSI2C_REG(HSI2C_TIMING_HS3), HSI2C_REG(HSI2C_TIMING_FS1),
+ HSI2C_REG(HSI2C_TIMING_FS2), HSI2C_REG(HSI2C_TIMING_FS3),
+ HSI2C_REG(HSI2C_TIMING_SLA), HSI2C_REG(HSI2C_ADDR),
+};
+
+static struct debugfs_regset32 exynos5_hsi2c_regset = {
+ .regs = exynos5_hsi2c_regs,
+ .nregs = ARRAY_SIZE(exynos5_hsi2c_regs),
+};
+
+static struct dentry *exynos5_hsi2c_reg_debugfs;
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = 1;
+ if (of_property_read_u32(np, "samsung,hs-clock", &i2c->clock))
+ i2c->clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = 0;
+ if (of_property_read_u32(np, "samsung,fs-clock", &i2c->clock))
+ i2c->clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ i2c->regs = of_iomap(np, 0);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = -EADDRNOTAVAIL;
+ goto err_clk;
+ }
+
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* parse device tree and inititalise the gpio */
+ if (exynos5_i2c_parse_dt_gpio(i2c))
+ return -EINVAL;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+
+ ret = exynos5_i2c_clock_info(i2c);
+ if (ret)
+ goto err_pm;
+
+ exynos5_i2c_init(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ goto err_iomap;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_iomap;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_iomap;
+
+ i2c->adap.nr = -1;
+ i2c->adap.dev.of_node = np;
+
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ exynos5_hsi2c_reg_debugfs = debugfs_create_regset32("exynos5-hsi2c",
+ S_IFREG | S_IRUGO,
+ NULL, &exynos5_hsi2c_regset);
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_iomap:
+ iounmap(i2c->regs);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2c_del_adapter(&i2c->adap);
+
+ iounmap(i2c->regs);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ clk_prepare_enable(i2c->clk);
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread
* [PATCH v8] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
@ 2013-05-07 2:50 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-05-07 2:50 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E,
Naveen Krishna Chatradhi
From: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Changes since v7:
1. used devm_ioremap_resource for register base address
2. merged FIFO fix from Yuvaraj
https://patchwork.kernel.org/patch/2420351/
3. merged hsi2c clock config patch from Yuvaraj
https://patchwork.kernel.org/patch/2464681/
.../devicetree/bindings/i2c/i2c-exynos5.txt | 41 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
4 files changed, 937 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..6e613b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,41 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ (a) "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+
+ - Pinctrl variant (preferred, if available):
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+ hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+ };
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..49a665f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..ef88fde
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,888 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if ((timeout == 0) || (ret < 0)) {
+ exynos5_i2c_reset(i2c);
+ if (timeout == 0) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
+ i2c->hs_clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v8] i2c: exynos5: add High Speed I2C controller driver
@ 2013-05-07 2:50 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-05-07 2:50 UTC (permalink / raw)
To: linux-i2c, linux-samsung-soc
Cc: linux-kernel, w.sang, grant.likely, devicetree-discuss, sjg,
naveenkrishna.ch, broonie, Naveen Krishna Chatradhi
From: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Reviewed-by: Simon Glass <sjg@google.com>
Tested-by: Andrew Bresticker <abrestic@google.com>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
Signed-off-by: Andrew Bresticker <abrestic@google.com>
---
Changes since v7:
1. used devm_ioremap_resource for register base address
2. merged FIFO fix from Yuvaraj
https://patchwork.kernel.org/patch/2420351/
3. merged hsi2c clock config patch from Yuvaraj
https://patchwork.kernel.org/patch/2464681/
.../devicetree/bindings/i2c/i2c-exynos5.txt | 41 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
4 files changed, 937 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..6e613b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,41 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ (a) "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+
+ - Pinctrl variant (preferred, if available):
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+ hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+ };
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..49a665f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..ef88fde
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,888 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if ((timeout == 0) || (ret < 0)) {
+ exynos5_i2c_reset(i2c);
+ if (timeout == 0) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
+ i2c->hs_clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (!i2c->regs) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* Re: [PATCH v8] i2c: exynos5: add High Speed I2C controller driver
2013-05-07 2:50 ` Naveen Krishna Chatradhi
(?)
@ 2013-05-07 12:06 ` Sachin Kamat
-1 siblings, 0 replies; 107+ messages in thread
From: Sachin Kamat @ 2013-05-07 12:06 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-samsung-soc, linux-kernel, w.sang, grant.likely,
devicetree-discuss, sjg, broonie, Naveen Krishna Chatradhi
On 7 May 2013 08:20, Naveen Krishna Chatradhi
<naveenkrishna.ch@gmail.com> wrote:
> From: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
> Changes since v7:
> 1. used devm_ioremap_resource for register base address
> 2. merged FIFO fix from Yuvaraj
> https://patchwork.kernel.org/patch/2420351/
> 3. merged hsi2c clock config patch from Yuvaraj
> https://patchwork.kernel.org/patch/2464681/
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 41 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
> 4 files changed, 937 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..6e613b6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,41 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + (a) "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
Since this is the only string supported as of now, "(a)" could be dropped.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> +
> + - Pinctrl variant (preferred, if available):
Is the non-pinctrl variant still supported?
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
What about address-cells and size-cells property?
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> + hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> + };
[snip]
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (!i2c->regs) {
Should be "if (IS_ERR(i2c->regs)) {"
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
--
With warm regards,
Sachin
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
@ 2013-05-17 10:10 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-05-17 10:10 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA
Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
ben-linux-elnMNo+KYs3YtjvyW6yDsg, khali-PUYAD+kWke1g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs (only supports pinctrl method)
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Changes since v8
1. improved the device tree bindings description page for i2c-exynos5
2. fixed the return value check for devm_ioremap_resource
.../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
4 files changed, 941 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..29c01c0
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,45 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..49a665f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..33c481d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,888 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if ((timeout == 0) || (ret < 0)) {
+ exynos5_i2c_reset(i2c);
+ if (timeout == 0) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
+ i2c->hs_clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
@ 2013-05-17 10:10 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-05-17 10:10 UTC (permalink / raw)
To: linux-i2c, linux-kernel, linux-samsung-soc
Cc: khali, ben-linux, grant.likely, devicetree-discuss, sjg,
naveenkrishna.ch
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs (only supports pinctrl method)
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Reviewed-by: Simon Glass <sjg@google.com>
Tested-by: Andrew Bresticker <abrestic@google.com>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
Signed-off-by: Andrew Bresticker <abrestic@google.com>
---
Changes since v8
1. improved the device tree bindings description page for i2c-exynos5
2. fixed the return value check for devm_ioremap_resource
.../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
4 files changed, 941 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..29c01c0
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,45 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
+ specified, default value is 0.
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ If not specified, the default value in Hz is 100000.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ /* Pinctrl variant begins here */
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+ /* Pinctrl variant ends here */
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..49a665f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GENERIC_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..b19366c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..33c481d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,888 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 1000000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (ret >= 0)
+ timeout = ret;
+ else
+ return ret;
+
+ ret = i2c->state;
+
+ if ((timeout == 0) || (ret < 0)) {
+ exynos5_i2c_reset(i2c);
+ if (timeout == 0) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int retry, i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (retry = 0; retry < adap->retries; retry++) {
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret == -EAGAIN) {
+ msgs_ptr = msgs;
+ break;
+ } else if (ret < 0) {
+ goto out;
+ }
+ }
+
+ if ((i == num) && (ret != -EAGAIN))
+ break;
+
+ dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
+
+ udelay(100);
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ /* Mode of operation High/Fast Speed mode */
+ if (of_get_property(np, "samsung,hs-mode", NULL)) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
+ i2c->hs_clock = HSI2C_HS_TX_CLOCK;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+ i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+static int __init i2c_adap_exynos5_init(void)
+{
+ return platform_driver_register(&exynos5_i2c_driver);
+}
+subsys_initcall(i2c_adap_exynos5_init);
+
+static void __exit i2c_adap_exynos5_exit(void)
+{
+ platform_driver_unregister(&exynos5_i2c_driver);
+}
+module_exit(i2c_adap_exynos5_exit);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread[parent not found: <1368785452-15140-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
2013-05-17 10:10 ` Naveen Krishna Chatradhi
@ 2013-05-23 6:29 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-05-23 6:29 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
khali-PUYAD+kWke1g9hUCZPvPmw, Ben Dooks,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw
On 17 May 2013 15:40, Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs (only supports pinctrl method)
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> ---
>
> Changes since v8
> 1. improved the device tree bindings description page for i2c-exynos5
> 2. fixed the return value check for devm_ioremap_resource
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
> 4 files changed, 941 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..29c01c0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,45 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..49a665f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GENERIC_GPIO
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..b19366c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
> obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..33c481d
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,888 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole transaction
> + * 2. Manual mode: Software controls the transaction by issuing commands
> + * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
> + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
> + *
> + * Special bits are available for both modes of operation to set commands
> + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 1000000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
Any comments please.
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
@ 2013-05-23 6:29 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-05-23 6:29 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-kernel, linux-samsung-soc@vger.kernel.org, khali,
Ben Dooks, grant.likely, devicetree-discuss, sjg
On 17 May 2013 15:40, Naveen Krishna Chatradhi <ch.naveen@samsung.com> wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs (only supports pinctrl method)
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
>
> Changes since v8
> 1. improved the device tree bindings description page for i2c-exynos5
> 2. fixed the return value check for devm_ioremap_resource
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
> 4 files changed, 941 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..29c01c0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,45 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..49a665f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GENERIC_GPIO
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..b19366c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
> obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..33c481d
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,888 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole transaction
> + * 2. Manual mode: Software controls the transaction by issuing commands
> + * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
> + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
> + *
> + * Special bits are available for both modes of operation to set commands
> + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 1000000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
Any comments please.
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
[not found] ` <1368785452-15140-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-05-23 6:29 ` Naveen Krishna Ch
@ 2013-06-10 8:07 ` Naveen Krishna Ch
2013-06-10 8:10 ` Naveen Krishna Ch
2 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-06-10 8:07 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
wsa-z923LK4zBo2bacvFa/9K2g,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
ben-linux-elnMNo+KYs3YtjvyW6yDsg, khali-PUYAD+kWke1g9hUCZPvPmw
[-- Attachment #1.1: Type: text/plain, Size: 37632 bytes --]
On 17 May 2013 15:40, Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs (only supports pinctrl method)
> 7. Use OF data string "clock-frequency" to get the bus operating
> frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
>
Hello Wolfram,
This driver was submitted, reviewed several times.
and been improved as we were using it.
I did not see any review comments on this driver.
Can we get this merged or Should we wait for few more reviews ?
Thanks & Regards,
Naveen Krishna
> ---
>
> Changes since v8
> 1. improved the device tree bindings description page for i2c-exynos5
> 2. fixed the return value check for devm_ioremap_resource
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888
> ++++++++++++++++++++
> 4 files changed, 941 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..29c01c0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,45 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C
> devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory
> mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If
> not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..49a665f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx
> series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T
> PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in
> the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GENERIC_GPIO
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..b19366c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
> obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c
> b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..33c481d
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,888 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole
> transaction
> + * 2. Manual mode: Software controls the transaction by issuing commands
> + * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
> + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF
> register
> + *
> + * Special bits are available for both modes of operation to set commands
> + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL |
> \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 1000000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su <<
> 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up
> failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN |
> HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for
> the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the
> last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) :
> i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL &
> ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c),
> GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency",
> &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency",
> &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n",
> i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
>
>
--
Shine bright,
(: Nav :)
[-- Attachment #1.2: Type: text/html, Size: 45713 bytes --]
[-- Attachment #2: Type: text/plain, Size: 192 bytes --]
_______________________________________________
devicetree-discuss mailing list
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org
https://lists.ozlabs.org/listinfo/devicetree-discuss
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
2013-05-17 10:10 ` Naveen Krishna Chatradhi
@ 2013-06-10 8:10 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-06-10 8:10 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
ben-linux-elnMNo+KYs3YtjvyW6yDsg, khali-PUYAD+kWke1g9hUCZPvPmw
On 17 May 2013 15:40, Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org> wrote:
>
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs (only supports pinctrl method)
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Hello Wolfram,
This driver was submitted, reviewed several times.
and been improved as we were using it.
I did not see any review comments on this driver.
Can we get this merged or Should we wait for few more reviews ?
Sorry for the earlier HTML version of the mail.
Thanks & Regards,
Naveen Krishna
>
> ---
>
> Changes since v8
> 1. improved the device tree bindings description page for i2c-exynos5
> 2. fixed the return value check for devm_ioremap_resource
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
> 4 files changed, 941 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..29c01c0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,45 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..49a665f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GENERIC_GPIO
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..b19366c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
> obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..33c481d
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,888 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole transaction
> + * 2. Manual mode: Software controls the transaction by issuing commands
> + * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
> + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
> + *
> + * Special bits are available for both modes of operation to set commands
> + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 1000000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
@ 2013-06-10 8:10 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-06-10 8:10 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-kernel, linux-samsung-soc, khali, ben-linux,
grant.likely, devicetree-discuss, sjg
On 17 May 2013 15:40, Naveen Krishna Chatradhi <ch.naveen@samsung.com> wrote:
>
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs (only supports pinctrl method)
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
Hello Wolfram,
This driver was submitted, reviewed several times.
and been improved as we were using it.
I did not see any review comments on this driver.
Can we get this merged or Should we wait for few more reviews ?
Sorry for the earlier HTML version of the mail.
Thanks & Regards,
Naveen Krishna
>
> ---
>
> Changes since v8
> 1. improved the device tree bindings description page for i2c-exynos5
> 2. fixed the return value check for devm_ioremap_resource
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 45 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 888 ++++++++++++++++++++
> 4 files changed, 941 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..29c01c0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,45 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + If not specified, the default value in Hz is 100000.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..49a665f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GENERIC_GPIO
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..b19366c 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
> obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
> new file mode 100644
> index 0000000..33c481d
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,888 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole transaction
> + * 2. Manual mode: Software controls the transaction by issuing commands
> + * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
> + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
> + *
> + * Special bits are available for both modes of operation to set commands
> + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
> +
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 1000000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
> +
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL v2");
> --
> 1.7.9.5
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v9] i2c: exynos5: add High Speed I2C controller driver
2013-05-17 10:10 ` Naveen Krishna Chatradhi
(?)
(?)
@ 2013-06-10 14:38 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-06-10 14:38 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-kernel, linux-samsung-soc, khali, ben-linux,
grant.likely, devicetree-discuss, sjg, naveenkrishna.ch
[-- Attachment #1: Type: text/plain, Size: 8304 bytes --]
Hi Naveen,
> +Optional properties:
> + - samsung,hs-mode: Mode of operation, High speed or Fast speed mode. If not
> + specified, default value is 0.
This was probably overlooked from my last review: Why can't you simply
enable hs-mode when clock-frequency is > 1MBit?
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + /* Pinctrl variant begins here */
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> + /* Pinctrl variant ends here */
I'd think the two comments above are not needed.
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for the
> + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be bought to idle
s/bought/brought/
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + ret = wait_for_completion_interruptible_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
Have you tested with SIGINT? Most drivers removed the _interruptible_
version of waiting since they couldn't get handling the signals proper
and the bus locked up.
> + if (ret >= 0)
> + timeout = ret;
> + else
> + return ret;
> +
> + ret = i2c->state;
> +
> + if ((timeout == 0) || (ret < 0)) {
> + exynos5_i2c_reset(i2c);
> + if (timeout == 0) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int retry, i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (retry = 0; retry < adap->retries; retry++) {
You don't need to retry. The core does it if you return -EAGAIN.
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret == -EAGAIN) {
> + msgs_ptr = msgs;
> + break;
> + } else if (ret < 0) {
> + goto out;
> + }
> + }
> +
> + if ((i == num) && (ret != -EAGAIN))
> + break;
> +
> + dev_dbg(i2c->dev, "retrying transfer (%d)\n", retry);
> +
> + udelay(100);
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + /* Mode of operation High/Fast Speed mode */
> + if (of_get_property(np, "samsung,hs-mode", NULL)) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->hs_clock))
> + i2c->hs_clock = HSI2C_HS_TX_CLOCK;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + if (of_property_read_u32(np, "clock-frequency", &i2c->fs_clock))
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
Don't use .class unless you have an explicit reason to do so. It may
cost boot-time.
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + dev_err(&pdev->dev, "cannot map HS-I2C IO\n");
devm_ioremap_resource will print the error message for you.
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
Is this planned somewhen?
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
> +
> + exynos5_i2c_init(i2c);
> +
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
Skip setting -1 and use i2c_add_adapter.
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
...
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +static int __init i2c_adap_exynos5_init(void)
> +{
> + return platform_driver_register(&exynos5_i2c_driver);
> +}
> +subsys_initcall(i2c_adap_exynos5_init);
> +
> +static void __exit i2c_adap_exynos5_exit(void)
> +{
> + platform_driver_unregister(&exynos5_i2c_driver);
> +}
> +module_exit(i2c_adap_exynos5_exit);
Use the module_platform_driver macro, please.
Thanks for keeping at it!
Wolfram
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
(?)
@ 2013-06-19 10:48 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-06-19 10:48 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ, wsa-z923LK4zBo2bacvFa/9K2g,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ, balbi-l0cyMroinI0,
thomas.abraham-QSEj5FYQhm4dnm+yROfE0A
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. Removed retries as core does retries if -EAGAIN is returned
11. Removed mode from device tree info (use speed to distinguish
the mode of operation)
12. Use wait_for_completion_timeout as the interruptible case is not tested well
13. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Changes since v9:
Fixed below comments given by Wolfram, Thanks for the reivew.
1. Removed retries as core does retries if -EAGAIN is returned
2. Removed mode from device tree info (use speed to distinguish
the mode of operation)
3. Use module_platform_driver macro instead of init and exit
4. Use wait_for_completion_timeout as the interruptible case is not tested well
.../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 861 ++++++++++++++++++++
4 files changed, 913 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..805e018
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,44 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ -> If not specified, the default value is 100khz in fast-speed mode and
+ 1Mhz in high-speed mode.
+ -> If specified, The bus operates in high-speed mode only if the
+ clock-frequency is >= 1Mhz.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 96c6d82..fecbe66 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GPIOLIB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 385f99d..af6fa37 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..696d16f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,861 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 100000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be brought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ timeout = wait_for_completion_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return timeout;
+ }
+
+ ret = i2c->state;
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ return ret;
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret < 0)
+ goto out;
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ unsigned int op_clock;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ if (op_clock >= HSI2C_HS_TX_CLOCK) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ i2c->hs_clock = op_clock;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = op_clock;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+module_platform_driver(exynos5_i2c_driver);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-06-19 10:48 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-06-19 10:48 UTC (permalink / raw)
To: linux-i2c
Cc: linux-arm-kernel, linux-samsung-soc, devicetree-discuss,
naveenkrishna.ch, kgene.kim, grant.likely, wsa, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. Removed retries as core does retries if -EAGAIN is returned
11. Removed mode from device tree info (use speed to distinguish
the mode of operation)
12. Use wait_for_completion_timeout as the interruptible case is not tested well
13. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Reviewed-by: Simon Glass <sjg@google.com>
Tested-by: Andrew Bresticker <abrestic@google.com>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
Signed-off-by: Andrew Bresticker <abrestic@google.com>
---
Changes since v9:
Fixed below comments given by Wolfram, Thanks for the reivew.
1. Removed retries as core does retries if -EAGAIN is returned
2. Removed mode from device tree info (use speed to distinguish
the mode of operation)
3. Use module_platform_driver macro instead of init and exit
4. Use wait_for_completion_timeout as the interruptible case is not tested well
.../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 861 ++++++++++++++++++++
4 files changed, 913 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..805e018
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,44 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ -> If not specified, the default value is 100khz in fast-speed mode and
+ 1Mhz in high-speed mode.
+ -> If specified, The bus operates in high-speed mode only if the
+ clock-frequency is >= 1Mhz.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 96c6d82..fecbe66 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GPIOLIB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 385f99d..af6fa37 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..696d16f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,861 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 100000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be brought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ timeout = wait_for_completion_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return timeout;
+ }
+
+ ret = i2c->state;
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ return ret;
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret < 0)
+ goto out;
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ unsigned int op_clock;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ if (op_clock >= HSI2C_HS_TX_CLOCK) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ i2c->hs_clock = op_clock;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = op_clock;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+module_platform_driver(exynos5_i2c_driver);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-06-19 10:48 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-06-19 10:48 UTC (permalink / raw)
To: linux-arm-kernel
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. Removed retries as core does retries if -EAGAIN is returned
11. Removed mode from device tree info (use speed to distinguish
the mode of operation)
12. Use wait_for_completion_timeout as the interruptible case is not tested well
13. few other bug fixes and cosmetic changes
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Reviewed-by: Simon Glass <sjg@google.com>
Tested-by: Andrew Bresticker <abrestic@google.com>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
Signed-off-by: Andrew Bresticker <abrestic@google.com>
---
Changes since v9:
Fixed below comments given by Wolfram, Thanks for the reivew.
1. Removed retries as core does retries if -EAGAIN is returned
2. Removed mode from device tree info (use speed to distinguish
the mode of operation)
3. Use module_platform_driver macro instead of init and exit
4. Use wait_for_completion_timeout as the interruptible case is not tested well
.../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 861 ++++++++++++++++++++
4 files changed, 913 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..805e018
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,44 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ -> If not specified, the default value is 100khz in fast-speed mode and
+ 1Mhz in high-speed mode.
+ -> If specified, The bus operates in high-speed mode only if the
+ clock-frequency is >= 1Mhz.
+
+Example:
+
+hsi2c at 12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic at 66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 96c6d82..fecbe66 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -434,6 +434,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GPIOLIB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 385f99d..af6fa37 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..696d16f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,861 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_FIFO_MAX (0x40)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
+ HSI2C_INT_RX_UNDERRUN | \
+ HSI2C_INT_RX_OVERRUN | \
+ HSI2C_INT_TRAILING)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
+ HSI2C_TX_FIFO_EMPTY)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 100000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* timeout for pm runtime autosuspend */
+#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ unsigned int msg_ptr;
+ unsigned int msg_len;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+ int bus_id;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned char byte;
+ int len = 0;
+
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+ /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
+ else if (int_status &
+ (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ /* To support probing the devices for detection */
+ if (i2c->msg->len == 0) {
+ i2c->state = -ENXIO;
+ goto stop;
+ }
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ goto stop;
+ }
+ /* If TX FIFO is full (give chance to clear) */
+ else if (int_status & HSI2C_INT_TX_OVERRUN)
+ i2c->state = 0;
+
+ if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
+ HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+
+ if (fifo_level >= i2c->msg->len)
+ len = i2c->msg->len;
+ else
+ len = fifo_level;
+
+ i2c->msg_len += len;
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+
+ stop:
+ if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ complete(&i2c->msg_complete);
+ }
+
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be brought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+ u32 i2c_timeout;
+
+ /*
+ * When the message length is > FIFO depth, set the FIFO trigger
+ * at FIFO_MAX - 4. Just for ease of handling.
+ */
+ unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
+ (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
+
+ /* Clear to enable Timeout */
+ i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf |= HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* In auto mode the length of xfer cannot be 0 */
+ if (i2c->msg->len == 0)
+ i2c_auto_conf |= 0x1;
+ else
+ i2c_auto_conf |= i2c->msg->len;
+
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ /* Start data transfer in Master mode */
+ i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->msg_len = 0;
+ i2c->trans_done = 0;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ timeout = wait_for_completion_timeout
+ (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
+ if (timeout == 0) {
+ exynos5_i2c_reset(i2c);
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return timeout;
+ }
+
+ ret = i2c->state;
+
+ if (ret == -EAGAIN) {
+ exynos5_i2c_reset(i2c);
+ return ret;
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ struct i2c_msg *msgs_ptr = msgs;
+ int i = 0;
+ int ret = 0, ret_pm;
+ int stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ ret_pm = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret_pm)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
+ msgs_ptr++;
+
+ if (ret < 0)
+ goto out;
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ unsigned int op_clock;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ }
+
+ if (op_clock >= HSI2C_HS_TX_CLOCK) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ i2c->hs_clock = op_clock;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = op_clock;
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+ i2c->adap.retries = 2;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = irq_of_parse_and_map(np, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ /*
+ * TODO: Use private lock to avoid race conditions as
+ * mentioned in pm_runtime.txt
+ */
+ pm_runtime_enable(i2c->dev);
+ pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(i2c->dev);
+
+ ret = pm_runtime_get_sync(i2c->dev);
+ if (IS_ERR_VALUE(ret))
+ goto err_clk;
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_pm;
+
+ i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
+
+ exynos5_i2c_init(i2c);
+
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_pm;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+ pm_runtime_mark_last_busy(i2c->dev);
+ pm_runtime_put_autosuspend(i2c->dev);
+
+ return 0;
+
+ err_pm:
+ pm_runtime_put(i2c->dev);
+ pm_runtime_disable(&pdev->dev);
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ i2c_del_adapter(&i2c->adap);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+module_platform_driver(exynos5_i2c_driver);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread[parent not found: <1371638905-30633-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2013-06-19 10:48 ` Naveen Krishna Chatradhi
(?)
@ 2013-07-01 6:17 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-07-01 6:17 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ, balbi-l0cyMroinI0,
thomas.abraham-QSEj5FYQhm4dnm+yROfE0A
[-- Attachment #1: Type: text/plain, Size: 3121 bytes --]
On Wed, Jun 19, 2013 at 04:18:25PM +0530, Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
> 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> ---
>
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode and
> + 1Mhz in high-speed mode.
? If not specified, the default is 100kHz. There is no way to get 1MHz,
or?
...
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
My compiler says:
drivers/i2c/busses/i2c-exynos5.c: In function ‘exynos5_i2c_probe’:
drivers/i2c/busses/i2c-exynos5.c:687:5: warning: ‘op_clock’ may be used uninitialized in this function [-Wuninitialized]
so...
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
... this should be 'else if'
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
...
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
Huh, the core already gets an alias for you. Can't you use 'adap.nr'?
Rest looks good.
Thanks!
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-07-01 6:17 ` Wolfram Sang
0 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-07-01 6:17 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-arm-kernel, linux-samsung-soc,
devicetree-discuss, naveenkrishna.ch, kgene.kim, grant.likely,
linux-kernel, taeggyun.ko, balbi, thomas.abraham
[-- Attachment #1: Type: text/plain, Size: 2950 bytes --]
On Wed, Jun 19, 2013 at 04:18:25PM +0530, Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
> 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
>
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode and
> + 1Mhz in high-speed mode.
? If not specified, the default is 100kHz. There is no way to get 1MHz,
or?
...
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
My compiler says:
drivers/i2c/busses/i2c-exynos5.c: In function ‘exynos5_i2c_probe’:
drivers/i2c/busses/i2c-exynos5.c:687:5: warning: ‘op_clock’ may be used uninitialized in this function [-Wuninitialized]
so...
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
... this should be 'else if'
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
...
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
Huh, the core already gets an alias for you. Can't you use 'adap.nr'?
Rest looks good.
Thanks!
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-07-01 6:17 ` Wolfram Sang
0 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-07-01 6:17 UTC (permalink / raw)
To: linux-arm-kernel
On Wed, Jun 19, 2013 at 04:18:25PM +0530, Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
> 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
>
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode and
> + 1Mhz in high-speed mode.
? If not specified, the default is 100kHz. There is no way to get 1MHz,
or?
...
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
My compiler says:
drivers/i2c/busses/i2c-exynos5.c: In function ?exynos5_i2c_probe?:
drivers/i2c/busses/i2c-exynos5.c:687:5: warning: ?op_clock? may be used uninitialized in this function [-Wuninitialized]
so...
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
... this should be 'else if'
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
...
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
Huh, the core already gets an alias for you. Can't you use 'adap.nr'?
Rest looks good.
Thanks!
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130701/5b228867/attachment.sig>
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2013-06-19 10:48 ` Naveen Krishna Chatradhi
(?)
@ 2013-07-01 10:25 ` Tomasz Figa
-1 siblings, 0 replies; 107+ messages in thread
From: Tomasz Figa @ 2013-07-01 10:25 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
kgene.kim-Sze3O3UU22JBDgjK7y7TUQ,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ, wsa-z923LK4zBo2bacvFa/9K2g,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ, balbi-l0cyMroinI0,
thomas.abraham-QSEj5FYQhm4dnm+yROfE0A
Hi Naveen,
Looks mostly good, but see some comments inline.
On Wednesday 19 of June 2013 16:18:25 Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating
> frequencies 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not
> tested well 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> ---
>
> Changes since v9:
> Fixed below comments given by Wolfram, Thanks for the reivew.
> 1. Removed retries as core does retries if -EAGAIN is returned
> 2. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 3. Use module_platform_driver macro instead of init and exit
> 4. Use wait_for_completion_timeout as the interruptible case is not
> tested well
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 861
> ++++++++++++++++++++ 4 files changed, 913 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt new file mode
> 100644
> index 0000000..805e018
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,44 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C
> devices +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
IMHO this compatible value is too wide. You might end up with new Exynos 5
SoC that has a high speed I2C controller as well, but slightly different,
requiring some extra quirks. Now exynos5 in compatible would suggest that
it covers all Exynos 5 SoCs, but such SoC would require new one.
Basically, my suggestion is to use a compatible value with name of first SoC
in which given IP appeared, as it is already done in most bindings.
> + - reg: physical base address of the controller and length of memory
> mapped + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode
> and + 1Mhz in high-speed mode.
> + -> If specified, The bus operates in high-speed mode only if the
> + clock-frequency is >= 1Mhz.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 96c6d82..fecbe66 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GPIOLIB
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 385f99d..af6fa37 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs :=
> i2c-designware-platdrv.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) +=
> i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c
> b/drivers/i2c/busses/i2c-exynos5.c new file mode 100644
> index 0000000..696d16f
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,861 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole
> transaction + * 2. Manual mode: Software controls the transaction by
> issuing commands + * START, READ, WRITE, STOP, RESTART in
> I2C_MANUAL_CMD register. + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF
> register + *
I think a comment about mode used in this driver would be good.
> + * Special bits are available for both modes of operation to set
> commands + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
nit: AFAIK lower case characters are preferred in hexadecimal numbers in
kernel coding style.
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
nit: You don't need parentheses in case of simple numbers.
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 100000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
1 second seems a lot, but I guess such block don't use too much power.
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
The comment says that both can happen, while your code assumes they are
exlusive.
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
Is this even possible? The only reason for TX overrun I can see would be
the driver trying to put data in FIFO when it doesn't have enough space,
which shouldn't happen.
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for
> the + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be brought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + timeout = wait_for_completion_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return timeout;
> + }
> +
> + ret = i2c->state;
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + return ret;
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
This looks wrong to me.
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
This makes it:
if (unlikely((ret_pm) >= 0xFFFFF001)
which is obviously impossible for a signed value, such as ret_pm, which can
be at most 0x7FFFFFFF.
Just check for ret_pm < 0 here and in other occurencies of IS_ERR_VALUE()
in this driver.
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
Shouldn't this (and any other clock gating/ungating) be inside a runtime PM
callback? (Also this driver enables runtime PM, but lacks any callbacks. Is
it really correct?)
> +
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret < 0)
> + goto out;
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
Please use platform_get_irq(pdev, 0) here. Don't waste the effort that is
put into creating all those resources by of_platform_populate() early in
boot process.
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
AFAIK it's responsibility of i2c core to handle this.
> +
> + exynos5_i2c_init(i2c);
> +
> + ret = i2c_add_adapter(&i2c->adap);
You can call i2c_add_numbered_adapter() here to make i2c core assign a
number to your adapter, either automatically or using device tree alias.
Best regards,
Tomasz
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +module_platform_driver(exynos5_i2c_driver);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-07-01 10:25 ` Tomasz Figa
0 siblings, 0 replies; 107+ messages in thread
From: Tomasz Figa @ 2013-07-01 10:25 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-arm-kernel, linux-samsung-soc,
devicetree-discuss, naveenkrishna.ch, kgene.kim, grant.likely,
wsa, linux-kernel, taeggyun.ko, balbi, thomas.abraham
Hi Naveen,
Looks mostly good, but see some comments inline.
On Wednesday 19 of June 2013 16:18:25 Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating
> frequencies 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not
> tested well 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
>
> Changes since v9:
> Fixed below comments given by Wolfram, Thanks for the reivew.
> 1. Removed retries as core does retries if -EAGAIN is returned
> 2. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 3. Use module_platform_driver macro instead of init and exit
> 4. Use wait_for_completion_timeout as the interruptible case is not
> tested well
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 861
> ++++++++++++++++++++ 4 files changed, 913 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt new file mode
> 100644
> index 0000000..805e018
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,44 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C
> devices +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
IMHO this compatible value is too wide. You might end up with new Exynos 5
SoC that has a high speed I2C controller as well, but slightly different,
requiring some extra quirks. Now exynos5 in compatible would suggest that
it covers all Exynos 5 SoCs, but such SoC would require new one.
Basically, my suggestion is to use a compatible value with name of first SoC
in which given IP appeared, as it is already done in most bindings.
> + - reg: physical base address of the controller and length of memory
> mapped + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode
> and + 1Mhz in high-speed mode.
> + -> If specified, The bus operates in high-speed mode only if the
> + clock-frequency is >= 1Mhz.
> +
> +Example:
> +
> +hsi2c@12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic@66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 96c6d82..fecbe66 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GPIOLIB
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 385f99d..af6fa37 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs :=
> i2c-designware-platdrv.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) +=
> i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c
> b/drivers/i2c/busses/i2c-exynos5.c new file mode 100644
> index 0000000..696d16f
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,861 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole
> transaction + * 2. Manual mode: Software controls the transaction by
> issuing commands + * START, READ, WRITE, STOP, RESTART in
> I2C_MANUAL_CMD register. + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF
> register + *
I think a comment about mode used in this driver would be good.
> + * Special bits are available for both modes of operation to set
> commands + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
nit: AFAIK lower case characters are preferred in hexadecimal numbers in
kernel coding style.
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
nit: You don't need parentheses in case of simple numbers.
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 100000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
1 second seems a lot, but I guess such block don't use too much power.
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
The comment says that both can happen, while your code assumes they are
exlusive.
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
Is this even possible? The only reason for TX overrun I can see would be
the driver trying to put data in FIFO when it doesn't have enough space,
which shouldn't happen.
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for
> the + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be brought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + timeout = wait_for_completion_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return timeout;
> + }
> +
> + ret = i2c->state;
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + return ret;
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
This looks wrong to me.
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
This makes it:
if (unlikely((ret_pm) >= 0xFFFFF001)
which is obviously impossible for a signed value, such as ret_pm, which can
be at most 0x7FFFFFFF.
Just check for ret_pm < 0 here and in other occurencies of IS_ERR_VALUE()
in this driver.
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
Shouldn't this (and any other clock gating/ungating) be inside a runtime PM
callback? (Also this driver enables runtime PM, but lacks any callbacks. Is
it really correct?)
> +
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret < 0)
> + goto out;
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
Please use platform_get_irq(pdev, 0) here. Don't waste the effort that is
put into creating all those resources by of_platform_populate() early in
boot process.
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
AFAIK it's responsibility of i2c core to handle this.
> +
> + exynos5_i2c_init(i2c);
> +
> + ret = i2c_add_adapter(&i2c->adap);
You can call i2c_add_numbered_adapter() here to make i2c core assign a
number to your adapter, either automatically or using device tree alias.
Best regards,
Tomasz
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +module_platform_driver(exynos5_i2c_driver);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL v2");
^ permalink raw reply [flat|nested] 107+ messages in thread* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-07-01 10:25 ` Tomasz Figa
0 siblings, 0 replies; 107+ messages in thread
From: Tomasz Figa @ 2013-07-01 10:25 UTC (permalink / raw)
To: linux-arm-kernel
Hi Naveen,
Looks mostly good, but see some comments inline.
On Wednesday 19 of June 2013 16:18:25 Naveen Krishna Chatradhi wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating
> frequencies 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not
> tested well 13. few other bug fixes and cosmetic changes
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
> ---
>
> Changes since v9:
> Fixed below comments given by Wolfram, Thanks for the reivew.
> 1. Removed retries as core does retries if -EAGAIN is returned
> 2. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 3. Use module_platform_driver macro instead of init and exit
> 4. Use wait_for_completion_timeout as the interruptible case is not
> tested well
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 +
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 861
> ++++++++++++++++++++ 4 files changed, 913 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt new file mode
> 100644
> index 0000000..805e018
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,44 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C
> devices +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
IMHO this compatible value is too wide. You might end up with new Exynos 5
SoC that has a high speed I2C controller as well, but slightly different,
requiring some extra quirks. Now exynos5 in compatible would suggest that
it covers all Exynos 5 SoCs, but such SoC would require new one.
Basically, my suggestion is to use a compatible value with name of first SoC
in which given IP appeared, as it is already done in most bindings.
> + - reg: physical base address of the controller and length of memory
> mapped + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode
> and + 1Mhz in high-speed mode.
> + -> If specified, The bus operates in high-speed mode only if the
> + clock-frequency is >= 1Mhz.
> +
> +Example:
> +
> +hsi2c at 12ca0000 {
> + compatible = "samsung,exynos5-hsi2c";
> + reg = <0x12ca0000 0x100>;
> + interrupts = <56>;
> + clock-frequency = <100000>;
> +
> + pinctrl-0 = <&i2c4_bus>;
> + pinctrl-names = "default";
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + s2mps11_pmic at 66 {
> + compatible = "samsung,s2mps11-pmic";
> + reg = <0x66>;
> + };
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 96c6d82..fecbe66 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -434,6 +434,13 @@ config I2C_EG20T
> ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
> ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
>
> +config I2C_EXYNOS5
> + tristate "Exynos5 high-speed I2C driver"
> + depends on ARCH_EXYNOS5 && OF
> + help
> + Say Y here to include support for high-speed I2C controller in the
> + Exynos5 based Samsung SoCs.
> +
> config I2C_GPIO
> tristate "GPIO-based bitbanging I2C"
> depends on GPIOLIB
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 385f99d..af6fa37 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -42,6 +42,7 @@ i2c-designware-platform-objs :=
> i2c-designware-platdrv.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) +=
> i2c-designware-pci.o
> i2c-designware-pci-objs := i2c-designware-pcidrv.o
> obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> +obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-exynos5.c
> b/drivers/i2c/busses/i2c-exynos5.c new file mode 100644
> index 0000000..696d16f
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-exynos5.c
> @@ -0,0 +1,861 @@
> +/**
> + * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
> + *
> + * Copyright (C) 2013 Samsung Electronics Co., Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/time.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/clk.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_i2c.h>
> +
> +/*
> + * HSI2C controller from Samsung supports 2 modes of operation
> + * 1. Auto mode: Where in master automatically controls the whole
> transaction + * 2. Manual mode: Software controls the transaction by
> issuing commands + * START, READ, WRITE, STOP, RESTART in
> I2C_MANUAL_CMD register. + *
> + * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF
> register + *
I think a comment about mode used in this driver would be good.
> + * Special bits are available for both modes of operation to set
> commands + * and for checking transfer status
> + */
> +
> +/* Register Map */
> +#define HSI2C_CTL 0x00
> +#define HSI2C_FIFO_CTL 0x04
> +#define HSI2C_TRAILIG_CTL 0x08
> +#define HSI2C_CLK_CTL 0x0C
> +#define HSI2C_CLK_SLOT 0x10
> +#define HSI2C_INT_ENABLE 0x20
> +#define HSI2C_INT_STATUS 0x24
> +#define HSI2C_ERR_STATUS 0x2C
> +#define HSI2C_FIFO_STATUS 0x30
> +#define HSI2C_TX_DATA 0x34
> +#define HSI2C_RX_DATA 0x38
> +#define HSI2C_CONF 0x40
> +#define HSI2C_AUTO_CONF 0x44
> +#define HSI2C_TIMEOUT 0x48
> +#define HSI2C_MANUAL_CMD 0x4C
> +#define HSI2C_TRANS_STATUS 0x50
> +#define HSI2C_TIMING_HS1 0x54
> +#define HSI2C_TIMING_HS2 0x58
> +#define HSI2C_TIMING_HS3 0x5C
> +#define HSI2C_TIMING_FS1 0x60
> +#define HSI2C_TIMING_FS2 0x64
> +#define HSI2C_TIMING_FS3 0x68
> +#define HSI2C_TIMING_SLA 0x6C
> +#define HSI2C_ADDR 0x70
nit: AFAIK lower case characters are preferred in hexadecimal numbers in
kernel coding style.
> +/* I2C_CTL Register bits */
> +#define HSI2C_FUNC_MODE_I2C (1u << 0)
> +#define HSI2C_MASTER (1u << 3)
> +#define HSI2C_RXCHON (1u << 6)
> +#define HSI2C_TXCHON (1u << 7)
> +#define HSI2C_SW_RST (1u << 31)
> +
> +/* I2C_FIFO_CTL Register bits */
> +#define HSI2C_RXFIFO_EN (1u << 0)
> +#define HSI2C_TXFIFO_EN (1u << 1)
> +#define HSI2C_FIFO_MAX (0x40)
nit: You don't need parentheses in case of simple numbers.
> +#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
> +#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
> +/* I2C_TRAILING_CTL Register bits */
> +#define HSI2C_TRAILING_COUNT (0xf)
> +
> +/* I2C_INT_EN Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
> +#define HSI2C_INT_TRAILING_EN (1u << 6)
> +#define HSI2C_INT_I2C_EN (1u << 9)
> +
> +/* I2C_INT_STAT Register bits */
> +#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
> +#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
> +#define HSI2C_INT_TX_UNDERRUN (1u << 2)
> +#define HSI2C_INT_TX_OVERRUN (1u << 3)
> +#define HSI2C_INT_RX_UNDERRUN (1u << 4)
> +#define HSI2C_INT_RX_OVERRUN (1u << 5)
> +#define HSI2C_INT_TRAILING (1u << 6)
> +#define HSI2C_INT_I2C (1u << 9)
> +#define HSI2C_RX_INT (HSI2C_INT_RX_ALMOSTFULL | \
> + HSI2C_INT_RX_UNDERRUN | \
> + HSI2C_INT_RX_OVERRUN | \
> + HSI2C_INT_TRAILING)
> +
> +/* I2C_FIFO_STAT Register bits */
> +#define HSI2C_RX_FIFO_EMPTY (1u << 24)
> +#define HSI2C_RX_FIFO_FULL (1u << 23)
> +#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
> +#define HSI2C_TX_FIFO_EMPTY (1u << 8)
> +#define HSI2C_TX_FIFO_FULL (1u << 7)
> +#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
> +#define HSI2C_FIFO_EMPTY (HSI2C_RX_FIFO_EMPTY | \
> + HSI2C_TX_FIFO_EMPTY)
> +
> +/* I2C_CONF Register bits */
> +#define HSI2C_AUTO_MODE (1u << 31)
> +#define HSI2C_10BIT_ADDR_MODE (1u << 30)
> +#define HSI2C_HS_MODE (1u << 29)
> +
> +/* I2C_AUTO_CONF Register bits */
> +#define HSI2C_READ_WRITE (1u << 16)
> +#define HSI2C_STOP_AFTER_TRANS (1u << 17)
> +#define HSI2C_MASTER_RUN (1u << 31)
> +
> +/* I2C_TIMEOUT Register bits */
> +#define HSI2C_TIMEOUT_EN (1u << 31)
> +
> +/* I2C_TRANS_STATUS register bits */
> +#define HSI2C_MASTER_BUSY (1u << 17)
> +#define HSI2C_SLAVE_BUSY (1u << 16)
> +#define HSI2C_TIMEOUT_AUTO (1u << 4)
> +#define HSI2C_NO_DEV (1u << 3)
> +#define HSI2C_NO_DEV_ACK (1u << 2)
> +#define HSI2C_TRANS_ABORT (1u << 1)
> +#define HSI2C_TRANS_DONE (1u << 0)
> +
> +/* I2C_ADDR register bits */
> +#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
> +#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
> +#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
> +#define MASTER_ID(x) ((x & 0x7) + 0x08)
> +
> +/*
> + * Controller operating frequency, timing values for operation
> + * are calculated against this frequency
> + */
> +#define HSI2C_HS_TX_CLOCK 1000000
> +#define HSI2C_FS_TX_CLOCK 100000
> +#define HSI2C_HIGH_SPD 1
> +#define HSI2C_FAST_SPD 0
> +
> +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
> +
> +/* timeout for pm runtime autosuspend */
> +#define EXYNOS5_I2C_PM_TIMEOUT 1000 /* ms */
1 second seems a lot, but I guess such block don't use too much power.
> +struct exynos5_i2c {
> + struct i2c_adapter adap;
> + unsigned int suspended:1;
> +
> + struct i2c_msg *msg;
> + struct completion msg_complete;
> + unsigned int msg_ptr;
> + unsigned int msg_len;
> +
> + unsigned int irq;
> +
> + void __iomem *regs;
> + struct clk *clk;
> + struct device *dev;
> + int state;
> +
> + /*
> + * Since the TRANS_DONE bit is cleared on read, and we may read it
> + * either during an IRQ or after a transaction, keep track of its
> + * state here.
> + */
> + int trans_done;
> +
> + /* Controller operating frequency */
> + unsigned int fs_clock;
> + unsigned int hs_clock;
> +
> + /*
> + * HSI2C Controller can operate in
> + * 1. High speed upto 3.4Mbps
> + * 2. Fast speed upto 1Mbps
> + */
> + int speed_mode;
> + int bus_id;
> +};
> +
> +static const struct of_device_id exynos5_i2c_match[] = {
> + { .compatible = "samsung,exynos5-hsi2c" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
> +
> +static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
> +{
> + writel(readl(i2c->regs + HSI2C_INT_STATUS),
> + i2c->regs + HSI2C_INT_STATUS);
> +}
> +
> +/*
> + * exynos5_i2c_set_timing: updates the registers with appropriate
> + * timing values calculated
> + *
> + * Returns 0 on success, -EINVAL if the cycle length cannot
> + * be calculated.
> + */
> +static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
> +{
> + u32 i2c_timing_s1;
> + u32 i2c_timing_s2;
> + u32 i2c_timing_s3;
> + u32 i2c_timing_sla;
> + unsigned int t_start_su, t_start_hd;
> + unsigned int t_stop_su;
> + unsigned int t_data_su, t_data_hd;
> + unsigned int t_scl_l, t_scl_h;
> + unsigned int t_sr_release;
> + unsigned int t_ftl_cycle;
> + unsigned int clkin = clk_get_rate(i2c->clk);
> + unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
> + unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
> + i2c->hs_clock : i2c->fs_clock;
> +
> + /*
> + * FPCLK / FI2C =
> + * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
> + * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
> + * utemp1 = (TSCLK_L + TSCLK_H + 2)
> + */
> + t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
> + utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
> +
> + /* CLK_DIV max is 256 */
> + for (div = 0; div < 256; div++) {
> + utemp1 = utemp0 / (div + 1);
> +
> + /*
> + * SCL_L and SCL_H each has max value of 255
> + * Hence, For the clk_cycle to the have right value
> + * utemp1 has to be less then 512 and more than 4.
> + */
> + if ((utemp1 < 512) && (utemp1 > 4)) {
> + clk_cycle = utemp1 - 2;
> + break;
> + } else if (div == 255) {
> + dev_warn(i2c->dev, "Failed to calculate divisor");
> + return -EINVAL;
> + }
> + }
> +
> + t_scl_l = clk_cycle / 2;
> + t_scl_h = clk_cycle / 2;
> + t_start_su = t_scl_l;
> + t_start_hd = t_scl_l;
> + t_stop_su = t_scl_l;
> + t_data_su = t_scl_l / 2;
> + t_data_hd = t_scl_l / 2;
> + t_sr_release = clk_cycle;
> +
> + i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
> + i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
> + i2c_timing_s3 = div << 16 | t_sr_release << 0;
> + i2c_timing_sla = t_data_hd << 0;
> +
> + dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
> + t_start_su, t_start_hd, t_stop_su);
> + dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
> + t_data_su, t_scl_l, t_scl_h);
> + dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
> + div, t_sr_release);
> + dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
> +
> + if (mode == HSI2C_HIGH_SPD) {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
> + } else {
> + writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
> + writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
> + writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
> + }
> + writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
> +
> + return 0;
> +}
> +
> +static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
> +{
> + /*
> + * Configure the Fast speed timing values
> + * Even the High Speed mode initially starts with Fast mode
> + */
> + if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
> + dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
> + return -EINVAL;
> + }
> +
> + /* configure the High speed timing values */
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
> + dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->bus_id)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_ctl;
> +
> + /* Set and clear the bit for reset */
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl |= HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~HSI2C_SW_RST;
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* We don't expect calculations to fail during the run */
> + exynos5_hsi2c_clock_setup(i2c);
> + /* Initialize the configure registers */
> + exynos5_i2c_init(i2c);
> +}
> +
> +/*
> + * exynos5_i2c_irq: top level IRQ servicing routine
> + *
> + * INT_STATUS registers gives the interrupt details. Further,
> + * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
> + * state of the bus.
> + */
> +static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
> +{
> + struct exynos5_i2c *i2c = dev_id;
> + u32 fifo_level, int_status, fifo_status, trans_status;
> + unsigned char byte;
> + int len = 0;
> +
> + i2c->state = -EINVAL;
> +
> + int_status = readl(i2c->regs + HSI2C_INT_STATUS);
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> +
> + if (int_status & HSI2C_INT_I2C) {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_NO_DEV_ACK) {
> + dev_dbg(i2c->dev, "No ACK from device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_NO_DEV) {
> + dev_dbg(i2c->dev, "No device\n");
> + i2c->state = -ENXIO;
> + } else if (trans_status & HSI2C_TRANS_ABORT) {
> + dev_dbg(i2c->dev, "Deal with arbitration lose\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
> + dev_dbg(i2c->dev, "Accessing device timed out\n");
> + i2c->state = -EAGAIN;
> + } else if (trans_status & HSI2C_TRANS_DONE) {
> + i2c->trans_done = 1;
> + i2c->state = 0;
> + }
> + }
> + /* TX_ALMOSTEMPTY can happen along with HSI2C_INT_I2C */
The comment says that both can happen, while your code assumes they are
exlusive.
> + else if (int_status &
> + (HSI2C_INT_TX_UNDERRUN | HSI2C_INT_TX_ALMOSTEMPTY)) {
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + /* To support probing the devices for detection */
> + if (i2c->msg->len == 0) {
> + i2c->state = -ENXIO;
> + goto stop;
> + }
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > i2c->msg->len)
> + len = i2c->msg->len;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + goto stop;
> + }
> + /* If TX FIFO is full (give chance to clear) */
> + else if (int_status & HSI2C_INT_TX_OVERRUN)
Is this even possible? The only reason for TX overrun I can see would be
the driver trying to put data in FIFO when it doesn't have enough space,
which shouldn't happen.
> + i2c->state = 0;
> +
> + if (int_status & (HSI2C_INT_RX_OVERRUN | HSI2C_INT_TRAILING |
> + HSI2C_INT_RX_UNDERRUN | HSI2C_INT_RX_ALMOSTFULL)) {
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> +
> + if (fifo_level >= i2c->msg->len)
> + len = i2c->msg->len;
> + else
> + len = fifo_level;
> +
> + i2c->msg_len += len;
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> +
> + stop:
> + if ((i2c->msg_len == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + complete(&i2c->msg_complete);
> + }
> +
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * exynos5_i2c_wait_bus_idle
> + *
> + * Wait for the transaction to complete (indicated by the TRANS_DONE bit
> + * being set), and, if this is the last message in a transfer, wait for
> the + * MASTER_BUSY bit to be cleared.
> + *
> + * Returns -EBUSY if the bus cannot be brought to idle
> + */
> +static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
> +{
> + unsigned long stop_time;
> + u32 trans_status;
> +
> + /* wait for 100 milli seconds for the bus to be idle */
> + stop_time = jiffies + msecs_to_jiffies(100) + 1;
> + do {
> + trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
> + if (trans_status & HSI2C_TRANS_DONE)
> + i2c->trans_done = 1;
> + /*
> + * Only wait for MASTER_BUSY to be cleared if this is the last
> + * message.
> + */
> + if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
> + i2c->trans_done)
> + return 0;
> +
> + usleep_range(50, 200);
> + } while (time_before(jiffies, stop_time));
> +
> + return -EBUSY;
> +}
> +
> +/*
> + * exynos5_i2c_message_start: Configures the bus and starts the xfer
> + * i2c: struct exynos5_i2c pointer for the current bus
> + * stop: Enables stop after transfer if set. Set for last transfer of
> + * in the list of messages.
> + *
> + * Configures the bus for read/write function
> + * Sets chip address to talk to, message length to be sent.
> + * Enables appropriate interrupts and sends start xfer command.
> + */
> +static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
> +{
> + u32 i2c_ctl;
> + u32 int_en = HSI2C_INT_I2C_EN;
> + u32 i2c_auto_conf = 0;
> + u32 fifo_ctl;
> + u32 i2c_timeout;
> +
> + /*
> + * When the message length is > FIFO depth, set the FIFO trigger
> + * at FIFO_MAX - 4. Just for ease of handling.
> + */
> + unsigned short len = (i2c->msg->len > HSI2C_FIFO_MAX) ?
> + (HSI2C_FIFO_MAX - 4) : i2c->msg->len;
> +
> + /* Clear to enable Timeout */
> + i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
> +
> + fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> +
> + i2c_ctl = readl(i2c->regs + HSI2C_CTL);
> + i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
> + if (i2c->msg->flags & I2C_M_RD) {
> + i2c_ctl |= HSI2C_RXCHON;
> +
> + i2c_auto_conf |= HSI2C_READ_WRITE;
> +
> + fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(len);
> + int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
> + HSI2C_INT_TRAILING_EN);
> + } else {
> + i2c_ctl |= HSI2C_TXCHON;
> +
> + fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(len);
> + int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
> + }
> +
> + if (stop == 1)
> + i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
> +
> + writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
> +
> + writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
> + writel(i2c_ctl, i2c->regs + HSI2C_CTL);
> +
> + /* In auto mode the length of xfer cannot be 0 */
> + if (i2c->msg->len == 0)
> + i2c_auto_conf |= 0x1;
> + else
> + i2c_auto_conf |= i2c->msg->len;
> +
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + /* Start data transfer in Master mode */
> + i2c_auto_conf = readl(i2c->regs + HSI2C_AUTO_CONF);
> + i2c_auto_conf |= HSI2C_MASTER_RUN;
> + writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
> +
> + writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
> +}
> +
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->msg_len = 0;
> + i2c->trans_done = 0;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + timeout = wait_for_completion_timeout
> + (&i2c->msg_complete, EXYNOS5_I2C_TIMEOUT);
> + if (timeout == 0) {
> + exynos5_i2c_reset(i2c);
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return timeout;
> + }
> +
> + ret = i2c->state;
> +
> + if (ret == -EAGAIN) {
> + exynos5_i2c_reset(i2c);
> + return ret;
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + struct i2c_msg *msgs_ptr = msgs;
> + int i = 0;
> + int ret = 0, ret_pm;
> + int stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + ret_pm = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret_pm)) {
This looks wrong to me.
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
This makes it:
if (unlikely((ret_pm) >= 0xFFFFF001)
which is obviously impossible for a signed value, such as ret_pm, which can
be at most 0x7FFFFFFF.
Just check for ret_pm < 0 here and in other occurencies of IS_ERR_VALUE()
in this driver.
> + ret = -EIO;
> + goto out;
> + }
> +
> + clk_prepare_enable(i2c->clk);
Shouldn't this (and any other clock gating/ungating) be inside a runtime PM
callback? (Also this driver enables runtime PM, but lacks any callbacks. Is
it really correct?)
> +
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs_ptr, stop);
> + msgs_ptr++;
> +
> + if (ret < 0)
> + goto out;
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
> + if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + }
> +
> + if (op_clock >= HSI2C_HS_TX_CLOCK) {
> + i2c->speed_mode = HSI2C_HIGH_SPD;
> + i2c->fs_clock = HSI2C_FS_TX_CLOCK;
> + i2c->hs_clock = op_clock;
> + } else {
> + i2c->speed_mode = HSI2C_FAST_SPD;
> + i2c->fs_clock = op_clock;
> + }
> +
> + strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &exynos5_i2c_algorithm;
> + i2c->adap.retries = 2;
> +
> + i2c->dev = &pdev->dev;
> + i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
> + if (IS_ERR(i2c->clk)) {
> + dev_err(&pdev->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(i2c->regs)) {
> + ret = PTR_ERR(i2c->regs);
> + goto err_clk;
> + }
> +
> + i2c->adap.dev.of_node = np;
> + i2c->adap.algo_data = i2c;
> + i2c->adap.dev.parent = &pdev->dev;
> +
> + /* Clear pending interrupts from u-boot or misc causes */
> + exynos5_i2c_clr_pend_irq(i2c);
> +
> + init_completion(&i2c->msg_complete);
> +
> + i2c->irq = ret = irq_of_parse_and_map(np, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
> + ret = -EINVAL;
> + goto err_clk;
> + }
Please use platform_get_irq(pdev, 0) here. Don't waste the effort that is
put into creating all those resources by of_platform_populate() early in
boot process.
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
> + 0, dev_name(&pdev->dev), i2c);
> +
> + if (ret != 0) {
> + dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
> + goto err_clk;
> + }
> +
> + /*
> + * TODO: Use private lock to avoid race conditions as
> + * mentioned in pm_runtime.txt
> + */
> + pm_runtime_enable(i2c->dev);
> + pm_runtime_set_autosuspend_delay(i2c->dev, EXYNOS5_I2C_PM_TIMEOUT);
> + pm_runtime_use_autosuspend(i2c->dev);
> +
> + ret = pm_runtime_get_sync(i2c->dev);
> + if (IS_ERR_VALUE(ret))
> + goto err_clk;
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret)
> + goto err_pm;
> +
> + i2c->bus_id = of_alias_get_id(i2c->adap.dev.of_node, "hsi2c");
AFAIK it's responsibility of i2c core to handle this.
> +
> + exynos5_i2c_init(i2c);
> +
> + ret = i2c_add_adapter(&i2c->adap);
You can call i2c_add_numbered_adapter() here to make i2c core assign a
number to your adapter, either automatically or using device tree alias.
Best regards,
Tomasz
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to add bus to i2c core\n");
> + goto err_pm;
> + }
> +
> + of_i2c_register_devices(&i2c->adap);
> + platform_set_drvdata(pdev, i2c);
> +
> + clk_disable_unprepare(i2c->clk);
> + pm_runtime_mark_last_busy(i2c->dev);
> + pm_runtime_put_autosuspend(i2c->dev);
> +
> + return 0;
> +
> + err_pm:
> + pm_runtime_put(i2c->dev);
> + pm_runtime_disable(&pdev->dev);
> + err_clk:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static int exynos5_i2c_remove(struct platform_device *pdev)
> +{
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret;
> +
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (IS_ERR_VALUE(ret))
> + return ret;
> +
> + i2c_del_adapter(&i2c->adap);
> +
> + pm_runtime_put(&pdev->dev);
> + pm_runtime_disable(&pdev->dev);
> +
> + clk_disable_unprepare(i2c->clk);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int exynos5_i2c_suspend_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c->suspended = 1;
> +
> + return 0;
> +}
> +
> +static int exynos5_i2c_resume_noirq(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + clk_prepare_enable(i2c->clk);
> +
> + ret = exynos5_hsi2c_clock_setup(i2c);
> + if (ret) {
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> + }
> +
> + exynos5_i2c_init(i2c);
> + clk_disable_unprepare(i2c->clk);
> + i2c->suspended = 0;
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver exynos5_i2c_driver = {
> + .probe = exynos5_i2c_probe,
> + .remove = exynos5_i2c_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "exynos5-hsi2c",
> + .pm = EXYNOS5_DEV_PM_OPS,
> + .of_match_table = exynos5_i2c_match,
> + },
> +};
> +
> +module_platform_driver(exynos5_i2c_driver);
> +
> +MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
> +MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
> +MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
> +MODULE_LICENSE("GPL v2");
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2013-07-01 10:25 ` Tomasz Figa
@ 2013-08-15 13:12 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-08-15 13:12 UTC (permalink / raw)
To: Tomasz Figa
Cc: Naveen Krishna Chatradhi, linux-i2c, linux-arm-kernel,
linux-samsung-soc, devicetree-discuss, naveenkrishna.ch,
kgene.kim, grant.likely, linux-kernel, taeggyun.ko, balbi,
thomas.abraham
[-- Attachment #1: Type: text/plain, Size: 143 bytes --]
On Mon, Jul 01, 2013 at 12:25:19PM +0200, Tomasz Figa wrote:
> Hi Naveen,
>
> Looks mostly good, but see some comments inline.
Ping?
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-08-15 13:12 ` Wolfram Sang
0 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-08-15 13:12 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, Jul 01, 2013 at 12:25:19PM +0200, Tomasz Figa wrote:
> Hi Naveen,
>
> Looks mostly good, but see some comments inline.
Ping?
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130815/56ba78df/attachment.sig>
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2013-08-15 13:12 ` Wolfram Sang
@ 2013-08-16 4:58 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-08-16 4:58 UTC (permalink / raw)
To: Wolfram Sang
Cc: Tomasz Figa, Naveen Krishna Chatradhi, linux-i2c,
linux-arm-kernel, linux-samsung-soc@vger.kernel.org,
devicetree-discuss, Kukjin Kim, grant.likely, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
On 15 August 2013 18:42, Wolfram Sang <wsa@the-dreams.de> wrote:
> On Mon, Jul 01, 2013 at 12:25:19PM +0200, Tomasz Figa wrote:
>> Hi Naveen,
>>
>> Looks mostly good, but see some comments inline.
>
> Ping?
Hello Wolfram,
I made a patch fixing your comments and from Thomas Figa as well.
Meanwhile, we hit a random crash in exynos5_i2c_irq function.
The "len" variable being is still being near the FIFO_MAX after this condition
len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
Once, we fix this problem. i planned to rebase and submit.
Is it okey with you?
Thanks,
Naveen
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
@ 2013-08-16 4:58 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-08-16 4:58 UTC (permalink / raw)
To: linux-arm-kernel
On 15 August 2013 18:42, Wolfram Sang <wsa@the-dreams.de> wrote:
> On Mon, Jul 01, 2013 at 12:25:19PM +0200, Tomasz Figa wrote:
>> Hi Naveen,
>>
>> Looks mostly good, but see some comments inline.
>
> Ping?
Hello Wolfram,
I made a patch fixing your comments and from Thomas Figa as well.
Meanwhile, we hit a random crash in exynos5_i2c_irq function.
The "len" variable being is still being near the FIFO_MAX after this condition
len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > i2c->msg->len)
+ len = i2c->msg->len;
Once, we fix this problem. i planned to rebase and submit.
Is it okey with you?
Thanks,
Naveen
>
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread
* Re: [PATCH v10] i2c: exynos5: add High Speed I2C controller driver
2013-08-16 4:58 ` Naveen Krishna Ch
@ 2013-08-16 7:05 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-08-16 7:05 UTC (permalink / raw)
To: Naveen Krishna Ch
Cc: Tomasz Figa, Naveen Krishna Chatradhi, linux-i2c,
linux-arm-kernel, linux-samsung-soc@vger.kernel.org,
devicetree-discuss, Kukjin Kim, grant.likely, linux-kernel,
taeggyun.ko, balbi, thomas.abraham
[-- Attachment #1: Type: text/plain, Size: 138 bytes --]
> Once, we fix this problem. i planned to rebase and submit.
>
> Is it okey with you?
Perfectly fine. Thanks for the heads up.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread
* [PATCH] i2c: exynos5: add High Speed I2C controller driver
2012-11-27 13:00 ` Naveen Krishna Chatradhi
@ 2013-08-21 9:24 ` Naveen Krishna Chatradhi
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-08-21 9:24 UTC (permalink / raw)
To: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA
Cc: w.sang-bIcnvbaLZ9MEGnE8C9+IrQ, t.figa-Sze3O3UU22JBDgjK7y7TUQ,
ben-linux-elnMNo+KYs3YtjvyW6yDsg,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Highspeed mode is a minor change in the i2c protocol.
Starts with
1. start condition,
2. 8-bit master ID code (00001xxx)
3. followed by a NACK bit
Once the above conditions are met, the bus is now operates in highspeed mode.
The rest of the I2C protocol applies the same.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. Removed retries as core does retries if -EAGAIN is returned
11. Removed mode from device tree info (use speed to distinguish
the mode of operation)
12. Use wait_for_completion_timeout as the interruptible case is not tested well
13. few other bug fixes and cosmetic changes
14. Removed the untested runtime_pm code
15. Removed the retries as core does that
16. Use adap.nr instead of alias
17. Added spinlocks around the irq code
18. Use i2c_add_numbered_adapter() instead of using aliases
Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Wolfram and Thomas Figa thanks for reviewing the code.
Changes since v10:
1. Remove the incomplete runtime_pm code
2. Correct the error checks as suggested by Thomas
3. Use i2c_add_numbered_adapter() as suggested
4. Modified the irq routine to handle the specific interrupts
5. Added spinlocks around the irq code
6. Remove the "mode" of operation field from device tree node and use the
clock-frequency to decide the mode.
.../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
4 files changed, 851 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..805e018
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,44 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ -> If not specified, the default value is 100khz in fast-speed mode and
+ 1Mhz in high-speed mode.
+ -> If specified, The bus operates in high-speed mode only if the
+ clock-frequency is >= 1Mhz.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fcdd321..69b1848 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -436,6 +436,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GPIOLIB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index d00997f..d1ad371 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..5cbe1d4
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,799 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+
+/* As per user manual FIFO max depth is 64bytes */
+#define HSI2C_FIFO_MAX 0x40
+/* default trigger levels for Tx and Rx FIFOs */
+#define HSI2C_DEF_TXFIFO_LVL (HSI2C_FIFO_MAX - 0x30)
+#define HSI2C_DEF_RXFIFO_LVL (HSI2C_FIFO_MAX - 0x10)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 100000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ spinlock_t lock; /* IRQ synchronization */
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+ u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Disable timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned long flags;
+ u32 len;
+ unsigned char byte;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+
+ /* handle interrupt related to the transfer status */
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ goto stop;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ goto stop;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ goto stop;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ goto stop;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+
+ if ((i2c->msg->flags & I2C_M_RD) && (int_status &
+ (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+ len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
+
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > (i2c->msg->len - i2c->msg_ptr))
+ len = i2c->msg->len - i2c->msg_ptr;
+
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+ stop:
+ if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ exynos5_i2c_clr_pend_irq(i2c);
+ complete(&i2c->msg_complete);
+ } else {
+ exynos5_i2c_clr_pend_irq(i2c);
+ }
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf = HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(HSI2C_DEF_TXFIFO_LVL);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(HSI2C_DEF_RXFIFO_LVL);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ i2c_auto_conf |= i2c->msg->len;
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ spin_lock_irq(&i2c->lock);
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->trans_done = 0;
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ spin_unlock_irq(&i2c->lock);
+ timeout = wait_for_completion_timeout(&i2c->msg_complete,
+ EXYNOS5_I2C_TIMEOUT);
+ if (timeout == 0)
+ ret = -ETIMEDOUT;
+ else
+ ret = i2c->state;
+
+ if (ret < 0) {
+ exynos5_i2c_reset(i2c);
+ if (ret == -ETIMEDOUT) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int i = 0, ret = 0, stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ if (msgs->len == 0) {
+ msgs++;
+ continue;
+ }
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
+ if (!stop)
+ msgs++;
+
+ if (ret < 0)
+ goto out;
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ unsigned int op_clock;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ } else {
+ if (op_clock >= HSI2C_HS_TX_CLOCK) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ i2c->hs_clock = op_clock;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = op_clock;
+ }
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ spin_lock_init(&i2c->lock);
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = platform_get_irq(pdev, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_clk;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_clk;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adap);
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+module_platform_driver(exynos5_i2c_driver);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread* [PATCH] i2c: exynos5: add High Speed I2C controller driver
@ 2013-08-21 9:24 ` Naveen Krishna Chatradhi
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-08-21 9:24 UTC (permalink / raw)
To: linux-i2c, linux-kernel, linux-samsung-soc
Cc: w.sang, t.figa, ben-linux, grant.likely, devicetree-discuss, sjg,
naveenkrishna.ch, broonie
Adds support for High Speed I2C driver found in Exynos5 and
later SoCs from Samsung.
Highspeed mode is a minor change in the i2c protocol.
Starts with
1. start condition,
2. 8-bit master ID code (00001xxx)
3. followed by a NACK bit
Once the above conditions are met, the bus is now operates in highspeed mode.
The rest of the I2C protocol applies the same.
Driver only supports Device Tree method.
Changes since v1:
1. Added FIFO functionality
2. Added High speed mode functionality
3. Remove SMBUS_QUICK
4. Remove the debugfs functionality
5. Use devm_* functions where ever possible
6. Driver is free from GPIO configs
7. Use OF data string "clock-frequency" to get the bus operating frequencies
8. Split the clock divisor calculation function
9. Add resets for the failed transacton cases
10. Removed retries as core does retries if -EAGAIN is returned
11. Removed mode from device tree info (use speed to distinguish
the mode of operation)
12. Use wait_for_completion_timeout as the interruptible case is not tested well
13. few other bug fixes and cosmetic changes
14. Removed the untested runtime_pm code
15. Removed the retries as core does that
16. Use adap.nr instead of alias
17. Added spinlocks around the irq code
18. Use i2c_add_numbered_adapter() instead of using aliases
Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Reviewed-by: Simon Glass <sjg@google.com>
Tested-by: Andrew Bresticker <abrestic@google.com>
Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
Signed-off-by: Andrew Bresticker <abrestic@google.com>
---
Wolfram and Thomas Figa thanks for reviewing the code.
Changes since v10:
1. Remove the incomplete runtime_pm code
2. Correct the error checks as suggested by Thomas
3. Use i2c_add_numbered_adapter() as suggested
4. Modified the irq routine to handle the specific interrupts
5. Added spinlocks around the irq code
6. Remove the "mode" of operation field from device tree node and use the
clock-frequency to decide the mode.
.../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
4 files changed, 851 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
create mode 100644 drivers/i2c/busses/i2c-exynos5.c
diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
new file mode 100644
index 0000000..805e018
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
@@ -0,0 +1,44 @@
+* Samsung's High Speed I2C controller
+
+The Samsung's High Speed I2C controller is used to interface with I2C devices
+at various speeds ranging from 100khz to 3.4Mhz.
+
+Required properties:
+ - compatible: value should be.
+ -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
+ - reg: physical base address of the controller and length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - #address-cells: always 1 (for i2c addresses)
+ - #size-cells: always 0
+
+ - Pinctrl:
+ - pinctrl-0: Pin control group to be used for this controller.
+ - pinctrl-names: Should contain only one value - "default".
+
+Optional properties:
+ - clock-frequency: Desired operating frequency in Hz of the bus.
+ -> If not specified, the default value is 100khz in fast-speed mode and
+ 1Mhz in high-speed mode.
+ -> If specified, The bus operates in high-speed mode only if the
+ clock-frequency is >= 1Mhz.
+
+Example:
+
+hsi2c@12ca0000 {
+ compatible = "samsung,exynos5-hsi2c";
+ reg = <0x12ca0000 0x100>;
+ interrupts = <56>;
+ clock-frequency = <100000>;
+
+ pinctrl-0 = <&i2c4_bus>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ s2mps11_pmic@66 {
+ compatible = "samsung,s2mps11-pmic";
+ reg = <0x66>;
+ };
+};
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fcdd321..69b1848 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -436,6 +436,13 @@ config I2C_EG20T
ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series.
ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+config I2C_EXYNOS5
+ tristate "Exynos5 high-speed I2C driver"
+ depends on ARCH_EXYNOS5 && OF
+ help
+ Say Y here to include support for high-speed I2C controller in the
+ Exynos5 based Samsung SoCs.
+
config I2C_GPIO
tristate "GPIO-based bitbanging I2C"
depends on GPIOLIB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index d00997f..d1ad371 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -42,6 +42,7 @@ i2c-designware-platform-objs := i2c-designware-platdrv.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-objs := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
+obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c
new file mode 100644
index 0000000..5cbe1d4
--- /dev/null
+++ b/drivers/i2c/busses/i2c-exynos5.c
@@ -0,0 +1,799 @@
+/**
+ * i2c-exynos5.c - Samsung Exynos5 I2C Controller Driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_i2c.h>
+
+/*
+ * HSI2C controller from Samsung supports 2 modes of operation
+ * 1. Auto mode: Where in master automatically controls the whole transaction
+ * 2. Manual mode: Software controls the transaction by issuing commands
+ * START, READ, WRITE, STOP, RESTART in I2C_MANUAL_CMD register.
+ *
+ * Operation mode can be selected by setting AUTO_MODE bit in I2C_CONF register
+ *
+ * Special bits are available for both modes of operation to set commands
+ * and for checking transfer status
+ */
+
+/* Register Map */
+#define HSI2C_CTL 0x00
+#define HSI2C_FIFO_CTL 0x04
+#define HSI2C_TRAILIG_CTL 0x08
+#define HSI2C_CLK_CTL 0x0C
+#define HSI2C_CLK_SLOT 0x10
+#define HSI2C_INT_ENABLE 0x20
+#define HSI2C_INT_STATUS 0x24
+#define HSI2C_ERR_STATUS 0x2C
+#define HSI2C_FIFO_STATUS 0x30
+#define HSI2C_TX_DATA 0x34
+#define HSI2C_RX_DATA 0x38
+#define HSI2C_CONF 0x40
+#define HSI2C_AUTO_CONF 0x44
+#define HSI2C_TIMEOUT 0x48
+#define HSI2C_MANUAL_CMD 0x4C
+#define HSI2C_TRANS_STATUS 0x50
+#define HSI2C_TIMING_HS1 0x54
+#define HSI2C_TIMING_HS2 0x58
+#define HSI2C_TIMING_HS3 0x5C
+#define HSI2C_TIMING_FS1 0x60
+#define HSI2C_TIMING_FS2 0x64
+#define HSI2C_TIMING_FS3 0x68
+#define HSI2C_TIMING_SLA 0x6C
+#define HSI2C_ADDR 0x70
+
+/* I2C_CTL Register bits */
+#define HSI2C_FUNC_MODE_I2C (1u << 0)
+#define HSI2C_MASTER (1u << 3)
+#define HSI2C_RXCHON (1u << 6)
+#define HSI2C_TXCHON (1u << 7)
+#define HSI2C_SW_RST (1u << 31)
+
+/* I2C_FIFO_CTL Register bits */
+#define HSI2C_RXFIFO_EN (1u << 0)
+#define HSI2C_TXFIFO_EN (1u << 1)
+#define HSI2C_RXFIFO_TRIGGER_LEVEL(x) ((x) << 4)
+#define HSI2C_TXFIFO_TRIGGER_LEVEL(x) ((x) << 16)
+
+/* As per user manual FIFO max depth is 64bytes */
+#define HSI2C_FIFO_MAX 0x40
+/* default trigger levels for Tx and Rx FIFOs */
+#define HSI2C_DEF_TXFIFO_LVL (HSI2C_FIFO_MAX - 0x30)
+#define HSI2C_DEF_RXFIFO_LVL (HSI2C_FIFO_MAX - 0x10)
+
+/* I2C_TRAILING_CTL Register bits */
+#define HSI2C_TRAILING_COUNT (0xf)
+
+/* I2C_INT_EN Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY_EN (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL_EN (1u << 1)
+#define HSI2C_INT_TRAILING_EN (1u << 6)
+#define HSI2C_INT_I2C_EN (1u << 9)
+
+/* I2C_INT_STAT Register bits */
+#define HSI2C_INT_TX_ALMOSTEMPTY (1u << 0)
+#define HSI2C_INT_RX_ALMOSTFULL (1u << 1)
+#define HSI2C_INT_TX_UNDERRUN (1u << 2)
+#define HSI2C_INT_TX_OVERRUN (1u << 3)
+#define HSI2C_INT_RX_UNDERRUN (1u << 4)
+#define HSI2C_INT_RX_OVERRUN (1u << 5)
+#define HSI2C_INT_TRAILING (1u << 6)
+#define HSI2C_INT_I2C (1u << 9)
+
+/* I2C_FIFO_STAT Register bits */
+#define HSI2C_RX_FIFO_EMPTY (1u << 24)
+#define HSI2C_RX_FIFO_FULL (1u << 23)
+#define HSI2C_RX_FIFO_LVL(x) ((x >> 16) & 0x7f)
+#define HSI2C_TX_FIFO_EMPTY (1u << 8)
+#define HSI2C_TX_FIFO_FULL (1u << 7)
+#define HSI2C_TX_FIFO_LVL(x) ((x >> 0) & 0x7f)
+
+/* I2C_CONF Register bits */
+#define HSI2C_AUTO_MODE (1u << 31)
+#define HSI2C_10BIT_ADDR_MODE (1u << 30)
+#define HSI2C_HS_MODE (1u << 29)
+
+/* I2C_AUTO_CONF Register bits */
+#define HSI2C_READ_WRITE (1u << 16)
+#define HSI2C_STOP_AFTER_TRANS (1u << 17)
+#define HSI2C_MASTER_RUN (1u << 31)
+
+/* I2C_TIMEOUT Register bits */
+#define HSI2C_TIMEOUT_EN (1u << 31)
+
+/* I2C_TRANS_STATUS register bits */
+#define HSI2C_MASTER_BUSY (1u << 17)
+#define HSI2C_SLAVE_BUSY (1u << 16)
+#define HSI2C_TIMEOUT_AUTO (1u << 4)
+#define HSI2C_NO_DEV (1u << 3)
+#define HSI2C_NO_DEV_ACK (1u << 2)
+#define HSI2C_TRANS_ABORT (1u << 1)
+#define HSI2C_TRANS_DONE (1u << 0)
+
+/* I2C_ADDR register bits */
+#define HSI2C_SLV_ADDR_SLV(x) ((x & 0x3ff) << 0)
+#define HSI2C_SLV_ADDR_MAS(x) ((x & 0x3ff) << 10)
+#define HSI2C_MASTER_ID(x) ((x & 0xff) << 24)
+#define MASTER_ID(x) ((x & 0x7) + 0x08)
+
+/*
+ * Controller operating frequency, timing values for operation
+ * are calculated against this frequency
+ */
+#define HSI2C_HS_TX_CLOCK 1000000
+#define HSI2C_FS_TX_CLOCK 100000
+#define HSI2C_HIGH_SPD 1
+#define HSI2C_FAST_SPD 0
+
+#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+struct exynos5_i2c {
+ struct i2c_adapter adap;
+ unsigned int suspended:1;
+
+ struct i2c_msg *msg;
+ struct completion msg_complete;
+ spinlock_t lock; /* IRQ synchronization */
+ unsigned int msg_ptr;
+
+ unsigned int irq;
+
+ void __iomem *regs;
+ struct clk *clk;
+ struct device *dev;
+ int state;
+
+ /*
+ * Since the TRANS_DONE bit is cleared on read, and we may read it
+ * either during an IRQ or after a transaction, keep track of its
+ * state here.
+ */
+ int trans_done;
+
+ /* Controller operating frequency */
+ unsigned int fs_clock;
+ unsigned int hs_clock;
+
+ /*
+ * HSI2C Controller can operate in
+ * 1. High speed upto 3.4Mbps
+ * 2. Fast speed upto 1Mbps
+ */
+ int speed_mode;
+};
+
+static const struct of_device_id exynos5_i2c_match[] = {
+ { .compatible = "samsung,exynos5-hsi2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
+
+static void exynos5_i2c_clr_pend_irq(struct exynos5_i2c *i2c)
+{
+ writel(readl(i2c->regs + HSI2C_INT_STATUS),
+ i2c->regs + HSI2C_INT_STATUS);
+}
+
+/*
+ * exynos5_i2c_set_timing: updates the registers with appropriate
+ * timing values calculated
+ *
+ * Returns 0 on success, -EINVAL if the cycle length cannot
+ * be calculated.
+ */
+static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, int mode)
+{
+ u32 i2c_timing_s1;
+ u32 i2c_timing_s2;
+ u32 i2c_timing_s3;
+ u32 i2c_timing_sla;
+ unsigned int t_start_su, t_start_hd;
+ unsigned int t_stop_su;
+ unsigned int t_data_su, t_data_hd;
+ unsigned int t_scl_l, t_scl_h;
+ unsigned int t_sr_release;
+ unsigned int t_ftl_cycle;
+ unsigned int clkin = clk_get_rate(i2c->clk);
+ unsigned int div, utemp0 = 0, utemp1 = 0, clk_cycle;
+ unsigned int op_clk = (mode == HSI2C_HIGH_SPD) ?
+ i2c->hs_clock : i2c->fs_clock;
+
+ /*
+ * FPCLK / FI2C =
+ * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
+ * utemp0 = (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2)
+ * utemp1 = (TSCLK_L + TSCLK_H + 2)
+ */
+ t_ftl_cycle = (readl(i2c->regs + HSI2C_CONF) >> 16) & 0x7;
+ utemp0 = (clkin / op_clk) - 8 - 2 * t_ftl_cycle;
+
+ /* CLK_DIV max is 256 */
+ for (div = 0; div < 256; div++) {
+ utemp1 = utemp0 / (div + 1);
+
+ /*
+ * SCL_L and SCL_H each has max value of 255
+ * Hence, For the clk_cycle to the have right value
+ * utemp1 has to be less then 512 and more than 4.
+ */
+ if ((utemp1 < 512) && (utemp1 > 4)) {
+ clk_cycle = utemp1 - 2;
+ break;
+ } else if (div == 255) {
+ dev_warn(i2c->dev, "Failed to calculate divisor");
+ return -EINVAL;
+ }
+ }
+
+ t_scl_l = clk_cycle / 2;
+ t_scl_h = clk_cycle / 2;
+ t_start_su = t_scl_l;
+ t_start_hd = t_scl_l;
+ t_stop_su = t_scl_l;
+ t_data_su = t_scl_l / 2;
+ t_data_hd = t_scl_l / 2;
+ t_sr_release = clk_cycle;
+
+ i2c_timing_s1 = t_start_su << 24 | t_start_hd << 16 | t_stop_su << 8;
+ i2c_timing_s2 = t_data_su << 24 | t_scl_l << 8 | t_scl_h << 0;
+ i2c_timing_s3 = div << 16 | t_sr_release << 0;
+ i2c_timing_sla = t_data_hd << 0;
+
+ dev_dbg(i2c->dev, "tSTART_SU: %X, tSTART_HD: %X, tSTOP_SU: %X\n",
+ t_start_su, t_start_hd, t_stop_su);
+ dev_dbg(i2c->dev, "tDATA_SU: %X, tSCL_L: %X, tSCL_H: %X\n",
+ t_data_su, t_scl_l, t_scl_h);
+ dev_dbg(i2c->dev, "nClkDiv: %X, tSR_RELEASE: %X\n",
+ div, t_sr_release);
+ dev_dbg(i2c->dev, "tDATA_HD: %X\n", t_data_hd);
+
+ if (mode == HSI2C_HIGH_SPD) {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_HS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_HS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+ } else {
+ writel(i2c_timing_s1, i2c->regs + HSI2C_TIMING_FS1);
+ writel(i2c_timing_s2, i2c->regs + HSI2C_TIMING_FS2);
+ writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+ }
+ writel(i2c_timing_sla, i2c->regs + HSI2C_TIMING_SLA);
+
+ return 0;
+}
+
+static int exynos5_hsi2c_clock_setup(struct exynos5_i2c *i2c)
+{
+ /*
+ * Configure the Fast speed timing values
+ * Even the High Speed mode initially starts with Fast mode
+ */
+ if (exynos5_i2c_set_timing(i2c, HSI2C_FAST_SPD)) {
+ dev_err(i2c->dev, "HSI2C FS Clock set up failed\n");
+ return -EINVAL;
+ }
+
+ /* configure the High speed timing values */
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ if (exynos5_i2c_set_timing(i2c, HSI2C_HIGH_SPD)) {
+ dev_err(i2c->dev, "HSI2C HS Clock set up failed\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * exynos5_i2c_init: configures the controller for I2C functionality
+ * Programs I2C controller for Master mode operation
+ */
+static void exynos5_i2c_init(struct exynos5_i2c *i2c)
+{
+ u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
+ u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
+
+ /* Disable timeout */
+ i2c_timeout &= ~HSI2C_TIMEOUT_EN;
+ writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
+
+ writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
+ i2c->regs + HSI2C_CTL);
+ writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
+
+ if (i2c->speed_mode == HSI2C_HIGH_SPD) {
+ writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
+ i2c->regs + HSI2C_ADDR);
+ i2c_conf |= HSI2C_HS_MODE;
+ }
+
+ writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
+}
+
+static void exynos5_i2c_reset(struct exynos5_i2c *i2c)
+{
+ u32 i2c_ctl;
+
+ /* Set and clear the bit for reset */
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl |= HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~HSI2C_SW_RST;
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ /* We don't expect calculations to fail during the run */
+ exynos5_hsi2c_clock_setup(i2c);
+ /* Initialize the configure registers */
+ exynos5_i2c_init(i2c);
+}
+
+/*
+ * exynos5_i2c_irq: top level IRQ servicing routine
+ *
+ * INT_STATUS registers gives the interrupt details. Further,
+ * FIFO_STATUS or TRANS_STATUS registers are to be check for detailed
+ * state of the bus.
+ */
+static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
+{
+ struct exynos5_i2c *i2c = dev_id;
+ u32 fifo_level, int_status, fifo_status, trans_status;
+ unsigned long flags;
+ u32 len;
+ unsigned char byte;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+ i2c->state = -EINVAL;
+
+ int_status = readl(i2c->regs + HSI2C_INT_STATUS);
+
+ /* handle interrupt related to the transfer status */
+ if (int_status & HSI2C_INT_I2C) {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_NO_DEV_ACK) {
+ dev_dbg(i2c->dev, "No ACK from device\n");
+ i2c->state = -ENXIO;
+ goto stop;
+ } else if (trans_status & HSI2C_NO_DEV) {
+ dev_dbg(i2c->dev, "No device\n");
+ i2c->state = -ENXIO;
+ goto stop;
+ } else if (trans_status & HSI2C_TRANS_ABORT) {
+ dev_dbg(i2c->dev, "Deal with arbitration lose\n");
+ i2c->state = -EAGAIN;
+ goto stop;
+ } else if (trans_status & HSI2C_TIMEOUT_AUTO) {
+ dev_dbg(i2c->dev, "Accessing device timed out\n");
+ i2c->state = -EAGAIN;
+ goto stop;
+ } else if (trans_status & HSI2C_TRANS_DONE) {
+ i2c->trans_done = 1;
+ i2c->state = 0;
+ }
+ }
+
+ if ((i2c->msg->flags & I2C_M_RD) && (int_status &
+ (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
+ len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
+
+ while (len > 0) {
+ byte = (unsigned char)
+ readl(i2c->regs + HSI2C_RX_DATA);
+ i2c->msg->buf[i2c->msg_ptr++] = byte;
+ len--;
+ }
+ i2c->state = 0;
+ } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
+ fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
+ fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
+
+ len = HSI2C_FIFO_MAX - fifo_level;
+ if (len > (i2c->msg->len - i2c->msg_ptr))
+ len = i2c->msg->len - i2c->msg_ptr;
+
+ while (len > 0) {
+ byte = i2c->msg->buf[i2c->msg_ptr++];
+ writel(byte, i2c->regs + HSI2C_TX_DATA);
+ len--;
+ }
+ i2c->state = 0;
+ }
+
+ stop:
+ if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
+ writel(0, i2c->regs + HSI2C_INT_ENABLE);
+ exynos5_i2c_clr_pend_irq(i2c);
+ complete(&i2c->msg_complete);
+ } else {
+ exynos5_i2c_clr_pend_irq(i2c);
+ }
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * exynos5_i2c_wait_bus_idle
+ *
+ * Wait for the transaction to complete (indicated by the TRANS_DONE bit
+ * being set), and, if this is the last message in a transfer, wait for the
+ * MASTER_BUSY bit to be cleared.
+ *
+ * Returns -EBUSY if the bus cannot be bought to idle
+ */
+static int exynos5_i2c_wait_bus_idle(struct exynos5_i2c *i2c, int stop)
+{
+ unsigned long stop_time;
+ u32 trans_status;
+
+ /* wait for 100 milli seconds for the bus to be idle */
+ stop_time = jiffies + msecs_to_jiffies(100) + 1;
+ do {
+ trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
+ if (trans_status & HSI2C_TRANS_DONE)
+ i2c->trans_done = 1;
+ /*
+ * Only wait for MASTER_BUSY to be cleared if this is the last
+ * message.
+ */
+ if ((!stop || !(trans_status & HSI2C_MASTER_BUSY)) &&
+ i2c->trans_done)
+ return 0;
+
+ usleep_range(50, 200);
+ } while (time_before(jiffies, stop_time));
+
+ return -EBUSY;
+}
+
+/*
+ * exynos5_i2c_message_start: Configures the bus and starts the xfer
+ * i2c: struct exynos5_i2c pointer for the current bus
+ * stop: Enables stop after transfer if set. Set for last transfer of
+ * in the list of messages.
+ *
+ * Configures the bus for read/write function
+ * Sets chip address to talk to, message length to be sent.
+ * Enables appropriate interrupts and sends start xfer command.
+ */
+static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
+{
+ u32 i2c_ctl;
+ u32 int_en = HSI2C_INT_I2C_EN;
+ u32 i2c_auto_conf = 0;
+ u32 fifo_ctl;
+
+ i2c_ctl = readl(i2c->regs + HSI2C_CTL);
+ i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);
+ fifo_ctl = HSI2C_RXFIFO_EN | HSI2C_TXFIFO_EN;
+
+ if (i2c->msg->flags & I2C_M_RD) {
+ i2c_ctl |= HSI2C_RXCHON;
+
+ i2c_auto_conf = HSI2C_READ_WRITE;
+
+ fifo_ctl |= HSI2C_RXFIFO_TRIGGER_LEVEL(HSI2C_DEF_TXFIFO_LVL);
+ int_en |= (HSI2C_INT_RX_ALMOSTFULL_EN |
+ HSI2C_INT_TRAILING_EN);
+ } else {
+ i2c_ctl |= HSI2C_TXCHON;
+
+ fifo_ctl |= HSI2C_TXFIFO_TRIGGER_LEVEL(HSI2C_DEF_RXFIFO_LVL);
+ int_en |= HSI2C_INT_TX_ALMOSTEMPTY_EN;
+ }
+
+ writel(HSI2C_SLV_ADDR_MAS(i2c->msg->addr), i2c->regs + HSI2C_ADDR);
+
+ writel(fifo_ctl, i2c->regs + HSI2C_FIFO_CTL);
+ writel(i2c_ctl, i2c->regs + HSI2C_CTL);
+
+ if (stop == 1)
+ i2c_auto_conf |= HSI2C_STOP_AFTER_TRANS;
+ i2c_auto_conf |= i2c->msg->len;
+ i2c_auto_conf |= HSI2C_MASTER_RUN;
+ writel(i2c_auto_conf, i2c->regs + HSI2C_AUTO_CONF);
+
+ writel(int_en, i2c->regs + HSI2C_INT_ENABLE);
+}
+
+static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
+ struct i2c_msg *msgs, int stop)
+{
+ unsigned long timeout;
+ int ret;
+
+ INIT_COMPLETION(i2c->msg_complete);
+
+ spin_lock_irq(&i2c->lock);
+ i2c->msg = msgs;
+ i2c->msg_ptr = 0;
+ i2c->trans_done = 0;
+
+ exynos5_i2c_message_start(i2c, stop);
+
+ spin_unlock_irq(&i2c->lock);
+ timeout = wait_for_completion_timeout(&i2c->msg_complete,
+ EXYNOS5_I2C_TIMEOUT);
+ if (timeout == 0)
+ ret = -ETIMEDOUT;
+ else
+ ret = i2c->state;
+
+ if (ret < 0) {
+ exynos5_i2c_reset(i2c);
+ if (ret == -ETIMEDOUT) {
+ dev_warn(i2c->dev, "%s timeout\n",
+ (msgs->flags & I2C_M_RD) ? "rx" : "tx");
+ return ret;
+ } else if (ret == -EAGAIN) {
+ return ret;
+ }
+ }
+
+ /*
+ * If this is the last message to be transfered (stop == 1)
+ * Then check if the bus can be brought back to idle.
+ *
+ * Return -EBUSY if the bus still busy.
+ */
+ if (exynos5_i2c_wait_bus_idle(i2c, stop))
+ return -EBUSY;
+
+ /* Return the state as in interrupt routine */
+ return ret;
+}
+
+static int exynos5_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
+ int i = 0, ret = 0, stop = 0;
+
+ if (i2c->suspended) {
+ dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
+ return -EIO;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ for (i = 0; i < num; i++) {
+ stop = (i == num - 1);
+
+ if (msgs->len == 0) {
+ msgs++;
+ continue;
+ }
+
+ ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
+ if (!stop)
+ msgs++;
+
+ if (ret < 0)
+ goto out;
+ }
+
+ if (i == num) {
+ ret = num;
+ } else {
+ /* Only one message, cannot access the device */
+ if (i == 1)
+ ret = -EREMOTEIO;
+ else
+ ret = i;
+
+ dev_warn(i2c->dev, "xfer message failed\n");
+ }
+
+ out:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static u32 exynos5_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm exynos5_i2c_algorithm = {
+ .master_xfer = exynos5_i2c_xfer,
+ .functionality = exynos5_i2c_func,
+};
+
+static int exynos5_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct exynos5_i2c *i2c;
+ struct resource *mem;
+ unsigned int op_clock;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "no device node\n");
+ return -ENOENT;
+ }
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
+ if (!i2c) {
+ dev_err(&pdev->dev, "no memory for state\n");
+ return -ENOMEM;
+ }
+
+ if (of_property_read_u32(np, "clock-frequency", &op_clock)) {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ } else {
+ if (op_clock >= HSI2C_HS_TX_CLOCK) {
+ i2c->speed_mode = HSI2C_HIGH_SPD;
+ i2c->fs_clock = HSI2C_FS_TX_CLOCK;
+ i2c->hs_clock = op_clock;
+ } else {
+ i2c->speed_mode = HSI2C_FAST_SPD;
+ i2c->fs_clock = op_clock;
+ }
+ }
+
+ strlcpy(i2c->adap.name, "exynos5-i2c", sizeof(i2c->adap.name));
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &exynos5_i2c_algorithm;
+
+ i2c->dev = &pdev->dev;
+ i2c->clk = devm_clk_get(&pdev->dev, "hsi2c");
+ if (IS_ERR(i2c->clk)) {
+ dev_err(&pdev->dev, "cannot get clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2c->clk);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(i2c->regs)) {
+ ret = PTR_ERR(i2c->regs);
+ goto err_clk;
+ }
+
+ i2c->adap.dev.of_node = np;
+ i2c->adap.algo_data = i2c;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ /* Clear pending interrupts from u-boot or misc causes */
+ exynos5_i2c_clr_pend_irq(i2c);
+
+ spin_lock_init(&i2c->lock);
+ init_completion(&i2c->msg_complete);
+
+ i2c->irq = ret = platform_get_irq(pdev, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "cannot find HS-I2C IRQ\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, i2c->irq, exynos5_i2c_irq,
+ 0, dev_name(&pdev->dev), i2c);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "cannot request HS-I2C IRQ %d\n", i2c->irq);
+ goto err_clk;
+ }
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret)
+ goto err_clk;
+
+ exynos5_i2c_init(i2c);
+
+ i2c->adap.nr = -1;
+ ret = i2c_add_numbered_adapter(&i2c->adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to add bus to i2c core\n");
+ goto err_clk;
+ }
+
+ of_i2c_register_devices(&i2c->adap);
+ platform_set_drvdata(pdev, i2c);
+
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+
+ err_clk:
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+}
+
+static int exynos5_i2c_remove(struct platform_device *pdev)
+{
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adap);
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_i2c_suspend_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c->suspended = 1;
+
+ return 0;
+}
+
+static int exynos5_i2c_resume_noirq(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos5_i2c *i2c = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ clk_prepare_enable(i2c->clk);
+
+ ret = exynos5_hsi2c_clock_setup(i2c);
+ if (ret) {
+ clk_disable_unprepare(i2c->clk);
+ return ret;
+ }
+
+ exynos5_i2c_init(i2c);
+ clk_disable_unprepare(i2c->clk);
+ i2c->suspended = 0;
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
+ .suspend_noirq = exynos5_i2c_suspend_noirq,
+ .resume_noirq = exynos5_i2c_resume_noirq,
+};
+
+#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
+#else
+#define EXYNOS5_DEV_PM_OPS NULL
+#endif
+
+static struct platform_driver exynos5_i2c_driver = {
+ .probe = exynos5_i2c_probe,
+ .remove = exynos5_i2c_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "exynos5-hsi2c",
+ .pm = EXYNOS5_DEV_PM_OPS,
+ .of_match_table = exynos5_i2c_match,
+ },
+};
+
+module_platform_driver(exynos5_i2c_driver);
+
+MODULE_DESCRIPTION("Exynos5 HS-I2C Bus driver");
+MODULE_AUTHOR("Naveen Krishna Chatradhi, <ch.naveen@samsung.com>");
+MODULE_AUTHOR("Taekgyun Ko, <taeggyun.ko@samsung.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 107+ messages in thread[parent not found: <1377077077-20896-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH] i2c: exynos5: add High Speed I2C controller driver
2013-08-21 9:24 ` Naveen Krishna Chatradhi
@ 2013-09-08 17:03 ` Wolfram Sang
-1 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-09-08 17:03 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ, t.figa-Sze3O3UU22JBDgjK7y7TUQ,
ben-linux-elnMNo+KYs3YtjvyW6yDsg,
grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw,
naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w,
broonie-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E
[-- Attachment #1: Type: text/plain, Size: 10684 bytes --]
On Wed, Aug 21, 2013 at 02:54:37PM +0530, Naveen Krishna Ch wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Highspeed mode is a minor change in the i2c protocol.
> Starts with
> 1. start condition,
> 2. 8-bit master ID code (00001xxx)
> 3. followed by a NACK bit
> Once the above conditions are met, the bus is now operates in highspeed mode.
> The rest of the I2C protocol applies the same.
The description is correct, but it is not a change in the protocol. This
is fully specified in the I2C specs.
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
> 13. few other bug fixes and cosmetic changes
> 14. Removed the untested runtime_pm code
> 15. Removed the retries as core does that
> 16. Use adap.nr instead of alias
> 17. Added spinlocks around the irq code
> 18. Use i2c_add_numbered_adapter() instead of using aliases
Changes since v1 are irrelevant for the patch description.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
>
> ---
> Wolfram and Thomas Figa thanks for reviewing the code.
>
> Changes since v10:
> 1. Remove the incomplete runtime_pm code
> 2. Correct the error checks as suggested by Thomas
> 3. Use i2c_add_numbered_adapter() as suggested
> 4. Modified the irq routine to handle the specific interrupts
> 5. Added spinlocks around the irq code
> 6. Remove the "mode" of operation field from device tree node and use the
> clock-frequency to decide the mode.
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
> 4 files changed, 851 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..805e018
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,44 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode and
> + 1Mhz in high-speed mode.
Description doesn't match the current code.
> + -> If specified, The bus operates in high-speed mode only if the
> + clock-frequency is >= 1Mhz.
s/The/the/
...
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> + u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Disable timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
Just curious: Can't you use HSI2C_TIMEOUT and wait_for_completion
instead of wait_for_completion_timeout? If so, it might save you a
little bit of overhead.
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +
> + if ((i2c->msg->flags & I2C_M_RD) && (int_status &
> + (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> + len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
> +
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
With all this copying going on, this should be threaded irq probably.
> + i2c->state = 0;
> + } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > (i2c->msg->len - i2c->msg_ptr))
> + len = i2c->msg->len - i2c->msg_ptr;
> +
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> + stop:
> + if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + exynos5_i2c_clr_pend_irq(i2c);
> + complete(&i2c->msg_complete);
> + } else {
> + exynos5_i2c_clr_pend_irq(i2c);
> + }
> +
> + spin_unlock_irqrestore(&i2c->lock, flags);
> +
> + return IRQ_HANDLED;
> +}
> +
...
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + spin_lock_irq(&i2c->lock);
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->trans_done = 0;
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + spin_unlock_irq(&i2c->lock);
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> + if (timeout == 0)
> + ret = -ETIMEDOUT;
> + else
> + ret = i2c->state;
> +
> + if (ret < 0) {
> + exynos5_i2c_reset(i2c);
Do you really need to reset the core after a failed transaction?
> + if (ret == -ETIMEDOUT) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
Why do you need this after the transaction?
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int i = 0, ret = 0, stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + if (msgs->len == 0) {
Did you need that? It shouldn't happen since you are not advertising
SMBUS_QUICK.
> + msgs++;
> + continue;
> + }
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
> + if (!stop)
> + msgs++;
Probably better put in the for-body (i.e. i++, msgs++).
> +
> + if (ret < 0)
> + goto out;
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
How should this happen?
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
...
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
Just use i2c_add_adapter and skip setting nr to -1.
...
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
Isn't there a macro for this? SIMPLE_DEV_PM_OPS*? Not sure, I always mix
them up...
Regards,
Wolfram
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH] i2c: exynos5: add High Speed I2C controller driver
@ 2013-09-08 17:03 ` Wolfram Sang
0 siblings, 0 replies; 107+ messages in thread
From: Wolfram Sang @ 2013-09-08 17:03 UTC (permalink / raw)
To: Naveen Krishna Chatradhi
Cc: linux-i2c, linux-kernel, linux-samsung-soc, w.sang, t.figa,
ben-linux, grant.likely, devicetree-discuss, sjg,
naveenkrishna.ch, broonie
[-- Attachment #1: Type: text/plain, Size: 10513 bytes --]
On Wed, Aug 21, 2013 at 02:54:37PM +0530, Naveen Krishna Ch wrote:
> Adds support for High Speed I2C driver found in Exynos5 and
> later SoCs from Samsung.
>
> Highspeed mode is a minor change in the i2c protocol.
> Starts with
> 1. start condition,
> 2. 8-bit master ID code (00001xxx)
> 3. followed by a NACK bit
> Once the above conditions are met, the bus is now operates in highspeed mode.
> The rest of the I2C protocol applies the same.
The description is correct, but it is not a change in the protocol. This
is fully specified in the I2C specs.
> Driver only supports Device Tree method.
>
> Changes since v1:
> 1. Added FIFO functionality
> 2. Added High speed mode functionality
> 3. Remove SMBUS_QUICK
> 4. Remove the debugfs functionality
> 5. Use devm_* functions where ever possible
> 6. Driver is free from GPIO configs
> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
> 8. Split the clock divisor calculation function
> 9. Add resets for the failed transacton cases
> 10. Removed retries as core does retries if -EAGAIN is returned
> 11. Removed mode from device tree info (use speed to distinguish
> the mode of operation)
> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
> 13. few other bug fixes and cosmetic changes
> 14. Removed the untested runtime_pm code
> 15. Removed the retries as core does that
> 16. Use adap.nr instead of alias
> 17. Added spinlocks around the irq code
> 18. Use i2c_add_numbered_adapter() instead of using aliases
Changes since v1 are irrelevant for the patch description.
>
> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Reviewed-by: Simon Glass <sjg@google.com>
> Tested-by: Andrew Bresticker <abrestic@google.com>
> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
> Signed-off-by: Andrew Bresticker <abrestic@google.com>
>
> ---
> Wolfram and Thomas Figa thanks for reviewing the code.
>
> Changes since v10:
> 1. Remove the incomplete runtime_pm code
> 2. Correct the error checks as suggested by Thomas
> 3. Use i2c_add_numbered_adapter() as suggested
> 4. Modified the irq routine to handle the specific interrupts
> 5. Added spinlocks around the irq code
> 6. Remove the "mode" of operation field from device tree node and use the
> clock-frequency to decide the mode.
>
> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
> drivers/i2c/busses/Kconfig | 7 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
> 4 files changed, 851 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> new file mode 100644
> index 0000000..805e018
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
> @@ -0,0 +1,44 @@
> +* Samsung's High Speed I2C controller
> +
> +The Samsung's High Speed I2C controller is used to interface with I2C devices
> +at various speeds ranging from 100khz to 3.4Mhz.
> +
> +Required properties:
> + - compatible: value should be.
> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
> + - reg: physical base address of the controller and length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - #address-cells: always 1 (for i2c addresses)
> + - #size-cells: always 0
> +
> + - Pinctrl:
> + - pinctrl-0: Pin control group to be used for this controller.
> + - pinctrl-names: Should contain only one value - "default".
> +
> +Optional properties:
> + - clock-frequency: Desired operating frequency in Hz of the bus.
> + -> If not specified, the default value is 100khz in fast-speed mode and
> + 1Mhz in high-speed mode.
Description doesn't match the current code.
> + -> If specified, The bus operates in high-speed mode only if the
> + clock-frequency is >= 1Mhz.
s/The/the/
...
> +/*
> + * exynos5_i2c_init: configures the controller for I2C functionality
> + * Programs I2C controller for Master mode operation
> + */
> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
> +{
> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
> + u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
> +
> + /* Disable timeout */
> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
Just curious: Can't you use HSI2C_TIMEOUT and wait_for_completion
instead of wait_for_completion_timeout? If so, it might save you a
little bit of overhead.
> +
> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
> + i2c->regs + HSI2C_CTL);
> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
> +
> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
> + i2c->regs + HSI2C_ADDR);
> + i2c_conf |= HSI2C_HS_MODE;
> + }
> +
> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
> +}
> +
> +
> + if ((i2c->msg->flags & I2C_M_RD) && (int_status &
> + (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
> + len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
> +
> + while (len > 0) {
> + byte = (unsigned char)
> + readl(i2c->regs + HSI2C_RX_DATA);
> + i2c->msg->buf[i2c->msg_ptr++] = byte;
> + len--;
> + }
With all this copying going on, this should be threaded irq probably.
> + i2c->state = 0;
> + } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
> +
> + len = HSI2C_FIFO_MAX - fifo_level;
> + if (len > (i2c->msg->len - i2c->msg_ptr))
> + len = i2c->msg->len - i2c->msg_ptr;
> +
> + while (len > 0) {
> + byte = i2c->msg->buf[i2c->msg_ptr++];
> + writel(byte, i2c->regs + HSI2C_TX_DATA);
> + len--;
> + }
> + i2c->state = 0;
> + }
> +
> + stop:
> + if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
> + exynos5_i2c_clr_pend_irq(i2c);
> + complete(&i2c->msg_complete);
> + } else {
> + exynos5_i2c_clr_pend_irq(i2c);
> + }
> +
> + spin_unlock_irqrestore(&i2c->lock, flags);
> +
> + return IRQ_HANDLED;
> +}
> +
...
> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
> + struct i2c_msg *msgs, int stop)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + INIT_COMPLETION(i2c->msg_complete);
> +
> + spin_lock_irq(&i2c->lock);
> + i2c->msg = msgs;
> + i2c->msg_ptr = 0;
> + i2c->trans_done = 0;
> +
> + exynos5_i2c_message_start(i2c, stop);
> +
> + spin_unlock_irq(&i2c->lock);
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + EXYNOS5_I2C_TIMEOUT);
> + if (timeout == 0)
> + ret = -ETIMEDOUT;
> + else
> + ret = i2c->state;
> +
> + if (ret < 0) {
> + exynos5_i2c_reset(i2c);
Do you really need to reset the core after a failed transaction?
> + if (ret == -ETIMEDOUT) {
> + dev_warn(i2c->dev, "%s timeout\n",
> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
> + return ret;
> + } else if (ret == -EAGAIN) {
> + return ret;
> + }
> + }
> +
> + /*
> + * If this is the last message to be transfered (stop == 1)
> + * Then check if the bus can be brought back to idle.
> + *
> + * Return -EBUSY if the bus still busy.
> + */
> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
> + return -EBUSY;
Why do you need this after the transaction?
> +
> + /* Return the state as in interrupt routine */
> + return ret;
> +}
> +
> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
> + int i = 0, ret = 0, stop = 0;
> +
> + if (i2c->suspended) {
> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
> + return -EIO;
> + }
> +
> + clk_prepare_enable(i2c->clk);
> +
> + for (i = 0; i < num; i++) {
> + stop = (i == num - 1);
> +
> + if (msgs->len == 0) {
Did you need that? It shouldn't happen since you are not advertising
SMBUS_QUICK.
> + msgs++;
> + continue;
> + }
> +
> + ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
> + if (!stop)
> + msgs++;
Probably better put in the for-body (i.e. i++, msgs++).
> +
> + if (ret < 0)
> + goto out;
> + }
> +
> + if (i == num) {
> + ret = num;
> + } else {
> + /* Only one message, cannot access the device */
> + if (i == 1)
> + ret = -EREMOTEIO;
> + else
> + ret = i;
> +
> + dev_warn(i2c->dev, "xfer message failed\n");
> + }
> +
> + out:
> + clk_disable_unprepare(i2c->clk);
> + return ret;
> +}
> +
> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
> +}
> +
> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
> + .master_xfer = exynos5_i2c_xfer,
> + .functionality = exynos5_i2c_func,
> +};
> +
> +static int exynos5_i2c_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct exynos5_i2c *i2c;
> + struct resource *mem;
> + unsigned int op_clock;
> + int ret;
> +
> + if (!np) {
> + dev_err(&pdev->dev, "no device node\n");
> + return -ENOENT;
> + }
How should this happen?
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
> + if (!i2c) {
> + dev_err(&pdev->dev, "no memory for state\n");
> + return -ENOMEM;
> + }
> +
...
> + i2c->adap.nr = -1;
> + ret = i2c_add_numbered_adapter(&i2c->adap);
Just use i2c_add_adapter and skip setting nr to -1.
...
> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
> + .suspend_noirq = exynos5_i2c_suspend_noirq,
> + .resume_noirq = exynos5_i2c_resume_noirq,
> +};
> +
> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
> +#else
> +#define EXYNOS5_DEV_PM_OPS NULL
> +#endif
Isn't there a macro for this? SIMPLE_DEV_PM_OPS*? Not sure, I always mix
them up...
Regards,
Wolfram
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH] i2c: exynos5: add High Speed I2C controller driver
2013-09-08 17:03 ` Wolfram Sang
@ 2013-10-11 11:43 ` Naveen Krishna Ch
-1 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-10-11 11:43 UTC (permalink / raw)
To: Wolfram Sang
Cc: Naveen Krishna Chatradhi, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ, t.figa-Sze3O3UU22JBDgjK7y7TUQ,
Ben Dooks, grant.likely-s3s/WqlpOiPyB63q8FvJNQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
sjg-F7+t8E8rja9g9hUCZPvPmw, Mark Brown
On 8 September 2013 22:33, Wolfram Sang <wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org> wrote:
>
> On Wed, Aug 21, 2013 at 02:54:37PM +0530, Naveen Krishna Ch wrote:
>> Adds support for High Speed I2C driver found in Exynos5 and
>> later SoCs from Samsung.
>>
>> Highspeed mode is a minor change in the i2c protocol.
>> Starts with
>> 1. start condition,
>> 2. 8-bit master ID code (00001xxx)
>> 3. followed by a NACK bit
>> Once the above conditions are met, the bus is now operates in highspeed mode.
>> The rest of the I2C protocol applies the same.
>
> The description is correct, but it is not a change in the protocol. This
> is fully specified in the I2C specs.
Understood
>
>> Driver only supports Device Tree method.
>>
>> Changes since v1:
>> 1. Added FIFO functionality
>> 2. Added High speed mode functionality
>> 3. Remove SMBUS_QUICK
>> 4. Remove the debugfs functionality
>> 5. Use devm_* functions where ever possible
>> 6. Driver is free from GPIO configs
>> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
>> 8. Split the clock divisor calculation function
>> 9. Add resets for the failed transacton cases
>> 10. Removed retries as core does retries if -EAGAIN is returned
>> 11. Removed mode from device tree info (use speed to distinguish
>> the mode of operation)
>> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
>> 13. few other bug fixes and cosmetic changes
>> 14. Removed the untested runtime_pm code
>> 15. Removed the retries as core does that
>> 16. Use adap.nr instead of alias
>> 17. Added spinlocks around the irq code
>> 18. Use i2c_add_numbered_adapter() instead of using aliases
>
> Changes since v1 are irrelevant for the patch description.
Will remove
>
>>
>> Signed-off-by: Taekgyun Ko <taeggyun.ko-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
>> Reviewed-by: Simon Glass <sjg-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
>> Tested-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
>> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
>> Signed-off-by: Andrew Bresticker <abrestic-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
>>
>> ---
>> Wolfram and Thomas Figa thanks for reviewing the code.
>>
>> Changes since v10:
>> 1. Remove the incomplete runtime_pm code
>> 2. Correct the error checks as suggested by Thomas
>> 3. Use i2c_add_numbered_adapter() as suggested
>> 4. Modified the irq routine to handle the specific interrupts
>> 5. Added spinlocks around the irq code
>> 6. Remove the "mode" of operation field from device tree node and use the
>> clock-frequency to decide the mode.
>>
>> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
>> drivers/i2c/busses/Kconfig | 7 +
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
>> 4 files changed, 851 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>>
>> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> new file mode 100644
>> index 0000000..805e018
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> @@ -0,0 +1,44 @@
>> +* Samsung's High Speed I2C controller
>> +
>> +The Samsung's High Speed I2C controller is used to interface with I2C devices
>> +at various speeds ranging from 100khz to 3.4Mhz.
>> +
>> +Required properties:
>> + - compatible: value should be.
>> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
>> + - reg: physical base address of the controller and length of memory mapped
>> + region.
>> + - interrupts: interrupt number to the cpu.
>> + - #address-cells: always 1 (for i2c addresses)
>> + - #size-cells: always 0
>> +
>> + - Pinctrl:
>> + - pinctrl-0: Pin control group to be used for this controller.
>> + - pinctrl-names: Should contain only one value - "default".
>> +
>> +Optional properties:
>> + - clock-frequency: Desired operating frequency in Hz of the bus.
>> + -> If not specified, the default value is 100khz in fast-speed mode and
>> + 1Mhz in high-speed mode.
>
> Description doesn't match the current code.
Will correct
>
>> + -> If specified, The bus operates in high-speed mode only if the
>> + clock-frequency is >= 1Mhz.
>
> s/The/the/
Will correct
>
> ...
>
>> +/*
>> + * exynos5_i2c_init: configures the controller for I2C functionality
>> + * Programs I2C controller for Master mode operation
>> + */
>> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
>> +{
>> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
>> + u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
>> +
>> + /* Disable timeout */
>> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
>> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
>
> Just curious: Can't you use HSI2C_TIMEOUT and wait_for_completion
> instead of wait_for_completion_timeout? If so, it might save you a
> little bit of overhead.
With timeout enabled, few transactions were timing out.
wait_for_completion_timeout seems to be very stable.
>
>> +
>> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
>> + i2c->regs + HSI2C_CTL);
>> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
>> +
>> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
>> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
>> + i2c->regs + HSI2C_ADDR);
>> + i2c_conf |= HSI2C_HS_MODE;
>> + }
>> +
>> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
>> +}
>> +
>> +
>> + if ((i2c->msg->flags & I2C_M_RD) && (int_status &
>> + (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
>> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
>> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
>> + len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
>> +
>> + while (len > 0) {
>> + byte = (unsigned char)
>> + readl(i2c->regs + HSI2C_RX_DATA);
>> + i2c->msg->buf[i2c->msg_ptr++] = byte;
>> + len--;
>> + }
>
> With all this copying going on, this should be threaded irq probably.
HSI2C driver is heavily dependent on irqs and trans_status register bits.
modifying the code to work with threaded_irq was not successful.
>
>> + i2c->state = 0;
>> + } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
>> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
>> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
>> +
>> + len = HSI2C_FIFO_MAX - fifo_level;
>> + if (len > (i2c->msg->len - i2c->msg_ptr))
>> + len = i2c->msg->len - i2c->msg_ptr;
>> +
>> + while (len > 0) {
>> + byte = i2c->msg->buf[i2c->msg_ptr++];
>> + writel(byte, i2c->regs + HSI2C_TX_DATA);
>> + len--;
>> + }
>> + i2c->state = 0;
>> + }
>> +
>> + stop:
>> + if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
>> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
>> + exynos5_i2c_clr_pend_irq(i2c);
>> + complete(&i2c->msg_complete);
>> + } else {
>> + exynos5_i2c_clr_pend_irq(i2c);
>> + }
>> +
>> + spin_unlock_irqrestore(&i2c->lock, flags);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>
> ...
>
>> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
>> + struct i2c_msg *msgs, int stop)
>> +{
>> + unsigned long timeout;
>> + int ret;
>> +
>> + INIT_COMPLETION(i2c->msg_complete);
>> +
>> + spin_lock_irq(&i2c->lock);
>> + i2c->msg = msgs;
>> + i2c->msg_ptr = 0;
>> + i2c->trans_done = 0;
>> +
>> + exynos5_i2c_message_start(i2c, stop);
>> +
>> + spin_unlock_irq(&i2c->lock);
>> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
>> + EXYNOS5_I2C_TIMEOUT);
>> + if (timeout == 0)
>> + ret = -ETIMEDOUT;
>> + else
>> + ret = i2c->state;
>> +
>> + if (ret < 0) {
>> + exynos5_i2c_reset(i2c);
>
> Do you really need to reset the core after a failed transaction?
Not needed for every transaction failed. intention is to do it for timeout cases
where the master_busy bit in TRANS_STATUS register won't get cleared.
>
>> + if (ret == -ETIMEDOUT) {
>> + dev_warn(i2c->dev, "%s timeout\n",
>> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
>> + return ret;
>> + } else if (ret == -EAGAIN) {
>> + return ret;
>> + }
>> + }
>> +
>> + /*
>> + * If this is the last message to be transfered (stop == 1)
>> + * Then check if the bus can be brought back to idle.
>> + *
>> + * Return -EBUSY if the bus still busy.
>> + */
>> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
>> + return -EBUSY;
>
> Why do you need this after the transaction?
Spec says we should wait for the master_busy bit be cleared as the
proper transaction complete condition.
Thus, we wait for bus idle. Also, we also do a check for the missed
out trans_done bit.
>
>> +
>> + /* Return the state as in interrupt routine */
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
>> + struct i2c_msg *msgs, int num)
>> +{
>> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
>> + int i = 0, ret = 0, stop = 0;
>> +
>> + if (i2c->suspended) {
>> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
>> + return -EIO;
>> + }
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + for (i = 0; i < num; i++) {
>> + stop = (i == num - 1);
>> +
>> + if (msgs->len == 0) {
>
> Did you need that? It shouldn't happen since you are not advertising
> SMBUS_QUICK.
Will remove this
>
>> + msgs++;
>> + continue;
>> + }
>> +
>> + ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
>> + if (!stop)
>> + msgs++;
>
> Probably better put in the for-body (i.e. i++, msgs++).
thanks
>
>> +
>> + if (ret < 0)
>> + goto out;
>> + }
>> +
>> + if (i == num) {
>> + ret = num;
>> + } else {
>> + /* Only one message, cannot access the device */
>> + if (i == 1)
>> + ret = -EREMOTEIO;
>> + else
>> + ret = i;
>> +
>> + dev_warn(i2c->dev, "xfer message failed\n");
>> + }
>> +
>> + out:
>> + clk_disable_unprepare(i2c->clk);
>> + return ret;
>> +}
>> +
>> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
>> +{
>> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
>> +}
>> +
>> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
>> + .master_xfer = exynos5_i2c_xfer,
>> + .functionality = exynos5_i2c_func,
>> +};
>> +
>> +static int exynos5_i2c_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *np = pdev->dev.of_node;
>> + struct exynos5_i2c *i2c;
>> + struct resource *mem;
>> + unsigned int op_clock;
>> + int ret;
>> +
>> + if (!np) {
>> + dev_err(&pdev->dev, "no device node\n");
>> + return -ENOENT;
>> + }
>
> How should this happen?
Taken from legacy code, will remove.
>
>> +
>> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
>> + if (!i2c) {
>> + dev_err(&pdev->dev, "no memory for state\n");
>> + return -ENOMEM;
>> + }
>> +
>
> ...
>
>> + i2c->adap.nr = -1;
>> + ret = i2c_add_numbered_adapter(&i2c->adap);
>
> Just use i2c_add_adapter and skip setting nr to -1.
Will do.
>
> ...
>
>> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
>> + .suspend_noirq = exynos5_i2c_suspend_noirq,
>> + .resume_noirq = exynos5_i2c_resume_noirq,
>> +};
>> +
>> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
>> +#else
>> +#define EXYNOS5_DEV_PM_OPS NULL
>> +#endif
>
> Isn't there a macro for this? SIMPLE_DEV_PM_OPS*? Not sure, I always mix
> them up...
You are right, will modify.
>
> Regards,
>
> Wolfram
>
Sorry for the very delayed response.
Will submit the next version.
Thanks for your review and time.
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread* Re: [PATCH] i2c: exynos5: add High Speed I2C controller driver
@ 2013-10-11 11:43 ` Naveen Krishna Ch
0 siblings, 0 replies; 107+ messages in thread
From: Naveen Krishna Ch @ 2013-10-11 11:43 UTC (permalink / raw)
To: Wolfram Sang
Cc: Naveen Krishna Chatradhi, linux-i2c, linux-kernel,
linux-samsung-soc@vger.kernel.org, w.sang, t.figa, Ben Dooks,
grant.likely, devicetree-discuss, sjg, Mark Brown
On 8 September 2013 22:33, Wolfram Sang <wsa@the-dreams.de> wrote:
>
> On Wed, Aug 21, 2013 at 02:54:37PM +0530, Naveen Krishna Ch wrote:
>> Adds support for High Speed I2C driver found in Exynos5 and
>> later SoCs from Samsung.
>>
>> Highspeed mode is a minor change in the i2c protocol.
>> Starts with
>> 1. start condition,
>> 2. 8-bit master ID code (00001xxx)
>> 3. followed by a NACK bit
>> Once the above conditions are met, the bus is now operates in highspeed mode.
>> The rest of the I2C protocol applies the same.
>
> The description is correct, but it is not a change in the protocol. This
> is fully specified in the I2C specs.
Understood
>
>> Driver only supports Device Tree method.
>>
>> Changes since v1:
>> 1. Added FIFO functionality
>> 2. Added High speed mode functionality
>> 3. Remove SMBUS_QUICK
>> 4. Remove the debugfs functionality
>> 5. Use devm_* functions where ever possible
>> 6. Driver is free from GPIO configs
>> 7. Use OF data string "clock-frequency" to get the bus operating frequencies
>> 8. Split the clock divisor calculation function
>> 9. Add resets for the failed transacton cases
>> 10. Removed retries as core does retries if -EAGAIN is returned
>> 11. Removed mode from device tree info (use speed to distinguish
>> the mode of operation)
>> 12. Use wait_for_completion_timeout as the interruptible case is not tested well
>> 13. few other bug fixes and cosmetic changes
>> 14. Removed the untested runtime_pm code
>> 15. Removed the retries as core does that
>> 16. Use adap.nr instead of alias
>> 17. Added spinlocks around the irq code
>> 18. Use i2c_add_numbered_adapter() instead of using aliases
>
> Changes since v1 are irrelevant for the patch description.
Will remove
>
>>
>> Signed-off-by: Taekgyun Ko <taeggyun.ko@samsung.com>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> Reviewed-by: Simon Glass <sjg@google.com>
>> Tested-by: Andrew Bresticker <abrestic@google.com>
>> Signed-off-by: Yuvaraj Kumar C D <yuvaraj.cd@samsung.com>
>> Signed-off-by: Andrew Bresticker <abrestic@google.com>
>>
>> ---
>> Wolfram and Thomas Figa thanks for reviewing the code.
>>
>> Changes since v10:
>> 1. Remove the incomplete runtime_pm code
>> 2. Correct the error checks as suggested by Thomas
>> 3. Use i2c_add_numbered_adapter() as suggested
>> 4. Modified the irq routine to handle the specific interrupts
>> 5. Added spinlocks around the irq code
>> 6. Remove the "mode" of operation field from device tree node and use the
>> clock-frequency to decide the mode.
>>
>> .../devicetree/bindings/i2c/i2c-exynos5.txt | 44 ++
>> drivers/i2c/busses/Kconfig | 7 +
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-exynos5.c | 799 ++++++++++++++++++++
>> 4 files changed, 851 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> create mode 100644 drivers/i2c/busses/i2c-exynos5.c
>>
>> diff --git a/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> new file mode 100644
>> index 0000000..805e018
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/i2c/i2c-exynos5.txt
>> @@ -0,0 +1,44 @@
>> +* Samsung's High Speed I2C controller
>> +
>> +The Samsung's High Speed I2C controller is used to interface with I2C devices
>> +at various speeds ranging from 100khz to 3.4Mhz.
>> +
>> +Required properties:
>> + - compatible: value should be.
>> + -> "samsung,exynos5-hsi2c", for i2c compatible with exynos5 hsi2c.
>> + - reg: physical base address of the controller and length of memory mapped
>> + region.
>> + - interrupts: interrupt number to the cpu.
>> + - #address-cells: always 1 (for i2c addresses)
>> + - #size-cells: always 0
>> +
>> + - Pinctrl:
>> + - pinctrl-0: Pin control group to be used for this controller.
>> + - pinctrl-names: Should contain only one value - "default".
>> +
>> +Optional properties:
>> + - clock-frequency: Desired operating frequency in Hz of the bus.
>> + -> If not specified, the default value is 100khz in fast-speed mode and
>> + 1Mhz in high-speed mode.
>
> Description doesn't match the current code.
Will correct
>
>> + -> If specified, The bus operates in high-speed mode only if the
>> + clock-frequency is >= 1Mhz.
>
> s/The/the/
Will correct
>
> ...
>
>> +/*
>> + * exynos5_i2c_init: configures the controller for I2C functionality
>> + * Programs I2C controller for Master mode operation
>> + */
>> +static void exynos5_i2c_init(struct exynos5_i2c *i2c)
>> +{
>> + u32 i2c_conf = readl(i2c->regs + HSI2C_CONF);
>> + u32 i2c_timeout = readl(i2c->regs + HSI2C_TIMEOUT);
>> +
>> + /* Disable timeout */
>> + i2c_timeout &= ~HSI2C_TIMEOUT_EN;
>> + writel(i2c_timeout, i2c->regs + HSI2C_TIMEOUT);
>
> Just curious: Can't you use HSI2C_TIMEOUT and wait_for_completion
> instead of wait_for_completion_timeout? If so, it might save you a
> little bit of overhead.
With timeout enabled, few transactions were timing out.
wait_for_completion_timeout seems to be very stable.
>
>> +
>> + writel((HSI2C_FUNC_MODE_I2C | HSI2C_MASTER),
>> + i2c->regs + HSI2C_CTL);
>> + writel(HSI2C_TRAILING_COUNT, i2c->regs + HSI2C_TRAILIG_CTL);
>> +
>> + if (i2c->speed_mode == HSI2C_HIGH_SPD) {
>> + writel(HSI2C_MASTER_ID(MASTER_ID(i2c->adap.nr)),
>> + i2c->regs + HSI2C_ADDR);
>> + i2c_conf |= HSI2C_HS_MODE;
>> + }
>> +
>> + writel(i2c_conf | HSI2C_AUTO_MODE, i2c->regs + HSI2C_CONF);
>> +}
>> +
>> +
>> + if ((i2c->msg->flags & I2C_M_RD) && (int_status &
>> + (HSI2C_INT_TRAILING | HSI2C_INT_RX_ALMOSTFULL))) {
>> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
>> + fifo_level = HSI2C_RX_FIFO_LVL(fifo_status);
>> + len = min(fifo_level, i2c->msg->len - i2c->msg_ptr);
>> +
>> + while (len > 0) {
>> + byte = (unsigned char)
>> + readl(i2c->regs + HSI2C_RX_DATA);
>> + i2c->msg->buf[i2c->msg_ptr++] = byte;
>> + len--;
>> + }
>
> With all this copying going on, this should be threaded irq probably.
HSI2C driver is heavily dependent on irqs and trans_status register bits.
modifying the code to work with threaded_irq was not successful.
>
>> + i2c->state = 0;
>> + } else if (int_status & HSI2C_INT_TX_ALMOSTEMPTY) {
>> + fifo_status = readl(i2c->regs + HSI2C_FIFO_STATUS);
>> + fifo_level = HSI2C_TX_FIFO_LVL(fifo_status);
>> +
>> + len = HSI2C_FIFO_MAX - fifo_level;
>> + if (len > (i2c->msg->len - i2c->msg_ptr))
>> + len = i2c->msg->len - i2c->msg_ptr;
>> +
>> + while (len > 0) {
>> + byte = i2c->msg->buf[i2c->msg_ptr++];
>> + writel(byte, i2c->regs + HSI2C_TX_DATA);
>> + len--;
>> + }
>> + i2c->state = 0;
>> + }
>> +
>> + stop:
>> + if ((i2c->msg_ptr == i2c->msg->len) || (i2c->state < 0)) {
>> + writel(0, i2c->regs + HSI2C_INT_ENABLE);
>> + exynos5_i2c_clr_pend_irq(i2c);
>> + complete(&i2c->msg_complete);
>> + } else {
>> + exynos5_i2c_clr_pend_irq(i2c);
>> + }
>> +
>> + spin_unlock_irqrestore(&i2c->lock, flags);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>
> ...
>
>> +static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
>> + struct i2c_msg *msgs, int stop)
>> +{
>> + unsigned long timeout;
>> + int ret;
>> +
>> + INIT_COMPLETION(i2c->msg_complete);
>> +
>> + spin_lock_irq(&i2c->lock);
>> + i2c->msg = msgs;
>> + i2c->msg_ptr = 0;
>> + i2c->trans_done = 0;
>> +
>> + exynos5_i2c_message_start(i2c, stop);
>> +
>> + spin_unlock_irq(&i2c->lock);
>> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
>> + EXYNOS5_I2C_TIMEOUT);
>> + if (timeout == 0)
>> + ret = -ETIMEDOUT;
>> + else
>> + ret = i2c->state;
>> +
>> + if (ret < 0) {
>> + exynos5_i2c_reset(i2c);
>
> Do you really need to reset the core after a failed transaction?
Not needed for every transaction failed. intention is to do it for timeout cases
where the master_busy bit in TRANS_STATUS register won't get cleared.
>
>> + if (ret == -ETIMEDOUT) {
>> + dev_warn(i2c->dev, "%s timeout\n",
>> + (msgs->flags & I2C_M_RD) ? "rx" : "tx");
>> + return ret;
>> + } else if (ret == -EAGAIN) {
>> + return ret;
>> + }
>> + }
>> +
>> + /*
>> + * If this is the last message to be transfered (stop == 1)
>> + * Then check if the bus can be brought back to idle.
>> + *
>> + * Return -EBUSY if the bus still busy.
>> + */
>> + if (exynos5_i2c_wait_bus_idle(i2c, stop))
>> + return -EBUSY;
>
> Why do you need this after the transaction?
Spec says we should wait for the master_busy bit be cleared as the
proper transaction complete condition.
Thus, we wait for bus idle. Also, we also do a check for the missed
out trans_done bit.
>
>> +
>> + /* Return the state as in interrupt routine */
>> + return ret;
>> +}
>> +
>> +static int exynos5_i2c_xfer(struct i2c_adapter *adap,
>> + struct i2c_msg *msgs, int num)
>> +{
>> + struct exynos5_i2c *i2c = (struct exynos5_i2c *)adap->algo_data;
>> + int i = 0, ret = 0, stop = 0;
>> +
>> + if (i2c->suspended) {
>> + dev_err(i2c->dev, "HS-I2C is not initialzed.\n");
>> + return -EIO;
>> + }
>> +
>> + clk_prepare_enable(i2c->clk);
>> +
>> + for (i = 0; i < num; i++) {
>> + stop = (i == num - 1);
>> +
>> + if (msgs->len == 0) {
>
> Did you need that? It shouldn't happen since you are not advertising
> SMBUS_QUICK.
Will remove this
>
>> + msgs++;
>> + continue;
>> + }
>> +
>> + ret = exynos5_i2c_xfer_msg(i2c, msgs, stop);
>> + if (!stop)
>> + msgs++;
>
> Probably better put in the for-body (i.e. i++, msgs++).
thanks
>
>> +
>> + if (ret < 0)
>> + goto out;
>> + }
>> +
>> + if (i == num) {
>> + ret = num;
>> + } else {
>> + /* Only one message, cannot access the device */
>> + if (i == 1)
>> + ret = -EREMOTEIO;
>> + else
>> + ret = i;
>> +
>> + dev_warn(i2c->dev, "xfer message failed\n");
>> + }
>> +
>> + out:
>> + clk_disable_unprepare(i2c->clk);
>> + return ret;
>> +}
>> +
>> +static u32 exynos5_i2c_func(struct i2c_adapter *adap)
>> +{
>> + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
>> +}
>> +
>> +static const struct i2c_algorithm exynos5_i2c_algorithm = {
>> + .master_xfer = exynos5_i2c_xfer,
>> + .functionality = exynos5_i2c_func,
>> +};
>> +
>> +static int exynos5_i2c_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *np = pdev->dev.of_node;
>> + struct exynos5_i2c *i2c;
>> + struct resource *mem;
>> + unsigned int op_clock;
>> + int ret;
>> +
>> + if (!np) {
>> + dev_err(&pdev->dev, "no device node\n");
>> + return -ENOENT;
>> + }
>
> How should this happen?
Taken from legacy code, will remove.
>
>> +
>> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct exynos5_i2c), GFP_KERNEL);
>> + if (!i2c) {
>> + dev_err(&pdev->dev, "no memory for state\n");
>> + return -ENOMEM;
>> + }
>> +
>
> ...
>
>> + i2c->adap.nr = -1;
>> + ret = i2c_add_numbered_adapter(&i2c->adap);
>
> Just use i2c_add_adapter and skip setting nr to -1.
Will do.
>
> ...
>
>> +static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = {
>> + .suspend_noirq = exynos5_i2c_suspend_noirq,
>> + .resume_noirq = exynos5_i2c_resume_noirq,
>> +};
>> +
>> +#define EXYNOS5_DEV_PM_OPS (&exynos5_i2c_dev_pm_ops)
>> +#else
>> +#define EXYNOS5_DEV_PM_OPS NULL
>> +#endif
>
> Isn't there a macro for this? SIMPLE_DEV_PM_OPS*? Not sure, I always mix
> them up...
You are right, will modify.
>
> Regards,
>
> Wolfram
>
Sorry for the very delayed response.
Will submit the next version.
Thanks for your review and time.
--
Shine bright,
(: Nav :)
^ permalink raw reply [flat|nested] 107+ messages in thread