serial: fsl_lpuart: zero out parity bit in CS7 mode
authorShenwei Wang <shenwei.wang@nxp.com>
Thu, 14 Jul 2022 18:58:58 +0000 (13:58 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 28 Jul 2022 08:34:04 +0000 (10:34 +0200)
The LPUART hardware doesn't zero out the parity bit on the received
characters. This behavior won't impact the use cases of CS8 because
the parity bit is the 9th bit which is not currently used by software.
But the parity bit for CS7 must be zeroed out by software in order to
get the correct raw data.

Signed-off-by: Shenwei Wang <shenwei.wang@nxp.com>
Link: https://lore.kernel.org/r/20220714185858.615373-1-shenwei.wang@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/fsl_lpuart.c

index fc7d235a1e270b40fdb8d1c1d5316471a009b1a4..afa0f941c862f2d0b2e11f52a9e7a63b7fac206c 100644 (file)
@@ -274,6 +274,8 @@ struct lpuart_port {
        int                     rx_dma_rng_buf_len;
        unsigned int            dma_tx_nents;
        wait_queue_head_t       dma_wait;
+       bool                    is_cs7; /* Set to true when character size is 7 */
+                                       /* and the parity is enabled            */
 };
 
 struct lpuart_soc_data {
@@ -1022,6 +1024,9 @@ static void lpuart32_rxint(struct lpuart_port *sport)
                                flg = TTY_OVERRUN;
                }
 
+               if (sport->is_cs7)
+                       rx &= 0x7F;
+
                if (tty_insert_flip_char(port, rx, flg) == 0)
                        sport->port.icount.buf_overrun++;
        }
@@ -1107,6 +1112,17 @@ static void lpuart_handle_sysrq(struct lpuart_port *sport)
        }
 }
 
+static int lpuart_tty_insert_flip_string(struct tty_port *port,
+       unsigned char *chars, size_t size, bool is_cs7)
+{
+       int i;
+
+       if (is_cs7)
+               for (i = 0; i < size; i++)
+                       chars[i] &= 0x7F;
+       return tty_insert_flip_string(port, chars, size);
+}
+
 static void lpuart_copy_rx_to_tty(struct lpuart_port *sport)
 {
        struct tty_port *port = &sport->port.state->port;
@@ -1217,7 +1233,8 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport)
        if (ring->head < ring->tail) {
                count = sport->rx_sgl.length - ring->tail;
 
-               copied = tty_insert_flip_string(port, ring->buf + ring->tail, count);
+               copied = lpuart_tty_insert_flip_string(port, ring->buf + ring->tail,
+                                       count, sport->is_cs7);
                if (copied != count)
                        sport->port.icount.buf_overrun++;
                ring->tail = 0;
@@ -1227,7 +1244,8 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport)
        /* Finally we read data from tail to head */
        if (ring->tail < ring->head) {
                count = ring->head - ring->tail;
-               copied = tty_insert_flip_string(port, ring->buf + ring->tail, count);
+               copied = lpuart_tty_insert_flip_string(port, ring->buf + ring->tail,
+                                       count, sport->is_cs7);
                if (copied != count)
                        sport->port.icount.buf_overrun++;
                /* Wrap ring->head if needed */
@@ -2066,6 +2084,7 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios,
        ctrl = old_ctrl = lpuart32_read(&sport->port, UARTCTRL);
        bd = lpuart32_read(&sport->port, UARTBAUD);
        modem = lpuart32_read(&sport->port, UARTMODIR);
+       sport->is_cs7 = false;
        /*
         * only support CS8 and CS7, and for CS7 must enable PE.
         * supported mode:
@@ -2184,6 +2203,9 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios,
        lpuart32_write(&sport->port, ctrl, UARTCTRL);
        /* restore control register */
 
+       if ((ctrl & (UARTCTRL_PE | UARTCTRL_M)) == UARTCTRL_PE)
+               sport->is_cs7 = true;
+
        if (old && sport->lpuart_dma_rx_use) {
                if (!lpuart_start_rx_dma(sport))
                        rx_dma_timer_init(sport);