From patchwork Wed Jun 17 19:31:15 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Felipe Balbi X-Patchwork-Id: 6627821 Return-Path: X-Original-To: patchwork-linux-omap@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 BF7A69F1C1 for ; Wed, 17 Jun 2015 19:34:19 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id AA3E52083C for ; Wed, 17 Jun 2015 19:34:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 7C71020838 for ; Wed, 17 Jun 2015 19:34:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755805AbbFQTeQ (ORCPT ); Wed, 17 Jun 2015 15:34:16 -0400 Received: from devils.ext.ti.com ([198.47.26.153]:55618 "EHLO devils.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751074AbbFQTeP (ORCPT ); Wed, 17 Jun 2015 15:34:15 -0400 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by devils.ext.ti.com (8.13.7/8.13.7) with ESMTP id t5HJXhvT002671; Wed, 17 Jun 2015 14:33:43 -0500 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id t5HJXh61025414; Wed, 17 Jun 2015 14:33:43 -0500 Received: from dlep33.itg.ti.com (157.170.170.75) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.3.224.2; Wed, 17 Jun 2015 14:33:43 -0500 Received: from localhost (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep33.itg.ti.com (8.14.3/8.13.8) with ESMTP id t5HJXgrC028190; Wed, 17 Jun 2015 14:33:42 -0500 From: Felipe Balbi To: CC: Tony Lindgren , Dave Gerlach , Nishanth Menon , , Linux OMAP Mailing List , Linux ARM Kernel Mailing List , Felipe Balbi Subject: [PATCH v3] i2c: omap: improve duty cycle on SCL Date: Wed, 17 Jun 2015 14:31:15 -0500 Message-ID: <1434569475-17378-1-git-send-email-balbi@ti.com> X-Mailer: git-send-email 2.4.3 MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Spam-Status: No, score=-7.5 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, 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 With this patch we try to be as close to 50% duty cycle as possible. The reason for this is that some devices present an erratic behavior with certain duty cycles. One such example is TPS65218 PMIC which fails to change voltages when running @ 400kHz and duty cycle is lower than 34%. The idea of the patch is simple: calculate desired scl_period from requested scl and use 50% for tLow and 50% for tHigh. tLow is calculated with a DIV_ROUND_UP() to make sure it's slightly higher than tHigh and to make sure that we end up within I2C specifications. Kudos to Nishanth Menon and Dave Gerlach for helping debugging the TPS65218 problem found on AM437x SK. Signed-off-by: Felipe Balbi --- Changes since v2: - use NSEC_PER_SEC from time.h - Fix 19.2MHz (it was missing one 0) Changes since v1: - Fix for dev->speed which is in kHz, rather than Hz ps: if there are any extra comments, I'll wait a couple days before resending to avoid spamming the list. drivers/i2c/busses/i2c-omap.c | 87 ++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index 0e894193accf..0e74bd4d6c82 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -25,6 +25,8 @@ */ #include +#include +#include #include #include #include @@ -359,6 +361,8 @@ static int omap_i2c_init(struct omap_i2c_dev *dev) u16 fsscll = 0, fssclh = 0, hsscll = 0, hssclh = 0; unsigned long fclk_rate = 12000000; unsigned long internal_clk = 0; + unsigned long internal_clk_period = 0; + unsigned long scl_period = 0; struct clk *fclk; if (dev->rev >= OMAP_I2C_REV_ON_3430_3530) { @@ -395,58 +399,79 @@ static int omap_i2c_init(struct omap_i2c_dev *dev) } if (!(dev->flags & OMAP_I2C_FLAG_SIMPLE_CLOCK)) { - /* * HSI2C controller internal clk rate should be 19.2 Mhz for - * HS and for all modes on 2430. On 34xx we can use lower rate - * to get longer filter period for better noise suppression. - * The filter is iclk (fclk for HS) period. + * HS and for all modes on 2430. For all other devices and + * speeds we will use a 12MHz internal clock. */ - if (dev->speed > 400 || - dev->flags & OMAP_I2C_FLAG_FORCE_19200_INT_CLK) - internal_clk = 19200; - else if (dev->speed > 100) - internal_clk = 9600; - else - internal_clk = 4000; + if (dev->flags & OMAP_I2C_FLAG_FORCE_19200_INT_CLK || + dev->speed > 400) { + internal_clk = 19200000; + internal_clk_period = NSEC_PER_SEC / + internal_clk; /* ns */ + } else { + internal_clk = 12000000; + internal_clk_period = NSEC_PER_SEC / + internal_clk; /* ns */ + } + fclk = clk_get(dev->dev, "fck"); - fclk_rate = clk_get_rate(fclk) / 1000; + fclk_rate = clk_get_rate(fclk); clk_put(fclk); /* Compute prescaler divisor */ psc = fclk_rate / internal_clk; psc = psc - 1; + /* + * Here's the tricky part, we want to make sure our duty cycle + * is as close to 50% as possible. In order to achieve that, we + * will first figure out what's the period on chosen scl is, + * then divide that by two and calculate SCLL and SCLH based on + * that. + * + * SCLL and SCLH equations are as folows: + * + * SCLL = (tLow / iclk_period) - 7; + * SCLH = (tHigh / iclk_period) - 5; + * + * Where iclk_period is period of Internal Clock. + * + * tLow and tHigh will be basically half of scl_period where + * possible as long as we can match I2C spec's minimum limits + * for them. + */ + scl_period = NSEC_PER_SEC / (dev->speed * 1000); + /* If configured for High Speed */ if (dev->speed > 400) { - unsigned long scl; + unsigned long fs_period; + + /* + * first phase of HS mode is up to + * 400kHz so we will use that. + */ + fs_period = NSEC_PER_SEC / 400000; /* For first phase of HS mode */ - scl = internal_clk / 400; - fsscll = scl - (scl / 3) - 7; - fssclh = (scl / 3) - 5; + fsscll = DIV_ROUND_UP(fs_period >> 1, + internal_clk_period) - 7; + fssclh = (fs_period >> 1) / internal_clk_period - 5; /* For second phase of HS mode */ - scl = fclk_rate / dev->speed; - hsscll = scl - (scl / 3) - 7; - hssclh = (scl / 3) - 5; - } else if (dev->speed > 100) { - unsigned long scl; - - /* Fast mode */ - scl = internal_clk / dev->speed; - fsscll = scl - (scl / 3) - 7; - fssclh = (scl / 3) - 5; - } else { - /* Standard mode */ - fsscll = internal_clk / (dev->speed * 2) - 7; - fssclh = internal_clk / (dev->speed * 2) - 5; + hsscll = DIV_ROUND_UP(scl_period >> 1, + internal_clk_period) - 7; + hssclh = (scl_period >> 1) / internal_clk_period - 5; + } else { + fsscll = DIV_ROUND_UP(scl_period >> 1, + internal_clk_period) - 7; + fssclh = (scl_period >> 1) / internal_clk_period - 5; } scll = (hsscll << OMAP_I2C_SCLL_HSSCLL) | fsscll; sclh = (hssclh << OMAP_I2C_SCLH_HSSCLH) | fssclh; } else { /* Program desired operating rate */ - fclk_rate /= (psc + 1) * 1000; + fclk_rate /= (psc + 1); if (psc > 2) psc = 2; scll = fclk_rate / (dev->speed * 2) - 7 + psc;