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

Myeonghun Pak <[email protected]> Wed, 29 Jul 2026 02:15:27 +0900
Newsgroups org.kernel.vger.linux-i2c,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
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: [email protected]
Co-developed-by: Ijae Kim <[email protected]>
Signed-off-by: Ijae Kim <[email protected]>
Signed-off-by: Myeonghun Pak <[email protected]>
---
 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