Skip to content

Commit

Permalink
i2c: npcm: Handle spurious interrupts
Browse files Browse the repository at this point in the history
[ Upstream commit e5222d4 ]

On some platforms in rare cases (1 to 100,000 transactions),
the i2c gets a spurious interrupt which means that we enter an interrupt
but in the interrupt handler we don't find any status bit that points to
the reason we got this interrupt.

This may be a case of a rare HW issue or signal integrity issue that is
still under investigation.

In order to overcome this we are doing the following:
1. Disable incoming interrupts in master mode only when slave mode is not
   enabled.
2. Clear end of busy (EOB) after every interrupt.
3. Clear other status bits (just in case since we found them cleared)
4. Return correct status during the interrupt that will finish the
   transaction.

On next xmit transaction if the bus is still busy the master will issue a
recovery process before issuing the new transaction.

Fixes: 56a1485 ("i2c: npcm7xx: Add Nuvoton NPCM I2C controller driver")
Signed-off-by: Tali Perry <tali.perry1@gmail.com>
Signed-off-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
TaliPerry authored and gregkh committed Jun 9, 2022
1 parent 3554851 commit 8987478
Showing 1 changed file with 62 additions and 29 deletions.
91 changes: 62 additions & 29 deletions drivers/i2c/busses/i2c-npcm7xx.c
Expand Up @@ -563,6 +563,15 @@ static inline void npcm_i2c_nack(struct npcm_i2c *bus)
iowrite8(val, bus->reg + NPCM_I2CCTL1);
}

static inline void npcm_i2c_clear_master_status(struct npcm_i2c *bus)
{
u8 val;

/* Clear NEGACK, STASTR and BER bits */
val = NPCM_I2CST_BER | NPCM_I2CST_NEGACK | NPCM_I2CST_STASTR;
iowrite8(val, bus->reg + NPCM_I2CST);
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
static void npcm_i2c_slave_int_enable(struct npcm_i2c *bus, bool enable)
{
Expand Down Expand Up @@ -642,8 +651,8 @@ static void npcm_i2c_reset(struct npcm_i2c *bus)
iowrite8(NPCM_I2CCST_BB, bus->reg + NPCM_I2CCST);
iowrite8(0xFF, bus->reg + NPCM_I2CST);

/* Clear EOB bit */
iowrite8(NPCM_I2CCST3_EO_BUSY, bus->reg + NPCM_I2CCST3);
/* Clear and disable EOB */
npcm_i2c_eob_int(bus, false);

/* Clear all fifo bits: */
iowrite8(NPCM_I2CFIF_CTS_CLR_FIFO, bus->reg + NPCM_I2CFIF_CTS);
Expand All @@ -655,6 +664,9 @@ static void npcm_i2c_reset(struct npcm_i2c *bus)
}
#endif

/* clear status bits for spurious interrupts */
npcm_i2c_clear_master_status(bus);

bus->state = I2C_IDLE;
}

Expand Down Expand Up @@ -815,15 +827,6 @@ static void npcm_i2c_read_fifo(struct npcm_i2c *bus, u8 bytes_in_fifo)
}
}

static inline void npcm_i2c_clear_master_status(struct npcm_i2c *bus)
{
u8 val;

/* Clear NEGACK, STASTR and BER bits */
val = NPCM_I2CST_BER | NPCM_I2CST_NEGACK | NPCM_I2CST_STASTR;
iowrite8(val, bus->reg + NPCM_I2CST);
}

static void npcm_i2c_master_abort(struct npcm_i2c *bus)
{
/* Only current master is allowed to issue a stop condition */
Expand Down Expand Up @@ -1231,7 +1234,16 @@ static irqreturn_t npcm_i2c_int_slave_handler(struct npcm_i2c *bus)
ret = IRQ_HANDLED;
} /* SDAST */

return ret;
/*
* if irq is not one of the above, make sure EOB is disabled and all
* status bits are cleared.
*/
if (ret == IRQ_NONE) {
npcm_i2c_eob_int(bus, false);
npcm_i2c_clear_master_status(bus);
}

return IRQ_HANDLED;
}

static int npcm_i2c_reg_slave(struct i2c_client *client)
Expand Down Expand Up @@ -1467,13 +1479,18 @@ static void npcm_i2c_irq_handle_nack(struct npcm_i2c *bus)
npcm_i2c_eob_int(bus, false);
npcm_i2c_master_stop(bus);

/* Clear SDA Status bit (by reading dummy byte) */
npcm_i2c_rd_byte(bus);

/*
* The bus is released from stall only after the SW clears
* NEGACK bit. Then a Stop condition is sent.
*/
npcm_i2c_clear_master_status(bus);
readx_poll_timeout_atomic(ioread8, bus->reg + NPCM_I2CCST, val,
!(val & NPCM_I2CCST_BUSY), 10, 200);
/* verify no status bits are still set after bus is released */
npcm_i2c_clear_master_status(bus);
}
bus->state = I2C_IDLE;

Expand Down Expand Up @@ -1672,10 +1689,10 @@ static int npcm_i2c_recovery_tgclk(struct i2c_adapter *_adap)
int iter = 27;

if ((npcm_i2c_get_SDA(_adap) == 1) && (npcm_i2c_get_SCL(_adap) == 1)) {
dev_dbg(bus->dev, "bus%d recovery skipped, bus not stuck",
bus->num);
dev_dbg(bus->dev, "bus%d-0x%x recovery skipped, bus not stuck",
bus->num, bus->dest_addr);
npcm_i2c_reset(bus);
return status;
return 0;
}

npcm_i2c_int_enable(bus, false);
Expand Down Expand Up @@ -1909,6 +1926,7 @@ static int npcm_i2c_init_module(struct npcm_i2c *bus, enum i2c_mode mode,
bus_freq_hz < I2C_FREQ_MIN_HZ || bus_freq_hz > I2C_FREQ_MAX_HZ)
return -EINVAL;

npcm_i2c_int_enable(bus, false);
npcm_i2c_disable(bus);

/* Configure FIFO mode : */
Expand Down Expand Up @@ -1937,10 +1955,17 @@ static int npcm_i2c_init_module(struct npcm_i2c *bus, enum i2c_mode mode,
val = (val | NPCM_I2CCTL1_NMINTE) & ~NPCM_I2CCTL1_RWS;
iowrite8(val, bus->reg + NPCM_I2CCTL1);

npcm_i2c_int_enable(bus, true);

npcm_i2c_reset(bus);

/* check HW is OK: SDA and SCL should be high at this point. */
if ((npcm_i2c_get_SDA(&bus->adap) == 0) || (npcm_i2c_get_SCL(&bus->adap) == 0)) {
dev_err(bus->dev, "I2C%d init fail: lines are low\n", bus->num);
dev_err(bus->dev, "SDA=%d SCL=%d\n", npcm_i2c_get_SDA(&bus->adap),
npcm_i2c_get_SCL(&bus->adap));
return -ENXIO;
}

npcm_i2c_int_enable(bus, true);
return 0;
}

Expand Down Expand Up @@ -1988,10 +2013,14 @@ static irqreturn_t npcm_i2c_bus_irq(int irq, void *dev_id)
#if IS_ENABLED(CONFIG_I2C_SLAVE)
if (bus->slave) {
bus->master_or_slave = I2C_SLAVE;
return npcm_i2c_int_slave_handler(bus);
if (npcm_i2c_int_slave_handler(bus))
return IRQ_HANDLED;
}
#endif
return IRQ_NONE;
/* clear status bits for spurious interrupts */
npcm_i2c_clear_master_status(bus);

return IRQ_HANDLED;
}

static bool npcm_i2c_master_start_xmit(struct npcm_i2c *bus,
Expand Down Expand Up @@ -2048,7 +2077,6 @@ static int npcm_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
u8 *write_data, *read_data;
u8 slave_addr;
unsigned long timeout;
int ret = 0;
bool read_block = false;
bool read_PEC = false;
u8 bus_busy;
Expand Down Expand Up @@ -2138,12 +2166,12 @@ static int npcm_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
bus->read_block_use = read_block;

reinit_completion(&bus->cmd_complete);
if (!npcm_i2c_master_start_xmit(bus, slave_addr, nwrite, nread,
write_data, read_data, read_PEC,
read_block))
ret = -EBUSY;

if (ret != -EBUSY) {
npcm_i2c_int_enable(bus, true);

if (npcm_i2c_master_start_xmit(bus, slave_addr, nwrite, nread,
write_data, read_data, read_PEC,
read_block)) {
time_left = wait_for_completion_timeout(&bus->cmd_complete,
timeout);

Expand All @@ -2157,26 +2185,31 @@ static int npcm_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
}
}
}
ret = bus->cmd_err;

/* if there was BER, check if need to recover the bus: */
if (bus->cmd_err == -EAGAIN)
ret = i2c_recover_bus(adap);
bus->cmd_err = i2c_recover_bus(adap);

/*
* After any type of error, check if LAST bit is still set,
* due to a HW issue.
* It cannot be cleared without resetting the module.
*/
if (bus->cmd_err &&
(NPCM_I2CRXF_CTL_LAST_PEC & ioread8(bus->reg + NPCM_I2CRXF_CTL)))
else if (bus->cmd_err &&
(NPCM_I2CRXF_CTL_LAST_PEC & ioread8(bus->reg + NPCM_I2CRXF_CTL)))
npcm_i2c_reset(bus);

/* after any xfer, successful or not, stall and EOB must be disabled */
npcm_i2c_stall_after_start(bus, false);
npcm_i2c_eob_int(bus, false);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
/* reenable slave if it was enabled */
if (bus->slave)
iowrite8((bus->slave->addr & 0x7F) | NPCM_I2CADDR_SAEN,
bus->reg + NPCM_I2CADDR1);
#else
npcm_i2c_int_enable(bus, false);
#endif
return bus->cmd_err;
}
Expand Down

0 comments on commit 8987478

Please sign in to comment.