FROMLIST: serial: 8250_dw: Avoid "too much work" from bogus rx timeout interrupt
[firefly-linux-kernel-4.4.55.git] / drivers / tty / serial / 8250 / 8250_dw.c
index 8435c3f204c1ed85ef501d0232632659a46d1ffc..fa9d1d811790c65588ddf45594388a0fda81b32d 100644 (file)
@@ -189,8 +189,31 @@ static unsigned int dw8250_serial_in32(struct uart_port *p, int offset)
 
 static int dw8250_handle_irq(struct uart_port *p)
 {
+       struct uart_8250_port *up = up_to_u8250p(p);
        struct dw8250_data *d = p->private_data;
        unsigned int iir = p->serial_in(p, UART_IIR);
+       unsigned int status;
+       unsigned long flags;
+
+       /*
+        * There are ways to get Designware-based UARTs into a state where
+        * they are asserting UART_IIR_RX_TIMEOUT but there is no actual
+        * data available.  If we see such a case then we'll do a bogus
+        * read.  If we don't do this then the "RX TIMEOUT" interrupt will
+        * fire forever.
+        *
+        * This problem has only been observed so far when not in DMA mode
+        * so we limit the workaround only to non-DMA mode.
+        */
+       if (!up->dma && ((iir & 0x3f) == UART_IIR_RX_TIMEOUT)) {
+               spin_lock_irqsave(&p->lock, flags);
+               status = p->serial_in(p, UART_LSR);
+
+               if (!(status & (UART_LSR_DR | UART_LSR_BI)))
+                       (void) p->serial_in(p, UART_RX);
+
+               spin_unlock_irqrestore(&p->lock, flags);
+       }
 
        if (serial8250_handle_irq(p, iir)) {
                return 1;