Merge remote-tracking branch 'lsk/v3.10/topic/gator' into linux-linaro-lsk
[firefly-linux-kernel-4.4.55.git] / drivers / tty / serial / 8250 / 8250_dw.c
index 5d880917850f632fce58244662081b54686d6e36..345b5ddcb1a01285f1c0d57af88771cedd15b4ea 100644 (file)
 
 
 struct dw8250_data {
-       int             last_lcr;
+       int             last_mcr;
        int             line;
        struct clk      *clk;
 };
 
+static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value)
+{
+       struct dw8250_data *d = p->private_data;
+
+       /* If reading MSR, report CTS asserted when auto-CTS/RTS enabled */
+       if (offset == UART_MSR && d->last_mcr & UART_MCR_AFE) {
+               value |= UART_MSR_CTS;
+               value &= ~UART_MSR_DCTS;
+       }
+
+       return value;
+}
+
+static void dw8250_force_idle(struct uart_port *p)
+{
+       serial8250_clear_and_reinit_fifos(container_of
+                                         (p, struct uart_8250_port, port));
+       (void)p->serial_in(p, UART_RX);
+}
+
 static void dw8250_serial_out(struct uart_port *p, int offset, int value)
 {
        struct dw8250_data *d = p->private_data;
 
-       if (offset == UART_LCR)
-               d->last_lcr = value;
+       if (offset == UART_MCR)
+               d->last_mcr = value;
+
+       writeb(value, p->membase + (offset << p->regshift));
 
-       offset <<= p->regshift;
-       writeb(value, p->membase + offset);
+       /* Make sure LCR write wasn't ignored */
+       if (offset == UART_LCR) {
+               int tries = 1000;
+               while (tries--) {
+                       unsigned int lcr = p->serial_in(p, UART_LCR);
+                       if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+                               return;
+                       dw8250_force_idle(p);
+                       writeb(value, p->membase + (UART_LCR << p->regshift));
+               }
+               dev_err(p->dev, "Couldn't set LCR to %d\n", value);
+       }
 }
 
 static unsigned int dw8250_serial_in(struct uart_port *p, int offset)
 {
-       offset <<= p->regshift;
+       unsigned int value = readb(p->membase + (offset << p->regshift));
 
-       return readb(p->membase + offset);
+       return dw8250_modify_msr(p, offset, value);
 }
 
 static void dw8250_serial_out32(struct uart_port *p, int offset, int value)
 {
        struct dw8250_data *d = p->private_data;
 
-       if (offset == UART_LCR)
-               d->last_lcr = value;
+       if (offset == UART_MCR)
+               d->last_mcr = value;
 
-       offset <<= p->regshift;
-       writel(value, p->membase + offset);
+       writel(value, p->membase + (offset << p->regshift));
+
+       /* Make sure LCR write wasn't ignored */
+       if (offset == UART_LCR) {
+               int tries = 1000;
+               while (tries--) {
+                       unsigned int lcr = p->serial_in(p, UART_LCR);
+                       if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+                               return;
+                       dw8250_force_idle(p);
+                       writel(value, p->membase + (UART_LCR << p->regshift));
+               }
+               dev_err(p->dev, "Couldn't set LCR to %d\n", value);
+       }
 }
 
 static unsigned int dw8250_serial_in32(struct uart_port *p, int offset)
 {
-       offset <<= p->regshift;
+       unsigned int value = readl(p->membase + (offset << p->regshift));
 
-       return readl(p->membase + offset);
+       return dw8250_modify_msr(p, offset, value);
 }
 
 static int dw8250_handle_irq(struct uart_port *p)
 {
-       struct dw8250_data *d = p->private_data;
        unsigned int iir = p->serial_in(p, UART_IIR);
 
        if (serial8250_handle_irq(p, iir)) {
                return 1;
        } else if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
-               /* Clear the USR and write the LCR again. */
+               /* Clear the USR */
                (void)p->serial_in(p, DW_UART_USR);
-               p->serial_out(p, UART_LCR, d->last_lcr);
 
                return 1;
        }