i2c: i2c-s3c2410: Add quirks for Exynos5433 ISP I2C
authorMarek Szyprowski <m.szyprowski@samsung.com>
Thu, 30 Jul 2015 10:35:18 +0000 (12:35 +0200)
committerJunghoon Kim <jhoon20.kim@samsung.com>
Thu, 14 Feb 2019 05:56:57 +0000 (14:56 +0900)
This is required to ensure proper operation of the ISP I2C controller
from the Linux driver and the FIMC-IS firmware level.

Change-Id: If845cf516fb8aa29a0e6b1b1533208b63fecdc69
Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
drivers/i2c/busses/Kconfig
drivers/i2c/busses/i2c-s3c2410.c

index 45a3f3c..1248827 100644 (file)
@@ -872,7 +872,7 @@ config HAVE_S3C2410_I2C
 
 config I2C_S3C2410
        tristate "S3C2410 I2C Driver"
-       depends on HAVE_S3C2410_I2C
+       depends on HAVE_S3C2410_I2C || ARCH_EXYNOS
        help
          Say Y here to include support for I2C controller in the
          Samsung SoCs.
index 5d97510..e932b9f 100644 (file)
 #define S3C2410_IICADD                 0x08
 #define S3C2410_IICDS                  0x0C
 #define S3C2440_IICLC                  0x10
+#define S3C2440_CLK_BYPASS             0x14
+#define S3C2440_IICINT                 0x20
+#define S3C2440_IICNCLK_DIV2           0x28
 
 #define S3C2410_IICCON_ACKEN           (1 << 7)
 #define S3C2410_IICCON_TXDIV_16                (0 << 6)
 #define S3C2410_IICCON_TXDIV_512       (1 << 6)
 #define S3C2410_IICCON_IRQEN           (1 << 5)
 #define S3C2410_IICCON_IRQPEND         (1 << 4)
+#define S3C2410_IICCON_BUS_RELEASE     (1 << 4)
+#define S3C2410_IICCON_IRQEN           (1 << 5)
 #define S3C2410_IICCON_SCALE(x)                ((x) & 0xf)
 #define S3C2410_IICCON_SCALEMASK       (0xf)
+#define S3C2410_IICCON_BUSHOLD_IRQEN   (1 << 8)
+#define S3C2410_IICCON_ACKEN           (1 << 7)
+#define S3C2410_IICCON_TXDIV_16                (0 << 6)
+#define S3C2410_IICCON_TXDIV_512       (1 << 6)
 
 #define S3C2410_IICSTAT_MASTER_RX      (2 << 6)
 #define S3C2410_IICSTAT_MASTER_TX      (3 << 6)
 
 #define S3C2410_IICLC_FILTER_ON                (1 << 2)
 
+#define S3C2440_IICINT_BUSHOLD_CLEAR   (1 << 8)
+
 /* Treat S3C2410 as baseline hardware, anything else is supported via quirks */
 #define QUIRK_S3C2440          (1 << 0)
 #define QUIRK_HDMIPHY          (1 << 1)
 #define QUIRK_NO_GPIO          (1 << 2)
 #define QUIRK_POLL             (1 << 3)
+#define QUIRK_ISP_I2C          (1 << 4)
 
 /* Max time to wait for bus to become idle after a xfer (in us) */
 #define S3C2410_IDLE_TIMEOUT   5000
@@ -130,6 +142,8 @@ struct s3c24xx_i2c {
 #endif
        struct regmap           *sysreg;
        unsigned int            sys_i2c_cfg;
+
+       u32                     reg_iiccon;
 };
 
 static const struct platform_device_id s3c24xx_driver_ids[] = {
@@ -158,6 +172,8 @@ static const struct of_device_id s3c24xx_i2c_match[] = {
          .data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) },
        { .compatible = "samsung,exynos5-sata-phy-i2c",
          .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },
+       { .compatible = "samsung,exynos5433-isp-i2c",
+         .data = (void *)(QUIRK_S3C2440 | QUIRK_ISP_I2C) },
        {},
 };
 MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);
@@ -210,6 +226,8 @@ static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)
        unsigned long tmp;
 
        tmp = readl(i2c->regs + S3C2410_IICCON);
+       if (i2c->quirks & QUIRK_ISP_I2C)
+               tmp &= ~S3C2410_IICCON_BUS_RELEASE;
        writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);
 }
 
@@ -219,7 +237,13 @@ static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c)
        unsigned long tmp;
 
        tmp = readl(i2c->regs + S3C2410_IICCON);
-       writel(tmp & ~S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);
+
+       if (i2c->quirks & QUIRK_ISP_I2C)
+               tmp &= ~S3C2410_IICCON_BUSHOLD_IRQEN;
+       else
+               tmp &= ~S3C2410_IICCON_IRQEN;
+
+       writel(tmp, i2c->regs + S3C2410_IICCON);
 }
 
 static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
@@ -227,7 +251,13 @@ static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
        unsigned long tmp;
 
        tmp = readl(i2c->regs + S3C2410_IICCON);
-       writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);
+
+       if (i2c->quirks & QUIRK_ISP_I2C)
+               tmp |= S3C2410_IICCON_BUSHOLD_IRQEN;
+       else
+               tmp |= S3C2410_IICCON_IRQEN;
+
+       writel(tmp, i2c->regs + S3C2410_IICCON);
 }
 
 static bool is_ack(struct s3c24xx_i2c *i2c)
@@ -556,9 +586,21 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
        /* acknowlegde the IRQ and get back on with the work */
 
  out_ack:
-       tmp = readl(i2c->regs + S3C2410_IICCON);
-       tmp &= ~S3C2410_IICCON_IRQPEND;
-       writel(tmp, i2c->regs + S3C2410_IICCON);
+       if (i2c->quirks & QUIRK_ISP_I2C) {
+               /* clear bus hold status flag */
+               tmp = readl(i2c->regs + S3C2440_IICINT);
+               tmp |= S3C2440_IICINT_BUSHOLD_CLEAR;
+               writel(tmp, i2c->regs + S3C2440_IICINT);
+
+               /* release the i2c bus */
+               tmp = readl(i2c->regs + S3C2410_IICCON);
+               tmp |= S3C2410_IICCON_IRQPEND;
+               writel(tmp, i2c->regs + S3C2410_IICCON);
+       } else {
+               tmp = readl(i2c->regs + S3C2410_IICCON);
+               tmp &= ~S3C2410_IICCON_IRQPEND;
+               writel(tmp, i2c->regs + S3C2410_IICCON);
+       }
  out:
        return ret;
 }
@@ -582,9 +624,22 @@ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
        if (i2c->state == STATE_IDLE) {
                dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
 
-               tmp = readl(i2c->regs + S3C2410_IICCON);
-               tmp &= ~S3C2410_IICCON_IRQPEND;
-               writel(tmp, i2c->regs +  S3C2410_IICCON);
+               if (i2c->quirks & QUIRK_ISP_I2C) {
+                       /* clear bus hold status flag */
+                       tmp = readl(i2c->regs + S3C2440_IICINT);
+                       tmp |= S3C2440_IICINT_BUSHOLD_CLEAR;
+                       writel(tmp, i2c->regs + S3C2440_IICINT);
+
+                       /* release the i2c bus */
+                       tmp = readl(i2c->regs + S3C2410_IICCON);
+                       tmp |= S3C2410_IICCON_IRQPEND;
+                       writel(tmp, i2c->regs + S3C2410_IICCON);
+               } else {
+                       tmp = readl(i2c->regs + S3C2410_IICCON);
+                       tmp &= ~S3C2410_IICCON_IRQPEND;
+                       writel(tmp, i2c->regs +  S3C2410_IICCON);
+               }
+
                goto out;
        }
 
@@ -775,6 +830,11 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
        if (ret)
                return ret;
 
+       if (i2c->quirks & QUIRK_ISP_I2C) {
+               writel(0, i2c->regs + S3C2410_IICSTAT);
+               writel(i2c->reg_iiccon, i2c->regs + S3C2410_IICCON);
+       }
+
        for (retry = 0; retry < adap->retries; retry++) {
 
                ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
@@ -809,24 +869,35 @@ static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
 /*
  * return the divisor settings for a given frequency
  */
-static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,
+
+static int s3c24xx_i2c_calcdivisor(struct s3c24xx_i2c *i2c,
+                                  unsigned long clkin, unsigned int wanted,
                                   unsigned int *div1, unsigned int *divs)
 {
        unsigned int calc_divs = clkin / wanted;
        unsigned int calc_div1;
+       unsigned int clk_prescaler;
+
+       if (i2c->quirks & QUIRK_ISP_I2C) {
+               /* Input NCLK is used directly in i2c */
+               writel(0, i2c->regs + S3C2440_IICNCLK_DIV2);
+               writeb(1, i2c->regs + S3C2440_CLK_BYPASS);
+               clk_prescaler = 32;
+       } else
+               clk_prescaler = 16;
 
        if (calc_divs > (16*16))
                calc_div1 = 512;
        else
-               calc_div1 = 16;
+               calc_div1 = clk_prescaler;
 
        calc_divs += calc_div1-1;
        calc_divs /= calc_div1;
 
        if (calc_divs == 0)
                calc_divs = 1;
-       if (calc_divs > 17)
-               calc_divs = 17;
+       if (calc_divs > (clk_prescaler + 1))
+               calc_divs = clk_prescaler + 1;
 
        *divs = calc_divs;
        *div1 = calc_div1;
@@ -842,12 +913,17 @@ static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,
 static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
 {
        struct s3c2410_platform_i2c *pdata = i2c->pdata;
-       unsigned long clkin = clk_get_rate(i2c->clk);
+       unsigned long clkin;
        unsigned int divs, div1;
        unsigned long target_frequency;
        u32 iiccon;
        int freq;
 
+       if (i2c->quirks & QUIRK_ISP_I2C)
+               clkin = 24000000UL; /* NCLK is sourced from OSCCLK (24 MHz) */
+       else
+               clkin = clk_get_rate(i2c->clk);
+
        i2c->clkrate = clkin;
        clkin /= 1000;          /* clkin now in KHz */
 
@@ -857,7 +933,8 @@ static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
 
        target_frequency /= 1000; /* Target frequency now in KHz */
 
-       freq = s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1, &divs);
+       freq = s3c24xx_i2c_calcdivisor(i2c, clkin, target_frequency, &div1,
+                                      &divs);
 
        if (freq > target_frequency) {
                dev_err(i2c->dev,
@@ -880,6 +957,8 @@ static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
 
        writel(iiccon, i2c->regs + S3C2410_IICCON);
 
+       i2c->reg_iiccon = iiccon;
+
        if (i2c->quirks & QUIRK_S3C2440) {
                unsigned long sda_delay;