rk3568实现串口485切换收发驱动

因硬件工程师改动板卡,需增加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不断收发数据测试,此方法是可行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值