|
@@ -24,6 +24,7 @@
|
|
#include <linux/wait.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regmap.h>
|
|
|
|
+#include <linux/math64.h>
|
|
|
|
|
|
|
|
|
|
/* Register Map */
|
|
/* Register Map */
|
|
@@ -428,18 +429,158 @@ out:
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
|
|
-static void rk3x_i2c_set_scl_rate(struct rk3x_i2c *i2c, unsigned long scl_rate)
|
|
|
|
|
|
+static int rk3x_i2c_calc_divs(unsigned long i2c_rate, unsigned long scl_rate,
|
|
|
|
+ unsigned long *div_low, unsigned long *div_high)
|
|
{
|
|
{
|
|
- unsigned long i2c_rate = clk_get_rate(i2c->clk);
|
|
|
|
- unsigned int div;
|
|
|
|
|
|
+ unsigned long min_low_ns, min_high_ns;
|
|
|
|
+ unsigned long max_data_hold_ns;
|
|
|
|
+ unsigned long data_hold_buffer_ns;
|
|
|
|
+ unsigned long max_low_ns, min_total_ns;
|
|
|
|
+
|
|
|
|
+ unsigned long i2c_rate_khz, scl_rate_khz;
|
|
|
|
+
|
|
|
|
+ unsigned long min_low_div, min_high_div;
|
|
|
|
+ unsigned long max_low_div;
|
|
|
|
+
|
|
|
|
+ unsigned long min_div_for_hold, min_total_div;
|
|
|
|
+ unsigned long extra_div, extra_low_div, ideal_low_div;
|
|
|
|
+
|
|
|
|
+ /* Only support standard-mode and fast-mode */
|
|
|
|
+ if (WARN_ON(scl_rate > 400000))
|
|
|
|
+ scl_rate = 400000;
|
|
|
|
+
|
|
|
|
+ /* prevent scl_rate_khz from becoming 0 */
|
|
|
|
+ if (WARN_ON(scl_rate < 1000))
|
|
|
|
+ scl_rate = 1000;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * min_low_ns: The minimum number of ns we need to hold low
|
|
|
|
+ * to meet i2c spec
|
|
|
|
+ * min_high_ns: The minimum number of ns we need to hold high
|
|
|
|
+ * to meet i2c spec
|
|
|
|
+ * max_low_ns: The maximum number of ns we can hold low
|
|
|
|
+ * to meet i2c spec
|
|
|
|
+ *
|
|
|
|
+ * Note: max_low_ns should be (max data hold time * 2 - buffer)
|
|
|
|
+ * This is because the i2c host on Rockchip holds the data line
|
|
|
|
+ * for half the low time.
|
|
|
|
+ */
|
|
|
|
+ if (scl_rate <= 100000) {
|
|
|
|
+ min_low_ns = 4700;
|
|
|
|
+ min_high_ns = 4000;
|
|
|
|
+ max_data_hold_ns = 3450;
|
|
|
|
+ data_hold_buffer_ns = 50;
|
|
|
|
+ } else {
|
|
|
|
+ min_low_ns = 1300;
|
|
|
|
+ min_high_ns = 600;
|
|
|
|
+ max_data_hold_ns = 900;
|
|
|
|
+ data_hold_buffer_ns = 50;
|
|
|
|
+ }
|
|
|
|
+ max_low_ns = max_data_hold_ns * 2 - data_hold_buffer_ns;
|
|
|
|
+ min_total_ns = min_low_ns + min_high_ns;
|
|
|
|
+
|
|
|
|
+ /* Adjust to avoid overflow */
|
|
|
|
+ i2c_rate_khz = DIV_ROUND_UP(i2c_rate, 1000);
|
|
|
|
+ scl_rate_khz = scl_rate / 1000;
|
|
|
|
|
|
- /* set DIV = DIVH = DIVL
|
|
|
|
- * SCL rate = (clk rate) / (8 * (DIVH + 1 + DIVL + 1))
|
|
|
|
- * = (clk rate) / (16 * (DIV + 1))
|
|
|
|
|
|
+ /*
|
|
|
|
+ * We need the total div to be >= this number
|
|
|
|
+ * so we don't clock too fast.
|
|
|
|
+ */
|
|
|
|
+ min_total_div = DIV_ROUND_UP(i2c_rate_khz, scl_rate_khz * 8);
|
|
|
|
+
|
|
|
|
+ /* These are the min dividers needed for min hold times. */
|
|
|
|
+ min_low_div = DIV_ROUND_UP(i2c_rate_khz * min_low_ns, 8 * 1000000);
|
|
|
|
+ min_high_div = DIV_ROUND_UP(i2c_rate_khz * min_high_ns, 8 * 1000000);
|
|
|
|
+ min_div_for_hold = (min_low_div + min_high_div);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * This is the maximum divider so we don't go over the max.
|
|
|
|
+ * We don't round up here (we round down) since this is a max.
|
|
*/
|
|
*/
|
|
- div = DIV_ROUND_UP(i2c_rate, scl_rate * 16) - 1;
|
|
|
|
|
|
+ max_low_div = i2c_rate_khz * max_low_ns / (8 * 1000000);
|
|
|
|
+
|
|
|
|
+ if (min_low_div > max_low_div) {
|
|
|
|
+ WARN_ONCE(true,
|
|
|
|
+ "Conflicting, min_low_div %lu, max_low_div %lu\n",
|
|
|
|
+ min_low_div, max_low_div);
|
|
|
|
+ max_low_div = min_low_div;
|
|
|
|
+ }
|
|
|
|
|
|
- i2c_writel(i2c, (div << 16) | (div & 0xffff), REG_CLKDIV);
|
|
|
|
|
|
+ if (min_div_for_hold > min_total_div) {
|
|
|
|
+ /*
|
|
|
|
+ * Time needed to meet hold requirements is important.
|
|
|
|
+ * Just use that.
|
|
|
|
+ */
|
|
|
|
+ *div_low = min_low_div;
|
|
|
|
+ *div_high = min_high_div;
|
|
|
|
+ } else {
|
|
|
|
+ /*
|
|
|
|
+ * We've got to distribute some time among the low and high
|
|
|
|
+ * so we don't run too fast.
|
|
|
|
+ */
|
|
|
|
+ extra_div = min_total_div - min_div_for_hold;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * We'll try to split things up perfectly evenly,
|
|
|
|
+ * biasing slightly towards having a higher div
|
|
|
|
+ * for low (spend more time low).
|
|
|
|
+ */
|
|
|
|
+ ideal_low_div = DIV_ROUND_UP(i2c_rate_khz * min_low_ns,
|
|
|
|
+ scl_rate_khz * 8 * min_total_ns);
|
|
|
|
+
|
|
|
|
+ /* Don't allow it to go over the max */
|
|
|
|
+ if (ideal_low_div > max_low_div)
|
|
|
|
+ ideal_low_div = max_low_div;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Handle when the ideal low div is going to take up
|
|
|
|
+ * more than we have.
|
|
|
|
+ */
|
|
|
|
+ if (ideal_low_div > min_low_div + extra_div)
|
|
|
|
+ ideal_low_div = min_low_div + extra_div;
|
|
|
|
+
|
|
|
|
+ /* Give low the "ideal" and give high whatever extra is left */
|
|
|
|
+ extra_low_div = ideal_low_div - min_low_div;
|
|
|
|
+ *div_low = ideal_low_div;
|
|
|
|
+ *div_high = min_high_div + (extra_div - extra_low_div);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Adjust to the fact that the hardware has an implicit "+1".
|
|
|
|
+ * NOTE: Above calculations always produce div_low > 0 and div_high > 0.
|
|
|
|
+ */
|
|
|
|
+ *div_low = *div_low - 1;
|
|
|
|
+ *div_high = *div_high - 1;
|
|
|
|
+
|
|
|
|
+ if (*div_low >= 0xffff || *div_high >= 0xffff)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ else
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int rk3x_i2c_set_scl_rate(struct rk3x_i2c *i2c, unsigned long scl_rate)
|
|
|
|
+{
|
|
|
|
+ unsigned long i2c_rate = clk_get_rate(i2c->clk);
|
|
|
|
+ unsigned long div_low, div_high;
|
|
|
|
+ u64 t_low_ns, t_high_ns;
|
|
|
|
+ int ret = 0;
|
|
|
|
+
|
|
|
|
+ ret = rk3x_i2c_calc_divs(i2c_rate, scl_rate, &div_low, &div_high);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ i2c_writel(i2c, (div_high << 16) | (div_low & 0xffff), REG_CLKDIV);
|
|
|
|
+
|
|
|
|
+ t_low_ns = div_u64(((u64)div_low + 1) * 8 * 1000000000, i2c_rate);
|
|
|
|
+ t_high_ns = div_u64(((u64)div_high + 1) * 8 * 1000000000, i2c_rate);
|
|
|
|
+ dev_dbg(i2c->dev,
|
|
|
|
+ "CLK %lukhz, Req %luns, Act low %lluns high %lluns\n",
|
|
|
|
+ i2c_rate / 1000,
|
|
|
|
+ 1000000000 / scl_rate,
|
|
|
|
+ t_low_ns, t_high_ns);
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -537,7 +678,9 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
|
|
clk_enable(i2c->clk);
|
|
clk_enable(i2c->clk);
|
|
|
|
|
|
/* The clock rate might have changed, so setup the divider again */
|
|
/* The clock rate might have changed, so setup the divider again */
|
|
- rk3x_i2c_set_scl_rate(i2c, i2c->scl_frequency);
|
|
|
|
|
|
+ ret = rk3x_i2c_set_scl_rate(i2c, i2c->scl_frequency);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ goto exit;
|
|
|
|
|
|
i2c->is_last_msg = false;
|
|
i2c->is_last_msg = false;
|
|
|
|
|
|
@@ -585,6 +728,7 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+exit:
|
|
clk_disable(i2c->clk);
|
|
clk_disable(i2c->clk);
|
|
spin_unlock_irqrestore(&i2c->lock, flags);
|
|
spin_unlock_irqrestore(&i2c->lock, flags);
|
|
|
|
|