iio: adc: at91: disable adc channel interrupt in timeout case [Linux 3.16.72]

This Linux kernel change "iio: adc: at91: disable adc channel interrupt in timeout case" is included in the Linux 3.16.72 release. This change is authored by Georg Ottinger <g.ottinger [at] abatec.at> on Wed Jan 30 14:42:02 2019 +0100. The commit for this change in Linux stable tree is 82bbd92 (patch) which is from upstream commit 09c6bde. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 09c6bde.

iio: adc: at91: disable adc channel interrupt in timeout case

commit 09c6bdee51183a575bf7546890c8c137a75a2b44 upstream.

Having a brief look at at91_adc_read_raw() it is obvious that in the case
of a timeout the setting of AT91_ADC_CHDR and AT91_ADC_IDR registers is
omitted. If 2 different channels are queried we can end up with a
situation where two interrupts are enabled, but only one interrupt is
cleared in the interrupt handler. Resulting in a interrupt loop and a
system hang.

Signed-off-by: Georg Ottinger <g.ottinger@abatec.at>
Acked-by: Ludovic Desroches <ludovic.desroches@microchip.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>

There are 28 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/iio/adc/at91_adc.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c
index 10d5ec2..2f7ad25 100644
--- a/drivers/iio/adc/at91_adc.c
+++ b/drivers/iio/adc/at91_adc.c
@@ -702,23 +702,29 @@ static int at91_adc_read_raw(struct iio_dev *idev,
        ret = wait_event_interruptible_timeout(st->wq_data_avail,
                               st->done,
                               msecs_to_jiffies(1000));
-       if (ret == 0)
-           ret = -ETIMEDOUT;
-       if (ret < 0) {
-           mutex_unlock(&st->lock);
-           return ret;
-       }
-
-       *val = st->last_value;

+       /* Disable interrupts, regardless if adc conversion was
+        * successful or not
+        */
        at91_adc_writel(st, AT91_ADC_CHDR,
                AT91_ADC_CH(chan->channel));
        at91_adc_writel(st, AT91_ADC_IDR, BIT(chan->channel));

-       st->last_value = 0;
-       st->done = false;
+       if (ret > 0) {
+           /* a valid conversion took place */
+           *val = st->last_value;
+           st->last_value = 0;
+           st->done = false;
+           ret = IIO_VAL_INT;
+       } else if (ret == 0) {
+           /* conversion timeout */
+           dev_err(&idev->dev, "ADC Channel %d timeout.\n",
+               chan->channel);
+           ret = -ETIMEDOUT;
+       }
+
        mutex_unlock(&st->lock);
-       return IIO_VAL_INT;
+       return ret;

    case IIO_CHAN_INFO_SCALE:
        *val = st->vref_mv;

Leave a Reply

Your email address will not be published. Required fields are marked *