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: Mark Jackson <mpfj@newflow.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Mark Jackson 2013-08-14 11:29:38 +01:00 committed by Greg Kroah-Hartman
parent 071eb0ff68
commit 4a0ac0f55b

View File

@ -40,8 +40,11 @@
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_data/serial-omap.h> #include <linux/platform_data/serial-omap.h>
#include <dt-bindings/gpio/gpio.h>
#define OMAP_MAX_HSUART_PORTS 6 #define OMAP_MAX_HSUART_PORTS 6
#define UART_BUILD_REVISION(x, y) (((x) << 8) | (y)) #define UART_BUILD_REVISION(x, y) (((x) << 8) | (y))
@ -162,6 +165,9 @@ struct uart_omap_port {
int DTR_inverted; int DTR_inverted;
int DTR_active; int DTR_active;
struct serial_rs485 rs485;
int rts_gpio;
struct pm_qos_request pm_qos_request; struct pm_qos_request pm_qos_request;
u32 latency; u32 latency;
u32 calc_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) static void serial_omap_stop_tx(struct uart_port *port)
{ {
struct uart_omap_port *up = to_uart_omap_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); 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) { if (up->ier & UART_IER_THRI) {
up->ier &= ~UART_IER_THRI; up->ier &= ~UART_IER_THRI;
serial_out(up, UART_IER, up->ier); 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_mark_last_busy(up->dev);
pm_runtime_put_autosuspend(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) static void serial_omap_start_tx(struct uart_port *port)
{ {
struct uart_omap_port *up = to_uart_omap_port(port); struct uart_omap_port *up = to_uart_omap_port(port);
int res;
pm_runtime_get_sync(up->dev); 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); serial_omap_enable_ier_thri(up);
pm_runtime_mark_last_busy(up->dev); pm_runtime_mark_last_busy(up->dev);
pm_runtime_put_autosuspend(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 #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 = { static struct uart_ops serial_omap_pops = {
.tx_empty = serial_omap_tx_empty, .tx_empty = serial_omap_tx_empty,
.set_mctrl = serial_omap_set_mctrl, .set_mctrl = serial_omap_set_mctrl,
@ -1283,6 +1409,7 @@ static struct uart_ops serial_omap_pops = {
.request_port = serial_omap_request_port, .request_port = serial_omap_request_port,
.config_port = serial_omap_config_port, .config_port = serial_omap_config_port,
.verify_port = serial_omap_verify_port, .verify_port = serial_omap_verify_port,
.ioctl = serial_omap_ioctl,
#ifdef CONFIG_CONSOLE_POLL #ifdef CONFIG_CONSOLE_POLL
.poll_put_char = serial_omap_poll_put_char, .poll_put_char = serial_omap_poll_put_char,
.poll_get_char = serial_omap_poll_get_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; 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) static int serial_omap_probe(struct platform_device *pdev)
{ {
struct uart_omap_port *up; struct uart_omap_port *up;
@ -1480,6 +1654,10 @@ static int serial_omap_probe(struct platform_device *pdev)
goto err_port_line; 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); sprintf(up->name, "OMAP UART%d", up->port.line);
up->port.mapbase = mem->start; up->port.mapbase = mem->start;
up->port.membase = devm_ioremap(&pdev->dev, 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_put(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
err_ioremap: err_ioremap:
err_rs485:
err_port_line: err_port_line:
dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n", dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n",
pdev->id, __func__, ret); pdev->id, __func__, ret);