i2c: exynos5: Add support for ExynosAutoV9 SoC
authorJaewon Kim <jaewon02.kim@samsung.com>
Sat, 4 Dec 2021 21:58:16 +0000 (23:58 +0200)
committerWolfram Sang <wsa@kernel.org>
Thu, 9 Dec 2021 09:05:11 +0000 (10:05 +0100)
ExynosAutoV9 functioning logic mostly follows I2C_TYPE_EXYNOS7, but
timing calculation and configuration procedure is changed: e.g. only
timing_s3 has to be set now.

Another change of HSI2C controller in ExynosAutoV9 SoC is that it's now
a part of USIv2 IP-core. No changes is needed for I2C driver though, as
all USI related configuration is done in USI driver.

Signed-off-by: Jaewon Kim <jaewon02.kim@samsung.com>
Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-exynos5.c

index 97d4f3a..c7e3cae 100644 (file)
 enum i2c_type_exynos {
        I2C_TYPE_EXYNOS5,
        I2C_TYPE_EXYNOS7,
+       I2C_TYPE_EXYNOSAUTOV9,
 };
 
 struct exynos5_i2c {
@@ -230,6 +231,11 @@ static const struct exynos_hsi2c_variant exynos7_hsi2c_data = {
        .hw             = I2C_TYPE_EXYNOS7,
 };
 
+static const struct exynos_hsi2c_variant exynosautov9_hsi2c_data = {
+       .fifo_depth     = 64,
+       .hw             = I2C_TYPE_EXYNOSAUTOV9,
+};
+
 static const struct of_device_id exynos5_i2c_match[] = {
        {
                .compatible = "samsung,exynos5-hsi2c",
@@ -243,6 +249,9 @@ static const struct of_device_id exynos5_i2c_match[] = {
        }, {
                .compatible = "samsung,exynos7-hsi2c",
                .data = &exynos7_hsi2c_data
+       }, {
+               .compatible = "samsung,exynosautov9-hsi2c",
+               .data = &exynosautov9_hsi2c_data
        }, {},
 };
 MODULE_DEVICE_TABLE(of, exynos5_i2c_match);
@@ -282,6 +291,31 @@ static int exynos5_i2c_set_timing(struct exynos5_i2c *i2c, bool hs_timings)
        int div, clk_cycle, temp;
 
        /*
+        * In case of HSI2C controllers in ExynosAutoV9:
+        *
+        * FSCL = IPCLK / ((CLK_DIV + 1) * 16)
+        * T_SCL_LOW = IPCLK * (CLK_DIV + 1) * (N + M)
+        *   [N : number of 0's in the TSCL_H_HS]
+        *   [M : number of 0's in the TSCL_L_HS]
+        * T_SCL_HIGH = IPCLK * (CLK_DIV + 1) * (N + M)
+        *   [N : number of 1's in the TSCL_H_HS]
+        *   [M : number of 1's in the TSCL_L_HS]
+        *
+        * Result of (N + M) is always 8.
+        * In general case, we don't need to control timing_s1 and timing_s2.
+        */
+       if (i2c->variant->hw == I2C_TYPE_EXYNOSAUTOV9) {
+               div = ((clkin / (16 * i2c->op_clock)) - 1);
+               i2c_timing_s3 = div << 16;
+               if (hs_timings)
+                       writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_HS3);
+               else
+                       writel(i2c_timing_s3, i2c->regs + HSI2C_TIMING_FS3);
+
+               return 0;
+       }
+
+       /*
         * In case of HSI2C controller in Exynos5 series
         * FPCLK / FI2C =
         * (CLK_DIV + 1) * (TSCLK_L + TSCLK_H + 2) + 8 + 2 * FLT_CYCLE
@@ -422,7 +456,10 @@ static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
        writel(int_status, i2c->regs + HSI2C_INT_STATUS);
 
        /* handle interrupt related to the transfer status */
-       if (i2c->variant->hw == I2C_TYPE_EXYNOS7) {
+       switch (i2c->variant->hw) {
+       case I2C_TYPE_EXYNOSAUTOV9:
+               fallthrough;
+       case I2C_TYPE_EXYNOS7:
                if (int_status & HSI2C_INT_TRANS_DONE) {
                        i2c->trans_done = 1;
                        i2c->state = 0;
@@ -443,7 +480,12 @@ static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
                        i2c->state = -ETIMEDOUT;
                        goto stop;
                }
-       } else if (int_status & HSI2C_INT_I2C) {
+
+               break;
+       case I2C_TYPE_EXYNOS5:
+               if (!(int_status & HSI2C_INT_I2C))
+                       break;
+
                trans_status = readl(i2c->regs + HSI2C_TRANS_STATUS);
                if (trans_status & HSI2C_NO_DEV_ACK) {
                        dev_dbg(i2c->dev, "No ACK from device\n");
@@ -465,6 +507,8 @@ static irqreturn_t exynos5_i2c_irq(int irqno, void *dev_id)
                        i2c->trans_done = 1;
                        i2c->state = 0;
                }
+
+               break;
        }
 
        if ((i2c->msg->flags & I2C_M_RD) && (int_status &
@@ -569,13 +613,13 @@ static void exynos5_i2c_bus_check(struct exynos5_i2c *i2c)
 {
        unsigned long timeout;
 
-       if (i2c->variant->hw != I2C_TYPE_EXYNOS7)
+       if (i2c->variant->hw == I2C_TYPE_EXYNOS5)
                return;
 
        /*
-        * HSI2C_MASTER_ST_LOSE state in EXYNOS7 variant before transaction
-        * indicates that bus is stuck (SDA is low). In such case bus recovery
-        * can be performed.
+        * HSI2C_MASTER_ST_LOSE state (in Exynos7 and ExynosAutoV9 variants)
+        * before transaction indicates that bus is stuck (SDA is low).
+        * In such case bus recovery can be performed.
         */
        timeout = jiffies + msecs_to_jiffies(100);
        for (;;) {
@@ -611,10 +655,10 @@ static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
        unsigned long flags;
        unsigned short trig_lvl;
 
-       if (i2c->variant->hw == I2C_TYPE_EXYNOS7)
-               int_en |= HSI2C_INT_I2C_TRANS;
-       else
+       if (i2c->variant->hw == I2C_TYPE_EXYNOS5)
                int_en |= HSI2C_INT_I2C;
+       else
+               int_en |= HSI2C_INT_I2C_TRANS;
 
        i2c_ctl = readl(i2c->regs + HSI2C_CTL);
        i2c_ctl &= ~(HSI2C_TXCHON | HSI2C_RXCHON);