Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a0ac0f5 authored by Mark Jackson's avatar Mark Jackson Committed by Greg Kroah-Hartman
Browse files

OMAP: add RS485 support



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: default avatarMark Jackson <mpfj@newflow.co.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 071eb0ff
Loading
Loading
Loading
Loading
+179 −0
Original line number Diff line number Diff line
@@ -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 @@ static int serial_omap_probe(struct platform_device *pdev)
	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);