[PATCH RFC v3 11/14] i2c: nomadik: save I2C_CR register before resetting HW in IRQ handler
From: Dmitry Guzman
Date: Thu Nov 05 2026 - 12:24:11 EST
On some interrupts, such as Bus Error interrupt or Arbitration Lost
interrupt, the interrupt handler calls init_hw() to reset the I2C
controller. This clears I2C_SR register value that might be used for
fault reporting.
Add i2c_sr field to nmk_i2c_dev structure, store there I2C_SR register
value before resetting the hardware, and use this value instead of
reading it directly in nmk_i2c_xfer_one() if it was read in IRQ handler.
Signed-off-by: Dmitry Guzman <Dmitry.Guzman@xxxxxxxxxxxx>
---
drivers/i2c/busses/i2c-nomadik.c | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c
index b9f839d5a6fa..f68d4cf7c0e7 100644
--- a/drivers/i2c/busses/i2c-nomadik.c
+++ b/drivers/i2c/busses/i2c-nomadik.c
@@ -193,6 +193,7 @@ struct i2c_nmk_client {
* @xfer_done: xfer done boolean.
* @has_32b_bus: controller is on a bus that only supports 32-bit accesses.
* @timeout_usecs: Slave response timeout
+ * @i2c_sr: last read I2C_SR register value
* @sm: speed mode
* @stop: stop condition.
* @xfer_wq: xfer done wait queue.
@@ -212,6 +213,7 @@ struct nmk_i2c_dev {
bool xfer_done;
bool has_32b_bus;
u32 timeout_usecs;
+ u32 i2c_sr;
enum i2c_freq_mode sm;
int stop;
struct wait_queue_head xfer_wq;
@@ -652,6 +654,7 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *priv, u16 flags)
{
int status;
+ priv->i2c_sr = 0;
if (flags & I2C_M_RD) {
/* read operation */
priv->cli.operation = I2C_READ;
@@ -663,12 +666,22 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *priv, u16 flags)
}
if (status || priv->result) {
- u32 i2c_sr;
u32 cause;
+ /*
+ * On some errors, the interrupt handler reads I2C_SR register value,
+ * stores it into priv->i2c_sr and resets the hardware with init_hw().
+ * If it is done, we use this value. The value cannot be 0 because
+ * I2C_SR_STATUS after transfer start may be I2C_ON_GOING, I2C_OK or
+ * I2C_ABORT but not I2C_NOP.
+ * If priv->i2c_sr == 0, it was not updated since transmission start, so
+ * init_hw() was not called and the hardware register I2C_SR still holds
+ * correct value.
+ */
+ if (!priv->i2c_sr)
+ priv->i2c_sr = readl(priv->virtbase + I2C_SR);
- i2c_sr = readl(priv->virtbase + I2C_SR);
- if (FIELD_GET(I2C_SR_STATUS, i2c_sr) == I2C_ABORT) {
- cause = FIELD_GET(I2C_SR_CAUSE, i2c_sr);
+ if (FIELD_GET(I2C_SR_STATUS, priv->i2c_sr) == I2C_ABORT) {
+ cause = FIELD_GET(I2C_SR_CAUSE, priv->i2c_sr);
dev_dbg(&priv->adev->dev, "%s\n",
cause >= ARRAY_SIZE(abort_causes) ?
"unknown reason" :
@@ -879,6 +892,7 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg)
priv->result = -EIO;
dev_err(dev, "%lu bytes still remain to be xfered\n",
priv->cli.count);
+ priv->i2c_sr = readl(priv->virtbase + I2C_SR);
init_hw(priv);
}
priv->xfer_done = true;
@@ -890,6 +904,7 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg)
/* Master Arbitration lost interrupt */
case I2C_IT_MAL:
priv->result = -EAGAIN;
+ priv->i2c_sr = readl(priv->virtbase + I2C_SR);
init_hw(priv);
i2c_set_bit(priv->virtbase + I2C_ICR, I2C_IT_MAL);
@@ -906,11 +921,9 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg)
*/
case I2C_IT_BERR:
{
- u32 sr;
-
- sr = readl(priv->virtbase + I2C_SR);
+ priv->i2c_sr = readl(priv->virtbase + I2C_SR);
priv->result = -EIO;
- if (FIELD_GET(I2C_SR_STATUS, sr) == I2C_ABORT)
+ if (FIELD_GET(I2C_SR_STATUS, priv->i2c_sr) == I2C_ABORT)
init_hw(priv);
i2c_set_bit(priv->virtbase + I2C_ICR, I2C_IT_BERR);
@@ -927,6 +940,7 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg)
*/
case I2C_IT_TXFOVR:
priv->result = -EIO;
+ priv->i2c_sr = readl(priv->virtbase + I2C_SR);
init_hw(priv);
dev_err(dev, "Tx Fifo Over run\n");
--
2.43.0