tty: serial: uartps: Add rs485 support to uartps driver
authorManikanta Guntupalli <manikanta.guntupalli@amd.com>
Tue, 23 Jan 2024 06:16:55 +0000 (11:46 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 28 Jan 2024 03:07:08 +0000 (19:07 -0800)
Add rs485 support to uartps driver. Use either rts-gpios or RTS
to control RS485 phy as driver or a receiver.

Signed-off-by: Manikanta Guntupalli <manikanta.guntupalli@amd.com>
Link: https://lore.kernel.org/r/20240123061655.2150946-4-manikanta.guntupalli@amd.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/xilinx_uartps.c

index 765a6e174052ae2aa117486cd74eb33dcaa0776e..5f48ec37cb25ff2e9b55950e5a7cfee7d35397bf 100644 (file)
@@ -22,7 +22,9 @@
 #include <linux/of.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
-#include <linux/iopoll.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
 
 #define CDNS_UART_TTY_NAME     "ttyPS"
 #define CDNS_UART_NAME         "xuartps"
@@ -193,6 +195,9 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
  * @clk_rate_change_nb:        Notifier block for clock changes
  * @quirks:            Flags for RXBS support.
  * @cts_override:      Modem control state override
+ * @gpiod_rts:         Pointer to the gpio descriptor
+ * @rs485_tx_started:  RS485 tx state
+ * @tx_timer:          Timer for tx
  */
 struct cdns_uart {
        struct uart_port        *port;
@@ -203,10 +208,21 @@ struct cdns_uart {
        struct notifier_block   clk_rate_change_nb;
        u32                     quirks;
        bool cts_override;
+       struct gpio_desc        *gpiod_rts;
+       bool                    rs485_tx_started;
+       struct hrtimer          tx_timer;
 };
 struct cdns_platform_data {
        u32 quirks;
 };
+
+struct serial_rs485 cdns_rs485_supported = {
+       .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
+                SER_RS485_RTS_AFTER_SEND,
+       .delay_rts_before_send = 1,
+       .delay_rts_after_send = 1,
+};
+
 #define to_cdns_uart(_nb) container_of(_nb, struct cdns_uart, \
                clk_rate_change_nb)
 
@@ -305,6 +321,55 @@ static void cdns_uart_handle_rx(void *dev_id, unsigned int isrstatus)
        tty_flip_buffer_push(&port->state->port);
 }
 
+/**
+ * cdns_rts_gpio_enable - Configure RTS/GPIO to high/low
+ * @cdns_uart: Handle to the cdns_uart
+ * @enable: Value to be set to RTS/GPIO
+ */
+static void cdns_rts_gpio_enable(struct cdns_uart *cdns_uart, bool enable)
+{
+       u32 val;
+
+       if (cdns_uart->gpiod_rts) {
+               gpiod_set_value(cdns_uart->gpiod_rts, enable);
+       } else {
+               val = readl(cdns_uart->port->membase + CDNS_UART_MODEMCR);
+               if (enable)
+                       val |= CDNS_UART_MODEMCR_RTS;
+               else
+                       val &= ~CDNS_UART_MODEMCR_RTS;
+               writel(val, cdns_uart->port->membase + CDNS_UART_MODEMCR);
+       }
+}
+
+/**
+ * cdns_rs485_tx_setup - Tx setup specific to rs485
+ * @cdns_uart: Handle to the cdns_uart
+ */
+static void cdns_rs485_tx_setup(struct cdns_uart *cdns_uart)
+{
+       bool enable;
+
+       enable = cdns_uart->port->rs485.flags & SER_RS485_RTS_ON_SEND;
+       cdns_rts_gpio_enable(cdns_uart, enable);
+
+       cdns_uart->rs485_tx_started = true;
+}
+
+/**
+ * cdns_rs485_rx_setup - Rx setup specific to rs485
+ * @cdns_uart: Handle to the cdns_uart
+ */
+static void cdns_rs485_rx_setup(struct cdns_uart *cdns_uart)
+{
+       bool enable;
+
+       enable = cdns_uart->port->rs485.flags & SER_RS485_RTS_AFTER_SEND;
+       cdns_rts_gpio_enable(cdns_uart, enable);
+
+       cdns_uart->rs485_tx_started = false;
+}
+
 /**
  * cdns_uart_tx_empty -  Check whether TX is empty
  * @port: Handle to the uart port structure
@@ -320,6 +385,37 @@ static unsigned int cdns_uart_tx_empty(struct uart_port *port)
        return (status == CDNS_UART_SR_TXEMPTY) ? TIOCSER_TEMT : 0;
 }
 
+/**
+ * cdns_rs485_rx_callback - Timer rx callback handler for rs485.
+ * @t: Handle to the hrtimer structure
+ */
+static enum hrtimer_restart cdns_rs485_rx_callback(struct hrtimer *t)
+{
+       struct cdns_uart *cdns_uart = container_of(t, struct cdns_uart, tx_timer);
+
+       /*
+        * Default Rx should be setup, because Rx signaling path
+        * need to enable to receive data.
+        */
+       cdns_rs485_rx_setup(cdns_uart);
+
+       return HRTIMER_NORESTART;
+}
+
+/**
+ * cdns_calc_after_tx_delay - calculate delay required for after tx.
+ * @cdns_uart: Handle to the cdns_uart
+ */
+static u64 cdns_calc_after_tx_delay(struct cdns_uart *cdns_uart)
+{
+       /*
+        * Frame time + stop bit time + rs485.delay_rts_after_send
+        */
+       return cdns_uart->port->frame_time
+              + DIV_ROUND_UP(cdns_uart->port->frame_time, 7)
+              + (u64)cdns_uart->port->rs485.delay_rts_after_send * NSEC_PER_MSEC;
+}
+
 /**
  * cdns_uart_handle_tx - Handle the bytes to be transmitted.
  * @dev_id: Id of the UART port
@@ -328,6 +424,7 @@ static unsigned int cdns_uart_tx_empty(struct uart_port *port)
 static void cdns_uart_handle_tx(void *dev_id)
 {
        struct uart_port *port = (struct uart_port *)dev_id;
+       struct cdns_uart *cdns_uart = port->private_data;
        struct circ_buf *xmit = &port->state->xmit;
        unsigned int numbytes;
 
@@ -348,6 +445,16 @@ static void cdns_uart_handle_tx(void *dev_id)
 
        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
                uart_write_wakeup(port);
+
+       /* Enable the TX Empty interrupt */
+       writel(CDNS_UART_IXR_TXEMPTY, cdns_uart->port->membase + CDNS_UART_IER);
+
+       if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED &&
+           (uart_circ_empty(xmit) || uart_tx_stopped(port))) {
+               cdns_uart->tx_timer.function = &cdns_rs485_rx_callback;
+               hrtimer_start(&cdns_uart->tx_timer,
+                             ns_to_ktime(cdns_calc_after_tx_delay(cdns_uart)), HRTIMER_MODE_REL);
+       }
 }
 
 /**
@@ -580,6 +687,21 @@ static int cdns_uart_clk_notifier_cb(struct notifier_block *nb,
 }
 #endif
 
+/**
+ * cdns_rs485_tx_callback - Timer tx callback handler for rs485.
+ * @t: Handle to the hrtimer structure
+ */
+static enum hrtimer_restart cdns_rs485_tx_callback(struct hrtimer *t)
+{
+       struct cdns_uart *cdns_uart = container_of(t, struct cdns_uart, tx_timer);
+
+       uart_port_lock(cdns_uart->port);
+       cdns_uart_handle_tx(cdns_uart->port);
+       uart_port_unlock(cdns_uart->port);
+
+       return HRTIMER_NORESTART;
+}
+
 /**
  * cdns_uart_start_tx -  Start transmitting bytes
  * @port: Handle to the uart port structure
@@ -587,6 +709,7 @@ static int cdns_uart_clk_notifier_cb(struct notifier_block *nb,
 static void cdns_uart_start_tx(struct uart_port *port)
 {
        unsigned int status;
+       struct cdns_uart *cdns_uart = port->private_data;
 
        if (uart_tx_stopped(port))
                return;
@@ -606,10 +729,16 @@ static void cdns_uart_start_tx(struct uart_port *port)
        /* Clear the TX Empty interrupt */
        writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_ISR);
 
+       if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED) {
+               if (!cdns_uart->rs485_tx_started) {
+                       cdns_uart->tx_timer.function = &cdns_rs485_tx_callback;
+                       cdns_rs485_tx_setup(cdns_uart);
+                       return hrtimer_start(&cdns_uart->tx_timer,
+                                            ms_to_ktime(port->rs485.delay_rts_before_send),
+                                            HRTIMER_MODE_REL);
+               }
+       }
        cdns_uart_handle_tx(port);
-
-       /* Enable the TX Empty interrupt */
-       writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_IER);
 }
 
 /**
@@ -619,6 +748,10 @@ static void cdns_uart_start_tx(struct uart_port *port)
 static void cdns_uart_stop_tx(struct uart_port *port)
 {
        unsigned int regval;
+       struct cdns_uart *cdns_uart = port->private_data;
+
+       if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
+               cdns_rs485_rx_setup(cdns_uart);
 
        regval = readl(port->membase + CDNS_UART_CR);
        regval |= CDNS_UART_CR_TX_DIS;
@@ -831,6 +964,9 @@ static int cdns_uart_startup(struct uart_port *port)
                (CDNS_UART_CR_TXRST | CDNS_UART_CR_RXRST))
                cpu_relax();
 
+       if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
+               cdns_rs485_rx_setup(cdns_uart);
+
        /*
         * Clear the RX disable bit and then set the RX enable bit to enable
         * the receiver.
@@ -890,6 +1026,10 @@ static void cdns_uart_shutdown(struct uart_port *port)
 {
        int status;
        unsigned long flags;
+       struct cdns_uart *cdns_uart = port->private_data;
+
+       if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED)
+               hrtimer_cancel(&cdns_uart->tx_timer);
 
        uart_port_lock_irqsave(port, &flags);
 
@@ -1035,6 +1175,8 @@ static void cdns_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
 
        if (mctrl & TIOCM_RTS)
                val |= CDNS_UART_MODEMCR_RTS;
+       if (cdns_uart_data->gpiod_rts)
+               gpiod_set_value(cdns_uart_data->gpiod_rts, !(mctrl & TIOCM_RTS));
        if (mctrl & TIOCM_DTR)
                val |= CDNS_UART_MODEMCR_DTR;
        if (mctrl & TIOCM_LOOP)
@@ -1457,6 +1599,39 @@ MODULE_DEVICE_TABLE(of, cdns_uart_of_match);
 /* Temporary variable for storing number of instances */
 static int instances;
 
+/**
+ * cdns_rs485_config - Called when an application calls TIOCSRS485 ioctl.
+ * @port: Pointer to the uart_port structure
+ * @termios: Pointer to the ktermios structure
+ * @rs485: Pointer to the serial_rs485 structure
+ *
+ * Return: 0
+ */
+static int cdns_rs485_config(struct uart_port *port, struct ktermios *termios,
+                            struct serial_rs485 *rs485)
+{
+       u32 val;
+       struct cdns_uart *cdns_uart = port->private_data;
+
+       if (rs485->flags & SER_RS485_ENABLED) {
+               dev_dbg(port->dev, "Setting UART to RS485\n");
+               /* Make sure auto RTS is disabled */
+               val = readl(port->membase + CDNS_UART_MODEMCR);
+               val &= ~CDNS_UART_MODEMCR_FCM;
+               writel(val, port->membase + CDNS_UART_MODEMCR);
+
+               /* Timer setup */
+               hrtimer_init(&cdns_uart->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+               cdns_uart->tx_timer.function = &cdns_rs485_tx_callback;
+
+               /* Disable transmitter and make Rx setup*/
+               cdns_uart_stop_tx(port);
+       } else {
+               hrtimer_cancel(&cdns_uart->tx_timer);
+       }
+       return 0;
+}
+
 /**
  * cdns_uart_probe - Platform driver probe
  * @pdev: Pointer to the platform device structure
@@ -1599,9 +1774,23 @@ static int cdns_uart_probe(struct platform_device *pdev)
        port->private_data = cdns_uart_data;
        port->read_status_mask = CDNS_UART_IXR_TXEMPTY | CDNS_UART_IXR_RXTRIG |
                        CDNS_UART_IXR_OVERRUN | CDNS_UART_IXR_TOUT;
+       port->rs485_config = cdns_rs485_config;
+       port->rs485_supported = cdns_rs485_supported;
        cdns_uart_data->port = port;
        platform_set_drvdata(pdev, port);
 
+       rc = uart_get_rs485_mode(port);
+       if (rc)
+               goto err_out_clk_notifier;
+
+       cdns_uart_data->gpiod_rts = devm_gpiod_get_optional(&pdev->dev, "rts",
+                                                           GPIOD_OUT_LOW);
+       if (IS_ERR(cdns_uart_data->gpiod_rts)) {
+               rc = PTR_ERR(cdns_uart_data->gpiod_rts);
+               dev_err(port->dev, "xuartps: devm_gpiod_get_optional failed\n");
+               goto err_out_clk_notifier;
+       }
+
        pm_runtime_use_autosuspend(&pdev->dev);
        pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
        pm_runtime_set_active(&pdev->dev);
@@ -1620,6 +1809,8 @@ static int cdns_uart_probe(struct platform_device *pdev)
                console_port = port;
        }
 #endif
+       if (cdns_uart_data->port->rs485.flags & SER_RS485_ENABLED)
+               cdns_rs485_rx_setup(cdns_uart_data);
 
        rc = uart_add_one_port(&cdns_uart_uart_driver, port);
        if (rc) {
@@ -1648,6 +1839,7 @@ err_out_pm_disable:
        pm_runtime_disable(&pdev->dev);
        pm_runtime_set_suspended(&pdev->dev);
        pm_runtime_dont_use_autosuspend(&pdev->dev);
+err_out_clk_notifier:
 #ifdef CONFIG_COMMON_CLK
        clk_notifier_unregister(cdns_uart_data->uartclk,
                        &cdns_uart_data->clk_rate_change_nb);