因硬件工程师改动板卡,需增加RS485串口。所以板卡就外扩了一个TTL转RS485芯片。
参考链接:迅为rk3568开发板RS485收发切换 linux485驱动修改_rk3568发送485信号-优快云博客
因为485通信是半双工,所以还需要对驱动部分做一些修改,以实现RS485收发切换控制。硬件原理图如下所示,在我这里使用的是uart7_m1和uart9_m1,以此来举例。
首先需要根据此部分修改设备树,添加uart7_m1和uart9_m1的信息。
这两个串口对应的GPIO收发控制切换引脚为gpio4 RK_PD2和gpio2 RK_PD2,需要写入到里面。
rs485_dir_active_high代表发送时,高电平有效。
代码修改部分如下所示
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 2882547b8553..646dc83aa777 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -27,7 +27,7 @@
#include <asm/byteorder.h>
-#include "8250.h"
+#include "8250_dw.h"
/* Offsets for the DesignWare specific registers */
#define DW_UART_USR 0x1f /* UART Status Register */
@@ -55,24 +55,6 @@
/* DesignWare specific register fields */
#define DW_UART_MCR_SIRE BIT(6)
-struct dw8250_data {
- u8 usr_reg;
- u8 dlf_size;
- int line;
- int msr_mask_on;
- int msr_mask_off;
- struct clk *clk;
- struct clk *pclk;
- struct reset_control *rst;
- struct uart_8250_dma dma;
-#ifdef CONFIG_ARCH_ROCKCHIP
- int irq;
- int irq_wake;
- int enable_wakeup;
-#endif
- unsigned int skip_autocfg:1;
- unsigned int uart_16550_compatible:1;
-};
static inline u32 dw8250_readl_ext(struct uart_port *p, int offset)
{
@@ -537,6 +519,30 @@ static void dw8250_setup_port(struct uart_port *p)
up->capabilities |= UART_CAP_IRDA;
}
+void serial8250_485_do_tasklet(unsigned long param)
+{
+
+ unsigned int i = 0;
+ struct uart_8250_port *p_uart_8250_port;
+ struct uart_port *port;
+ unsigned int lsr;
+ struct dw8250_data *p_data = (struct dw8250_data *)param;
+
+ p_uart_8250_port = serial8250_get_port(p_data->line);
+ port = &(p_uart_8250_port->port);
+ for (i = 0; i < WAIT_SEND_TIMEOUT; i++) {
+ lsr = serial_port_in(port, UART_LSR);
+ if (UART_LSR_TEMT == (lsr & UART_LSR_TEMT)) {
+ break;
+ }
+ }
+ if (i >= WAIT_SEND_TIMEOUT) {
+ dev_err(port->dev, "uart_send timeout\n");
+ }
+ //printk("tx:%d\n", port->icount.tx);
+ gpio_set_value(p_data->data.gpioctl_485, p_data->data.active_high ? 0 : 1);
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {};
@@ -547,7 +553,8 @@ static int dw8250_probe(struct platform_device *pdev)
struct dw8250_data *data;
int err;
u32 val;
-
+ int ret;
+
if (!regs) {
dev_err(dev, "no registers defined\n");
return -EINVAL;
@@ -591,6 +598,21 @@ static int dw8250_probe(struct platform_device *pdev)
data->uart_16550_compatible = device_property_read_bool(dev,
"snps,uart-16550-compatible");
+ data->data.gpioctl_485 = of_get_named_gpio(p->dev->of_node, "rs485-dir-gpio", 0);
+ if (gpio_is_valid(data->data.gpioctl_485)) {
+ data->data.active_high = of_property_read_bool(p->dev->of_node, "rs485_dir_active_high");
+ ret = devm_gpio_request(uart.port.dev, data->data.gpioctl_485, "rs485_dir_gpio");
+ if (ret < 0) {
+ dev_err(dev, "devm_gpio_request error\n");
+ }
+ else {
+ ret = gpio_direction_output(data->data.gpioctl_485, data->data.active_high ? 0:1);
+ if (ret < 0) {
+ dev_err(dev, "gpio_direction_output error\n");
+ }
+ }
+ tasklet_init(&(data->data.rs485_tasklet), serial8250_485_do_tasklet, (unsigned long)data);
+ }
err = device_property_read_u32(dev, "reg-shift", &val);
if (!err)
p->regshift = val;
diff --git a/drivers/tty/serial/8250/8250_dw.h b/drivers/tty/serial/8250/8250_dw.h
new file mode 100644
index 000000000000..dc588211cc8c
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_dw.h
@@ -0,0 +1,43 @@
+#ifndef __8250_DW__H
+#define __8250_DW__H
+
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/serial_8250.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include "8250.h"
+
+/*8250转485数据结构*/
+struct dw8250_port_data {
+ int gpioctl_485;
+ int active_high;
+ struct tasklet_struct rs485_tasklet;
+};
+
+/*8250数据结构*/
+struct dw8250_data {
+ unsigned char usr_reg;
+ unsigned char dlf_size;
+ int line;
+ int msr_mask_on;
+ int msr_mask_off;
+ struct clk *clk;
+ struct clk *pclk;
+ struct reset_control *rst;
+ struct uart_8250_dma dma;
+#if CONFIG_ARCH_ROCKCHIP
+ int irq;
+ int irq_wake;
+ int enable_wakeup;
+#endif
+ unsigned int skip_autocfg:1;
+ unsigned int uart_16550_compatible:1;
+
+ struct dw8250_port_data data;
+};
+
+#define WAIT_SEND_TIMEOUT 30000/*等待发送完成计数,超过此数,退出循环*/
+#endif
\ No newline at end of file
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 39156ecbeb11..f6556c9cc0b6 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -38,7 +38,7 @@
#include <asm/io.h>
#include <asm/irq.h>
-#include "8250.h"
+#include "8250_dw.h"
/*
* These are definitions for the Exar XR17V35X and XR17(C|D)15X
@@ -1779,6 +1779,7 @@ unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr)
} while (lsr & (UART_LSR_DR | UART_LSR_BI));
tty_flip_buffer_push(&port->state->port);
+ //printk("rx:%d\n", port->icount.rx);
return lsr;
}
EXPORT_SYMBOL_GPL(serial8250_rx_chars);
@@ -1787,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;
+ struct dw8250_data* p_data = (struct dw8250_data*)(port->private_data);
int count;
if (port->x_char) {
@@ -1805,6 +1807,10 @@ void serial8250_tx_chars(struct uart_8250_port *up)
}
count = up->tx_loadsz;
+
+ if (gpio_is_valid(p_data->data.gpioctl_485)) {
+ gpio_set_value(p_data->data.gpioctl_485, p_data->data.active_high ? 1 : 0);
+ }
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
@@ -1828,8 +1834,13 @@ 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 (gpio_is_valid(p_data->data.gpioctl_485)) {
+ tasklet_hi_schedule(&(p_data->data.rs485_tasklet));
+ }
+ }
+
}
EXPORT_SYMBOL_GPL(serial8250_tx_chars);
以上部分为最终修改。
测试过程中遇到的问题:
在发送结束后,将gpio由发送切换为接收状态时,如果直接调用gpio_set_value函数,改变GPIO状态,这个在单串口,通信并且一次发送数据不超过248的情况下,是没有问题的。
但是,将uart7和uart9相接,内部进行自收自发,连续发送,每一次发送2048个字节进行压力测试时,会丢包报错。
经分析,原因为当uart7接收数据时,uart9进行发送,并且数据量较大时,uart9需要轮训发送寄存器状态为空后,才会重新将GPIO切换为接收状态。由于此时是在中断之中,uart9发送完大量数据需要大量时间,从而会导致此轮训过程时间较长;而uart7无法触发接收中断,无法接收uart9发送过来的数据,从而有数据丢失,产生报错。
解决方式是将 uart9轮训发送状态寄存器,并切换为接收的过程,注册到一个tasklet任务中;由于tasklet优先级低于中断,所以此时还能正常触发uart7的接收中断,能够接收来自uart9的数据。
经过24小时,uart7和uart9不断收发数据测试,此方法是可行的。