| From 29143d76b32e658d921bda40f61825dd53f27ae1 Mon Sep 17 00:00:00 2001 |
| From: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Date: Tue, 1 Oct 2019 10:51:09 +0200 |
| Subject: [PATCH] i2c: stm32f7: fix a race in slave mode with arbitration loss |
| irq |
| |
| commit 6d6b0d0d5afc8c4c84b08261260ba11dfa5206f2 upstream. |
| |
| When in slave mode, an arbitration loss (ARLO) may be detected before the |
| slave had a chance to detect the stop condition (STOPF in ISR). |
| This is seen when two master + slave adapters switch their roles. It |
| provokes the i2c bus to be stuck, busy as SCL line is stretched. |
| - the I2C_SLAVE_STOP event is never generated due to STOPF flag is set but |
| don't generate an irq (race with ARLO irq, STOPIE is masked). STOPF flag |
| remains set until next master xfer (e.g. when STOPIE irq get unmasked). |
| In this case, completion is generated too early: immediately upon new |
| transfer request (then it doesn't send all data). |
| - Some data get stuck in TXDR register. As a consequence, the controller |
| stretches the SCL line: the bus gets busy until a future master transfer |
| triggers the bus busy / recovery mechanism (this can take time... and |
| may never happen at all) |
| |
| So choice is to let the STOPF being detected by the slave isr handler, |
| to properly handle this stop condition. E.g. don't mask IRQs in error |
| handler, when the slave is running. |
| |
| Fixes: 60d609f30de2 ("i2c: i2c-stm32f7: Add slave support") |
| Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Reviewed-by: Pierre-Yves MORDRET <pierre-yves.mordret@st.com> |
| Signed-off-by: Wolfram Sang <wsa@the-dreams.de> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/i2c/busses/i2c-stm32f7.c b/drivers/i2c/busses/i2c-stm32f7.c |
| index 25572fd63a4c..6c8da49c7727 100644 |
| --- a/drivers/i2c/busses/i2c-stm32f7.c |
| +++ b/drivers/i2c/busses/i2c-stm32f7.c |
| @@ -1500,7 +1500,7 @@ static irqreturn_t stm32f7_i2c_isr_error(int irq, void *data) |
| void __iomem *base = i2c_dev->base; |
| struct device *dev = i2c_dev->dev; |
| struct stm32_i2c_dma *dma = i2c_dev->dma; |
| - u32 mask, status; |
| + u32 status; |
| |
| status = readl_relaxed(i2c_dev->base + STM32F7_I2C_ISR); |
| |
| @@ -1525,12 +1525,15 @@ static irqreturn_t stm32f7_i2c_isr_error(int irq, void *data) |
| f7_msg->result = -EINVAL; |
| } |
| |
| - /* Disable interrupts */ |
| - if (stm32f7_i2c_is_slave_registered(i2c_dev)) |
| - mask = STM32F7_I2C_XFER_IRQ_MASK; |
| - else |
| - mask = STM32F7_I2C_ALL_IRQ_MASK; |
| - stm32f7_i2c_disable_irq(i2c_dev, mask); |
| + if (!i2c_dev->slave_running) { |
| + u32 mask; |
| + /* Disable interrupts */ |
| + if (stm32f7_i2c_is_slave_registered(i2c_dev)) |
| + mask = STM32F7_I2C_XFER_IRQ_MASK; |
| + else |
| + mask = STM32F7_I2C_ALL_IRQ_MASK; |
| + stm32f7_i2c_disable_irq(i2c_dev, mask); |
| + } |
| |
| /* Disable dma */ |
| if (i2c_dev->use_dma) { |
| -- |
| 2.7.4 |
| |