]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/tty/serial/amba-pl011.c
Merge tag 'sound-4.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[karo-tx-linux.git] / drivers / tty / serial / amba-pl011.c
index d4171d71a258f2696ab2ab2109376ee5a516bfba..8789ea423ccfd1054d9d5433538d0a8e466525dd 100644 (file)
@@ -97,6 +97,7 @@ struct vendor_data {
        unsigned int            fr_dsr;
        unsigned int            fr_cts;
        unsigned int            fr_ri;
+       unsigned int            inv_fr;
        bool                    access_32b;
        bool                    oversampling;
        bool                    dma_threshold;
@@ -141,6 +142,30 @@ static struct vendor_data vendor_sbsa = {
        .fixed_options          = true,
 };
 
+/*
+ * Erratum 44 for QDF2432v1 and QDF2400v1 SoCs describes the BUSY bit as
+ * occasionally getting stuck as 1. To avoid the potential for a hang, check
+ * TXFE == 0 instead of BUSY == 1. This may not be suitable for all UART
+ * implementations, so only do so if an affected platform is detected in
+ * parse_spcr().
+ */
+static bool qdf2400_e44_present = false;
+
+static struct vendor_data vendor_qdt_qdf2400_e44 = {
+       .reg_offset             = pl011_std_offsets,
+       .fr_busy                = UART011_FR_TXFE,
+       .fr_dsr                 = UART01x_FR_DSR,
+       .fr_cts                 = UART01x_FR_CTS,
+       .fr_ri                  = UART011_FR_RI,
+       .inv_fr                 = UART011_FR_TXFE,
+       .access_32b             = true,
+       .oversampling           = false,
+       .dma_threshold          = false,
+       .cts_event_workaround   = false,
+       .always_enabled         = true,
+       .fixed_options          = true,
+};
+
 static u16 pl011_st_offsets[REG_ARRAY_SIZE] = {
        [REG_DR] = UART01x_DR,
        [REG_ST_DMAWM] = ST_UART011_DMAWM,
@@ -1518,7 +1543,10 @@ static unsigned int pl011_tx_empty(struct uart_port *port)
 {
        struct uart_amba_port *uap =
            container_of(port, struct uart_amba_port, port);
-       unsigned int status = pl011_read(uap, REG_FR);
+
+       /* Allow feature register bits to be inverted to work around errata */
+       unsigned int status = pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr;
+
        return status & (uap->vendor->fr_busy | UART01x_FR_TXFF) ?
                                                        0 : TIOCSER_TEMT;
 }
@@ -2114,7 +2142,7 @@ static int pl011_verify_port(struct uart_port *port, struct serial_struct *ser)
        return ret;
 }
 
-static struct uart_ops amba_pl011_pops = {
+static const struct uart_ops amba_pl011_pops = {
        .tx_empty       = pl011_tx_empty,
        .set_mctrl      = pl011_set_mctrl,
        .get_mctrl      = pl011_get_mctrl,
@@ -2215,10 +2243,12 @@ pl011_console_write(struct console *co, const char *s, unsigned int count)
        uart_console_write(&uap->port, s, count, pl011_console_putchar);
 
        /*
-        *      Finally, wait for transmitter to become empty
-        *      and restore the TCR
+        *      Finally, wait for transmitter to become empty and restore the
+        *      TCR. Allow feature register bits to be inverted to work around
+        *      errata.
         */
-       while (pl011_read(uap, REG_FR) & uap->vendor->fr_busy)
+       while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr)
+                                               & uap->vendor->fr_busy)
                cpu_relax();
        if (!uap->vendor->always_enabled)
                pl011_write(old_cr, uap, REG_CR);
@@ -2340,8 +2370,12 @@ static int __init pl011_console_match(struct console *co, char *name, int idx,
        resource_size_t addr;
        int i;
 
-       if (strcmp(name, "pl011") != 0)
+       if (strcmp(name, "qdf2400_e44") == 0) {
+               pr_info_once("UART: Working around QDF2400 SoC erratum 44");
+               qdf2400_e44_present = true;
+       } else if (strcmp(name, "pl011") != 0 || strcmp(name, "ttyAMA") != 0) {
                return -ENODEV;
+       }
 
        if (uart_parse_earlycon(options, &iotype, &addr, &options))
                return -ENODEV;
@@ -2376,13 +2410,29 @@ static struct console amba_console = {
        .device         = uart_console_device,
        .setup          = pl011_console_setup,
        .match          = pl011_console_match,
-       .flags          = CON_PRINTBUFFER,
+       .flags          = CON_PRINTBUFFER | CON_ANYTIME,
        .index          = -1,
        .data           = &amba_reg,
 };
 
 #define AMBA_CONSOLE   (&amba_console)
 
+static void qdf2400_e44_putc(struct uart_port *port, int c)
+{
+       while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
+               cpu_relax();
+       writel(c, port->membase + UART01x_DR);
+       while (!(readl(port->membase + UART01x_FR) & UART011_FR_TXFE))
+               cpu_relax();
+}
+
+static void qdf2400_e44_early_write(struct console *con, const char *s, unsigned n)
+{
+       struct earlycon_device *dev = con->data;
+
+       uart_console_write(&dev->port, s, n, qdf2400_e44_putc);
+}
+
 static void pl011_putc(struct uart_port *port, int c)
 {
        while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
@@ -2408,7 +2458,8 @@ static int __init pl011_early_console_setup(struct earlycon_device *device,
        if (!device->port.membase)
                return -ENODEV;
 
-       device->con->write = pl011_early_write;
+       device->con->write = qdf2400_e44_present ?
+                               qdf2400_e44_early_write : pl011_early_write;
        return 0;
 }
 OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup);
@@ -2645,7 +2696,8 @@ static int sbsa_uart_probe(struct platform_device *pdev)
        uap->port.irq   = ret;
 
        uap->reg_offset = vendor_sbsa.reg_offset;
-       uap->vendor     = &vendor_sbsa;
+       uap->vendor     = qdf2400_e44_present ?
+                                       &vendor_qdt_qdf2400_e44 : &vendor_sbsa;
        uap->fifosize   = 32;
        uap->port.iotype = vendor_sbsa.access_32b ? UPIO_MEM32 : UPIO_MEM;
        uap->port.ops   = &sbsa_uart_pops;