This patch adds RS485 support to the OMAP serial driver, as
defined in:-

Documentation/devicetree/bindings/serial/rs485.txt

When a UART transmitter is connected to (eg) a RS485 driver, it is
necessary to turn the driver on/off as quickly as possible.  This is
best achieved in the serial driver itself (rather than in userspace
where the latency can be quite large).

This patch allows a GPIO pin to be defined (via DT) that controls
the enabling of the driver at the start of a message, and disables
the driver when the message has been completed.

When RS485 is disabled, the RTS pin is set to on.

Signed-off-by: Mark Jackson <m...@newflow.co.uk>
---
Changes in v2:
- Rebased against Greg KH's tty-next

Changes in v2:
- Fix incorrect logic in serial_omap_config_rs485()

 drivers/tty/serial/omap-serial.c |  179 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 179 insertions(+)

diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
index c751706..2706c11 100644
--- a/drivers/tty/serial/omap-serial.c
+++ b/drivers/tty/serial/omap-serial.c
@@ -40,8 +40,11 @@
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
 #include <linux/gpio.h>
+#include <linux/of_gpio.h>
 #include <linux/platform_data/serial-omap.h>
 
+#include <dt-bindings/gpio/gpio.h>
+
 #define OMAP_MAX_HSUART_PORTS  6
 
 #define UART_BUILD_REVISION(x, y)      (((x) << 8) | (y))
@@ -162,6 +165,9 @@ struct uart_omap_port {
        int                     DTR_inverted;
        int                     DTR_active;
 
+       struct serial_rs485     rs485;
+       int                     rts_gpio;
+
        struct pm_qos_request   pm_qos_request;
        u32                     latency;
        u32                     calc_latency;
@@ -277,13 +283,42 @@ static void serial_omap_enable_ms(struct uart_port *port)
 static void serial_omap_stop_tx(struct uart_port *port)
 {
        struct uart_omap_port *up = to_uart_omap_port(port);
+       struct circ_buf *xmit = &up->port.state->xmit;
+       int res;
 
        pm_runtime_get_sync(up->dev);
+
+       /* handle rs485 */
+       if (up->rs485.flags & SER_RS485_ENABLED) {
+               /* do nothing if current tx not yet completed */
+               res = serial_in(up, UART_LSR) & UART_LSR_TEMT;
+               if (!res)
+                       return;
+
+               /* if there's no more data to send, turn off rts */
+               if (uart_circ_empty(xmit)) {
+                       /* if rts not already disabled */
+                       res = (up->rs485.flags & SER_RS485_RTS_AFTER_SEND) ? 1 
: 0;
+                       if (gpio_get_value(up->rts_gpio) != res) {
+                               if (up->rs485.delay_rts_after_send > 0) {
+                                       mdelay(up->rs485.delay_rts_after_send);
+                               }
+                               gpio_set_value(up->rts_gpio, res);
+                       }
+               }
+       }
+
        if (up->ier & UART_IER_THRI) {
                up->ier &= ~UART_IER_THRI;
                serial_out(up, UART_IER, up->ier);
        }
 
+       if ((up->rs485.flags & SER_RS485_ENABLED) &&
+           !(up->rs485.flags & SER_RS485_RX_DURING_TX)) {
+               up->ier = UART_IER_RLSI | UART_IER_RDI;
+               serial_out(up, UART_IER, up->ier);
+       }
+
        pm_runtime_mark_last_busy(up->dev);
        pm_runtime_put_autosuspend(up->dev);
 }
@@ -346,8 +381,26 @@ static inline void serial_omap_enable_ier_thri(struct 
uart_omap_port *up)
 static void serial_omap_start_tx(struct uart_port *port)
 {
        struct uart_omap_port *up = to_uart_omap_port(port);
+       int res;
 
        pm_runtime_get_sync(up->dev);
+
+       /* handle rs485 */
+       if (up->rs485.flags & SER_RS485_ENABLED) {
+               /* if rts not already enabled */
+               res = (up->rs485.flags & SER_RS485_RTS_ON_SEND) ? 1 : 0;
+               if (gpio_get_value(up->rts_gpio) != res) {
+                       gpio_set_value(up->rts_gpio, res);
+                       if (up->rs485.delay_rts_before_send > 0) {
+                               mdelay(up->rs485.delay_rts_before_send);
+                       }
+               }
+       }
+
+       if ((up->rs485.flags & SER_RS485_ENABLED) &&
+           !(up->rs485.flags & SER_RS485_RX_DURING_TX))
+               serial_omap_stop_rx(port);
+
        serial_omap_enable_ier_thri(up);
        pm_runtime_mark_last_busy(up->dev);
        pm_runtime_put_autosuspend(up->dev);
@@ -1262,6 +1315,79 @@ static inline void serial_omap_add_console_port(struct 
uart_omap_port *up)
 
 #endif
 
+/* Enable or disable the rs485 support */
+static void
+serial_omap_config_rs485(struct uart_port *port, struct serial_rs485 
*rs485conf)
+{
+       struct uart_omap_port *up = to_uart_omap_port(port);
+       unsigned long flags;
+       unsigned int mode;
+       int val;
+
+       pm_runtime_get_sync(up->dev);
+       spin_lock_irqsave(&up->port.lock, flags);
+
+       up->ier &= ~(UART_IER_RLSI | UART_IER_RDI);
+       serial_out(up, UART_IER, up->ier);
+
+       /* Disable interrupts from this port */
+       mode = up->ier;
+       up->ier = 0;
+       serial_out(up, UART_IER, 0);
+
+       /* store new config */
+       up->rs485 = *rs485conf;
+
+       /*
+        * Just as a precaution, only allow rs485
+        * to be enabled if the gpio pin is valid
+        */
+       if (gpio_is_valid(up->rts_gpio)) {
+               /* enable / disable rts */
+               val = (up->rs485.flags & SER_RS485_ENABLED) ?
+                       SER_RS485_RTS_AFTER_SEND : SER_RS485_RTS_ON_SEND;
+               val = (up->rs485.flags & val) ? 1 : 0;
+               gpio_set_value(up->rts_gpio, val);
+       } else
+               up->rs485.flags &= ~SER_RS485_ENABLED;
+
+       /* Enable interrupts */
+       up->ier = mode;
+       serial_out(up, UART_IER, up->ier);
+
+       spin_unlock_irqrestore(&up->port.lock, flags);
+       pm_runtime_mark_last_busy(up->dev);
+       pm_runtime_put_autosuspend(up->dev);
+}
+
+static int
+serial_omap_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
+{
+       struct serial_rs485 rs485conf;
+
+       switch (cmd) {
+       case TIOCSRS485:
+               if (copy_from_user(&rs485conf, (struct serial_rs485 *) arg,
+                                       sizeof(rs485conf)))
+                       return -EFAULT;
+
+               serial_omap_config_rs485(port, &rs485conf);
+               break;
+
+       case TIOCGRS485:
+               if (copy_to_user((struct serial_rs485 *) arg,
+                                       &(to_uart_omap_port(port)->rs485),
+                                       sizeof(rs485conf)))
+                       return -EFAULT;
+               break;
+
+       default:
+               return -ENOIOCTLCMD;
+       }
+       return 0;
+}
+
+
 static struct uart_ops serial_omap_pops = {
        .tx_empty       = serial_omap_tx_empty,
        .set_mctrl      = serial_omap_set_mctrl,
@@ -1283,6 +1409,7 @@ static struct uart_ops serial_omap_pops = {
        .request_port   = serial_omap_request_port,
        .config_port    = serial_omap_config_port,
        .verify_port    = serial_omap_verify_port,
+       .ioctl          = serial_omap_ioctl,
 #ifdef CONFIG_CONSOLE_POLL
        .poll_put_char  = serial_omap_poll_put_char,
        .poll_get_char  = serial_omap_poll_get_char,
@@ -1405,6 +1532,53 @@ static struct omap_uart_port_info 
*of_get_uart_port_info(struct device *dev)
        return omap_up_info;
 }
 
+static int serial_omap_probe_rs485(struct uart_omap_port *up,
+                                  struct device_node *np)
+{
+       struct serial_rs485 *rs485conf = &up->rs485;
+       u32 rs485_delay[2];
+       enum of_gpio_flags flags;
+       int ret;
+
+       rs485conf->flags = 0;
+       up->rts_gpio = -EINVAL;
+
+       if (!np)
+               return 0;
+
+       if (of_property_read_bool(np, "rs485-rts-active-high"))
+               rs485conf->flags |= SER_RS485_RTS_ON_SEND;
+       else
+               rs485conf->flags |= SER_RS485_RTS_AFTER_SEND;
+
+       /* check for tx enable gpio */
+       up->rts_gpio = of_get_named_gpio_flags(np, "rts-gpio", 0, &flags);
+       if (gpio_is_valid(up->rts_gpio)) {
+               ret = gpio_request(up->rts_gpio, "omap-serial");
+               if (ret < 0)
+                       return ret;
+               ret = gpio_direction_output(up->rts_gpio,
+                                           flags & SER_RS485_RTS_AFTER_SEND);
+               if (ret < 0)
+                       return ret;
+       } else
+               up->rts_gpio = -EINVAL;
+
+       if (of_property_read_u32_array(np, "rs485-rts-delay",
+                                   rs485_delay, 2) == 0) {
+               rs485conf->delay_rts_before_send = rs485_delay[0];
+               rs485conf->delay_rts_after_send = rs485_delay[1];
+       }
+
+       if (of_property_read_bool(np, "rs485-rx-during-tx"))
+               rs485conf->flags |= SER_RS485_RX_DURING_TX;
+
+       if (of_property_read_bool(np, "linux,rs485-enabled-at-boot-time"))
+               rs485conf->flags |= SER_RS485_ENABLED;
+
+       return 0;
+}
+
 static int serial_omap_probe(struct platform_device *pdev)
 {
        struct uart_omap_port   *up;
@@ -1480,6 +1654,10 @@ static int serial_omap_probe(struct platform_device 
*pdev)
                goto err_port_line;
        }
 
+       ret = serial_omap_probe_rs485(up, pdev->dev.of_node);
+       if (ret < 0)
+               goto err_rs485;
+
        sprintf(up->name, "OMAP UART%d", up->port.line);
        up->port.mapbase = mem->start;
        up->port.membase = devm_ioremap(&pdev->dev, mem->start,
@@ -1535,6 +1713,7 @@ err_add_port:
        pm_runtime_put(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
 err_ioremap:
+err_rs485:
 err_port_line:
        dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n",
                                pdev->id, __func__, ret);
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to