Merge tag 'regmap-fix-v4.9-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git...
[cascardo/linux.git] / drivers / tty / serial / xilinx_uartps.c
index 9ca1a4d..dd4c02f 100644 (file)
@@ -57,7 +57,7 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 #define CDNS_UART_IMR          0x10  /* Interrupt Mask */
 #define CDNS_UART_ISR          0x14  /* Interrupt Status */
 #define CDNS_UART_BAUDGEN      0x18  /* Baud Rate Generator */
-#define CDNS_UART_RXTOUT               0x1C  /* RX Timeout */
+#define CDNS_UART_RXTOUT       0x1C  /* RX Timeout */
 #define CDNS_UART_RXWM         0x20  /* RX FIFO Trigger Level */
 #define CDNS_UART_MODEMCR      0x24  /* Modem Control */
 #define CDNS_UART_MODEMSR      0x28  /* Modem Status */
@@ -68,6 +68,7 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 #define CDNS_UART_IRRX_PWIDTH  0x3C  /* IR Min Received Pulse Width */
 #define CDNS_UART_IRTX_PWIDTH  0x40  /* IR Transmitted pulse Width */
 #define CDNS_UART_TXWM         0x44  /* TX FIFO Trigger Level */
+#define CDNS_UART_RXBS         0x48  /* RX FIFO byte status register */
 
 /* Control Register Bit Definitions */
 #define CDNS_UART_CR_STOPBRK   0x00000100  /* Stop TX break */
@@ -79,6 +80,9 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 #define CDNS_UART_CR_TXRST     0x00000002  /* TX logic reset */
 #define CDNS_UART_CR_RXRST     0x00000001  /* RX logic reset */
 #define CDNS_UART_CR_RST_TO    0x00000040  /* Restart Timeout Counter */
+#define CDNS_UART_RXBS_PARITY    0x00000001 /* Parity error status */
+#define CDNS_UART_RXBS_FRAMING   0x00000002 /* Framing error status */
+#define CDNS_UART_RXBS_BRK       0x00000004 /* Overrun error status */
 
 /*
  * Mode Register:
@@ -126,13 +130,27 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 #define CDNS_UART_IXR_RXEMPTY  0x00000002 /* RX FIFO empty interrupt. */
 #define CDNS_UART_IXR_MASK     0x00001FFF /* Valid bit mask */
 
-#define CDNS_UART_RX_IRQS      (CDNS_UART_IXR_PARITY | CDNS_UART_IXR_FRAMING | \
-                                CDNS_UART_IXR_OVERRUN | CDNS_UART_IXR_RXTRIG | \
+       /*
+        * Do not enable parity error interrupt for the following
+        * reason: When parity error interrupt is enabled, each Rx
+        * parity error always results in 2 events. The first one
+        * being parity error interrupt and the second one with a
+        * proper Rx interrupt with the incoming data.  Disabling
+        * parity error interrupt ensures better handling of parity
+        * error events. With this change, for a parity error case, we
+        * get a Rx interrupt with parity error set in ISR register
+        * and we still handle parity errors in the desired way.
+        */
+
+#define CDNS_UART_RX_IRQS      (CDNS_UART_IXR_FRAMING | \
+                                CDNS_UART_IXR_OVERRUN | \
+                                CDNS_UART_IXR_RXTRIG |  \
                                 CDNS_UART_IXR_TOUT)
 
 /* Goes in read_status_mask for break detection as the HW doesn't do it*/
-#define CDNS_UART_IXR_BRK      0x80000000
+#define CDNS_UART_IXR_BRK      0x00002000
 
+#define CDNS_UART_RXBS_SUPPORT BIT(1)
 /*
  * Modem Control register:
  * The read/write Modem Control register controls the interface with the modem
@@ -172,46 +190,66 @@ struct cdns_uart {
        struct clk              *pclk;
        unsigned int            baud;
        struct notifier_block   clk_rate_change_nb;
+       u32                     quirks;
+};
+struct cdns_platform_data {
+       u32 quirks;
 };
 #define to_cdns_uart(_nb) container_of(_nb, struct cdns_uart, \
                clk_rate_change_nb);
 
-static void cdns_uart_handle_rx(struct uart_port *port, unsigned int isrstatus)
+/**
+ * cdns_uart_handle_rx - Handle the received bytes along with Rx errors.
+ * @dev_id: Id of the UART port
+ * @isrstatus: The interrupt status register value as read
+ * Return: None
+ */
+static void cdns_uart_handle_rx(void *dev_id, unsigned int isrstatus)
 {
-       /*
-        * There is no hardware break detection, so we interpret framing
-        * error with all-zeros data as a break sequence. Most of the time,
-        * there's another non-zero byte at the end of the sequence.
-        */
-       if (isrstatus & CDNS_UART_IXR_FRAMING) {
-               while (!(readl(port->membase + CDNS_UART_SR) &
-                                       CDNS_UART_SR_RXEMPTY)) {
-                       if (!readl(port->membase + CDNS_UART_FIFO)) {
+       struct uart_port *port = (struct uart_port *)dev_id;
+       struct cdns_uart *cdns_uart = port->private_data;
+       unsigned int data;
+       unsigned int rxbs_status = 0;
+       unsigned int status_mask;
+       unsigned int framerrprocessed = 0;
+       char status = TTY_NORMAL;
+       bool is_rxbs_support;
+
+       is_rxbs_support = cdns_uart->quirks & CDNS_UART_RXBS_SUPPORT;
+
+       while ((readl(port->membase + CDNS_UART_SR) &
+               CDNS_UART_SR_RXEMPTY) != CDNS_UART_SR_RXEMPTY) {
+               if (is_rxbs_support)
+                       rxbs_status = readl(port->membase + CDNS_UART_RXBS);
+               data = readl(port->membase + CDNS_UART_FIFO);
+               port->icount.rx++;
+               /*
+                * There is no hardware break detection in Zynq, so we interpret
+                * framing error with all-zeros data as a break sequence.
+                * Most of the time, there's another non-zero byte at the
+                * end of the sequence.
+                */
+               if (!is_rxbs_support && (isrstatus & CDNS_UART_IXR_FRAMING)) {
+                       if (!data) {
                                port->read_status_mask |= CDNS_UART_IXR_BRK;
-                               isrstatus &= ~CDNS_UART_IXR_FRAMING;
+                               framerrprocessed = 1;
+                               continue;
                        }
                }
-               writel(CDNS_UART_IXR_FRAMING, port->membase + CDNS_UART_ISR);
-       }
-
-       /* drop byte with parity error if IGNPAR specified */
-       if (isrstatus & port->ignore_status_mask & CDNS_UART_IXR_PARITY)
-               isrstatus &= ~(CDNS_UART_IXR_RXTRIG | CDNS_UART_IXR_TOUT);
-
-       isrstatus &= port->read_status_mask;
-       isrstatus &= ~port->ignore_status_mask;
-
-       if (!(isrstatus & (CDNS_UART_IXR_TOUT | CDNS_UART_IXR_RXTRIG)))
-               return;
-
-       while (!(readl(port->membase + CDNS_UART_SR) & CDNS_UART_SR_RXEMPTY)) {
-               u32 data;
-               char status = TTY_NORMAL;
+               if (is_rxbs_support && (rxbs_status & CDNS_UART_RXBS_BRK)) {
+                       port->icount.brk++;
+                       status = TTY_BREAK;
+                       if (uart_handle_break(port))
+                               continue;
+               }
 
-               data = readl(port->membase + CDNS_UART_FIFO);
+               isrstatus &= port->read_status_mask;
+               isrstatus &= ~port->ignore_status_mask;
+               status_mask = port->read_status_mask;
+               status_mask &= ~port->ignore_status_mask;
 
-               /* Non-NULL byte after BREAK is garbage (99%) */
-               if (data && (port->read_status_mask & CDNS_UART_IXR_BRK)) {
+               if (data &&
+                   (port->read_status_mask & CDNS_UART_IXR_BRK)) {
                        port->read_status_mask &= ~CDNS_UART_IXR_BRK;
                        port->icount.brk++;
                        if (uart_handle_break(port))
@@ -221,57 +259,83 @@ static void cdns_uart_handle_rx(struct uart_port *port, unsigned int isrstatus)
                if (uart_handle_sysrq_char(port, data))
                        continue;
 
-               port->icount.rx++;
-
-               if (isrstatus & CDNS_UART_IXR_PARITY) {
-                       port->icount.parity++;
-                       status = TTY_PARITY;
-               } else if (isrstatus & CDNS_UART_IXR_FRAMING) {
-                       port->icount.frame++;
-                       status = TTY_FRAME;
-               } else if (isrstatus & CDNS_UART_IXR_OVERRUN) {
+               if (is_rxbs_support) {
+                       if ((rxbs_status & CDNS_UART_RXBS_PARITY)
+                           && (status_mask & CDNS_UART_IXR_PARITY)) {
+                               port->icount.parity++;
+                               status = TTY_PARITY;
+                       }
+                       if ((rxbs_status & CDNS_UART_RXBS_FRAMING)
+                           && (status_mask & CDNS_UART_IXR_PARITY)) {
+                               port->icount.frame++;
+                               status = TTY_FRAME;
+                       }
+               } else {
+                       if (isrstatus & CDNS_UART_IXR_PARITY) {
+                               port->icount.parity++;
+                               status = TTY_PARITY;
+                       }
+                       if ((isrstatus & CDNS_UART_IXR_FRAMING) &&
+                           !framerrprocessed) {
+                               port->icount.frame++;
+                               status = TTY_FRAME;
+                       }
+               }
+               if (isrstatus & CDNS_UART_IXR_OVERRUN) {
                        port->icount.overrun++;
+                       tty_insert_flip_char(&port->state->port, 0,
+                                            TTY_OVERRUN);
                }
-
-               uart_insert_char(port, isrstatus, CDNS_UART_IXR_OVERRUN,
-                                data, status);
+               tty_insert_flip_char(&port->state->port, data, status);
+               isrstatus = 0;
        }
+       spin_unlock(&port->lock);
        tty_flip_buffer_push(&port->state->port);
+       spin_lock(&port->lock);
 }
 
-static void cdns_uart_handle_tx(struct uart_port *port)
+/**
+ * cdns_uart_handle_tx - Handle the bytes to be Txed.
+ * @dev_id: Id of the UART port
+ * Return: None
+ */
+static void cdns_uart_handle_tx(void *dev_id)
 {
+       struct uart_port *port = (struct uart_port *)dev_id;
        unsigned int numbytes;
 
        if (uart_circ_empty(&port->state->xmit)) {
                writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_IDR);
-               return;
-       }
-
-       numbytes = port->fifosize;
-       while (numbytes && !uart_circ_empty(&port->state->xmit) &&
-              !(readl(port->membase + CDNS_UART_SR) & CDNS_UART_SR_TXFULL)) {
-               /*
-                * Get the data from the UART circular buffer
-                * and write it to the cdns_uart's TX_FIFO
-                * register.
-                */
-               writel(port->state->xmit.buf[port->state->xmit.tail],
-                       port->membase + CDNS_UART_FIFO);
-               port->icount.tx++;
-
-               /*
-                * Adjust the tail of the UART buffer and wrap
-                * the buffer if it reaches limit.
-                */
-               port->state->xmit.tail =
-                       (port->state->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
+       } else {
+               numbytes = port->fifosize;
+               while (numbytes && !uart_circ_empty(&port->state->xmit) &&
+                      !(readl(port->membase + CDNS_UART_SR) & CDNS_UART_SR_TXFULL)) {
+                       /*
+                        * Get the data from the UART circular buffer
+                        * and write it to the cdns_uart's TX_FIFO
+                        * register.
+                        */
+                       writel(
+                               port->state->xmit.buf[port->state->xmit.
+                               tail], port->membase + CDNS_UART_FIFO);
+
+                       port->icount.tx++;
+
+                       /*
+                        * Adjust the tail of the UART buffer and wrap
+                        * the buffer if it reaches limit.
+                        */
+                       port->state->xmit.tail =
+                               (port->state->xmit.tail + 1) &
+                                       (UART_XMIT_SIZE - 1);
+
+                       numbytes--;
+               }
 
-               numbytes--;
+               if (uart_circ_chars_pending(
+                               &port->state->xmit) < WAKEUP_CHARS)
+                       uart_write_wakeup(port);
        }
-
-       if (uart_circ_chars_pending(&port->state->xmit) < WAKEUP_CHARS)
-               uart_write_wakeup(port);
 }
 
 /**
@@ -284,27 +348,24 @@ static void cdns_uart_handle_tx(struct uart_port *port)
 static irqreturn_t cdns_uart_isr(int irq, void *dev_id)
 {
        struct uart_port *port = (struct uart_port *)dev_id;
-       unsigned long flags;
        unsigned int isrstatus;
 
-       spin_lock_irqsave(&port->lock, flags);
+       spin_lock(&port->lock);
 
        /* Read the interrupt status register to determine which
-        * interrupt(s) is/are active.
+        * interrupt(s) is/are active and clear them.
         */
        isrstatus = readl(port->membase + CDNS_UART_ISR);
-
-       if (isrstatus & CDNS_UART_RX_IRQS)
-               cdns_uart_handle_rx(port, isrstatus);
-
-       if ((isrstatus & CDNS_UART_IXR_TXEMPTY) == CDNS_UART_IXR_TXEMPTY)
-               cdns_uart_handle_tx(port);
-
        writel(isrstatus, port->membase + CDNS_UART_ISR);
 
-       /* be sure to release the lock and tty before leaving */
-       spin_unlock_irqrestore(&port->lock, flags);
+       if (isrstatus & CDNS_UART_IXR_TXEMPTY) {
+               cdns_uart_handle_tx(dev_id);
+               isrstatus &= ~CDNS_UART_IXR_TXEMPTY;
+       }
+       if (isrstatus & CDNS_UART_IXR_MASK)
+               cdns_uart_handle_rx(dev_id, isrstatus);
 
+       spin_unlock(&port->lock);
        return IRQ_HANDLED;
 }
 
@@ -653,6 +714,10 @@ static void cdns_uart_set_termios(struct uart_port *port,
        ctrl_reg |= CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST;
        writel(ctrl_reg, port->membase + CDNS_UART_CR);
 
+       while (readl(port->membase + CDNS_UART_CR) &
+               (CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
+               cpu_relax();
+
        /*
         * Clear the RX disable and TX disable bits and then set the TX enable
         * bit and RX enable bit to enable the transmitter and receiver.
@@ -736,10 +801,14 @@ static void cdns_uart_set_termios(struct uart_port *port,
  */
 static int cdns_uart_startup(struct uart_port *port)
 {
+       struct cdns_uart *cdns_uart = port->private_data;
+       bool is_brk_support;
        int ret;
        unsigned long flags;
        unsigned int status = 0;
 
+       is_brk_support = cdns_uart->quirks & CDNS_UART_RXBS_SUPPORT;
+
        spin_lock_irqsave(&port->lock, flags);
 
        /* Disable the TX and RX */
@@ -752,6 +821,10 @@ static int cdns_uart_startup(struct uart_port *port)
        writel(CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST,
                        port->membase + CDNS_UART_CR);
 
+       while (readl(port->membase + CDNS_UART_CR) &
+               (CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
+               cpu_relax();
+
        /*
         * Clear the RX disable bit and then set the RX enable bit to enable
         * the receiver.
@@ -794,7 +867,11 @@ static int cdns_uart_startup(struct uart_port *port)
        }
 
        /* Set the Interrupt Registers with desired interrupts */
-       writel(CDNS_UART_RX_IRQS, port->membase + CDNS_UART_IER);
+       if (is_brk_support)
+               writel(CDNS_UART_RX_IRQS | CDNS_UART_IXR_BRK,
+                                       port->membase + CDNS_UART_IER);
+       else
+               writel(CDNS_UART_RX_IRQS, port->membase + CDNS_UART_IER);
 
        return 0;
 }
@@ -993,7 +1070,7 @@ static void cdns_uart_pm(struct uart_port *port, unsigned int state,
        }
 }
 
-static struct uart_ops cdns_uart_ops = {
+static const struct uart_ops cdns_uart_ops = {
        .set_mctrl      = cdns_uart_set_mctrl,
        .get_mctrl      = cdns_uart_get_mctrl,
        .start_tx       = cdns_uart_start_tx,
@@ -1088,9 +1165,34 @@ static void __init cdns_early_write(struct console *con, const char *s,
 static int __init cdns_early_console_setup(struct earlycon_device *device,
                                           const char *opt)
 {
-       if (!device->port.membase)
+       struct uart_port *port = &device->port;
+
+       if (!port->membase)
                return -ENODEV;
 
+       /* initialise control register */
+       writel(CDNS_UART_CR_TX_EN|CDNS_UART_CR_TXRST|CDNS_UART_CR_RXRST,
+              port->membase + CDNS_UART_CR);
+
+       /* only set baud if specified on command line - otherwise
+        * assume it has been initialized by a boot loader.
+        */
+       if (device->baud) {
+               u32 cd = 0, bdiv = 0;
+               u32 mr;
+               int div8;
+
+               cdns_uart_calc_baud_divs(port->uartclk, device->baud,
+                                        &bdiv, &cd, &div8);
+               mr = CDNS_UART_MR_PARITY_NONE;
+               if (div8)
+                       mr |= CDNS_UART_MR_CLKSEL;
+
+               writel(mr,   port->membase + CDNS_UART_MR);
+               writel(cd,   port->membase + CDNS_UART_BAUDGEN);
+               writel(bdiv, port->membase + CDNS_UART_BAUDDIV);
+       }
+
        device->con->write = cdns_early_write;
 
        return 0;
@@ -1098,6 +1200,7 @@ static int __init cdns_early_console_setup(struct earlycon_device *device,
 OF_EARLYCON_DECLARE(cdns, "xlnx,xuartps", cdns_early_console_setup);
 OF_EARLYCON_DECLARE(cdns, "cdns,uart-r1p8", cdns_early_console_setup);
 OF_EARLYCON_DECLARE(cdns, "cdns,uart-r1p12", cdns_early_console_setup);
+OF_EARLYCON_DECLARE(cdns, "xlnx,zynqmp-uart", cdns_early_console_setup);
 
 /**
  * cdns_uart_console_write - perform write operation
@@ -1328,6 +1431,19 @@ static int cdns_uart_resume(struct device *device)
 static SIMPLE_DEV_PM_OPS(cdns_uart_dev_pm_ops, cdns_uart_suspend,
                cdns_uart_resume);
 
+static const struct cdns_platform_data zynqmp_uart_def = {
+                               .quirks = CDNS_UART_RXBS_SUPPORT, };
+
+/* Match table for of_platform binding */
+static const struct of_device_id cdns_uart_of_match[] = {
+       { .compatible = "xlnx,xuartps", },
+       { .compatible = "cdns,uart-r1p8", },
+       { .compatible = "cdns,uart-r1p12", .data = &zynqmp_uart_def },
+       { .compatible = "xlnx,zynqmp-uart", .data = &zynqmp_uart_def },
+       {}
+};
+MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
+
 /**
  * cdns_uart_probe - Platform driver probe
  * @pdev: Pointer to the platform device structure
@@ -1340,12 +1456,20 @@ static int cdns_uart_probe(struct platform_device *pdev)
        struct uart_port *port;
        struct resource *res;
        struct cdns_uart *cdns_uart_data;
+       const struct of_device_id *match;
 
        cdns_uart_data = devm_kzalloc(&pdev->dev, sizeof(*cdns_uart_data),
                        GFP_KERNEL);
        if (!cdns_uart_data)
                return -ENOMEM;
 
+       match = of_match_node(cdns_uart_of_match, pdev->dev.of_node);
+       if (match && match->data) {
+               const struct cdns_platform_data *data = match->data;
+
+               cdns_uart_data->quirks = data->quirks;
+       }
+
        cdns_uart_data->pclk = devm_clk_get(&pdev->dev, "pclk");
        if (IS_ERR(cdns_uart_data->pclk)) {
                cdns_uart_data->pclk = devm_clk_get(&pdev->dev, "aper_clk");
@@ -1471,14 +1595,6 @@ static int cdns_uart_remove(struct platform_device *pdev)
        return rc;
 }
 
-/* Match table for of_platform binding */
-static const struct of_device_id cdns_uart_of_match[] = {
-       { .compatible = "xlnx,xuartps", },
-       { .compatible = "cdns,uart-r1p8", },
-       {}
-};
-MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
-
 static struct platform_driver cdns_uart_platform_driver = {
        .probe   = cdns_uart_probe,
        .remove  = cdns_uart_remove,