| From dcb10920179ab74caf88a6f2afadecfc2743b910 Mon Sep 17 00:00:00 2001 |
| From: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Date: Tue, 17 Sep 2019 14:38:16 +0200 |
| Subject: iio: adc: stm32-adc: fix a race when using several adcs with dma and irq |
| |
| From: Fabrice Gasnier <fabrice.gasnier@st.com> |
| |
| commit dcb10920179ab74caf88a6f2afadecfc2743b910 upstream. |
| |
| End of conversion may be handled by using IRQ or DMA. There may be a |
| race when two conversions complete at the same time on several ADCs. |
| EOC can be read as 'set' for several ADCs, with: |
| - an ADC configured to use IRQs. EOCIE bit is set. The handler is normally |
| called in this case. |
| - an ADC configured to use DMA. EOCIE bit isn't set. EOC triggers the DMA |
| request instead. It's then automatically cleared by DMA read. But the |
| handler gets called due to status bit is temporarily set (IRQ triggered |
| by the other ADC). |
| So both EOC status bit in CSR and EOCIE control bit must be checked |
| before invoking the interrupt handler (e.g. call ISR only for |
| IRQ-enabled ADCs). |
| |
| Fixes: 2763ea0585c9 ("iio: adc: stm32: add optional dma support") |
| |
| Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> |
| Cc: <Stable@vger.kernel.org> |
| Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| |
| --- |
| drivers/iio/adc/stm32-adc-core.c | 43 ++++++++++++++++++++++++++++++++++++--- |
| drivers/iio/adc/stm32-adc-core.h | 1 |
| 2 files changed, 41 insertions(+), 3 deletions(-) |
| |
| --- a/drivers/iio/adc/stm32-adc-core.c |
| +++ b/drivers/iio/adc/stm32-adc-core.c |
| @@ -45,12 +45,16 @@ |
| * @eoc1: adc1 end of conversion flag in @csr |
| * @eoc2: adc2 end of conversion flag in @csr |
| * @eoc3: adc3 end of conversion flag in @csr |
| + * @ier: interrupt enable register offset for each adc |
| + * @eocie_msk: end of conversion interrupt enable mask in @ier |
| */ |
| struct stm32_adc_common_regs { |
| u32 csr; |
| u32 eoc1_msk; |
| u32 eoc2_msk; |
| u32 eoc3_msk; |
| + u32 ier; |
| + u32 eocie_msk; |
| }; |
| |
| struct stm32_adc_priv; |
| @@ -244,6 +248,8 @@ static const struct stm32_adc_common_reg |
| .eoc1_msk = STM32F4_EOC1, |
| .eoc2_msk = STM32F4_EOC2, |
| .eoc3_msk = STM32F4_EOC3, |
| + .ier = STM32F4_ADC_CR1, |
| + .eocie_msk = STM32F4_EOCIE, |
| }; |
| |
| /* STM32H7 common registers definitions */ |
| @@ -251,8 +257,24 @@ static const struct stm32_adc_common_reg |
| .csr = STM32H7_ADC_CSR, |
| .eoc1_msk = STM32H7_EOC_MST, |
| .eoc2_msk = STM32H7_EOC_SLV, |
| + .ier = STM32H7_ADC_IER, |
| + .eocie_msk = STM32H7_EOCIE, |
| }; |
| |
| +static const unsigned int stm32_adc_offset[STM32_ADC_MAX_ADCS] = { |
| + 0, STM32_ADC_OFFSET, STM32_ADC_OFFSET * 2, |
| +}; |
| + |
| +static unsigned int stm32_adc_eoc_enabled(struct stm32_adc_priv *priv, |
| + unsigned int adc) |
| +{ |
| + u32 ier, offset = stm32_adc_offset[adc]; |
| + |
| + ier = readl_relaxed(priv->common.base + offset + priv->cfg->regs->ier); |
| + |
| + return ier & priv->cfg->regs->eocie_msk; |
| +} |
| + |
| /* ADC common interrupt for all instances */ |
| static void stm32_adc_irq_handler(struct irq_desc *desc) |
| { |
| @@ -263,13 +285,28 @@ static void stm32_adc_irq_handler(struct |
| chained_irq_enter(chip, desc); |
| status = readl_relaxed(priv->common.base + priv->cfg->regs->csr); |
| |
| - if (status & priv->cfg->regs->eoc1_msk) |
| + /* |
| + * End of conversion may be handled by using IRQ or DMA. There may be a |
| + * race here when two conversions complete at the same time on several |
| + * ADCs. EOC may be read 'set' for several ADCs, with: |
| + * - an ADC configured to use DMA (EOC triggers the DMA request, and |
| + * is then automatically cleared by DR read in hardware) |
| + * - an ADC configured to use IRQs (EOCIE bit is set. The handler must |
| + * be called in this case) |
| + * So both EOC status bit in CSR and EOCIE control bit must be checked |
| + * before invoking the interrupt handler (e.g. call ISR only for |
| + * IRQ-enabled ADCs). |
| + */ |
| + if (status & priv->cfg->regs->eoc1_msk && |
| + stm32_adc_eoc_enabled(priv, 0)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 0)); |
| |
| - if (status & priv->cfg->regs->eoc2_msk) |
| + if (status & priv->cfg->regs->eoc2_msk && |
| + stm32_adc_eoc_enabled(priv, 1)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 1)); |
| |
| - if (status & priv->cfg->regs->eoc3_msk) |
| + if (status & priv->cfg->regs->eoc3_msk && |
| + stm32_adc_eoc_enabled(priv, 2)) |
| generic_handle_irq(irq_find_mapping(priv->domain, 2)); |
| |
| chained_irq_exit(chip, desc); |
| --- a/drivers/iio/adc/stm32-adc-core.h |
| +++ b/drivers/iio/adc/stm32-adc-core.h |
| @@ -37,6 +37,7 @@ |
| * -------------------------------------------------------- |
| */ |
| #define STM32_ADC_MAX_ADCS 3 |
| +#define STM32_ADC_OFFSET 0x100 |
| #define STM32_ADCX_COMN_OFFSET 0x300 |
| |
| /* STM32F4 - Registers for each ADC instance */ |