diff mbox series

[v3,11/23] i2c: designware_i2c: Rewrite timing calculation

Message ID 20200123114556.v3.11.Ia75f54ec69293acac8756db00e547decf09e95c2@changeid
State Accepted
Commit e71b6f6622d6a3380d866943799f36e473a3cd9b
Headers show
Series i2c: designware_ic2: Improvements to timing and general cleanup | expand

Commit Message

Simon Glass Jan. 23, 2020, 6:48 p.m. UTC
At present the driver can end up with timing parameters which are slightly
faster than those expected. It is possible to optimise the parameters to
get the best possible result.

Create a new function to handle the timing calculation. This uses a table
of defaults for each speed mode rather than writing it in code.

The function works by calculating the 'period' of each bit on the bus in
terms of the input clock to the controller (IC_CLK). It makes sure that
the constraints are met and that the different components of that period
add up correctly.

This code was taken from coreboot which has ended up with this same
driver, but now in a much-different form.

Signed-off-by: Simon Glass <sjg at chromium.org>
Reviewed-by: Heiko Schocher <hs at denx.de>
---

Changes in v3: None
Changes in v2: None

 drivers/i2c/designware_i2c.c | 169 ++++++++++++++++++++++++++++++-----
 1 file changed, 147 insertions(+), 22 deletions(-)
diff mbox series

Patch

diff --git a/drivers/i2c/designware_i2c.c b/drivers/i2c/designware_i2c.c
index e50987a717..0069602103 100644
--- a/drivers/i2c/designware_i2c.c
+++ b/drivers/i2c/designware_i2c.c
@@ -63,6 +63,147 @@  static int dw_i2c_enable(struct i2c_regs *i2c_base, bool enable)
 }
 #endif
 
+/* High and low times in different speed modes (in ns) */
+enum {
+	/* SDA Hold Time */
+	DEFAULT_SDA_HOLD_TIME		= 300,
+};
+
+/**
+ * calc_counts() - Convert a period to a number of IC clk cycles
+ *
+ * @ic_clk: Input clock in Hz
+ * @period_ns: Period to represent, in ns
+ * @return calculated count
+ */
+static uint calc_counts(uint ic_clk, uint period_ns)
+{
+	return DIV_ROUND_UP(ic_clk / 1000 * period_ns, NANO_TO_KILO);
+}
+
+/**
+ * struct i2c_mode_info - Information about an I2C speed mode
+ *
+ * Each speed mode has its own characteristics. This struct holds these to aid
+ * calculations in dw_i2c_calc_timing().
+ *
+ * @speed: Speed in Hz
+ * @min_scl_lowtime_ns: Minimum value for SCL low period in ns
+ * @min_scl_hightime_ns: Minimum value for SCL high period in ns
+ * @def_rise_time_ns: Default rise time in ns
+ * @def_fall_time_ns: Default fall time in ns
+ */
+struct i2c_mode_info {
+	int speed;
+	int min_scl_hightime_ns;
+	int min_scl_lowtime_ns;
+	int def_rise_time_ns;
+	int def_fall_time_ns;
+};
+
+static const struct i2c_mode_info info_for_mode[] = {
+	[IC_SPEED_MODE_STANDARD] = {
+		I2C_STANDARD_SPEED,
+		MIN_SS_SCL_HIGHTIME,
+		MIN_SS_SCL_LOWTIME,
+		1000,
+		300,
+	},
+	[IC_SPEED_MODE_FAST] = {
+		I2C_FAST_SPEED,
+		MIN_FS_SCL_HIGHTIME,
+		MIN_FS_SCL_LOWTIME,
+		300,
+		300,
+	},
+	[IC_SPEED_MODE_HIGH] = {
+		I2C_HIGH_SPEED,
+		MIN_HS_SCL_HIGHTIME,
+		MIN_HS_SCL_LOWTIME,
+		120,
+		120,
+	},
+};
+
+/**
+ * dw_i2c_calc_timing() - Calculate the timings to use for a bus
+ *
+ * @priv: Bus private information (NULL if not using driver model)
+ * @mode: Speed mode to use
+ * @ic_clk: IC clock speed in Hz
+ * @spk_cnt: Spike-suppression count
+ * @config: Returns value to use
+ * @return 0 if OK, -EINVAL if the calculation failed due to invalid data
+ */
+static int dw_i2c_calc_timing(struct dw_i2c *priv, enum i2c_speed_mode mode,
+			      int ic_clk, int spk_cnt,
+			      struct dw_i2c_speed_config *config)
+{
+	int fall_cnt, rise_cnt, min_tlow_cnt, min_thigh_cnt;
+	int hcnt, lcnt, period_cnt, diff, tot;
+	int sda_hold_time_ns, scl_rise_time_ns, scl_fall_time_ns;
+	const struct i2c_mode_info *info;
+
+	/*
+	 * Find the period, rise, fall, min tlow, and min thigh in terms of
+	 * counts of the IC clock
+	 */
+	info = &info_for_mode[mode];
+	period_cnt = ic_clk / info->speed;
+	scl_rise_time_ns = priv && priv->scl_rise_time_ns ?
+		 priv->scl_rise_time_ns : info->def_rise_time_ns;
+	scl_fall_time_ns = priv && priv->scl_fall_time_ns ?
+		 priv->scl_fall_time_ns : info->def_fall_time_ns;
+	rise_cnt = calc_counts(ic_clk, scl_rise_time_ns);
+	fall_cnt = calc_counts(ic_clk, scl_fall_time_ns);
+	min_tlow_cnt = calc_counts(ic_clk, info->min_scl_lowtime_ns);
+	min_thigh_cnt = calc_counts(ic_clk, info->min_scl_hightime_ns);
+
+	debug("dw_i2c: period %d rise %d fall %d tlow %d thigh %d spk %d\n",
+	      period_cnt, rise_cnt, fall_cnt, min_tlow_cnt, min_thigh_cnt,
+	      spk_cnt);
+
+	/*
+	 * Back-solve for hcnt and lcnt according to the following equations:
+	 * SCL_High_time = [(HCNT + IC_*_SPKLEN + 7) * ic_clk] + SCL_Fall_time
+	 * SCL_Low_time = [(LCNT + 1) * ic_clk] - SCL_Fall_time + SCL_Rise_time
+	 */
+	hcnt = min_thigh_cnt - fall_cnt - 7 - spk_cnt;
+	lcnt = min_tlow_cnt - rise_cnt + fall_cnt - 1;
+
+	if (hcnt < 0 || lcnt < 0) {
+		debug("dw_i2c: bad counts. hcnt = %d lcnt = %d\n", hcnt, lcnt);
+		return -EINVAL;
+	}
+
+	/*
+	 * Now add things back up to ensure the period is hit. If it is off,
+	 * split the difference and bias to lcnt for remainder
+	 */
+	tot = hcnt + lcnt + 7 + spk_cnt + rise_cnt + 1;
+
+	if (tot < period_cnt) {
+		diff = (period_cnt - tot) / 2;
+		hcnt += diff;
+		lcnt += diff;
+		tot = hcnt + lcnt + 7 + spk_cnt + rise_cnt + 1;
+		lcnt += period_cnt - tot;
+	}
+
+	config->scl_lcnt = lcnt;
+	config->scl_hcnt = hcnt;
+
+	/* Use internal default unless other value is specified */
+	sda_hold_time_ns = priv && priv->sda_hold_time_ns ?
+		 priv->sda_hold_time_ns : DEFAULT_SDA_HOLD_TIME;
+	config->sda_hold = calc_counts(ic_clk, sda_hold_time_ns);
+
+	debug("dw_i2c: hcnt = %d lcnt = %d sda hold = %d\n", hcnt, lcnt,
+	      config->sda_hold);
+
+	return 0;
+}
+
 /*
  * i2c_set_bus_speed - Set the i2c speed
  * @speed:	required i2c speed
@@ -76,10 +217,10 @@  static unsigned int __dw_i2c_set_bus_speed(struct dw_i2c *priv,
 {
 	const struct dw_scl_sda_cfg *scl_sda_cfg = NULL;
 	struct dw_i2c_speed_config config;
-	ulong bus_khz = bus_clk / 1000;
 	enum i2c_speed_mode i2c_spd;
 	unsigned int cntl;
 	unsigned int ena;
+	int ret;
 
 	if (priv)
 		scl_sda_cfg = priv->scl_sda_cfg;
@@ -100,9 +241,6 @@  static unsigned int __dw_i2c_set_bus_speed(struct dw_i2c *priv,
 
 	cntl = (readl(&i2c_base->ic_con) & (~IC_CON_SPD_MSK));
 
-	config.scl_hcnt = 0;
-	config.scl_lcnt = 0;
-	config.sda_hold = 0;
 	if (scl_sda_cfg) {
 		config.sda_hold = scl_sda_cfg->sda_hold;
 		if (i2c_spd == IC_SPEED_MODE_STANDARD) {
@@ -112,29 +250,22 @@  static unsigned int __dw_i2c_set_bus_speed(struct dw_i2c *priv,
 			config.scl_hcnt = scl_sda_cfg->fs_hcnt;
 			config.scl_lcnt = scl_sda_cfg->fs_lcnt;
 		}
+	} else {
+		ret = dw_i2c_calc_timing(priv, i2c_spd, bus_clk, 0,
+					 &config);
+		if (ret)
+			return log_msg_ret("gen_confg", ret);
 	}
 
 	switch (i2c_spd) {
 	case IC_SPEED_MODE_HIGH:
 		cntl |= IC_CON_SPD_SS;
-		if (!scl_sda_cfg) {
-			config.scl_hcnt = (bus_khz * MIN_HS_SCL_HIGHTIME) /
-				 NANO_TO_KILO;
-			config.scl_lcnt = (bus_khz * MIN_HS_SCL_LOWTIME) /
-				 NANO_TO_KILO;
-		}
 		writel(config.scl_hcnt, &i2c_base->ic_hs_scl_hcnt);
 		writel(config.scl_lcnt, &i2c_base->ic_hs_scl_lcnt);
 		break;
 
 	case IC_SPEED_MODE_STANDARD:
 		cntl |= IC_CON_SPD_SS;
-		if (!scl_sda_cfg) {
-			config.scl_hcnt = (bus_khz * MIN_SS_SCL_HIGHTIME) /
-				 NANO_TO_KILO;
-			config.scl_lcnt = (bus_khz * MIN_SS_SCL_LOWTIME) /
-				 NANO_TO_KILO;
-		}
 		writel(config.scl_hcnt, &i2c_base->ic_ss_scl_hcnt);
 		writel(config.scl_lcnt, &i2c_base->ic_ss_scl_lcnt);
 		break;
@@ -142,12 +273,6 @@  static unsigned int __dw_i2c_set_bus_speed(struct dw_i2c *priv,
 	case IC_SPEED_MODE_FAST:
 	default:
 		cntl |= IC_CON_SPD_FS;
-		if (!scl_sda_cfg) {
-			config.scl_hcnt = (bus_khz * MIN_FS_SCL_HIGHTIME) /
-				 NANO_TO_KILO;
-			config.scl_lcnt = (bus_khz * MIN_FS_SCL_LOWTIME) /
-				 NANO_TO_KILO;
-		}
 		writel(config.scl_hcnt, &i2c_base->ic_fs_scl_hcnt);
 		writel(config.scl_lcnt, &i2c_base->ic_fs_scl_lcnt);
 		break;