Skip to content

Commit

Permalink
iio: adc: ti_am335x_adc: fix fifo overrun recovery
Browse files Browse the repository at this point in the history
The tiadc_irq_h(int irq, void *private) function is handling FIFO
overruns by clearing flags, disabling and enabling the ADC to
recover.

If the ADC is running in continuous mode a FIFO overrun happens
regularly. If the disabling of the ADC happens concurrently with
a new conversion. It might happen that the enabling of the ADC
is ignored by the hardware. This stops the ADC permanently. No
more interrupts are triggered.

According to the AM335x Reference Manual (SPRUH73H October 2011 -
Revised April 2013 - Chapter 12.4 and 12.5) it is necessary to
check the ADC FSM bits in REG_ADCFSM before enabling the ADC
again. Because the disabling of the ADC is done right after the
current conversion has been finished.

To trigger this bug it is necessary to run the ADC in continuous
mode. The ADC values of all channels need to be read in an endless
loop. The bug appears within the first 6 hours (~5.4 million
handled FIFO overruns). The user space application will hang on
reading new values from the character device.

Fixes: ca9a563 ("iio: ti_am335x_adc: Add continuous sampling
support")
Signed-off-by: Michael Engl <michael.engl@wjw-solutions.com>
Cc: <Stable@vger.kernel.org>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
  • Loading branch information
Michael Engl authored and jic23 committed Mar 15, 2017
1 parent c42f821 commit e83bb3e
Showing 1 changed file with 12 additions and 1 deletion.
13 changes: 12 additions & 1 deletion drivers/iio/adc/ti_am335x_adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ static irqreturn_t tiadc_irq_h(int irq, void *private)
{
struct iio_dev *indio_dev = private;
struct tiadc_device *adc_dev = iio_priv(indio_dev);
unsigned int status, config;
unsigned int status, config, adc_fsm;
unsigned short count = 0;

status = tiadc_readl(adc_dev, REG_IRQSTATUS);

/*
Expand All @@ -183,6 +185,15 @@ static irqreturn_t tiadc_irq_h(int irq, void *private)
tiadc_writel(adc_dev, REG_CTRL, config);
tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1OVRRUN
| IRQENB_FIFO1UNDRFLW | IRQENB_FIFO1THRES);

/* wait for idle state.
* ADC needs to finish the current conversion
* before disabling the module
*/
do {
adc_fsm = tiadc_readl(adc_dev, REG_ADCFSM);
} while (adc_fsm != 0x10 && count++ < 100);

tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_TSCSSENB));
return IRQ_HANDLED;
} else if (status & IRQENB_FIFO1THRES) {
Expand Down

0 comments on commit e83bb3e

Please sign in to comment.