From patchwork Thu Jan 14 12:31:38 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Wu X-Patchwork-Id: 8031531 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 166A19F3F6 for ; Thu, 14 Jan 2016 12:33:46 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id B4A3720451 for ; Thu, 14 Jan 2016 12:33:44 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 5F46C20454 for ; Thu, 14 Jan 2016 12:33:43 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1aJh4d-0003j6-OH; Thu, 14 Jan 2016 12:32:19 +0000 Received: from lucky1.263xmail.com ([211.157.147.133]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1aJh3w-0003Hk-Ia; Thu, 14 Jan 2016 12:31:42 +0000 Received: from david.wu?rock-chips.com (unknown [192.168.167.12]) by lucky1.263xmail.com (Postfix) with SMTP id 328604E79A; Thu, 14 Jan 2016 20:31:11 +0800 (CST) X-263anti-spam: KSV:0; X-MAIL-GRAY: 1 X-MAIL-DELIVERY: 0 X-KSVirus-check: 0 X-ABS-CHECKED: 4 X-ADDR-CHECKED: 0 Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.263.net (Postfix) with ESMTP id 0AE6B3E4; Thu, 14 Jan 2016 20:31:08 +0800 (CST) X-RL-SENDER: david.wu@rock-chips.com X-SENDER-IP: 58.22.7.114 X-LOGIN-NAME: david.wu@rock-chips.com X-UNIQUE-TAG: X-ATTACHMENT-NUM: 0 X-SENDER: wdc@rock-chips.com X-DNS-TYPE: 0 Received: from unknown (unknown [58.22.7.114]) by smtp.263.net (Postfix) whith SMTP id 11366MRFV5N; Thu, 14 Jan 2016 20:31:10 +0800 (CST) From: David Wu To: heiko@sntech.de Subject: [PATCH v3 3/4] i2c: rk3x: new method to caculate i2c clocks Date: Thu, 14 Jan 2016 20:31:38 +0800 Message-Id: <1452774699-57455-4-git-send-email-david.wu@rock-chips.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1452774699-57455-1-git-send-email-david.wu@rock-chips.com> References: <1452774699-57455-1-git-send-email-david.wu@rock-chips.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160114_043138_044448_0031914F X-CRM114-Status: GOOD ( 20.55 ) X-Spam-Score: -1.9 (-) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: huangtao@rock-chips.com, hl@rock-chips.com, wsa@the-dreams.de, linux-gpio@vger.kernel.org, dianders@chromium.org, linux-kernel@vger.kernel.org, cf@rock-chips.com, andy.shevchenko@gmail.com, xjq@rock-chips.com, David Wu , zyw@rock-chips.com, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP There was an timing issue about "repeated start" time at the I2C controller of version0, controller appears to drop SDA at .875x (7/8) programmed clk high. The rule(.875x) isn't enough to meet tSU;STA requirements on 100k's Standard-mode. To resolve this issue, data_upd_st, start_setup_cnt and stop_setup_cnt configs for I2C timing information are added, new rules are designed to calculate the timing information at new v1. There are two examples for clocks calculated by the rules, not contain 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 = 53, divh = 45; l = 54, h = 46; tLow = 5.400us, tHigh = 4.600us; start_setup_cnt = 1, stop_setup_cnt = 0; u = 2, p = 1; tSU;sta = (8h * u + 1) * T = 9.2125us; tHD;sta = [8h * (u + 1) - 1] * T = 13.7875us; tSU;sto = (8h * p + 1) * T = 4.6125; data_upd_st = 2; s = data_upd_st + 1 = 3; tHD;sda = (l * s + 1) * T = 2.038us; tSU;sda = [(8 - s) * l + 1] * T = 3.388us; 2. Fast-mode: Source Pclk: 80M, Target scl: 400K, Final scl: 400K; Tpclk = 12.5ns; divl = 16, divh = 7; l = 17, h = 8; tLow = 1.7us, tHigh = 0.8us; start_setup_cnt = stop_setup_cnt = 0; u = p = 1; tSU;sta = (8h * u + 1) * T = 0.8125us; tHD;sta = [8h * (u + 1) - 1] * T = 1.5875us; tSU;sto = (8h * p + 1) * T = 0.8125us; data_upd_st = 2; s = data_upd_st + 1 = 3; tHD;sda = (l * s + 1) * T = 650ns; tSU;sda = [(8 - l) * s + 1] * T = 1075ns; It seemed the rules make the timing meet the I2C spec requirements both Standard-mode and Fast-mode. Signed-off-by: David Wu --- changes in v3: - Add more comments - Calculate counts behind divh and divl changes in v2: - split patch to three patches(Heiko) drivers/i2c/busses/i2c-rk3x.c | 236 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c index 185e0f9..b7229a7 100644 --- a/drivers/i2c/busses/i2c-rk3x.c +++ b/drivers/i2c/busses/i2c-rk3x.c @@ -58,10 +58,15 @@ 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 REG_CON_SDA_CNT(cnt) ((cnt) << 8) +#define REG_CON_STA_CNT(cnt) ((cnt) << 12) +#define REG_CON_STO_CNT(cnt) ((cnt) << 14) + #define VERSION_MASK GENMASK(31, 16) #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 */ @@ -99,10 +104,16 @@ struct rk3x_i2c_soc_data { * struct rk3x_priv_i2c_timings - rk3x I2C timing information * @div_low: Divider output for low * @div_high: Divider output for high + * @thd_sda_count: SDA update point config used to adjust sda setup/hold time + * @tsu_sta_count: Start setup config for setup start time and hold start time + * @tsu_sto_count: Stop setup config for setup stop time */ struct rk3x_priv_i2c_timings { unsigned long div_low; unsigned long div_high; + unsigned int thd_sda_count; + unsigned int tsu_sta_count; + unsigned int tsu_sto_count; }; struct rk3x_i2c_ops { @@ -154,6 +165,13 @@ static inline u32 i2c_readl(struct rk3x_i2c *i2c, unsigned int offset) return readl(i2c->regs + offset); } +static inline u32 rk3x_i2c_get_con_count(struct rk3x_i2c *i2c) +{ + return REG_CON_SDA_CNT(i2c->t_priv.thd_sda_count) | + REG_CON_STA_CNT(i2c->t_priv.tsu_sta_count) | + REG_CON_STO_CNT(i2c->t_priv.tsu_sto_count); +} + /* Reset all interrupt pending bits */ static inline void rk3x_i2c_clean_ipd(struct rk3x_i2c *i2c) { @@ -165,13 +183,13 @@ static inline void rk3x_i2c_clean_ipd(struct rk3x_i2c *i2c) */ static void rk3x_i2c_start(struct rk3x_i2c *i2c) { - u32 val; + u32 val = rk3x_i2c_get_con_count(i2c); rk3x_i2c_clean_ipd(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 = val | 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)) @@ -212,7 +230,7 @@ static void rk3x_i2c_stop(struct rk3x_i2c *i2c, int error) * get the intended effect by resetting its internal state * and issuing an ordinary START. */ - i2c_writel(i2c, 0, REG_CON); + i2c_writel(i2c, rk3x_i2c_get_con_count(i2c), REG_CON); /* signal that we are finished with the current msg */ wake_up(&i2c->wait); @@ -630,6 +648,211 @@ static int rk3x_i2c_v0_calc_clock(unsigned long clk_rate, return ret; } +/** + * Calculate timing clock info values for desired SCL frequency + * + * @clk_rate: I2C input clock rate + * @t_input: Known I2C timing information + * @t_output: Caculated rk3x private timing information that would + * be written into regs + * 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. + * The following formulas are v1's method to calculate clock. + * + * 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 - s) * l + 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_clock(unsigned long clk_rate, + struct i2c_timings *t_input, + struct rk3x_priv_i2c_timings *t_output) +{ + unsigned long spec_min_low_ns, spec_min_high_ns; + unsigned long spec_min_setup_start_ns, spec_min_stop_setup_ns; + unsigned long spec_min_data_setup_ns, spec_max_data_hold_ns; + + unsigned long min_low_ns, min_high_ns, min_total_ns; + unsigned long min_setup_start_ns, min_setup_data_ns; + unsigned long min_stop_setup_ns, max_hold_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 data_hd_cnt; + + int ret = 0; + + /* Support standard-mode and fast-mode */ + if (WARN_ON(t_input->bus_freq_hz > 400000)) + t_input->bus_freq_hz = 400000; + + /* prevent scl_rate_khz from becoming 0 */ + if (WARN_ON(t_input->bus_freq_hz < 1000)) + t_input->bus_freq_hz = 1000; + + /* + * min_low_ns: The minimum number of ns we need to hold low to + * meet I2C specification, should include fall time. + * min_high_ns: The minimum number of ns we need to hold high to + * meet I2C specification, should include rise time. + */ + if (t_input->bus_freq_hz <= 100000) { + spec_min_low_ns = 4700; + spec_min_high_ns = 4000; + + spec_min_setup_start_ns = 4700; + spec_min_stop_setup_ns = 4000; + + spec_min_data_setup_ns = 250; + spec_max_data_hold_ns = 3450; + } else if (t_input->bus_freq_hz <= 400000) { + spec_min_low_ns = 1300; + spec_min_high_ns = 600; + + spec_min_setup_start_ns = 600; + spec_min_stop_setup_ns = 600; + + spec_min_data_setup_ns = 100; + spec_max_data_hold_ns = 900; + } + + /* caculate min-divh and min-divl */ + clk_rate_khz = DIV_ROUND_UP(clk_rate, 1000); + scl_rate_khz = t_input->bus_freq_hz / 1000; + min_total_div = DIV_ROUND_UP(clk_rate_khz, scl_rate_khz * 8); + + min_high_ns = t_input->scl_rise_ns + spec_min_high_ns; + min_high_div = DIV_ROUND_UP(clk_rate_khz * min_high_ns, 8 * 1000000); + + min_low_ns = t_input->scl_fall_ns + spec_min_low_ns; + min_low_div = DIV_ROUND_UP(clk_rate_khz * min_low_ns, 8 * 1000000); + + /* Final divh and divl must be greater than 0, otherwise the + * hardware would not output the i2c clk. + */ + if (min_high_div <= 1) + min_high_div = 2; + if (min_low_div <= 1) + min_low_div = 2; + + /* These are the min dividers needed for min hold times. */ + 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. + */ + t_output->div_low = min_low_div; + t_output->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. + * We'll try to split things up by the scale of min_low_div and + * min_high_div, biasing slightly towards having a higher div + * for low (spend more time low). + */ + extra_div = min_total_div - min_div_for_hold; + extra_low_div = DIV_ROUND_UP(min_low_div * extra_div, + min_div_for_hold); + + t_output->div_low = min_low_div + extra_low_div; + t_output->div_high = min_high_div + (extra_div - extra_low_div); + } + + /* + * calculate sda data hold count by the rules, thd_sda_count:3 + * is a appropriate value to reduce calculated times. + * tHD;sda = (l * s + 1) * T + * tSU;sda = ((8 - s) * l + 1) * T + */ + for (data_hd_cnt = 3; data_hd_cnt >= 0; data_hd_cnt--) { + max_hold_data_ns = DIV_ROUND_UP((data_hd_cnt + * (t_output->div_low) + 1) + * 1000000, clk_rate_khz); + min_setup_data_ns = DIV_ROUND_UP(((8 - data_hd_cnt) + * (t_output->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_ns)) { + t_output->thd_sda_count = data_hd_cnt; + break; + } + } + + /* + * calculate start setup count, and we aren't care tHD;STA. + * If the start setup count meets the rule of tSU;sta, it also + * meets the rule of tHD;STA. + * tSU;sta = (8h * u + 1) * T + * tHD;sta = [8h * (u + 1) - 1] * T + */ + min_setup_start_ns = t_input->scl_rise_ns + spec_min_setup_start_ns; + t_output->tsu_sta_count = DIV_ROUND_UP(clk_rate_khz * min_setup_start_ns + - 1000000, 8 * 1000000 * (t_output->div_high)); + + /* + * calculate start setup count by the rule: + * tSU;sto =(8h * p + 1) * T + */ + min_stop_setup_ns = t_input->scl_rise_ns + spec_min_stop_setup_ns; + t_output->tsu_sto_count = DIV_ROUND_UP(clk_rate_khz * min_stop_setup_ns + - 1000000, 8 * 1000000 * (t_output->div_high)); + + /* + * Adjust to the fact that the hardware has an implicit "+1". + * NOTE: Above calculations always produce div_low > 0 and div_high > 0. + */ + t_output->div_low -= 1; + t_output->div_high -= 1; + + /* Maximum divider supported by hw is 0xffff */ + if (t_output->div_low > 0xffff) { + t_output->div_low = 0xffff; + ret = -EINVAL; + } + + if (t_output->div_high > 0xffff) { + t_output->div_high = 0xffff; + ret = -EINVAL; + } + + /* + * Adjust to the fact that the hardware has an implicit "+1". + * NOTE: Above calculations always produce thd_sda_count > 0, + * tsu_sta_count > 0 and tsu_sta_count > 0. + */ + t_output->thd_sda_count -= 1; + t_output->tsu_sta_count -= 1; + t_output->tsu_sto_count -= 1; + + return ret; +} + static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate) { u64 t_low_ns, t_high_ns; @@ -829,7 +1052,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, rk3x_i2c_get_con_count(i2c) | + REG_CON_EN | REG_CON_STOP, REG_CON); i2c->state = STATE_IDLE; @@ -969,7 +1193,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_clock = rk3x_i2c_v1_calc_clock; + else i2c->ops.calc_clock = rk3x_i2c_v0_calc_clock; ret = clk_prepare(i2c->clk);