From bc00512438e8df139477908b79663d81df594261 Mon Sep 17 00:00:00 2001 From: Siva Durga Prasad Paladugu Date: Thu, 7 Mar 2019 11:52:49 +0100 Subject: i2c: i2c_cdns: Add support for handling arbitration lost This patch adds support for handling arbitration lost in case of multi master mode. When an arbitration lost is detected, it retries for 10 times before failing. Signed-off-by: Siva Durga Prasad Paladugu Signed-off-by: Michal Simek Reviewed-by: Heiko Schocher --- drivers/i2c/i2c-cdns.c | 66 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) (limited to 'drivers/i2c/i2c-cdns.c') diff --git a/drivers/i2c/i2c-cdns.c b/drivers/i2c/i2c-cdns.c index b391c0b..1af94d1 100644 --- a/drivers/i2c/i2c-cdns.c +++ b/drivers/i2c/i2c-cdns.c @@ -80,6 +80,8 @@ struct cdns_i2c_regs { #define CDNS_I2C_BROKEN_HOLD_BIT BIT(0) +#define CDNS_I2C_ARB_LOST_MAX_RETRIES 10 + #ifdef DEBUG static void cdns_i2c_debug_status(struct cdns_i2c_regs *cdns_i2c) { @@ -234,11 +236,17 @@ static int cdns_i2c_set_bus_speed(struct udevice *dev, unsigned int speed) return 0; } +static inline u32 is_arbitration_lost(struct cdns_i2c_regs *regs) +{ + return (readl(®s->interrupt_status) & CDNS_I2C_INTERRUPT_ARBLOST); +} + static int cdns_i2c_write_data(struct i2c_cdns_bus *i2c_bus, u32 addr, u8 *data, u32 len) { u8 *cur_data = data; struct cdns_i2c_regs *regs = i2c_bus->regs; + u32 ret; /* Set the controller in Master transmit mode and clear FIFO */ setbits_le32(®s->control, CDNS_I2C_CONTROL_CLR_FIFO); @@ -255,25 +263,38 @@ static int cdns_i2c_write_data(struct i2c_cdns_bus *i2c_bus, u32 addr, u8 *data, writel(addr, ®s->address); - while (len--) { + while (len-- && !is_arbitration_lost(regs)) { writel(*(cur_data++), ®s->data); if (readl(®s->transfer_size) == CDNS_I2C_FIFO_DEPTH) { - if (!cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP)) { - /* Release the bus */ - clrbits_le32(®s->control, - CDNS_I2C_CONTROL_HOLD); - return -ETIMEDOUT; - } + ret = cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP | + CDNS_I2C_INTERRUPT_ARBLOST); + if (ret & CDNS_I2C_INTERRUPT_ARBLOST) + return -EAGAIN; + if (ret & CDNS_I2C_INTERRUPT_COMP) + continue; + /* Release the bus */ + clrbits_le32(®s->control, + CDNS_I2C_CONTROL_HOLD); + return -ETIMEDOUT; } } + if (len && is_arbitration_lost(regs)) + return -EAGAIN; + /* All done... release the bus */ if (!i2c_bus->hold_flag) clrbits_le32(®s->control, CDNS_I2C_CONTROL_HOLD); /* Wait for the address and data to be sent */ - if (!cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP)) + ret = cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP | + CDNS_I2C_INTERRUPT_ARBLOST); + if (!(ret & (CDNS_I2C_INTERRUPT_ARBLOST | + CDNS_I2C_INTERRUPT_COMP))) return -ETIMEDOUT; + if (ret & CDNS_I2C_INTERRUPT_ARBLOST) + return -EAGAIN; + return 0; } @@ -289,6 +310,7 @@ static int cdns_i2c_read_data(struct i2c_cdns_bus *i2c_bus, u32 addr, u8 *data, struct cdns_i2c_regs *regs = i2c_bus->regs; int curr_recv_count; int updatetx, hold_quirk; + u32 ret; /* Check the hardware can handle the requested bytes */ if ((recv_count < 0)) @@ -317,7 +339,7 @@ static int cdns_i2c_read_data(struct i2c_cdns_bus *i2c_bus, u32 addr, u8 *data, hold_quirk = (i2c_bus->quirks & CDNS_I2C_BROKEN_HOLD_BIT) && updatetx; - while (recv_count) { + while (recv_count && !is_arbitration_lost(regs)) { while (readl(®s->status) & CDNS_I2C_STATUS_RXDV) { if (recv_count < CDNS_I2C_FIFO_DEPTH && !i2c_bus->hold_flag) { @@ -366,8 +388,13 @@ static int cdns_i2c_read_data(struct i2c_cdns_bus *i2c_bus, u32 addr, u8 *data, } /* Wait for the address and data to be sent */ - if (!cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP)) + ret = cdns_i2c_wait(regs, CDNS_I2C_INTERRUPT_COMP | + CDNS_I2C_INTERRUPT_ARBLOST); + if (!(ret & (CDNS_I2C_INTERRUPT_ARBLOST | + CDNS_I2C_INTERRUPT_COMP))) return -ETIMEDOUT; + if (ret & CDNS_I2C_INTERRUPT_ARBLOST) + return -EAGAIN; return 0; } @@ -376,8 +403,11 @@ static int cdns_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs) { struct i2c_cdns_bus *i2c_bus = dev_get_priv(dev); - int ret, count; + int ret = 0; + int count; bool hold_quirk; + struct i2c_msg *message = msg; + int num_msgs = nmsgs; hold_quirk = !!(i2c_bus->quirks & CDNS_I2C_BROKEN_HOLD_BIT); @@ -403,7 +433,8 @@ static int cdns_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, } debug("i2c_xfer: %d messages\n", nmsgs); - for (; nmsgs > 0; nmsgs--, msg++) { + for (u8 retry = 0; retry < CDNS_I2C_ARB_LOST_MAX_RETRIES && + nmsgs > 0; nmsgs--, msg++) { debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len); if (msg->flags & I2C_M_RD) { ret = cdns_i2c_read_data(i2c_bus, msg->addr, msg->buf, @@ -412,13 +443,22 @@ static int cdns_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, ret = cdns_i2c_write_data(i2c_bus, msg->addr, msg->buf, msg->len); } + if (ret == -EAGAIN) { + msg = message; + nmsgs = num_msgs; + retry++; + printf("%s,arbitration lost, retrying:%d\n", __func__, + retry); + continue; + } + if (ret) { debug("i2c_write: error sending\n"); return -EREMOTEIO; } } - return 0; + return ret; } static int cdns_i2c_ofdata_to_platdata(struct udevice *dev) -- cgit v1.1