在设备开发过程中,经常需要使用到RS485,但很多驱动工程师的处理方式基本都是:默认收,告诉应用自己去控制gpio引脚已达到收发数据的目的。其实可以通过修改驱动来控制收发,应用不需要关心控制IO,直接按串口的使用方式即可。
不同的平台代码会有差异,但思路是一样。
基本思路:
1)通过dts的uart节点compatible,找到对应的驱动代码。如rk的compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart"; 可以在kernel/driver目录中找到对应驱动代码:tty/serial/8250
2) 需要修改dts,区别于RS232等串口,以识别是RS485串口以及相应的GPIO引脚、高低电平及延迟参数。示例:
&uart4 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart4m1_xfer>;
rts-gpio = <&gpio0 RK_PC1 GPIO_ACTIVE_HIGH>;
rs485-rts-active-low;
rs485-rts-delay = <5 100>; // in milliseconds
linux,rs485-enabled-at-boot-time;
};
3)修改驱动代码,两条线:(1) 加载dts配置(xxx_probe) 2) 发送数据前,根据配置改变引脚状态,发送完成后,根据配置恢复引脚状态,改动示例:
diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h
index 3fdd0dee8b41..22ee0720326e 100644
--- a/include/uapi/linux/serial.h
+++ b/include/uapi/linux/serial.h
@@ -130,6 +130,7 @@ struct serial_rs485 {
__u32 delay_rts_after_send; /* Delay after send (milliseconds) */
__u32 padding[5]; /* Memory is cheap, new structs
are a royal PITA .. */
+ __u32 rts_gpio;
};
#endif /* _UAPI_LINUX_SERIAL_H */
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 2882547b8553..288d4a453d89 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -24,7 +24,8 @@
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/pm_runtime.h>
-
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
#include <asm/byteorder.h>
#include "8250.h"
@@ -537,6 +538,81 @@ static void dw8250_setup_port(struct uart_port *p)
up->capabilities |= UART_CAP_IRDA;
}
+static int dw8250_rs485_config(struct uart_port *port,
+ struct serial_rs485 *rs485)
+{
+ struct uart_8250_port *up = up_to_u8250p(port);
+ /* Clamp the delays to [0, 100ms] */
+ rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U);
+ rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U);
+
+ port->rs485 = *rs485;
+ /*
+ * Both serial8250_em485_init and serial8250_em485_destroy
+ * are idempotent
+ */
+ if (rs485->flags & SER_RS485_ENABLED) {
+ int ret = serial8250_em485_init(up);
+ if (ret) {
+ rs485->flags &= ~SER_RS485_ENABLED;
+ port->rs485.flags &= ~SER_RS485_ENABLED;
+ }
+
+ gpio_set_value(rs485->rts_gpio, (rs485->flags & SER_RS485_RTS_AFTER_SEND ? 1 : 0));
+ return ret;
+ }
+
+ serial8250_em485_destroy(up);
+
+ return 0;
+}
+
+static int dw8250_probe_rs485(struct uart_8250_port *up,
+ struct device_node *np)
+{
+ struct serial_rs485 *rs485conf = &up->port.rs485;
+ int ret;
+ rs485conf->flags = 0;
+ rs485conf->rts_gpio = -EINVAL;
+
+ if (!np)
+ return 0;
+
+ uart_get_rs485_mode(up->port.dev, rs485conf);
+ if(rs485conf->flags & SER_RS485_ENABLED) {
+ if (of_property_read_bool(np, "rs485-rts-active-high")) {
+ rs485conf->flags |= SER_RS485_RTS_ON_SEND;
+ rs485conf->flags &= ~SER_RS485_RTS_AFTER_SEND;
+ } else {
+ rs485conf->flags &= ~SER_RS485_RTS_ON_SEND;
+ rs485conf->flags |= SER_RS485_RTS_AFTER_SEND;
+ }
+
+ /* check for tx enable gpio */
+ rs485conf->rts_gpio = of_get_named_gpio(np, "rts-gpio", 0);
+ if (gpio_is_valid(rs485conf->rts_gpio)) {
+ ret = devm_gpio_request(up->port.dev, rs485conf->rts_gpio, "snps,dw-apb-uart");
+ if (ret < 0) {
+ return ret;
+ }
+ ret = rs485conf->flags & SER_RS485_RTS_AFTER_SEND ? 1 : 0;
+ ret = gpio_direction_output(rs485conf->rts_gpio, ret);
+ if (ret < 0) {
+ return ret;
+ }
+ } else {
+ rs485conf->flags &= ~SER_RS485_ENABLED;
+ }
+ }
+
+ if(rs485conf->flags & SER_RS485_ENABLED) {
+ struct uart_port *p = &up->port;
+ p->rs485_config = dw8250_rs485_config;
+ }
+
+ return 0;
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {};
@@ -591,6 +667,8 @@ static int dw8250_probe(struct platform_device *pdev)
data->uart_16550_compatible = device_property_read_bool(dev,
"snps,uart-16550-compatible");
+ dw8250_probe_rs485(&uart, p->dev->of_node);
+
err = device_property_read_u32(dev, "reg-shift", &val);
if (!err)
p->regshift = val;
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 39156ecbeb11..8d784ddab37e 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -34,7 +34,7 @@
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
#include <linux/ktime.h>
-
+#include <linux/of_gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
@@ -1788,6 +1788,7 @@ void serial8250_tx_chars(struct uart_8250_port *up)
struct uart_port *port = &up->port;
struct circ_buf *xmit = &port->state->xmit;
int count;
+ int res;
if (port->x_char) {
serial_out(up, UART_TX, port->x_char);
@@ -1805,6 +1806,15 @@ void serial8250_tx_chars(struct uart_8250_port *up)
}
count = up->tx_loadsz;
+ if(up->port.rs485.flags & SER_RS485_ENABLED) {
+ res = (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) ? 0 : 1;
+ if (gpio_get_value(up->port.rs485.rts_gpio) != res) {
+ if (port->rs485.delay_rts_before_send > 0)
+ mdelay(port->rs485.delay_rts_before_send);
+ gpio_set_value(up->port.rs485.rts_gpio, res);
+ }
+ }
+
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
@@ -1828,8 +1838,17 @@ void serial8250_tx_chars(struct uart_8250_port *up)
* HW can go idle. So we get here once again with empty FIFO and disable
* the interrupt and RPM in __stop_tx()
*/
- if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM))
+ if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM)){
__stop_tx(up);
+ if(up->port.rs485.flags & SER_RS485_ENABLED) {
+ res = (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) ? 1 : 0;
+ if (gpio_get_value(up->port.rs485.rts_gpio) != res) {
+ if (port->rs485.delay_rts_after_send > 0)
+ mdelay(port->rs485.delay_rts_after_send);
+ gpio_set_value(up->port.rs485.rts_gpio, res);
+ }
+ }
+ }
}
EXPORT_SYMBOL_GPL(serial8250_tx_chars);