[PATCH] i2c: iop3xx: fix adapter and IRQ teardown

From: Myeonghun Pak

Date: Tue Jul 28 2026 - 14:00:15 EST


The probe path enables the controller and requests its IRQ before
registering the I2C adapter, but ignores an adapter registration
failure. The remove path also frees the IRQ data and unmaps its
registers without first unregistering the adapter or IRQ handler.

Check the adapter registration result and unwind the IRQ on failure.
On removal, unregister the adapter first, then quiesce the controller
and synchronize and release the IRQ before freeing its backing
resources.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Co-developed-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Myeonghun Pak <mhun512@xxxxxxxxx>
---
drivers/i2c/busses/i2c-iop3xx.c | 31 ++++++++++++++++++++++---------
drivers/i2c/busses/i2c-iop3xx.h | 1 +
2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/drivers/i2c/busses/i2c-iop3xx.c b/drivers/i2c/busses/i2c-iop3xx.c
index 4c67c5d18f..c375509c93 100644
--- a/drivers/i2c/busses/i2c-iop3xx.c
+++ b/drivers/i2c/busses/i2c-iop3xx.c
@@ -89,6 +89,16 @@ iop3xx_i2c_enable(struct i2c_algo_iop3xx_data *iop3xx_adap)
__raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET);
}

+static void
+iop3xx_i2c_disable(struct i2c_algo_iop3xx_data *iop3xx_adap)
+{
+ unsigned long cr = __raw_readl(iop3xx_adap->ioaddr + CR_OFFSET);
+
+ cr &= ~(IOP3XX_ICR_ALD_IE | IOP3XX_ICR_BERR_IE |
+ IOP3XX_ICR_RXFULL_IE | IOP3XX_ICR_TXEMPTY_IE);
+ __raw_writel(cr, iop3xx_adap->ioaddr + CR_OFFSET);
+}
+
static void
iop3xx_i2c_transaction_cleanup(struct i2c_algo_iop3xx_data *iop3xx_adap)
{
@@ -392,14 +402,10 @@ iop3xx_i2c_remove(struct platform_device *pdev)
struct i2c_algo_iop3xx_data *adapter_data =
(struct i2c_algo_iop3xx_data *)padapter->algo_data;
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- unsigned long cr = __raw_readl(adapter_data->ioaddr + CR_OFFSET);

- /*
- * Disable the actual HW unit
- */
- cr &= ~(IOP3XX_ICR_ALD_IE | IOP3XX_ICR_BERR_IE |
- IOP3XX_ICR_RXFULL_IE | IOP3XX_ICR_TXEMPTY_IE);
- __raw_writel(cr, adapter_data->ioaddr + CR_OFFSET);
+ i2c_del_adapter(padapter);
+ iop3xx_i2c_disable(adapter_data);
+ free_irq(adapter_data->irq, adapter_data);

iounmap(adapter_data->ioaddr);
release_mem_region(res->start, IOP3XX_I2C_IO_SIZE);
@@ -467,6 +473,7 @@ iop3xx_i2c_probe(struct platform_device *pdev)
ret = irq;
goto unmap;
}
+ adapter_data->irq = irq;
ret = request_irq(irq, iop3xx_i2c_irq_handler, 0,
pdev->name, adapter_data);

@@ -492,13 +499,19 @@ iop3xx_i2c_probe(struct platform_device *pdev)
iop3xx_i2c_reset(adapter_data);
iop3xx_i2c_enable(adapter_data);

- platform_set_drvdata(pdev, new_adapter);
new_adapter->algo_data = adapter_data;

- i2c_add_numbered_adapter(new_adapter);
+ ret = i2c_add_numbered_adapter(new_adapter);
+ if (ret)
+ goto disable;
+ platform_set_drvdata(pdev, new_adapter);

return 0;

+disable:
+ iop3xx_i2c_disable(adapter_data);
+ free_irq(adapter_data->irq, adapter_data);
+
unmap:
iounmap(adapter_data->ioaddr);

diff --git a/drivers/i2c/busses/i2c-iop3xx.h b/drivers/i2c/busses/i2c-iop3xx.h
index 1435483355..6c07ea16bf 100644
--- a/drivers/i2c/busses/i2c-iop3xx.h
+++ b/drivers/i2c/busses/i2c-iop3xx.h
@@ -92,6 +92,7 @@ struct i2c_algo_iop3xx_data {
spinlock_t lock;
u32 SR_enabled, SR_received;
int id;
+ int irq;
struct gpio_desc *gpio_scl;
struct gpio_desc *gpio_sda;
};
--
2.47.1