serial: stm32: add support for RS485 hardware control mode
authorBich HEMON <bich.hemon@st.com>
Mon, 12 Mar 2018 09:50:05 +0000 (09:50 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 14 Mar 2018 12:35:37 +0000 (13:35 +0100)
Implement Driver Enable signal (DE) to activate the transmission mode
of the external transceiver.

Signed-off-by: Yves Coppeaux <yves.coppeaux@st.com>
Signed-off-by: Bich Hemon <bich.hemon@st.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/stm32-usart.c
drivers/tty/serial/stm32-usart.h

index 0fa735b60f2dcb2e65d3b8cee7442a0fa0418673..345fbf3142696f02e94d522017e74d7b4013e78e 100644 (file)
@@ -62,6 +62,113 @@ static void stm32_clr_bits(struct uart_port *port, u32 reg, u32 bits)
        writel_relaxed(val, port->membase + reg);
 }
 
+static void stm32_config_reg_rs485(u32 *cr1, u32 *cr3, u32 delay_ADE,
+                                  u32 delay_DDE, u32 baud)
+{
+       u32 rs485_deat_dedt;
+       u32 rs485_deat_dedt_max = (USART_CR1_DEAT_MASK >> USART_CR1_DEAT_SHIFT);
+       bool over8;
+
+       *cr3 |= USART_CR3_DEM;
+       over8 = *cr1 & USART_CR1_OVER8;
+
+       if (over8)
+               rs485_deat_dedt = delay_ADE * baud * 8;
+       else
+               rs485_deat_dedt = delay_ADE * baud * 16;
+
+       rs485_deat_dedt = DIV_ROUND_CLOSEST(rs485_deat_dedt, 1000);
+       rs485_deat_dedt = rs485_deat_dedt > rs485_deat_dedt_max ?
+                         rs485_deat_dedt_max : rs485_deat_dedt;
+       rs485_deat_dedt = (rs485_deat_dedt << USART_CR1_DEAT_SHIFT) &
+                          USART_CR1_DEAT_MASK;
+       *cr1 |= rs485_deat_dedt;
+
+       if (over8)
+               rs485_deat_dedt = delay_DDE * baud * 8;
+       else
+               rs485_deat_dedt = delay_DDE * baud * 16;
+
+       rs485_deat_dedt = DIV_ROUND_CLOSEST(rs485_deat_dedt, 1000);
+       rs485_deat_dedt = rs485_deat_dedt > rs485_deat_dedt_max ?
+                         rs485_deat_dedt_max : rs485_deat_dedt;
+       rs485_deat_dedt = (rs485_deat_dedt << USART_CR1_DEDT_SHIFT) &
+                          USART_CR1_DEDT_MASK;
+       *cr1 |= rs485_deat_dedt;
+}
+
+static int stm32_config_rs485(struct uart_port *port,
+                             struct serial_rs485 *rs485conf)
+{
+       struct stm32_port *stm32_port = to_stm32_port(port);
+       struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
+       struct stm32_usart_config *cfg = &stm32_port->info->cfg;
+       u32 usartdiv, baud, cr1, cr3;
+       bool over8;
+       unsigned long flags;
+
+       spin_lock_irqsave(&port->lock, flags);
+       stm32_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
+
+       port->rs485 = *rs485conf;
+
+       rs485conf->flags |= SER_RS485_RX_DURING_TX;
+
+       if (rs485conf->flags & SER_RS485_ENABLED) {
+               cr1 = readl_relaxed(port->membase + ofs->cr1);
+               cr3 = readl_relaxed(port->membase + ofs->cr3);
+               usartdiv = readl_relaxed(port->membase + ofs->brr);
+               usartdiv = usartdiv & GENMASK(15, 0);
+               over8 = cr1 & USART_CR1_OVER8;
+
+               if (over8)
+                       usartdiv = usartdiv | (usartdiv & GENMASK(4, 0))
+                                  << USART_BRR_04_R_SHIFT;
+
+               baud = DIV_ROUND_CLOSEST(port->uartclk, usartdiv);
+               stm32_config_reg_rs485(&cr1, &cr3,
+                                      rs485conf->delay_rts_before_send,
+                                      rs485conf->delay_rts_after_send, baud);
+
+               if (rs485conf->flags & SER_RS485_RTS_ON_SEND) {
+                       cr3 &= ~USART_CR3_DEP;
+                       rs485conf->flags &= ~SER_RS485_RTS_AFTER_SEND;
+               } else {
+                       cr3 |= USART_CR3_DEP;
+                       rs485conf->flags |= SER_RS485_RTS_AFTER_SEND;
+               }
+
+               writel_relaxed(cr3, port->membase + ofs->cr3);
+               writel_relaxed(cr1, port->membase + ofs->cr1);
+       } else {
+               stm32_clr_bits(port, ofs->cr3, USART_CR3_DEM | USART_CR3_DEP);
+               stm32_clr_bits(port, ofs->cr1,
+                              USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK);
+       }
+
+       stm32_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
+       spin_unlock_irqrestore(&port->lock, flags);
+
+       return 0;
+}
+
+static int stm32_init_rs485(struct uart_port *port,
+                           struct platform_device *pdev)
+{
+       struct serial_rs485 *rs485conf = &port->rs485;
+
+       rs485conf->flags = 0;
+       rs485conf->delay_rts_before_send = 0;
+       rs485conf->delay_rts_after_send = 0;
+
+       if (!pdev->dev.of_node)
+               return -ENODEV;
+
+       uart_get_rs485_mode(&pdev->dev, rs485conf);
+
+       return 0;
+}
+
 static int stm32_pending_rx(struct uart_port *port, u32 *sr, int *last_res,
                            bool threaded)
 {
@@ -498,6 +605,7 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
        struct stm32_port *stm32_port = to_stm32_port(port);
        struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
        struct stm32_usart_config *cfg = &stm32_port->info->cfg;
+       struct serial_rs485 *rs485conf = &port->rs485;
        unsigned int baud;
        u32 usartdiv, mantissa, fraction, oversampling;
        tcflag_t cflag = termios->c_cflag;
@@ -515,7 +623,7 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
        writel_relaxed(0, port->membase + ofs->cr1);
 
        cr1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
-       cr1 |= BIT(cfg->uart_enable_bit);
+
        if (stm32_port->fifoen)
                cr1 |= USART_CR1_FIFOEN;
        cr2 = 0;
@@ -553,9 +661,11 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
         */
        if (usartdiv < 16) {
                oversampling = 8;
+               cr1 |= USART_CR1_OVER8;
                stm32_set_bits(port, ofs->cr1, USART_CR1_OVER8);
        } else {
                oversampling = 16;
+               cr1 &= ~USART_CR1_OVER8;
                stm32_clr_bits(port, ofs->cr1, USART_CR1_OVER8);
        }
 
@@ -592,10 +702,28 @@ static void stm32_set_termios(struct uart_port *port, struct ktermios *termios,
        if (stm32_port->rx_ch)
                cr3 |= USART_CR3_DMAR;
 
+       if (rs485conf->flags & SER_RS485_ENABLED) {
+               stm32_config_reg_rs485(&cr1, &cr3,
+                                      rs485conf->delay_rts_before_send,
+                                      rs485conf->delay_rts_after_send, baud);
+               if (rs485conf->flags & SER_RS485_RTS_ON_SEND) {
+                       cr3 &= ~USART_CR3_DEP;
+                       rs485conf->flags &= ~SER_RS485_RTS_AFTER_SEND;
+               } else {
+                       cr3 |= USART_CR3_DEP;
+                       rs485conf->flags |= SER_RS485_RTS_AFTER_SEND;
+               }
+
+       } else {
+               cr3 &= ~(USART_CR3_DEM | USART_CR3_DEP);
+               cr1 &= ~(USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK);
+       }
+
        writel_relaxed(cr3, port->membase + ofs->cr3);
        writel_relaxed(cr2, port->membase + ofs->cr2);
        writel_relaxed(cr1, port->membase + ofs->cr1);
 
+       stm32_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
        spin_unlock_irqrestore(&port->lock, flags);
 }
 
index 2294d0f0587204e29ac9d5a6685c0a21af9ecbbb..6f294e280ea301a72be694b53f3dfa1374b397c3 100644 (file)
@@ -135,6 +135,7 @@ struct stm32_usart_info stm32h7_info = {
 #define USART_BRR_DIV_F_MASK   GENMASK(3, 0)
 #define USART_BRR_DIV_M_MASK   GENMASK(15, 4)
 #define USART_BRR_DIV_M_SHIFT  4
+#define USART_BRR_04_R_SHIFT   1
 
 /* USART_CR1 */
 #define USART_CR1_SBK          BIT(0)
@@ -162,6 +163,8 @@ struct stm32_usart_info stm32h7_info = {
 #define USART_CR1_M1           BIT(28)         /* F7 */
 #define USART_CR1_IE_MASK      (GENMASK(8, 4) | BIT(14) | BIT(26) | BIT(27))
 #define USART_CR1_FIFOEN       BIT(29)         /* H7 */
+#define USART_CR1_DEAT_SHIFT 21
+#define USART_CR1_DEDT_SHIFT 16
 
 /* USART_CR2 */
 #define USART_CR2_ADD_MASK     GENMASK(3, 0)   /* F4 */