* [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version
@ 2015-12-11 14:33 David Wu
2015-12-11 14:33 ` [PATCH v1 2/3] i2c: rk3x: new way to calc_divs " David Wu
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: David Wu @ 2015-12-11 14:33 UTC (permalink / raw)
To: linux-arm-kernel
The calc_divs of new version is different form the old.
The time of tHD;sda, tHD;sda, tSU;sta, tHD;sta and tSU;sto
could be configured by RKI2C_CON register.
So it need a new way to calc_divs for new i2c controller.
Signed-off-by: David Wu <wdc@rock-chips.com>
Signed-off-by: David Wu <david.wu@rock-chips.com>
---
drivers/i2c/busses/i2c-rk3x.c | 54 ++++++++++++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 11 deletions(-)
diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c
index c1935eb..0ff299f 100644
--- a/drivers/i2c/busses/i2c-rk3x.c
+++ b/drivers/i2c/busses/i2c-rk3x.c
@@ -58,6 +58,12 @@ enum {
#define REG_CON_LASTACK BIT(5) /* 1: send NACK after last received byte */
#define REG_CON_ACTACK BIT(6) /* 1: stop if NACK is received */
+#define VERSION_MASK 0xffff0000
+#define VERSION_SHIFT 16
+
+#define RK3X_I2C_V0 0x0
+#define RK3X_I2C_V1 0x1
+
/* REG_MRXADDR bits */
#define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */
@@ -90,10 +96,22 @@ struct rk3x_i2c_soc_data {
int grf_offset;
};
+struct rk3x_i2c_ops {
+ int (*calc_divs)(unsigned long,
+ unsigned long,
+ unsigned long,
+ unsigned long,
+ unsigned long,
+ unsigned long *,
+ unsigned long *,
+ unsigned int *);
+};
+
struct rk3x_i2c {
struct i2c_adapter adap;
struct device *dev;
struct rk3x_i2c_soc_data *soc_data;
+ struct rk3x_i2c_ops ops;
/* Hardware resources */
void __iomem *regs;
@@ -116,6 +134,7 @@ struct rk3x_i2c {
u8 addr;
unsigned int mode;
bool is_last_msg;
+ unsigned int time_con;
/* I2C state machine */
enum rk3x_i2c_state state;
@@ -151,7 +170,8 @@ static void rk3x_i2c_start(struct rk3x_i2c *i2c)
i2c_writel(i2c, REG_INT_START, REG_IEN);
/* enable adapter with correct mode, send START condition */
- val = REG_CON_EN | REG_CON_MOD(i2c->mode) | REG_CON_START;
+ val = i2c->time_con | REG_CON_EN | REG_CON_MOD(i2c->mode)
+ | REG_CON_START;
/* if we want to react to NACK, set ACTACK bit */
if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
@@ -443,16 +463,19 @@ out:
* @sda_fall_ns: How many ns it takes for SDA to fall.
* @div_low: Divider output for low
* @div_high: Divider output for high
+ * @con: version0 is not used
*
* Returns: 0 on success, -EINVAL if the goal SCL rate is too slow. In that case
* a best-effort divider value is returned in divs. If the target rate is
* too high, we silently use the highest possible rate.
*/
-static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
- unsigned long scl_rise_ns,
- unsigned long scl_fall_ns,
- unsigned long sda_fall_ns,
- unsigned long *div_low, unsigned long *div_high)
+static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
+ unsigned long scl_rise_ns,
+ unsigned long scl_fall_ns,
+ unsigned long sda_fall_ns,
+ unsigned long *div_low,
+ unsigned long *div_high,
+ unsigned int *con)
{
unsigned long spec_min_low_ns, spec_min_high_ns;
unsigned long spec_setup_start, spec_max_data_hold_ns;
@@ -616,17 +639,19 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate)
{
+ unsigned int con = 0;
unsigned long div_low, div_high;
u64 t_low_ns, t_high_ns;
int ret;
- ret = rk3x_i2c_calc_divs(clk_rate, i2c->scl_frequency, i2c->scl_rise_ns,
+ ret = i2c->ops.calc_divs(clk_rate, i2c->scl_frequency, i2c->scl_rise_ns,
i2c->scl_fall_ns, i2c->sda_fall_ns,
- &div_low, &div_high);
+ &div_low, &div_high, &con);
WARN_ONCE(ret != 0, "Could not reach SCL freq %u", i2c->scl_frequency);
clk_enable(i2c->clk);
i2c_writel(i2c, (div_high << 16) | (div_low & 0xffff), REG_CLKDIV);
+ i2c->time_con = con;
clk_disable(i2c->clk);
t_low_ns = div_u64(((u64)div_low + 1) * 8 * 1000000000, clk_rate);
@@ -661,13 +686,14 @@ static int rk3x_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long
struct clk_notifier_data *ndata = data;
struct rk3x_i2c *i2c = container_of(nb, struct rk3x_i2c, clk_rate_nb);
unsigned long div_low, div_high;
+ unsigned int con = 0;
switch (event) {
case PRE_RATE_CHANGE:
- if (rk3x_i2c_calc_divs(ndata->new_rate, i2c->scl_frequency,
+ if (i2c->ops.calc_divs(ndata->new_rate, i2c->scl_frequency,
i2c->scl_rise_ns, i2c->scl_fall_ns,
i2c->sda_fall_ns,
- &div_low, &div_high) != 0)
+ &div_low, &div_high, &con) != 0)
return NOTIFY_STOP;
/* scale up */
@@ -816,7 +842,8 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
/* Force a STOP condition without interrupt */
i2c_writel(i2c, 0, REG_IEN);
- i2c_writel(i2c, REG_CON_EN | REG_CON_STOP, REG_CON);
+ i2c_writel(i2c, i2c->time_con | REG_CON_EN |
+ REG_CON_STOP, REG_CON);
i2c->state = STATE_IDLE;
@@ -871,6 +898,7 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
u32 value;
int irq;
unsigned long clk_rate;
+ unsigned int version;
i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
if (!i2c)
@@ -983,6 +1011,10 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, i2c);
+ version = (readl(i2c->regs + REG_CON) & VERSION_MASK) >> VERSION_SHIFT;
+ if (version == RK3X_I2C_V0)
+ i2c->ops.calc_divs = rk3x_i2c_v0_calc_divs;
+
ret = clk_prepare(i2c->clk);
if (ret < 0) {
dev_err(&pdev->dev, "Could not prepare clock\n");
--
1.9.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 2/3] i2c: rk3x: new way to calc_divs for new version
2015-12-11 14:33 [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version David Wu
@ 2015-12-11 14:33 ` David Wu
2015-12-11 14:33 ` [PATCH v1 3/3] i2c: rk3x: support I2C Hs-mode for rk3399 David Wu
2015-12-15 0:32 ` [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version Jianqun Xu
2 siblings, 0 replies; 7+ messages in thread
From: David Wu @ 2015-12-11 14:33 UTC (permalink / raw)
To: linux-arm-kernel
From: David Wu <david.wu@rock-chips.com>
There was an issue about "repeated start" timing at the I2C controller
of old version:
- controller appears to drop SDA at .875x (7/8) programmed clk high.
- controller appears to keep SCL high for 2x programmed clk high.
The first rule isn't enough to meet tSU;STA requirements in Standard-mode
on the system. To resolve the issue, some configs for I2C timing is added,
and new rules is designed for cal_div.
Every i2c scl cycle includes 8 low FSM and 8 high FSM time.
SCL = Pclk / SCL Divisor;
SCL Divisor = 8 * (divl + 1 + divh + 1);
l: how many Pclk cycles of every low FSM(finite state machine).
h: how many Pclk cycles of every high FSM(finite state machine).
s: how many l is it taken in the tLow time, it is a value between 1~8,
determines the tHD;sda and tSU;sda time.
u: start setup timing config, determines the tSU;sta and tHD;sta time.
p: stop setup timing config, determines the tSU;sto time.
T = 1000000000 / Pclk_i2c;
l = divl + 1;
h = divh + 1;
s = data_upd_st + 1;
u = start_setup_cnt + 1;
p = stop_setup_cnt + 1;
tHigh = 8 * h * T;
tLow = 8 * l * T;
tLow = tHD;sda + tSU;sda;
tHD;sda = (l * s + 1) * T;
tSU;sda = [(8 - l) * s + 1] * T;
tI2C = 8 * (l + h) * T;
tSU;sta = (8h * u + 1) * T;
tHD;sta = [8h * (u + 1) - 1] * T;
tSU;sto = (8h * p + 1) * T;
There are two examples of div calculated by the rules, not include
hardware elements like scl_rise time, scl_fall time and sda_rise time:
1. Standard-mode:
Source Pclk: 80M, Target scl:100K, Final scl = 100K;
Tpclk = 12.5ns;
divl = divh = 0x31;
l = h = 0x32;
tHigh = tLow = 5.0us
start_setup_cnt = stop_setup_cnt = 0;
u = p = 1;
tSU;sta = (8h * u + 1) * T = 5.0125us;
tHD;sta = [8h * (u + 1) - 1] * T = 9.9875us;
tSU;sto = (8h * p + 1) * T = 5.0125us;
data_upd_st = 2;
s = data_upd_st + 1 = 3;
tHD;sda = (l * s + 1) * T = 1.8875us;
tSU;sda = [(8 - l) * s + 1] * T = 3.1125us;
2. Fast-mode:
Source Pclk: 80M, Target scl:400K, Final scl = 400K;
Tpclk = 12.5ns;
divl = 0xf;
divh = 0x8;
l = 0x10;
h = 0x9;
tHigh = 0.9us;
tLow = 1.6us;
start_setup_cnt = stop_setup_cnt = 0;
u = p = 1;
tSU;sta = (8h * u + 1) * T = 0.9125us;
tHD;sta = [8h * (u + 1) - 1] * T = 1.5875us;
tSU;sto = (8h * p + 1) * T = 0.9125us;
data_upd_st = 1;
s = data_upd_st + 1 = 2;
tHD;sda = (l * s + 1) * T = 0.4125us;
tSU;sda = [(8 - l) * s + 1] * T = 1.1875us;
The rules make the timing meet the I2C spec requirements whether
Standard-mode or Fast-mode.
Signed-off-by: David Wu <david.wu@rock-chips.com>
---
drivers/i2c/busses/i2c-rk3x.c | 201 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 200 insertions(+), 1 deletion(-)
diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c
index 0ff299f..fae5099 100644
--- a/drivers/i2c/busses/i2c-rk3x.c
+++ b/drivers/i2c/busses/i2c-rk3x.c
@@ -637,6 +637,203 @@ static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
return ret;
}
+/**
+ * Calculate divider values for desired SCL frequency
+ *
+ * @clk_rate: I2C input clock rate
+ * @scl_rate: Desired SCL rate
+ * @scl_rise_ns: How many ns it takes for SCL to rise.
+ * @scl_fall_ns: How many ns it takes for SCL to fall.
+ * @sda_fall_ns: How many ns it takes for SDA to fall.
+ * @div_low: Divider output for low
+ * @div_high: Divider output for high
+ * @con: SDA update point config used to adjust setup/hold time,
+ * start setup config for setup_start and hold_start time,
+ * stop_setup config for setup_stop time.
+ *
+ * Returns: 0 on success, -EINVAL if the goal SCL rate is too slow. In that case
+ * a best-effort divider value is returned in divs. If the target rate is
+ * too high, we silently use the highest possible rate.
+
+ * l = divl + 1;
+ * h = divh + 1;
+ * s = data_upd_st + 1;
+ * u = start_setup_cnt + 1;
+ * p = stop_setup_cnt + 1;
+ * T = Tclk_i2c;
+
+ * tHigh = 8 * h * T;
+ * tLow = 8 * l * T;
+
+ * tHD;sda = (l * s + 1) * T;
+ * tSU;sda = ((8 - l) * s + 1) * T;
+ * tI2C = 8 * (l + h) * T;
+
+ * tSU;sta = (8h * u + 1) * T;
+ * tHD;sta = [8h * (u + 1) - 1] * T;
+ * tSU;sto =(8h * p + 1) * T;
+ */
+static int rk3x_i2c_v1_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
+ unsigned long scl_rise_ns,
+ unsigned long scl_fall_ns,
+ unsigned long sda_fall_ns,
+ unsigned long *div_low,
+ unsigned long *div_high,
+ unsigned int *con)
+{
+ unsigned long spec_min_low_ns, spec_min_high_ns;
+ unsigned long spec_min_setup_start, spec_min_hold_start;
+ unsigned long spec_min_data_setup, spec_max_data_hold_ns;
+ unsigned long spec_min_stop_setup;
+
+ unsigned long min_low_ns, min_high_ns, min_total_ns;
+ unsigned long min_setup_start_ns, min_hold_start_ns;
+ unsigned long min_stop_setup_ns, max_hold_data_ns, min_setup_data_ns;
+
+ unsigned long clk_rate_khz, scl_rate_khz;
+
+ unsigned long min_low_div, min_high_div;
+
+ unsigned long min_div_for_hold, min_total_div;
+ unsigned long extra_div, extra_low_div;
+ unsigned long start_setup_cnt, stop_setup_cnt, data_upd_st;
+
+ int ret = 0;
+
+ if (WARN_ON(scl_rate > 400000))
+ scl_rate = 400000;
+
+ if (WARN_ON(scl_rate < 100000))
+ scl_rate = 100000;
+
+ if (scl_rate <= 100000) {
+ spec_min_low_ns = 4700;
+ spec_min_high_ns = 4000;
+
+ spec_min_setup_start = 4700;
+ spec_min_hold_start = 4000;
+
+ spec_max_data_hold_ns = 3450;
+ spec_min_data_setup = 250;
+ spec_min_stop_setup = 4000;
+
+ start_setup_cnt = 0;
+ stop_setup_cnt = 0;
+ } else {
+ spec_min_setup_start = 600;
+ spec_min_hold_start = 600;
+
+ spec_min_low_ns = 1300;
+ spec_min_high_ns = 600;
+
+ spec_min_data_setup = 100;
+ spec_max_data_hold_ns = 900;
+ spec_min_stop_setup = 600;
+
+ start_setup_cnt = 0;
+ stop_setup_cnt = 0;
+ }
+
+ clk_rate_khz = DIV_ROUND_UP(clk_rate, 1000);
+ scl_rate_khz = scl_rate / 1000;
+ min_total_div = DIV_ROUND_UP(clk_rate_khz, scl_rate_khz * 8);
+
+ /* tHigh = 8 * h *T; */
+ min_high_ns = scl_rise_ns + spec_min_high_ns;
+ min_high_div = DIV_ROUND_UP(clk_rate_khz * min_high_ns, 8 * 1000000);
+
+ /* tSU;sta = (u*8*h + 4)*T + T; */
+ min_setup_start_ns = scl_rise_ns + spec_min_setup_start;
+ min_high_div = max(min_high_div,
+ DIV_ROUND_UP(clk_rate_khz * min_setup_start_ns
+ - 1000000, 8 * 1000000 * (1 + start_setup_cnt)));
+
+ /* tHD;sta = (u + 1) * 8h * T - T; */
+ min_hold_start_ns = scl_rise_ns + spec_min_hold_start;
+ min_high_div = max(min_high_div,
+ DIV_ROUND_UP(clk_rate_khz * min_hold_start_ns
+ + 1000000, 8 * 1000000 * (2 + start_setup_cnt)));
+
+ /* tSU;sto = (p*8*h + 4)*T + T; */
+ min_stop_setup_ns = scl_rise_ns + spec_min_stop_setup;
+ min_high_div = max(min_high_div,
+ DIV_ROUND_UP(clk_rate_khz * min_stop_setup_ns
+ - 1000000, 8 * 1000000 * (1 + stop_setup_cnt)));
+
+ min_low_ns = scl_fall_ns + spec_min_low_ns;
+
+ /* These are the min dividers needed for min hold times. */
+ min_low_div = DIV_ROUND_UP(clk_rate_khz * min_low_ns, 8 * 1000000);
+
+ min_div_for_hold = (min_low_div + min_high_div);
+ min_total_ns = min_low_ns + min_high_ns;
+
+ /*
+ * This is the maximum divider so we don't go over the maximum.
+ * We don't round up here (we round down) since this is a maximum.
+ */
+ 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;
+ extra_low_div = DIV_ROUND_UP(min_low_div * extra_div,
+ min_div_for_hold);
+
+ *div_low = min_low_div + extra_low_div;
+ *div_high = min_high_div + (extra_div - extra_low_div);
+ }
+
+ /*
+ * tHD;sda = (l * s + 1) * T;
+ * tSU;sda = ((8 - l) * s + 1) * T;
+ */
+ for (data_upd_st = 2; data_upd_st >= 0; data_upd_st--) {
+ max_hold_data_ns = DIV_ROUND_UP(((data_upd_st + 1)
+ * (*div_low) + 1) * 1000000,
+ clk_rate_khz);
+ min_setup_data_ns = DIV_ROUND_UP(((9 - data_upd_st)
+ * (*div_low) + 1) * 1000000,
+ clk_rate_khz);
+ if ((max_hold_data_ns < spec_max_data_hold_ns) &&
+ (min_setup_data_ns > spec_min_data_setup))
+ break;
+ }
+
+ /*
+ * 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;
+
+ /* Maximum divider supported by hw is 0xffff */
+ if (*div_low > 0xffff) {
+ *div_low = 0xffff;
+ ret = -EINVAL;
+ }
+
+ if (*div_high > 0xffff) {
+ *div_high = 0xffff;
+ ret = -EINVAL;
+ }
+
+ *con = *con & 0x00ff;
+ *con |= data_upd_st << 8;
+ *con |= start_setup_cnt << 12;
+ *con |= stop_setup_cnt << 14;
+
+ return ret;
+}
+
static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate)
{
unsigned int con = 0;
@@ -1012,7 +1209,9 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, i2c);
version = (readl(i2c->regs + REG_CON) & VERSION_MASK) >> VERSION_SHIFT;
- if (version == RK3X_I2C_V0)
+ if (version == RK3X_I2C_V1)
+ i2c->ops.calc_divs = rk3x_i2c_v1_calc_divs;
+ else
i2c->ops.calc_divs = rk3x_i2c_v0_calc_divs;
ret = clk_prepare(i2c->clk);
--
1.9.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 3/3] i2c: rk3x: support I2C Hs-mode for rk3399
2015-12-11 14:33 [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version David Wu
2015-12-11 14:33 ` [PATCH v1 2/3] i2c: rk3x: new way to calc_divs " David Wu
@ 2015-12-11 14:33 ` David Wu
2015-12-15 0:32 ` [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version Jianqun Xu
2 siblings, 0 replies; 7+ messages in thread
From: David Wu @ 2015-12-11 14:33 UTC (permalink / raw)
To: linux-arm-kernel
From: David Wu <david.wu@rock-chips.com>
The i2c controller of new version1 supports highspeed mode,
1.7M and 3.4M rate. It also could be calculated divs by the rules.
The final divs would be effected a lot by hardware elements like
scl_rise_ns, scl_fall_ns and sda_rise_ns,sds_fall_ns.
Signed-off-by: David Wu <david.wu@rock-chips.com>
---
drivers/i2c/busses/i2c-rk3x.c | 56 ++++++++++++++++++++++++++++++++++++-------
1 file changed, 48 insertions(+), 8 deletions(-)
diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c
index fae5099..ec0e37f 100644
--- a/drivers/i2c/busses/i2c-rk3x.c
+++ b/drivers/i2c/busses/i2c-rk3x.c
@@ -700,8 +700,8 @@ static int rk3x_i2c_v1_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
int ret = 0;
- if (WARN_ON(scl_rate > 400000))
- scl_rate = 400000;
+ if (WARN_ON(scl_rate > 3400000))
+ scl_rate = 3400000;
if (WARN_ON(scl_rate < 100000))
scl_rate = 100000;
@@ -719,7 +719,7 @@ static int rk3x_i2c_v1_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
start_setup_cnt = 0;
stop_setup_cnt = 0;
- } else {
+ } else if (scl_rate <= 400000) {
spec_min_setup_start = 600;
spec_min_hold_start = 600;
@@ -732,6 +732,32 @@ static int rk3x_i2c_v1_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
start_setup_cnt = 0;
stop_setup_cnt = 0;
+ } else if (scl_rate <= 1700000) {
+ spec_min_low_ns = 320;
+ spec_min_high_ns = 120;
+
+ spec_min_setup_start = 160;
+ spec_min_hold_start = 160;
+
+ spec_max_data_hold_ns = 150;
+ spec_min_data_setup = 10;
+ spec_min_stop_setup = 160;
+
+ start_setup_cnt = 1;
+ stop_setup_cnt = 1;
+ } else {
+ spec_min_low_ns = 160;
+ spec_min_high_ns = 60;
+
+ spec_min_setup_start = 160;
+ spec_min_hold_start = 160;
+
+ spec_min_data_setup = 10;
+ spec_max_data_hold_ns = 70;
+ spec_min_stop_setup = 160;
+
+ start_setup_cnt = 2;
+ stop_setup_cnt = 2;
}
clk_rate_khz = DIV_ROUND_UP(clk_rate, 1000);
@@ -1126,15 +1152,29 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
&i2c->scl_rise_ns)) {
if (i2c->scl_frequency <= 100000)
i2c->scl_rise_ns = 1000;
- else
+ else if (i2c->scl_frequency <= 400000)
i2c->scl_rise_ns = 300;
+ else if (i2c->scl_frequency <= 1700000)
+ i2c->scl_rise_ns = 80;
+ else
+ i2c->scl_rise_ns = 40;
}
if (of_property_read_u32(pdev->dev.of_node, "i2c-scl-falling-time-ns",
- &i2c->scl_fall_ns))
- i2c->scl_fall_ns = 300;
+ &i2c->scl_fall_ns)) {
+ if (i2c->scl_frequency <= 400000)
+ i2c->scl_fall_ns = 300;
+ else if (i2c->scl_frequency <= 1700000)
+ i2c->scl_fall_ns = 80;
+ else
+ i2c->scl_fall_ns = 40;
+ }
if (of_property_read_u32(pdev->dev.of_node, "i2c-sda-falling-time-ns",
- &i2c->scl_fall_ns))
- i2c->sda_fall_ns = i2c->scl_fall_ns;
+ &i2c->scl_fall_ns)) {
+ if (i2c->scl_frequency <= 400000)
+ i2c->sda_fall_ns = i2c->scl_fall_ns;
+ else
+ i2c->sda_fall_ns = 2 * i2c->scl_fall_ns;
+ }
strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
--
1.9.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version
2015-12-11 14:33 [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version David Wu
2015-12-11 14:33 ` [PATCH v1 2/3] i2c: rk3x: new way to calc_divs " David Wu
2015-12-11 14:33 ` [PATCH v1 3/3] i2c: rk3x: support I2C Hs-mode for rk3399 David Wu
@ 2015-12-15 0:32 ` Jianqun Xu
2015-12-20 15:43 ` Andy Shevchenko
2 siblings, 1 reply; 7+ messages in thread
From: Jianqun Xu @ 2015-12-15 0:32 UTC (permalink / raw)
To: linux-arm-kernel
Hi David:
It's better to add a cover-letter, and add changes note for new version,
such as:
changes since v0:
- split patch to two patches or more, one patch for one new feature (Heiko)
and NOT to have two same signed-off-by
? 11/12/2015 22:33, David Wu ??:
> The calc_divs of new version is different form the old.
> The time of tHD;sda, tHD;sda, tSU;sta, tHD;sta and tSU;sto
> could be configured by RKI2C_CON register.
> So it need a new way to calc_divs for new i2c controller.
>
> Signed-off-by: David Wu <wdc@rock-chips.com>
> Signed-off-by: David Wu <david.wu@rock-chips.com>
> ---
>
> drivers/i2c/busses/i2c-rk3x.c | 54 ++++++++++++++++++++++++++++++++++---------
> 1 file changed, 43 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c
> index c1935eb..0ff299f 100644
> --- a/drivers/i2c/busses/i2c-rk3x.c
> +++ b/drivers/i2c/busses/i2c-rk3x.c
> @@ -58,6 +58,12 @@ enum {
> #define REG_CON_LASTACK BIT(5) /* 1: send NACK after last received byte */
> #define REG_CON_ACTACK BIT(6) /* 1: stop if NACK is received */
>
> +#define VERSION_MASK 0xffff0000
> +#define VERSION_SHIFT 16
> +
> +#define RK3X_I2C_V0 0x0
> +#define RK3X_I2C_V1 0x1
> +
> /* REG_MRXADDR bits */
> #define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */
>
> @@ -90,10 +96,22 @@ struct rk3x_i2c_soc_data {
> int grf_offset;
> };
>
> +struct rk3x_i2c_ops {
> + int (*calc_divs)(unsigned long,
> + unsigned long,
> + unsigned long,
> + unsigned long,
> + unsigned long,
> + unsigned long *,
> + unsigned long *,
> + unsigned int *);
> +};
> +
> struct rk3x_i2c {
> struct i2c_adapter adap;
> struct device *dev;
> struct rk3x_i2c_soc_data *soc_data;
> + struct rk3x_i2c_ops ops;
>
> /* Hardware resources */
> void __iomem *regs;
> @@ -116,6 +134,7 @@ struct rk3x_i2c {
> u8 addr;
> unsigned int mode;
> bool is_last_msg;
> + unsigned int time_con;
>
> /* I2C state machine */
> enum rk3x_i2c_state state;
> @@ -151,7 +170,8 @@ static void rk3x_i2c_start(struct rk3x_i2c *i2c)
> i2c_writel(i2c, REG_INT_START, REG_IEN);
>
> /* enable adapter with correct mode, send START condition */
> - val = REG_CON_EN | REG_CON_MOD(i2c->mode) | REG_CON_START;
> + val = i2c->time_con | REG_CON_EN | REG_CON_MOD(i2c->mode)
> + | REG_CON_START;
>
> /* if we want to react to NACK, set ACTACK bit */
> if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
> @@ -443,16 +463,19 @@ out:
> * @sda_fall_ns: How many ns it takes for SDA to fall.
> * @div_low: Divider output for low
> * @div_high: Divider output for high
> + * @con: version0 is not used
> *
> * Returns: 0 on success, -EINVAL if the goal SCL rate is too slow. In that case
> * a best-effort divider value is returned in divs. If the target rate is
> * too high, we silently use the highest possible rate.
> */
> -static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
> - unsigned long scl_rise_ns,
> - unsigned long scl_fall_ns,
> - unsigned long sda_fall_ns,
> - unsigned long *div_low, unsigned long *div_high)
> +static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
> + unsigned long scl_rise_ns,
> + unsigned long scl_fall_ns,
> + unsigned long sda_fall_ns,
> + unsigned long *div_low,
> + unsigned long *div_high,
> + unsigned int *con)
> {
> unsigned long spec_min_low_ns, spec_min_high_ns;
> unsigned long spec_setup_start, spec_max_data_hold_ns;
> @@ -616,17 +639,19 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate,
>
> static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate)
> {
> + unsigned int con = 0;
> unsigned long div_low, div_high;
> u64 t_low_ns, t_high_ns;
> int ret;
>
> - ret = rk3x_i2c_calc_divs(clk_rate, i2c->scl_frequency, i2c->scl_rise_ns,
> + ret = i2c->ops.calc_divs(clk_rate, i2c->scl_frequency, i2c->scl_rise_ns,
> i2c->scl_fall_ns, i2c->sda_fall_ns,
> - &div_low, &div_high);
> + &div_low, &div_high, &con);
> WARN_ONCE(ret != 0, "Could not reach SCL freq %u", i2c->scl_frequency);
>
> clk_enable(i2c->clk);
> i2c_writel(i2c, (div_high << 16) | (div_low & 0xffff), REG_CLKDIV);
> + i2c->time_con = con;
> clk_disable(i2c->clk);
>
> t_low_ns = div_u64(((u64)div_low + 1) * 8 * 1000000000, clk_rate);
> @@ -661,13 +686,14 @@ static int rk3x_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long
> struct clk_notifier_data *ndata = data;
> struct rk3x_i2c *i2c = container_of(nb, struct rk3x_i2c, clk_rate_nb);
> unsigned long div_low, div_high;
> + unsigned int con = 0;
>
> switch (event) {
> case PRE_RATE_CHANGE:
> - if (rk3x_i2c_calc_divs(ndata->new_rate, i2c->scl_frequency,
> + if (i2c->ops.calc_divs(ndata->new_rate, i2c->scl_frequency,
> i2c->scl_rise_ns, i2c->scl_fall_ns,
> i2c->sda_fall_ns,
> - &div_low, &div_high) != 0)
> + &div_low, &div_high, &con) != 0)
> return NOTIFY_STOP;
>
> /* scale up */
> @@ -816,7 +842,8 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
>
> /* Force a STOP condition without interrupt */
> i2c_writel(i2c, 0, REG_IEN);
> - i2c_writel(i2c, REG_CON_EN | REG_CON_STOP, REG_CON);
> + i2c_writel(i2c, i2c->time_con | REG_CON_EN |
> + REG_CON_STOP, REG_CON);
>
> i2c->state = STATE_IDLE;
>
> @@ -871,6 +898,7 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
> u32 value;
> int irq;
> unsigned long clk_rate;
> + unsigned int version;
>
> i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
> if (!i2c)
> @@ -983,6 +1011,10 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, i2c);
>
> + version = (readl(i2c->regs + REG_CON) & VERSION_MASK) >> VERSION_SHIFT;
> + if (version == RK3X_I2C_V0)
> + i2c->ops.calc_divs = rk3x_i2c_v0_calc_divs;
> +
> ret = clk_prepare(i2c->clk);
> if (ret < 0) {
> dev_err(&pdev->dev, "Could not prepare clock\n");
>
--
****************************************************************************
???
Fuzhou Rockchip Electronics Co., Ltd
????????????????89????A?21??
ADDR: No.21 bldg, Fuzhou Software Park,Fuzhou,Fujian,P.R.China
Email: xjq at rock-chips.com
****************************************************************************
????????????????????????????????????
??????????????????????????? ????????
????????????????????????????????????
????????????????????????????????????
??????????????????????
IMPORTANT NOTICE: This email is from Fuzhou Rockchip Electronics Co.,
Ltd .The contents of this email and any attachments may contain
information that is privileged, confidential and/or exempt from
disclosure under applicable law and relevant NDA. If you are not the
intended recipient, you are hereby notified that any disclosure,
copying, distribution, or use of the information is STRICTLY PROHIBITED.
Please immediately contact the sender as soon as possible and destroy
the material in its entirety in any format. Thank you.
****************************************************************************
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version
2015-12-15 0:32 ` [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version Jianqun Xu
@ 2015-12-20 15:43 ` Andy Shevchenko
2016-01-04 19:40 ` Wolfram Sang
0 siblings, 1 reply; 7+ messages in thread
From: Andy Shevchenko @ 2015-12-20 15:43 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, Dec 15, 2015 at 2:32 AM, Jianqun Xu <jay.xu@rock-chips.com> wrote:
> Hi David:
>
> It's better to add a cover-letter, and add changes note for new version,
> such as:
>
> changes since v0:
> - split patch to two patches or more, one patch for one new feature (Heiko)
>
> and NOT to have two same signed-off-by
Besides that?
>> #define REG_CON_LASTACK BIT(5) /* 1: send NACK after last received
>> byte */
>> #define REG_CON_ACTACK BIT(6) /* 1: stop if NACK is received */
>>
>> +#define VERSION_MASK 0xffff0000
GENMASK()
>> +#define VERSION_SHIFT 16
>> +
>> +#define RK3X_I2C_V0 0x0
>> +#define RK3X_I2C_V1 0x1
>> +
>> /* REG_MRXADDR bits */
>> #define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR
>> valid */
>>
>> @@ -90,10 +96,22 @@ struct rk3x_i2c_soc_data {
>> int grf_offset;
>> };
>>
>> +struct rk3x_i2c_ops {
>> + int (*calc_divs)(unsigned long,
>> + unsigned long,
>> + unsigned long,
>> + unsigned long,
>> + unsigned long,
>> + unsigned long *,
>> + unsigned long *,
>> + unsigned int *);
Too many arguments, what about struct for them (perhaps two groups)?
>> +};
>> +
>> struct rk3x_i2c {
>> struct i2c_adapter adap;
>> struct device *dev;
>> struct rk3x_i2c_soc_data *soc_data;
>> + struct rk3x_i2c_ops ops;
>>
>> /* Hardware resources */
>> void __iomem *regs;
>> @@ -116,6 +134,7 @@ struct rk3x_i2c {
>> u8 addr;
>> unsigned int mode;
>> bool is_last_msg;
>> + unsigned int time_con;
>>
>> /* I2C state machine */
>> enum rk3x_i2c_state state;
>> @@ -151,7 +170,8 @@ static void rk3x_i2c_start(struct rk3x_i2c *i2c)
>> i2c_writel(i2c, REG_INT_START, REG_IEN);
>>
>> /* enable adapter with correct mode, send START condition */
>> - val = REG_CON_EN | REG_CON_MOD(i2c->mode) | REG_CON_START;
>> + val = i2c->time_con | REG_CON_EN | REG_CON_MOD(i2c->mode)
>> + | REG_CON_START;
>>
>> /* if we want to react to NACK, set ACTACK bit */
>> if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
>> @@ -443,16 +463,19 @@ out:
>> * @sda_fall_ns: How many ns it takes for SDA to fall.
>> * @div_low: Divider output for low
>> * @div_high: Divider output for high
>> + * @con: version0 is not used
>> *
>> * Returns: 0 on success, -EINVAL if the goal SCL rate is too slow. In
>> that case
>> * a best-effort divider value is returned in divs. If the target rate
>> is
>> * too high, we silently use the highest possible rate.
>> */
>> -static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long
>> scl_rate,
>> - unsigned long scl_rise_ns,
>> - unsigned long scl_fall_ns,
>> - unsigned long sda_fall_ns,
>> - unsigned long *div_low, unsigned long
>> *div_high)
>> +static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long
>> scl_rate,
>> + unsigned long scl_rise_ns,
>> + unsigned long scl_fall_ns,
>> + unsigned long sda_fall_ns,
Wolfram did some sturct to assign the parameters from device properties.
It might be re-used here.
>> + unsigned long *div_low,
>> + unsigned long *div_high,
>> + unsigned int *con)
>> {
>> unsigned long spec_min_low_ns, spec_min_high_ns;
>> unsigned long spec_setup_start, spec_max_data_hold_ns;
>> @@ -616,17 +639,19 @@ static int rk3x_i2c_calc_divs(unsigned long
>> clk_rate, unsigned long scl_rate,
>>
>> static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long
>> clk_rate)
>> {
>> + unsigned int con = 0;
>> unsigned long div_low, div_high;
>> u64 t_low_ns, t_high_ns;
>> int ret;
>>
>> - ret = rk3x_i2c_calc_divs(clk_rate, i2c->scl_frequency,
>> i2c->scl_rise_ns,
>> + ret = i2c->ops.calc_divs(clk_rate, i2c->scl_frequency,
>> i2c->scl_rise_ns,
>> i2c->scl_fall_ns, i2c->sda_fall_ns,
>> - &div_low, &div_high);
>> + &div_low, &div_high, &con);
>> WARN_ONCE(ret != 0, "Could not reach SCL freq %u",
>> i2c->scl_frequency);
>>
>> clk_enable(i2c->clk);
>> i2c_writel(i2c, (div_high << 16) | (div_low & 0xffff),
>> REG_CLKDIV);
>> + i2c->time_con = con;
>> clk_disable(i2c->clk);
>>
>> t_low_ns = div_u64(((u64)div_low + 1) * 8 * 1000000000, clk_rate);
>> @@ -661,13 +686,14 @@ static int rk3x_i2c_clk_notifier_cb(struct
>> notifier_block *nb, unsigned long
>> struct clk_notifier_data *ndata = data;
>> struct rk3x_i2c *i2c = container_of(nb, struct rk3x_i2c,
>> clk_rate_nb);
>> unsigned long div_low, div_high;
>> + unsigned int con = 0;
>>
>> switch (event) {
>> case PRE_RATE_CHANGE:
>> - if (rk3x_i2c_calc_divs(ndata->new_rate,
>> i2c->scl_frequency,
>> + if (i2c->ops.calc_divs(ndata->new_rate,
>> i2c->scl_frequency,
>> i2c->scl_rise_ns, i2c->scl_fall_ns,
>> i2c->sda_fall_ns,
>> - &div_low, &div_high) != 0)
>> + &div_low, &div_high, &con) != 0)
>> return NOTIFY_STOP;
>>
>> /* scale up */
>> @@ -816,7 +842,8 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
>>
>> /* Force a STOP condition without interrupt */
>> i2c_writel(i2c, 0, REG_IEN);
>> - i2c_writel(i2c, REG_CON_EN | REG_CON_STOP,
>> REG_CON);
>> + i2c_writel(i2c, i2c->time_con | REG_CON_EN |
>> + REG_CON_STOP, REG_CON);
>>
>> i2c->state = STATE_IDLE;
>>
>> @@ -871,6 +898,7 @@ static int rk3x_i2c_probe(struct platform_device
>> *pdev)
>> u32 value;
>> int irq;
>> unsigned long clk_rate;
>> + unsigned int version;
>>
>> i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c),
>> GFP_KERNEL);
>> if (!i2c)
>> @@ -983,6 +1011,10 @@ static int rk3x_i2c_probe(struct platform_device
>> *pdev)
>>
>> platform_set_drvdata(pdev, i2c);
>>
>> + version = (readl(i2c->regs + REG_CON) & VERSION_MASK) >>
>> VERSION_SHIFT;
>> + if (version == RK3X_I2C_V0)
>> + i2c->ops.calc_divs = rk3x_i2c_v0_calc_divs;
>> +
>> ret = clk_prepare(i2c->clk);
>> if (ret < 0) {
>> dev_err(&pdev->dev, "Could not prepare clock\n");
>>
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version
2015-12-20 15:43 ` Andy Shevchenko
@ 2016-01-04 19:40 ` Wolfram Sang
2016-01-08 13:12 ` David.Wu
0 siblings, 1 reply; 7+ messages in thread
From: Wolfram Sang @ 2016-01-04 19:40 UTC (permalink / raw)
To: linux-arm-kernel
> >> +static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long
> >> scl_rate,
> >> + unsigned long scl_rise_ns,
> >> + unsigned long scl_fall_ns,
> >> + unsigned long sda_fall_ns,
>
> Wolfram did some sturct to assign the parameters from device properties.
> It might be re-used here.
Yes, I think it makes sense to convert the driver first to use
the new i2c_parse_fw_timings() function and see if it fits (or if we need
to extend it perhaps).
David, does this make sense to you?
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160104/dbabbb89/attachment.sig>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version
2016-01-04 19:40 ` Wolfram Sang
@ 2016-01-08 13:12 ` David.Wu
0 siblings, 0 replies; 7+ messages in thread
From: David.Wu @ 2016-01-08 13:12 UTC (permalink / raw)
To: linux-arm-kernel
Hi Wolfram, Andy
Thanks for your suggestion.
I will use i2c_parse_fw_timings() function to replace it.
? 2016/1/5 3:40, Wolfram Sang ??:
>>>> +static int rk3x_i2c_v0_calc_divs(unsigned long clk_rate, unsigned long
>>>> scl_rate,
>>>> + unsigned long scl_rise_ns,
>>>> + unsigned long scl_fall_ns,
>>>> + unsigned long sda_fall_ns,
>> Wolfram did some sturct to assign the parameters from device properties.
>> It might be re-used here.
> Yes, I think it makes sense to convert the driver first to use
> the new i2c_parse_fw_timings() function and see if it fits (or if we need
> to extend it perhaps).
>
> David, does this make sense to you?
>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2016-01-08 13:12 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-12-11 14:33 [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version David Wu
2015-12-11 14:33 ` [PATCH v1 2/3] i2c: rk3x: new way to calc_divs " David Wu
2015-12-11 14:33 ` [PATCH v1 3/3] i2c: rk3x: support I2C Hs-mode for rk3399 David Wu
2015-12-15 0:32 ` [PATCH v1 1/3] i2c: rk3x: add calc_divs ops for new version Jianqun Xu
2015-12-20 15:43 ` Andy Shevchenko
2016-01-04 19:40 ` Wolfram Sang
2016-01-08 13:12 ` David.Wu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).