serial: st-asc: Provide RTS functionality
[sfrench/cifs-2.6.git] / drivers / tty / serial / st-asc.c
index 379e5bd37df947013b71440333bc0d268936b5aa..397df507579e943edb72e53358b7f473b5a6a9bf 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/of_platform.h>
 #include <linux/serial_core.h>
 #include <linux/clk.h>
+#include <linux/gpio/consumer.h>
 
 #define DRIVER_NAME "st-asc"
 #define ASC_SERIAL_NAME "ttyAS"
@@ -38,6 +39,7 @@
 
 struct asc_port {
        struct uart_port port;
+       struct gpio_desc *rts;
        struct clk *clk;
        unsigned int hw_flow_control:1;
        unsigned int force_m1:1;
@@ -287,9 +289,19 @@ static void asc_transmit_chars(struct uart_port *port)
 static void asc_receive_chars(struct uart_port *port)
 {
        struct tty_port *tport = &port->state->port;
-       unsigned long status;
+       unsigned long status, mode;
        unsigned long c = 0;
        char flag;
+       bool ignore_pe = false;
+
+       /*
+        * Datasheet states: If the MODE field selects an 8-bit frame then
+        * this [parity error] bit is undefined. Software should ignore this
+        * bit when reading 8-bit frames.
+        */
+       mode = asc_in(port, ASC_CTL) & ASC_CTL_MODE_MSK;
+       if (mode == ASC_CTL_MODE_8BIT || mode == ASC_CTL_MODE_8BIT_PAR)
+               ignore_pe = true;
 
        if (port->irq_wake)
                pm_wakeup_event(tport->tty->dev, 0);
@@ -299,8 +311,8 @@ static void asc_receive_chars(struct uart_port *port)
                flag = TTY_NORMAL;
                port->icount.rx++;
 
-               if ((c & (ASC_RXBUF_FE | ASC_RXBUF_PE)) ||
-                       status & ASC_STA_OE) {
+               if (status & ASC_STA_OE || c & ASC_RXBUF_FE ||
+                   (c & ASC_RXBUF_PE && !ignore_pe)) {
 
                        if (c & ASC_RXBUF_FE) {
                                if (c == (ASC_RXBUF_FE | ASC_RXBUF_DUMMY_RX)) {
@@ -381,12 +393,27 @@ static unsigned int asc_tx_empty(struct uart_port *port)
 
 static void asc_set_mctrl(struct uart_port *port, unsigned int mctrl)
 {
+       struct asc_port *ascport = to_asc_port(port);
+
        /*
-        * This routine is used for seting signals of: DTR, DCD, CTS/RTS
-        * We use ASC's hardware for CTS/RTS, so don't need any for that.
-        * Some boards have DTR and DCD implemented using PIO pins,
-        * code to do this should be hooked in here.
+        * This routine is used for seting signals of: DTR, DCD, CTS and RTS.
+        * We use ASC's hardware for CTS/RTS when hardware flow-control is
+        * enabled, however if the RTS line is required for another purpose,
+        * commonly controlled using HUP from userspace, then we need to toggle
+        * it manually, using GPIO.
+        *
+        * Some boards also have DTR and DCD implemented using PIO pins, code to
+        * do this should be hooked in here.
         */
+
+       if (!ascport->rts)
+               return;
+
+       /* If HW flow-control is enabled, we can't fiddle with the RTS line */
+       if (asc_in(port, ASC_CTL) & ASC_CTL_CTSENABLE)
+               return;
+
+       gpiod_set_value(ascport->rts, mctrl & TIOCM_RTS);
 }
 
 static unsigned int asc_get_mctrl(struct uart_port *port)
@@ -716,6 +743,8 @@ static struct asc_port *asc_of_get_asc_port(struct platform_device *pdev)
                                                        "st,hw-flow-control");
        asc_ports[id].force_m1 =  of_property_read_bool(np, "st,force_m1");
        asc_ports[id].port.line = id;
+       asc_ports[id].rts = NULL;
+
        return &asc_ports[id];
 }