serial: mcf: Add support RS485 in ColdFire serial driver
authorQuoc-Viet Nguyen <afelion@gmail.com>
Mon, 14 Jan 2013 23:32:53 +0000 (09:32 +1000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 16 Jan 2013 06:56:34 +0000 (22:56 -0800)
Signed-off-by: Quoc-Viet Nguyen <afelion@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/mcf.c

index 7ed99274572f39b0427b904d1ae5423e75a7c3d2..e956377a38fe53e299766724c278a05a35d802c4 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/serial.h>
 #include <linux/serial_core.h>
 #include <linux/io.h>
+#include <linux/uaccess.h>
 #include <asm/coldfire.h>
 #include <asm/mcfsim.h>
 #include <asm/mcfuart.h>
@@ -55,6 +56,7 @@ struct mcf_uart {
        struct uart_port        port;
        unsigned int            sigs;           /* Local copy of line sigs */
        unsigned char           imr;            /* Local IMR mirror */
+       struct serial_rs485     rs485;          /* RS485 settings */
 };
 
 /****************************************************************************/
@@ -101,6 +103,12 @@ static void mcf_start_tx(struct uart_port *port)
 {
        struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
 
+       if (pp->rs485.flags & SER_RS485_ENABLED) {
+               /* Enable Transmitter */
+               writeb(MCFUART_UCR_TXENABLE, port->membase + MCFUART_UCR);
+               /* Manually assert RTS */
+               writeb(MCFUART_UOP_RTS, port->membase + MCFUART_UOP1);
+       }
        pp->imr |= MCFUART_UIR_TXREADY;
        writeb(pp->imr, port->membase + MCFUART_UIMR);
 }
@@ -196,6 +204,7 @@ static void mcf_shutdown(struct uart_port *port)
 static void mcf_set_termios(struct uart_port *port, struct ktermios *termios,
        struct ktermios *old)
 {
+       struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
        unsigned long flags;
        unsigned int baud, baudclk;
 #if defined(CONFIG_M5272)
@@ -248,6 +257,11 @@ static void mcf_set_termios(struct uart_port *port, struct ktermios *termios,
                mr2 |= MCFUART_MR2_TXCTS;
        }
 
+       if (pp->rs485.flags & SER_RS485_ENABLED) {
+               dev_dbg(port->dev, "Setting UART to RS485\n");
+               mr2 |= MCFUART_MR2_TXRTS;
+       }
+
        spin_lock_irqsave(&port->lock, flags);
        uart_update_timeout(port, termios->c_cflag, baud);
        writeb(MCFUART_UCR_CMDRESETRX, port->membase + MCFUART_UCR);
@@ -342,6 +356,10 @@ static void mcf_tx_chars(struct mcf_uart *pp)
        if (xmit->head == xmit->tail) {
                pp->imr &= ~MCFUART_UIR_TXREADY;
                writeb(pp->imr, port->membase + MCFUART_UIMR);
+               /* Disable TX to negate RTS automatically */
+               if (pp->rs485.flags & SER_RS485_ENABLED)
+                       writeb(MCFUART_UCR_TXDISABLE,
+                               port->membase + MCFUART_UCR);
        }
 }
 
@@ -418,6 +436,58 @@ static int mcf_verify_port(struct uart_port *port, struct serial_struct *ser)
 
 /****************************************************************************/
 
+/* Enable or disable the RS485 support */
+static void mcf_config_rs485(struct uart_port *port, struct serial_rs485 *rs485)
+{
+       struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
+       unsigned long flags;
+       unsigned char mr1, mr2;
+
+       spin_lock_irqsave(&port->lock, flags);
+       /* Get mode registers */
+       mr1 = readb(port->membase + MCFUART_UMR);
+       mr2 = readb(port->membase + MCFUART_UMR);
+       if (rs485->flags & SER_RS485_ENABLED) {
+               dev_dbg(port->dev, "Setting UART to RS485\n");
+               /* Automatically negate RTS after TX completes */
+               mr2 |= MCFUART_MR2_TXRTS;
+       } else {
+               dev_dbg(port->dev, "Setting UART to RS232\n");
+               mr2 &= ~MCFUART_MR2_TXRTS;
+       }
+       writeb(mr1, port->membase + MCFUART_UMR);
+       writeb(mr2, port->membase + MCFUART_UMR);
+       pp->rs485 = *rs485;
+       spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int mcf_ioctl(struct uart_port *port, unsigned int cmd,
+               unsigned long arg)
+{
+       switch (cmd) {
+       case TIOCSRS485: {
+               struct serial_rs485 rs485;
+               if (copy_from_user(&rs485, (struct serial_rs485 *)arg,
+                               sizeof(struct serial_rs485)))
+                       return -EFAULT;
+               mcf_config_rs485(port, &rs485);
+               break;
+       }
+       case TIOCGRS485: {
+               struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
+               if (copy_to_user((struct serial_rs485 *)arg, &pp->rs485,
+                               sizeof(struct serial_rs485)))
+                       return -EFAULT;
+               break;
+       }
+       default:
+               return -ENOIOCTLCMD;
+       }
+       return 0;
+}
+
+/****************************************************************************/
+
 /*
  *     Define the basic serial functions we support.
  */
@@ -438,6 +508,7 @@ static const struct uart_ops mcf_uart_ops = {
        .release_port   = mcf_release_port,
        .config_port    = mcf_config_port,
        .verify_port    = mcf_verify_port,
+       .ioctl          = mcf_ioctl,
 };
 
 static struct mcf_uart mcf_ports[4];