serial: xuartps: Adds RXBS register support for zynqmp
[cascardo/linux.git] / drivers / tty / serial / xilinx_uartps.c
index 11a2b36..bf06d2c 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:
@@ -131,8 +135,9 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
                                 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,18 +177,29 @@ 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)
 {
+       struct cdns_uart *cdns_uart = port->private_data;
+       bool is_rxbs_support;
+       unsigned int rxbs_status = 0;
+       unsigned int status_mask;
+
+       is_rxbs_support = cdns_uart->quirks & CDNS_UART_RXBS_SUPPORT;
+
        /*
         * 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) {
+       if (!is_rxbs_support && (isrstatus & CDNS_UART_IXR_FRAMING)) {
                while (!(readl(port->membase + CDNS_UART_SR) &
                                        CDNS_UART_SR_RXEMPTY)) {
                        if (!readl(port->membase + CDNS_UART_FIFO)) {
@@ -200,6 +216,8 @@ static void cdns_uart_handle_rx(struct uart_port *port, unsigned int isrstatus)
 
        isrstatus &= port->read_status_mask;
        isrstatus &= ~port->ignore_status_mask;
+       status_mask = port->read_status_mask;
+       status_mask &= ~port->ignore_status_mask;
 
        if (!(isrstatus & (CDNS_UART_IXR_TOUT | CDNS_UART_IXR_RXTRIG)))
                return;
@@ -208,6 +226,9 @@ static void cdns_uart_handle_rx(struct uart_port *port, unsigned int isrstatus)
                u32 data;
                char status = TTY_NORMAL;
 
+               if (is_rxbs_support)
+                       rxbs_status = readl(port->membase + CDNS_UART_RXBS);
+
                data = readl(port->membase + CDNS_UART_FIFO);
 
                /* Non-NULL byte after BREAK is garbage (99%) */
@@ -217,21 +238,41 @@ static void cdns_uart_handle_rx(struct uart_port *port, unsigned int isrstatus)
                        if (uart_handle_break(port))
                                continue;
                }
+               if (is_rxbs_support && (rxbs_status & CDNS_UART_RXBS_BRK)) {
+                       port->icount.brk++;
+                       status = TTY_BREAK;
+                       if (uart_handle_break(port))
+                               continue;
+               }
 
                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) {
-                       port->icount.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) {
+                               port->icount.frame++;
+                               status = TTY_FRAME;
+                       }
                }
+               if (isrstatus & CDNS_UART_IXR_OVERRUN)
+                       port->icount.overrun++;
 
                uart_insert_char(port, isrstatus, CDNS_UART_IXR_OVERRUN,
                                 data, status);
@@ -736,10 +777,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 */
@@ -794,7 +839,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;
 }
@@ -1328,6 +1377,18 @@ 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 },
+       {}
+};
+MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
+
 /**
  * cdns_uart_probe - Platform driver probe
  * @pdev: Pointer to the platform device structure
@@ -1340,12 +1401,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 +1540,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,