Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755401AbcDKP32 (ORCPT ); Mon, 11 Apr 2016 11:29:28 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:35450 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755334AbcDKP3T (ORCPT ); Mon, 11 Apr 2016 11:29:19 -0400 From: Jan Glauber To: Wolfram Sang Cc: linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, David Daney , David Daney , Jan Glauber Subject: [PATCH v6 13/19] i2c: octeon: Add workaround for broken irqs on CN3860 Date: Mon, 11 Apr 2016 17:28:44 +0200 Message-Id: <3e9c18a66b449837ace80e1c86f0a6fd6a638a9d.1460387640.git.jglauber@cavium.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: References: In-Reply-To: References: Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 3351 Lines: 114 From: David Daney CN3860 does not interrupt the CPU when the i2c status changes. If we get a timeout, and see the status has in fact changed, we know we have this problem, and drop back to polling. Signed-off-by: David Daney Signed-off-by: Jan Glauber --- drivers/i2c/busses/i2c-octeon.c | 55 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-octeon.c b/drivers/i2c/busses/i2c-octeon.c index bad49cf..bfaee37 100644 --- a/drivers/i2c/busses/i2c-octeon.c +++ b/drivers/i2c/busses/i2c-octeon.c @@ -119,6 +119,8 @@ struct octeon_i2c { void __iomem *twsi_base; struct device *dev; bool hlc_enabled; + bool broken_irq_mode; + bool broken_irq_check; void (*int_en)(struct octeon_i2c *); void (*int_dis)(struct octeon_i2c *); void (*hlc_int_en)(struct octeon_i2c *); @@ -378,10 +380,33 @@ static int octeon_i2c_wait(struct octeon_i2c *i2c) long time_left; int first = 1; + if (i2c->broken_irq_mode) { + /* + * Some chip revisions seem to not assert the irq in + * the interrupt controller. So we must poll for the + * IFLG change. + */ + u64 end = get_jiffies_64() + i2c->adap.timeout; + + while (!octeon_i2c_test_iflg(i2c) && + time_before64(get_jiffies_64(), end)) + udelay(50); + + return octeon_i2c_test_iflg(i2c) ? 0 : -ETIMEDOUT; + } + i2c->int_en(i2c); time_left = wait_event_timeout(i2c->queue, poll_iflg(i2c, &first), i2c->adap.timeout); i2c->int_dis(i2c); + + if (time_left <= 0 && i2c->broken_irq_check && + octeon_i2c_test_iflg(i2c)) { + dev_err(i2c->dev, + "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = true; + return 0; + } if (!time_left) { dev_dbg(i2c->dev, "%s: timeout\n", __func__); return -ETIMEDOUT; @@ -477,17 +502,40 @@ static int octeon_i2c_hlc_wait(struct octeon_i2c *i2c) { int time_left; + if (i2c->broken_irq_mode) { + /* + * Some cn38xx boards did not assert the irq in + * the interrupt controller. So we must poll for the + * IFLG change. + */ + u64 end = get_jiffies_64() + i2c->adap.timeout; + + while (!octeon_i2c_hlc_test_ready(i2c) && + time_before64(get_jiffies_64(), end)) + udelay(50); + + return octeon_i2c_hlc_test_ready(i2c) ? 0 : -ETIMEDOUT; + } + i2c->hlc_int_en(i2c); time_left = wait_event_interruptible_timeout(i2c->queue, octeon_i2c_hlc_test_ready(i2c), i2c->adap.timeout); i2c->hlc_int_dis(i2c); - if (!time_left) { + if (!time_left) octeon_i2c_hlc_int_clear(i2c); + + if (time_left <= 0 && i2c->broken_irq_check && + octeon_i2c_hlc_test_ready(i2c)) { + dev_err(i2c->dev, "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = true; + return 0; + } + + if (!time_left) { dev_dbg(i2c->dev, "%s: timeout\n", __func__); return -ETIMEDOUT; } - if (time_left < 0) { dev_dbg(i2c->dev, "%s: wait interrupted\n", __func__); return time_left; @@ -1143,6 +1191,9 @@ static int octeon_i2c_probe(struct platform_device *pdev) goto out; } + if (OCTEON_IS_MODEL(OCTEON_CN38XX)) + i2c->broken_irq_check = true; + result = octeon_i2c_init_lowlevel(i2c); if (result) { dev_err(i2c->dev, "init low level failed\n"); -- 1.9.1