tty/serial: atmel: add fractional baud rate support
authorLudovic Desroches <ludovic.desroches@atmel.com>
Thu, 25 Aug 2016 13:47:56 +0000 (15:47 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 31 Aug 2016 13:39:58 +0000 (15:39 +0200)
The USART device provides a fractional baud rate generator to get a more
accurate baud rate. It can be used only when the USART is configured in
'normal mode' and this feature is not available on AT91RM9200 SoC.

Signed-off-by: Ludovic Desroches <ludovic.desroches@atmel.com>
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/atmel_serial.c
include/linux/atmel_serial.h

index 2eaa18ddef6171640729bf8a770363331f5edaa3..17592390e85c433ffb69a2fc5f29e9c16a138281 100644 (file)
@@ -166,6 +166,7 @@ struct atmel_uart_port {
        u32                     rts_low;
        bool                    ms_irq_enabled;
        u32                     rtor;   /* address of receiver timeout register if it exists */
+       bool                    has_frac_baudrate;
        bool                    has_hw_timer;
        struct timer_list       uart_timer;
 
@@ -1745,6 +1746,11 @@ static void atmel_get_ip_name(struct uart_port *port)
        dbgu_uart = 0x44424755; /* DBGU */
        new_uart = 0x55415254;  /* UART */
 
+       /*
+        * Only USART devices from at91sam9260 SOC implement fractional
+        * baudrate.
+        */
+       atmel_port->has_frac_baudrate = false;
        atmel_port->has_hw_timer = false;
 
        if (name == new_uart) {
@@ -1753,6 +1759,7 @@ static void atmel_get_ip_name(struct uart_port *port)
                atmel_port->rtor = ATMEL_UA_RTOR;
        } else if (name == usart) {
                dev_dbg(port->dev, "Usart\n");
+               atmel_port->has_frac_baudrate = true;
                atmel_port->has_hw_timer = true;
                atmel_port->rtor = ATMEL_US_RTOR;
        } else if (name == dbgu_uart) {
@@ -1764,6 +1771,7 @@ static void atmel_get_ip_name(struct uart_port *port)
                case 0x302:
                case 0x10213:
                        dev_dbg(port->dev, "This version is usart\n");
+                       atmel_port->has_frac_baudrate = true;
                        atmel_port->has_hw_timer = true;
                        atmel_port->rtor = ATMEL_US_RTOR;
                        break;
@@ -2025,8 +2033,9 @@ static void atmel_serial_pm(struct uart_port *port, unsigned int state,
 static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
                              struct ktermios *old)
 {
+       struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
        unsigned long flags;
-       unsigned int old_mode, mode, imr, quot, baud;
+       unsigned int old_mode, mode, imr, quot, baud, div, cd, fp = 0;
 
        /* save the current mode register */
        mode = old_mode = atmel_uart_readl(port, ATMEL_US_MR);
@@ -2036,12 +2045,6 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
                  ATMEL_US_PAR | ATMEL_US_USMODE);
 
        baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
-       quot = uart_get_divisor(port, baud);
-
-       if (quot > 65535) {     /* BRGR is 16-bit, so switch to slower clock */
-               quot /= 8;
-               mode |= ATMEL_US_USCLKS_MCK_DIV8;
-       }
 
        /* byte size */
        switch (termios->c_cflag & CSIZE) {
@@ -2160,7 +2163,29 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
                atmel_uart_writel(port, ATMEL_US_CR, rts_state);
        }
 
-       /* set the baud rate */
+       /*
+        * Set the baud rate:
+        * Fractional baudrate allows to setup output frequency more
+        * accurately. This feature is enabled only when using normal mode.
+        * baudrate = selected clock / (8 * (2 - OVER) * (CD + FP / 8))
+        * Currently, OVER is always set to 0 so we get
+        * baudrate = selected clock (16 * (CD + FP / 8))
+        */
+       if (atmel_port->has_frac_baudrate &&
+           (mode & ATMEL_US_USMODE) == ATMEL_US_USMODE_NORMAL) {
+               div = DIV_ROUND_CLOSEST(port->uartclk, baud);
+               cd = div / 16;
+               fp = DIV_ROUND_CLOSEST(div % 16, 2);
+       } else {
+               cd = uart_get_divisor(port, baud);
+       }
+
+       if (cd > 65535) {       /* BRGR is 16-bit, so switch to slower clock */
+               cd /= 8;
+               mode |= ATMEL_US_USCLKS_MCK_DIV8;
+       }
+       quot = cd | fp << ATMEL_US_FP_OFFSET;
+
        atmel_uart_writel(port, ATMEL_US_BRGR, quot);
        atmel_uart_writel(port, ATMEL_US_CR, ATMEL_US_RSTSTA | ATMEL_US_RSTRX);
        atmel_uart_writel(port, ATMEL_US_CR, ATMEL_US_TXEN | ATMEL_US_RXEN);
index 5a4d664af87a7a8034943827ad806b9043559c52..f8e452aa48d7769b1d4dd7646f4947ce2a821136 100644 (file)
 
 #define ATMEL_US_BRGR          0x20    /* Baud Rate Generator Register */
 #define        ATMEL_US_CD             GENMASK(15, 0)  /* Clock Divider */
+#define ATMEL_US_FP_OFFSET     16      /* Fractional Part */
 
 #define ATMEL_US_RTOR          0x24    /* Receiver Time-out Register for USART */
 #define ATMEL_UA_RTOR          0x28    /* Receiver Time-out Register for UART */