Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932768AbcDYOjf (ORCPT ); Mon, 25 Apr 2016 10:39:35 -0400 Received: from mail-wm0-f65.google.com ([74.125.82.65]:35714 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754780AbcDYOd6 (ORCPT ); Mon, 25 Apr 2016 10:33:58 -0400 From: Jan Glauber To: Wolfram Sang Cc: linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, David Daney , Jan Glauber Subject: [PATCH v7 02/15] i2c: octeon: Use i2c recovery framework Date: Mon, 25 Apr 2016 16:33:31 +0200 Message-Id: <1461594824-7215-3-git-send-email-jglauber@cavium.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1461594824-7215-1-git-send-email-jglauber@cavium.com> References: <1461594824-7215-1-git-send-email-jglauber@cavium.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 7055 Lines: 254 Switch to the i2c bus recovery framework using generic SCL recovery. If this fails try to reset the hardware. The recovery is triggered during START on timeout of the interrupt or failure to reach the START / repeated-START condition. The START function is moved to xfer and while at it remove the xfer debug message (i2c core already provides a debug message for this). Signed-off-by: Jan Glauber --- drivers/i2c/busses/i2c-octeon.c | 160 ++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 63 deletions(-) diff --git a/drivers/i2c/busses/i2c-octeon.c b/drivers/i2c/busses/i2c-octeon.c index 471d06e..0f536a1 100644 --- a/drivers/i2c/busses/i2c-octeon.c +++ b/drivers/i2c/busses/i2c-octeon.c @@ -90,6 +90,8 @@ #define TWSI_INT_CORE_EN BIT_ULL(6) #define TWSI_INT_SDA_OVR BIT_ULL(8) #define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) struct octeon_i2c { wait_queue_head_t queue; @@ -152,6 +154,18 @@ static u8 octeon_i2c_reg_read(struct octeon_i2c *i2c, u64 eop_reg) #define octeon_i2c_stat_read(i2c) \ octeon_i2c_reg_read(i2c, SW_TWSI_EOP_TWSI_STAT) + +/** + * octeon_i2c_read_int - read the TWSI_INT register + * @i2c: The struct octeon_i2c + * + * Returns the value of the register. + */ +static u64 octeon_i2c_read_int(struct octeon_i2c *i2c) +{ + return __raw_readq(i2c->twsi_base + TWSI_INT); +} + /** * octeon_i2c_write_int - write the TWSI_INT register * @i2c: The struct octeon_i2c @@ -182,33 +196,6 @@ static void octeon_i2c_int_disable(struct octeon_i2c *i2c) octeon_i2c_write_int(i2c, 0); } -/** - * octeon_i2c_unblock - unblock the bus - * @i2c: The struct octeon_i2c - * - * If there was a reset while a device was driving 0 to bus, bus is blocked. - * We toggle it free manually by some clock cycles and send a stop. - */ -static void octeon_i2c_unblock(struct octeon_i2c *i2c) -{ - int i; - - dev_dbg(i2c->dev, "%s\n", __func__); - - for (i = 0; i < 9; i++) { - octeon_i2c_write_int(i2c, 0); - udelay(5); - octeon_i2c_write_int(i2c, TWSI_INT_SCL_OVR); - udelay(5); - } - /* hand-crank a STOP */ - octeon_i2c_write_int(i2c, TWSI_INT_SDA_OVR | TWSI_INT_SCL_OVR); - udelay(5); - octeon_i2c_write_int(i2c, TWSI_INT_SDA_OVR); - udelay(5); - octeon_i2c_write_int(i2c, 0); -} - /* interrupt service routine */ static irqreturn_t octeon_i2c_isr(int irq, void *dev_id) { @@ -371,6 +358,17 @@ static int octeon_i2c_init_lowlevel(struct octeon_i2c *i2c) return -EIO; } +static int octeon_i2c_recovery(struct octeon_i2c *i2c) +{ + int ret; + + ret = i2c_recover_bus(&i2c->adap); + if (ret) + /* recover failed, try hardware re-init */ + ret = octeon_i2c_init_lowlevel(i2c); + return ret; +} + /** * octeon_i2c_start - send START to the bus * @i2c: The struct octeon_i2c @@ -379,34 +377,23 @@ static int octeon_i2c_init_lowlevel(struct octeon_i2c *i2c) */ static int octeon_i2c_start(struct octeon_i2c *i2c) { - int result; - u8 data; + int ret; + u8 stat; octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_STA); + ret = octeon_i2c_wait(i2c); + if (ret) + goto error; - result = octeon_i2c_wait(i2c); - if (result) { - if (octeon_i2c_stat_read(i2c) == STAT_IDLE) { - /* - * Controller refused to send start flag May - * be a client is holding SDA low - let's try - * to free it. - */ - octeon_i2c_unblock(i2c); - octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_STA); - result = octeon_i2c_wait(i2c); - } - if (result) - return result; - } - - data = octeon_i2c_stat_read(i2c); - if ((data != STAT_START) && (data != STAT_REP_START)) { - dev_err(i2c->dev, "%s: bad status (0x%x)\n", __func__, data); - return -EIO; - } + stat = octeon_i2c_stat_read(i2c); + if (stat == STAT_START || stat == STAT_REP_START) + /* START successful, bail out */ + return 0; - return 0; +error: + /* START failed, try to recover */ + ret = octeon_i2c_recovery(i2c); + return (ret) ? ret : -EAGAIN; } /* send STOP to the bus */ @@ -431,10 +418,6 @@ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, { int i, result; - result = octeon_i2c_start(i2c); - if (result) - return result; - octeon_i2c_data_write(i2c, target << 1); octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); @@ -479,10 +462,6 @@ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, if (length < 1) return -EINVAL; - result = octeon_i2c_start(i2c); - if (result) - return result; - octeon_i2c_data_write(i2c, (target << 1) | 1); octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); @@ -546,10 +525,10 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, for (i = 0; ret == 0 && i < num; i++) { struct i2c_msg *pmsg = &msgs[i]; - dev_dbg(i2c->dev, - "Doing %s %d byte(s) to/from 0x%02x - %d of %d messages\n", - pmsg->flags & I2C_M_RD ? "read" : "write", - pmsg->len, pmsg->addr, i + 1, num); + ret = octeon_i2c_start(i2c); + if (ret) + return ret; + if (pmsg->flags & I2C_M_RD) ret = octeon_i2c_read(i2c, pmsg->addr, pmsg->buf, &pmsg->len, pmsg->flags & I2C_M_RECV_LEN); @@ -562,6 +541,60 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, return (ret != 0) ? ret : num; } +static int octeon_i2c_get_scl(struct i2c_adapter *adap) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + u64 state; + + state = octeon_i2c_read_int(i2c); + return state & TWSI_INT_SCL; +} + +static void octeon_i2c_set_scl(struct i2c_adapter *adap, int val) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + + octeon_i2c_write_int(i2c, TWSI_INT_SCL_OVR); +} + +static int octeon_i2c_get_sda(struct i2c_adapter *adap) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + u64 state; + + state = octeon_i2c_read_int(i2c); + return state & TWSI_INT_SDA; +} + +static void octeon_i2c_prepare_recovery(struct i2c_adapter *adap) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + + /* + * The stop resets the state machine, does not _transmit_ STOP unless + * engine was active. + */ + octeon_i2c_stop(i2c); + + octeon_i2c_write_int(i2c, 0); +} + +static void octeon_i2c_unprepare_recovery(struct i2c_adapter *adap) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + + octeon_i2c_write_int(i2c, 0); +} + +static struct i2c_bus_recovery_info octeon_i2c_recovery_info = { + .recover_bus = i2c_generic_scl_recovery, + .get_scl = octeon_i2c_get_scl, + .set_scl = octeon_i2c_set_scl, + .get_sda = octeon_i2c_get_sda, + .prepare_recovery = octeon_i2c_prepare_recovery, + .unprepare_recovery = octeon_i2c_unprepare_recovery, +}; + static u32 octeon_i2c_functionality(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | @@ -642,6 +675,7 @@ static int octeon_i2c_probe(struct platform_device *pdev) i2c->adap = octeon_i2c_ops; i2c->adap.timeout = msecs_to_jiffies(2); i2c->adap.retries = 5; + i2c->adap.bus_recovery_info = &octeon_i2c_recovery_info; i2c->adap.dev.parent = &pdev->dev; i2c->adap.dev.of_node = node; i2c_set_adapdata(&i2c->adap, i2c); -- 1.9.1