i2c: mediatek: Add i2c ac-timing adjust support
authorQii Wang <qii.wang@mediatek.com>
Thu, 14 May 2020 13:09:05 +0000 (21:09 +0800)
committerWolfram Sang <wsa@kernel.org>
Wed, 20 May 2020 13:25:55 +0000 (15:25 +0200)
This patch adds a algorithm to calculate some ac-timing parameters
which can fully meet I2C Spec.

Signed-off-by: Qii Wang <qii.wang@mediatek.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-mt65xx.c

index 0ca6c38a15ebc242e85b3a763d9a19f853810e80..deef69e5690627cee29f1d6c733ce0ee9b2ccb63 100644 (file)
 #define I2C_SOFT_RST                   0x0001
 #define I2C_FIFO_ADDR_CLR              0x0001
 #define I2C_DELAY_LEN                  0x0002
-#define I2C_ST_START_CON               0x8001
-#define I2C_FS_START_CON               0x1800
 #define I2C_TIME_CLR_VALUE             0x0000
 #define I2C_TIME_DEFAULT_VALUE         0x0003
 #define I2C_WRRD_TRANAC_VALUE          0x0002
 #define I2C_RD_TRANAC_VALUE            0x0001
+#define I2C_SCL_MIS_COMP_VALUE         0x0000
 
 #define I2C_DMA_CON_TX                 0x0000
 #define I2C_DMA_CON_RX                 0x0001
 #define I2C_DMA_HARD_RST               0x0002
 #define I2C_DMA_4G_MODE                        0x0001
 
-#define I2C_DEFAULT_CLK_DIV            5
 #define MAX_SAMPLE_CNT_DIV             8
 #define MAX_STEP_CNT_DIV               64
+#define MAX_CLOCK_DIV                  256
 #define MAX_HS_STEP_CNT_DIV            8
+#define I2C_STANDARD_MODE_BUFFER       (1000 / 2)
+#define I2C_FAST_MODE_BUFFER           (300 / 2)
+#define I2C_FAST_MODE_PLUS_BUFFER      (20 / 2)
 
 #define I2C_CONTROL_RS                  (0x1 << 1)
 #define I2C_CONTROL_DMA_EN              (0x1 << 2)
@@ -123,6 +125,12 @@ enum I2C_REGS_OFFSET {
        OFFSET_TRANSFER_LEN_AUX,
        OFFSET_CLOCK_DIV,
        OFFSET_LTIMING,
+       OFFSET_SCL_HIGH_LOW_RATIO,
+       OFFSET_HS_SCL_HIGH_LOW_RATIO,
+       OFFSET_SCL_MIS_COMP_POINT,
+       OFFSET_STA_STO_AC_TIMING,
+       OFFSET_HS_STA_STO_AC_TIMING,
+       OFFSET_SDA_TIMING,
 };
 
 static const u16 mt_i2c_regs_v1[] = {
@@ -150,6 +158,12 @@ static const u16 mt_i2c_regs_v1[] = {
        [OFFSET_DEBUGCTRL] = 0x68,
        [OFFSET_TRANSFER_LEN_AUX] = 0x6c,
        [OFFSET_CLOCK_DIV] = 0x70,
+       [OFFSET_SCL_HIGH_LOW_RATIO] = 0x74,
+       [OFFSET_HS_SCL_HIGH_LOW_RATIO] = 0x78,
+       [OFFSET_SCL_MIS_COMP_POINT] = 0x7C,
+       [OFFSET_STA_STO_AC_TIMING] = 0x80,
+       [OFFSET_HS_STA_STO_AC_TIMING] = 0x84,
+       [OFFSET_SDA_TIMING] = 0x88,
 };
 
 static const u16 mt_i2c_regs_v2[] = {
@@ -168,9 +182,11 @@ static const u16 mt_i2c_regs_v2[] = {
        [OFFSET_HS] = 0x30,
        [OFFSET_IO_CONFIG] = 0x34,
        [OFFSET_FIFO_ADDR_CLR] = 0x38,
+       [OFFSET_SDA_TIMING] = 0x3c,
        [OFFSET_TRANSFER_LEN_AUX] = 0x44,
        [OFFSET_CLOCK_DIV] = 0x48,
        [OFFSET_SOFTRESET] = 0x50,
+       [OFFSET_SCL_MIS_COMP_POINT] = 0x90,
        [OFFSET_DEBUGSTAT] = 0xe0,
        [OFFSET_DEBUGCTRL] = 0xe8,
        [OFFSET_FIFO_STAT] = 0xf4,
@@ -191,6 +207,19 @@ struct mtk_i2c_compatible {
        unsigned char ltiming_adjust: 1;
 };
 
+struct mtk_i2c_ac_timing {
+       u16 htiming;
+       u16 ltiming;
+       u16 hs;
+       u16 ext;
+       u16 inter_clk_div;
+       u16 scl_hl_ratio;
+       u16 hs_scl_hl_ratio;
+       u16 sta_stop;
+       u16 hs_sta_stop;
+       u16 sda_timing;
+};
+
 struct mtk_i2c {
        struct i2c_adapter adap;        /* i2c host adapter */
        struct device *dev;
@@ -215,9 +244,46 @@ struct mtk_i2c {
        u16 ltiming_reg;
        unsigned char auto_restart;
        bool ignore_restart_irq;
+       struct mtk_i2c_ac_timing ac_timing;
        const struct mtk_i2c_compatible *dev_comp;
 };
 
+/**
+ * struct i2c_spec_values:
+ * min_low_ns: min LOW period of the SCL clock
+ * min_su_sta_ns: min set-up time for a repeated START condition
+ * max_hd_dat_ns: max data hold time
+ * min_su_dat_ns: min data set-up time
+ */
+struct i2c_spec_values {
+       unsigned int min_low_ns;
+       unsigned int min_high_ns;
+       unsigned int min_su_sta_ns;
+       unsigned int max_hd_dat_ns;
+       unsigned int min_su_dat_ns;
+};
+
+static const struct i2c_spec_values standard_mode_spec = {
+       .min_low_ns = 4700 + I2C_STANDARD_MODE_BUFFER,
+       .min_su_sta_ns = 4700 + I2C_STANDARD_MODE_BUFFER,
+       .max_hd_dat_ns = 3450 - I2C_STANDARD_MODE_BUFFER,
+       .min_su_dat_ns = 250 + I2C_STANDARD_MODE_BUFFER,
+};
+
+static const struct i2c_spec_values fast_mode_spec = {
+       .min_low_ns = 1300 + I2C_FAST_MODE_BUFFER,
+       .min_su_sta_ns = 600 + I2C_FAST_MODE_BUFFER,
+       .max_hd_dat_ns = 900 - I2C_FAST_MODE_BUFFER,
+       .min_su_dat_ns = 100 + I2C_FAST_MODE_BUFFER,
+};
+
+static const struct i2c_spec_values fast_mode_plus_spec = {
+       .min_low_ns = 500 + I2C_FAST_MODE_PLUS_BUFFER,
+       .min_su_sta_ns = 260 + I2C_FAST_MODE_PLUS_BUFFER,
+       .max_hd_dat_ns = 400 - I2C_FAST_MODE_PLUS_BUFFER,
+       .min_su_dat_ns = 50 + I2C_FAST_MODE_PLUS_BUFFER,
+};
+
 static const struct i2c_adapter_quirks mt6577_i2c_quirks = {
        .flags = I2C_AQ_COMB_WRITE_THEN_READ,
        .max_num_msgs = 1,
@@ -397,14 +463,38 @@ static void mtk_i2c_init_hw(struct mtk_i2c *i2c)
        if (i2c->dev_comp->dcm)
                mtk_i2c_writew(i2c, I2C_DCM_DISABLE, OFFSET_DCM_EN);
 
-       if (i2c->dev_comp->timing_adjust)
-               mtk_i2c_writew(i2c, I2C_DEFAULT_CLK_DIV - 1, OFFSET_CLOCK_DIV);
-
        mtk_i2c_writew(i2c, i2c->timing_reg, OFFSET_TIMING);
        mtk_i2c_writew(i2c, i2c->high_speed_reg, OFFSET_HS);
        if (i2c->dev_comp->ltiming_adjust)
                mtk_i2c_writew(i2c, i2c->ltiming_reg, OFFSET_LTIMING);
 
+       if (i2c->dev_comp->timing_adjust) {
+               mtk_i2c_writew(i2c, i2c->ac_timing.ext, OFFSET_EXT_CONF);
+               mtk_i2c_writew(i2c, i2c->ac_timing.inter_clk_div,
+                              OFFSET_CLOCK_DIV);
+               mtk_i2c_writew(i2c, I2C_SCL_MIS_COMP_VALUE,
+                              OFFSET_SCL_MIS_COMP_POINT);
+               mtk_i2c_writew(i2c, i2c->ac_timing.sda_timing,
+                              OFFSET_SDA_TIMING);
+
+               if (i2c->dev_comp->ltiming_adjust) {
+                       mtk_i2c_writew(i2c, i2c->ac_timing.htiming,
+                                      OFFSET_TIMING);
+                       mtk_i2c_writew(i2c, i2c->ac_timing.hs, OFFSET_HS);
+                       mtk_i2c_writew(i2c, i2c->ac_timing.ltiming,
+                                      OFFSET_LTIMING);
+               } else {
+                       mtk_i2c_writew(i2c, i2c->ac_timing.scl_hl_ratio,
+                                      OFFSET_SCL_HIGH_LOW_RATIO);
+                       mtk_i2c_writew(i2c, i2c->ac_timing.hs_scl_hl_ratio,
+                                      OFFSET_HS_SCL_HIGH_LOW_RATIO);
+                       mtk_i2c_writew(i2c, i2c->ac_timing.sta_stop,
+                                      OFFSET_STA_STO_AC_TIMING);
+                       mtk_i2c_writew(i2c, i2c->ac_timing.hs_sta_stop,
+                                      OFFSET_HS_STA_STO_AC_TIMING);
+               }
+       }
+
        /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
        if (i2c->have_pmic)
                mtk_i2c_writew(i2c, I2C_CONTROL_WRAPPER, OFFSET_PATH_DIR);
@@ -422,6 +512,126 @@ static void mtk_i2c_init_hw(struct mtk_i2c *i2c)
        writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_RST);
 }
 
+static const struct i2c_spec_values *mtk_i2c_get_spec(unsigned int speed)
+{
+       if (speed <= I2C_MAX_STANDARD_MODE_FREQ)
+               return &standard_mode_spec;
+       else if (speed <= I2C_MAX_FAST_MODE_FREQ)
+               return &fast_mode_spec;
+       else
+               return &fast_mode_plus_spec;
+}
+
+static int mtk_i2c_max_step_cnt(unsigned int target_speed)
+{
+       if (target_speed > I2C_MAX_FAST_MODE_FREQ)
+               return MAX_HS_STEP_CNT_DIV;
+       else
+               return MAX_STEP_CNT_DIV;
+}
+
+/*
+ * Check and Calculate i2c ac-timing
+ *
+ * Hardware design:
+ * sample_ns = (1000000000 * (sample_cnt + 1)) / clk_src
+ * xxx_cnt_div =  spec->min_xxx_ns / sample_ns
+ *
+ * Sample_ns is rounded down for xxx_cnt_div would be greater
+ * than the smallest spec.
+ * The sda_timing is chosen as the middle value between
+ * the largest and smallest.
+ */
+static int mtk_i2c_check_ac_timing(struct mtk_i2c *i2c,
+                                  unsigned int clk_src,
+                                  unsigned int check_speed,
+                                  unsigned int step_cnt,
+                                  unsigned int sample_cnt)
+{
+       const struct i2c_spec_values *spec;
+       unsigned int su_sta_cnt, low_cnt, high_cnt, max_step_cnt;
+       unsigned int sda_max, sda_min, clk_ns, max_sta_cnt = 0x3f;
+       unsigned int sample_ns = div_u64(1000000000ULL * (sample_cnt + 1),
+                                        clk_src);
+
+       if (!i2c->dev_comp->timing_adjust)
+               return 0;
+
+       if (i2c->dev_comp->ltiming_adjust)
+               max_sta_cnt = 0x100;
+
+       spec = mtk_i2c_get_spec(check_speed);
+
+       if (i2c->dev_comp->ltiming_adjust)
+               clk_ns = 1000000000 / clk_src;
+       else
+               clk_ns = sample_ns / 2;
+
+       su_sta_cnt = DIV_ROUND_UP(spec->min_su_sta_ns, clk_ns);
+       if (su_sta_cnt > max_sta_cnt)
+               return -1;
+
+       low_cnt = DIV_ROUND_UP(spec->min_low_ns, sample_ns);
+       max_step_cnt = mtk_i2c_max_step_cnt(check_speed);
+       if ((2 * step_cnt) > low_cnt && low_cnt < max_step_cnt) {
+               if (low_cnt > step_cnt) {
+                       high_cnt = 2 * step_cnt - low_cnt;
+               } else {
+                       high_cnt = step_cnt;
+                       low_cnt = step_cnt;
+               }
+       } else {
+               return -2;
+       }
+
+       sda_max = spec->max_hd_dat_ns / sample_ns;
+       if (sda_max > low_cnt)
+               sda_max = 0;
+
+       sda_min = DIV_ROUND_UP(spec->min_su_dat_ns, sample_ns);
+       if (sda_min < low_cnt)
+               sda_min = 0;
+
+       if (sda_min > sda_max)
+               return -3;
+
+       if (check_speed > I2C_MAX_FAST_MODE_FREQ) {
+               if (i2c->dev_comp->ltiming_adjust) {
+                       i2c->ac_timing.hs = I2C_TIME_DEFAULT_VALUE |
+                               (sample_cnt << 12) | (high_cnt << 8);
+                       i2c->ac_timing.ltiming &= ~GENMASK(15, 9);
+                       i2c->ac_timing.ltiming |= (sample_cnt << 12) |
+                               (low_cnt << 9);
+                       i2c->ac_timing.ext &= ~GENMASK(7, 1);
+                       i2c->ac_timing.ext |= (su_sta_cnt << 1) | (1 << 0);
+               } else {
+                       i2c->ac_timing.hs_scl_hl_ratio = (1 << 12) |
+                               (high_cnt << 6) | low_cnt;
+                       i2c->ac_timing.hs_sta_stop = (su_sta_cnt << 8) |
+                               su_sta_cnt;
+               }
+               i2c->ac_timing.sda_timing &= ~GENMASK(11, 6);
+               i2c->ac_timing.sda_timing |= (1 << 12) |
+                       ((sda_max + sda_min) / 2) << 6;
+       } else {
+               if (i2c->dev_comp->ltiming_adjust) {
+                       i2c->ac_timing.htiming = (sample_cnt << 8) | (high_cnt);
+                       i2c->ac_timing.ltiming = (sample_cnt << 6) | (low_cnt);
+                       i2c->ac_timing.ext = (su_sta_cnt << 8) | (1 << 0);
+               } else {
+                       i2c->ac_timing.scl_hl_ratio = (1 << 12) |
+                               (high_cnt << 6) | low_cnt;
+                       i2c->ac_timing.sta_stop = (su_sta_cnt << 8) |
+                               su_sta_cnt;
+               }
+
+               i2c->ac_timing.sda_timing = (1 << 12) |
+                       (sda_max + sda_min) / 2;
+       }
+
+       return 0;
+}
+
 /*
  * Calculate i2c port speed
  *
@@ -446,15 +656,12 @@ static int mtk_i2c_calculate_speed(struct mtk_i2c *i2c, unsigned int clk_src,
        unsigned int opt_div;
        unsigned int best_mul;
        unsigned int cnt_mul;
+       int ret = -EINVAL;
 
        if (target_speed > I2C_MAX_FAST_MODE_PLUS_FREQ)
                target_speed = I2C_MAX_FAST_MODE_PLUS_FREQ;
 
-       if (target_speed > I2C_MAX_FAST_MODE_FREQ)
-               max_step_cnt = MAX_HS_STEP_CNT_DIV;
-       else
-               max_step_cnt = MAX_STEP_CNT_DIV;
-
+       max_step_cnt = mtk_i2c_max_step_cnt(target_speed);
        base_step_cnt = max_step_cnt;
        /* Find the best combination */
        opt_div = DIV_ROUND_UP(clk_src >> 1, target_speed);
@@ -473,6 +680,11 @@ static int mtk_i2c_calculate_speed(struct mtk_i2c *i2c, unsigned int clk_src,
                        continue;
 
                if (cnt_mul < best_mul) {
+                       ret = mtk_i2c_check_ac_timing(i2c, clk_src,
+                               target_speed, step_cnt - 1, sample_cnt - 1);
+                       if (ret)
+                               continue;
+
                        best_mul = cnt_mul;
                        base_sample_cnt = sample_cnt;
                        base_step_cnt = step_cnt;
@@ -481,6 +693,9 @@ static int mtk_i2c_calculate_speed(struct mtk_i2c *i2c, unsigned int clk_src,
                }
        }
 
+       if (ret)
+               return -EINVAL;
+
        sample_cnt = base_sample_cnt;
        step_cnt = base_step_cnt;
 
@@ -506,47 +721,68 @@ static int mtk_i2c_set_speed(struct mtk_i2c *i2c, unsigned int parent_clk)
        unsigned int l_step_cnt;
        unsigned int l_sample_cnt;
        unsigned int target_speed;
+       unsigned int clk_div;
+       unsigned int max_clk_div;
        int ret;
 
-       clk_src = parent_clk / i2c->clk_src_div;
        target_speed = i2c->speed_hz;
+       parent_clk /= i2c->clk_src_div;
 
-       if (target_speed > I2C_MAX_FAST_MODE_FREQ) {
-               /* Set master code speed register */
-               ret = mtk_i2c_calculate_speed(i2c, clk_src, I2C_MAX_FAST_MODE_FREQ,
-                                             &l_step_cnt, &l_sample_cnt);
-               if (ret < 0)
-                       return ret;
-
-               i2c->timing_reg = (l_sample_cnt << 8) | l_step_cnt;
-
-               /* Set the high speed mode register */
-               ret = mtk_i2c_calculate_speed(i2c, clk_src, target_speed,
-                                             &step_cnt, &sample_cnt);
-               if (ret < 0)
-                       return ret;
-
-               i2c->high_speed_reg = I2C_TIME_DEFAULT_VALUE |
-                       (sample_cnt << 12) | (step_cnt << 8);
+       if (i2c->dev_comp->timing_adjust)
+               max_clk_div = MAX_CLOCK_DIV;
+       else
+               max_clk_div = 1;
+
+       for (clk_div = 1; clk_div <= max_clk_div; clk_div++) {
+               clk_src = parent_clk / clk_div;
+
+               if (target_speed > I2C_MAX_FAST_MODE_FREQ) {
+                       /* Set master code speed register */
+                       ret = mtk_i2c_calculate_speed(i2c, clk_src,
+                                                     I2C_MAX_FAST_MODE_FREQ,
+                                                     &l_step_cnt,
+                                                     &l_sample_cnt);
+                       if (ret < 0)
+                               continue;
+
+                       i2c->timing_reg = (l_sample_cnt << 8) | l_step_cnt;
+
+                       /* Set the high speed mode register */
+                       ret = mtk_i2c_calculate_speed(i2c, clk_src,
+                                                     target_speed, &step_cnt,
+                                                     &sample_cnt);
+                       if (ret < 0)
+                               continue;
+
+                       i2c->high_speed_reg = I2C_TIME_DEFAULT_VALUE |
+                                       (sample_cnt << 12) | (step_cnt << 8);
+
+                       if (i2c->dev_comp->ltiming_adjust)
+                               i2c->ltiming_reg =
+                                       (l_sample_cnt << 6) | l_step_cnt |
+                                       (sample_cnt << 12) | (step_cnt << 9);
+               } else {
+                       ret = mtk_i2c_calculate_speed(i2c, clk_src,
+                                                     target_speed, &l_step_cnt,
+                                                     &l_sample_cnt);
+                       if (ret < 0)
+                               continue;
 
-               if (i2c->dev_comp->ltiming_adjust)
-                       i2c->ltiming_reg = (l_sample_cnt << 6) | l_step_cnt |
-                                          (sample_cnt << 12) | (step_cnt << 9);
-       } else {
-               ret = mtk_i2c_calculate_speed(i2c, clk_src, target_speed,
-                                             &step_cnt, &sample_cnt);
-               if (ret < 0)
-                       return ret;
+                       i2c->timing_reg = (l_sample_cnt << 8) | l_step_cnt;
 
-               i2c->timing_reg = (sample_cnt << 8) | step_cnt;
+                       /* Disable the high speed transaction */
+                       i2c->high_speed_reg = I2C_TIME_CLR_VALUE;
 
-               /* Disable the high speed transaction */
-               i2c->high_speed_reg = I2C_TIME_CLR_VALUE;
+                       if (i2c->dev_comp->ltiming_adjust)
+                               i2c->ltiming_reg =
+                                       (l_sample_cnt << 6) | l_step_cnt;
+               }
 
-               if (i2c->dev_comp->ltiming_adjust)
-                       i2c->ltiming_reg = (sample_cnt << 6) | step_cnt;
+               break;
        }
 
+       i2c->ac_timing.inter_clk_div = clk_div - 1;
+
        return 0;
 }
 
@@ -586,12 +822,6 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs,
 
        mtk_i2c_writew(i2c, control_reg, OFFSET_CONTROL);
 
-       /* set start condition */
-       if (i2c->speed_hz <= I2C_MAX_STANDARD_MODE_FREQ)
-               mtk_i2c_writew(i2c, I2C_ST_START_CON, OFFSET_EXT_CONF);
-       else
-               mtk_i2c_writew(i2c, I2C_FS_START_CON, OFFSET_EXT_CONF);
-
        addr_reg = i2c_8bit_addr_from_msg(msgs);
        mtk_i2c_writew(i2c, addr_reg, OFFSET_SLAVE_ADDR);
 
@@ -948,9 +1178,6 @@ static int mtk_i2c_probe(struct platform_device *pdev)
        if (ret)
                return -EINVAL;
 
-       if (i2c->dev_comp->timing_adjust)
-               i2c->clk_src_div *= I2C_DEFAULT_CLK_DIV;
-
        if (i2c->have_pmic && !i2c->dev_comp->pmic_i2c)
                return -EINVAL;