| From d3c817e7e7add303c129607b7560baf2c2b9dfb8 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Sat, 12 Apr 2025 10:33:03 +0800 |
| Subject: i2c: designware: Invoke runtime suspend on quick slave |
| re-registration |
| |
| From: Tan En De <ende.tan@starfivetech.com> |
| |
| [ Upstream commit 2fe2b969d911a09abcd6a47401a3c66c38a310e6 ] |
| |
| Replaced pm_runtime_put() with pm_runtime_put_sync_suspend() to ensure |
| the runtime suspend is invoked immediately when unregistering a slave. |
| This prevents a race condition where suspend was skipped when |
| unregistering and registering slave in quick succession. |
| |
| For example, consider the rapid sequence of |
| `delete_device -> new_device -> delete_device -> new_device`. |
| In this sequence, it is observed that the dw_i2c_plat_runtime_suspend() |
| might not be invoked after `delete_device` operation. |
| |
| This is because after `delete_device` operation, when the |
| pm_runtime_put() is about to trigger suspend, the following `new_device` |
| operation might race and cancel the suspend. |
| |
| If that happens, during the `new_device` operation, |
| dw_i2c_plat_runtime_resume() is skipped (since there was no suspend), which |
| means `i_dev->init()`, i.e. i2c_dw_init_slave(), is skipped. |
| Since i2c_dw_init_slave() is skipped, i2c_dw_configure_fifo_slave() is |
| skipped too, which leaves `DW_IC_INTR_MASK` unconfigured. If we inspect |
| the interrupt mask register using devmem, it will show as zero. |
| |
| Example shell script to reproduce the issue: |
| ``` |
| #!/bin/sh |
| |
| SLAVE_LADDR=0x1010 |
| SLAVE_BUS=13 |
| NEW_DEVICE=/sys/bus/i2c/devices/i2c-$SLAVE_BUS/new_device |
| DELETE_DEVICE=/sys/bus/i2c/devices/i2c-$SLAVE_BUS/delete_device |
| |
| # Create initial device |
| echo slave-24c02 $SLAVE_LADDR > $NEW_DEVICE |
| sleep 2 |
| |
| # Rapid sequence of |
| # delete_device -> new_device -> delete_device -> new_device |
| echo $SLAVE_LADDR > $DELETE_DEVICE |
| echo slave-24c02 $SLAVE_LADDR > $NEW_DEVICE |
| echo $SLAVE_LADDR > $DELETE_DEVICE |
| echo slave-24c02 $SLAVE_LADDR > $NEW_DEVICE |
| |
| # Using devmem to inspect IC_INTR_MASK will show as zero |
| ``` |
| |
| Signed-off-by: Tan En De <ende.tan@starfivetech.com> |
| Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com> |
| Link: https://lore.kernel.org/r/20250412023303.378600-1-ende.tan@starfivetech.com |
| Signed-off-by: Andi Shyti <andi.shyti@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/i2c/busses/i2c-designware-slave.c | 2 +- |
| 1 file changed, 1 insertion(+), 1 deletion(-) |
| |
| diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c |
| index f5f001738df5e..57e67962a602f 100644 |
| --- a/drivers/i2c/busses/i2c-designware-slave.c |
| +++ b/drivers/i2c/busses/i2c-designware-slave.c |
| @@ -96,7 +96,7 @@ static int i2c_dw_unreg_slave(struct i2c_client *slave) |
| dev->disable(dev); |
| synchronize_irq(dev->irq); |
| dev->slave = NULL; |
| - pm_runtime_put(dev->dev); |
| + pm_runtime_put_sync_suspend(dev->dev); |
| |
| return 0; |
| } |
| -- |
| 2.39.5 |
| |