⑮tiny4412 Linux驱动开发之tty子系统(UART)驱动程序

本文介绍了Linux驱动开发中的TTY子系统,特别是针对tiny4412平台的UART3驱动编程。内容涵盖了串口通信的基础知识,包括RS-232协议和串口通信模型。讲解了tiny4412板载UART资源,重点讨论了UART3的特性。接着阐述了TTY子系统的层次架构,以及UART驱动程序的映射。最后,分享了驱动程序的实现过程和测试步骤,展示了通过UART3与电脑通信的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本次说一下tty子系统的驱动编程,因为UART相关的寄存器比较多,同时,应用比较广泛,所以本次的驱动程序量也不少,而且只是完成和特定CPU相关的一部分,通用的部分本次都没有涉及到.在写驱动之前,我们先来了解一下硬件资源和tty子系统框架.

1、串口通信简介

串口通信指串口按位(bit)发送和接收字节,串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线 接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2 米;而对于串口而言,长度可达1200米。

串口通信所采用的通信协议为RS-232,RS-232通信方式允许简单连接三线:Tx、Rx和地线。但是对于数据传输,双方必须对数据定时采用使用相同的波特率。RS-232(ANSI/EIA-232标准)是IBM-PC及其兼容机上的串行连接标准。可用于许多用途,比如连接鼠标、打印机或者Modem,同时也 可以接工业仪器仪表。用于驱动和连线的改进,实际应用中RS-232的传输长度或者速度常常超过标准的值。RS-232只限于PC串口和设备间点对点的通信。

2、串口的通信基本模型如下图所示:

3,硬件资源;

如上图,tiny4412的板子上引出了3个UART接口,分别是UART0 , 2, 3,它有5组UART,另外一组UART1用在了WIFI + BT模块,UART4没有引出来,这几组UART的区别主要是FIFO的大小,UART0的FIFO为256byte,UART1, 4的FIFO大小为64byte,UART2, 3的FIFO大小为16 byte,这5组都支持DMA操作,中断等,最大传输速率是4Mbps(512k byte/s),在这里,我们会使用UART3来测试串口功能.

4,UART寄存器及地址

如上图是官方给的UART寄存器map,这个跟STM32是很类似的,需要了解的小伙伴也可以直接找STM32的UART资料了解.UART0上集成了红外功能,上一次,我们通过GPIO口中断的方式,开发了红外驱动,这次也可以使用UART0来做红外驱动,相对简单一些,但是,我们的UART0已经用作了调试终端,所以,就先不这样做了.

下面是UART的一些寄存器:

网上有很多对于寄存器进行讲解如何使用的一些博文和视频,这方面的基础知识,请自行学习,这里就不多说了,接下来说一下tty子系统的一下知识.

上图是TTY子系统的层次架构,我们需要实现的就是底层驱动程序,因为这一部分是和特定CPU相关的,其它几层都是通用的.

上图是UART驱动程序的映射图,也就是tty的框架.了解了这些之后,我们看一看代码实现的具体细节实现(图用的是网上的):

有了具体的实现框图之后,我们来实现一下驱动程序(驱动暂时没这么顺利,没调好输入部分,先贴一个三星官方的):

#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif

#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/sysrq.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <linux/spinlock.h>
#endif
#include <linux/of.h>

#include <asm/irq.h>

#include <mach/hardware.h>
#include <mach/map.h>
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <mach/regs-clock.h>
#include <mach/dma.h>
#endif

#include <plat/regs-serial.h>
#include <plat/clock.h>

#ifdef CONFIG_SERIAL_SAMSUNG_DMA
#include <linux/dma-mapping.h>
#endif

#include "samsung.h"

/* UART name and device definitions */

#define S3C24XX_SERIAL_NAME	"ttySAC"
#define S3C24XX_SERIAL_MAJOR	204
#define S3C24XX_SERIAL_MINOR	64

/* Baudrate definition*/
#define MAX_BAUD	3000000
#define MIN_BAUD	0


#ifdef CONFIG_SERIAL_SAMSUNG_DMA
/* definitions for dma mode */
#define ENABLE_UART_DMA_MODE	true
#define DMA_TRANS_LIMIT	64
#define RX_BUFFER_SIZE	256
#define BURST_1BYTE	0
#endif

/* macros to change one thing to another */

#define tx_enabled(port) ((port)->unused[0])
#define rx_enabled(port) ((port)->unused[1])

/* flag to ignore all characters coming in */
#define RXSTAT_DUMMY_READ (0x10000000)

/* ? - where has parity gone?? */
#define S3C2410_UERSTAT_PARITY (0x1000)

#ifdef CONFIG_SERIAL_SAMSUNG_DMA
static struct s3c2410_dma_client samsung_uart_dma_client = {
	.name = "samsung-uart-dma",
};

static void prepare_dma(struct uart_dma_data *dma,
					unsigned len, dma_addr_t buf);
#endif

static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
{
	return container_of(port, struct s3c24xx_uart_port, port);
}

/* translate a port to the device name */

static inline const char *s3c24xx_serial_portname(struct uart_port *port)
{
	return to_platform_device(port->dev)->name;
}

static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{
	return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}

/*
 * s3c64xx and later SoC's include the interrupt mask and status registers in
 * the controller itself, unlike the s3c24xx SoC's which have these registers
 * in the interrupt controller. Check if the port type is s3c64xx or higher.
 */
static int s3c24xx_serial_has_interrupt_mask(struct uart_port *port)
{
	return to_ourport(port)->info->type == PORT_S3C6400;
}

static void s3c24xx_serial_rx_enable(struct uart_port *port)
{
	unsigned long flags;
	unsigned int ucon, ufcon;
	int count = 10000;

	spin_lock_irqsave(&port->lock, flags);

	while (--count && !s3c24xx_serial_txempty_nofifo(port))
		udelay(100);

	ufcon = rd_regl(port, S3C2410_UFCON);
	ufcon |= S3C2410_UFCON_RESETRX;
	wr_regl(port, S3C2410_UFCON, ufcon);

	ucon = rd_regl(port, S3C2410_UCON);
	ucon |= S3C2410_UCON_RXIRQMODE;
	wr_regl(port, S3C2410_UCON, ucon);

	rx_enabled(port) = 1;
	spin_unlock_irqrestore(&port->lock, flags);
}

static void s3c24xx_serial_rx_disable(struct uart_port *port)
{
	unsigned long flags;
	unsigned int ucon;

	spin_lock_irqsave(&port->lock, flags);

	ucon = rd_regl(port, S3C2410_UCON);
	ucon &= ~S3C2410_UCON_RXIRQMODE;
	wr_regl(port, S3C2410_UCON, ucon);

	rx_enabled(port) = 0;
	spin_unlock_irqrestore(&port->lock, flags);
}

static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
	struct s3c24xx_uart_port *ourport = to_ourport(port);
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
	struct exynos_uart_dma *uart_dma = &ourport->uart_dma;
#endif

	if (tx_enabled(port)) {
		if (s3c24xx_serial_has_interrupt_mask(port))
			__set_bit(S3C64XX_UINTM_TXD,
				portaddrl(port, S3C64XX_UINTM));
		else
			disable_irq_nosync(ourport->tx_irq);
		tx_enabled(port) = 0;
		if (port->flags & UPF_CONS_FLOW)
			s3c24xx_serial_rx_enable(port);
	}

#ifdef CONFIG_SERIAL_SAMSUNG_DMA
	uart_dma->ops->stop(uart_dma->tx.ch);
#endif
}

static void s3c24xx_serial_start_tx(struct uart_port *port)
{
	struct s3c24xx_uart_port *ourport = to_ourport(port);

	if (!tx_enabled(port)) {
		if (port->flags & UPF_CONS_FLOW)
			s3c24xx_serial_rx_disable(port);

		if (s3c24xx_serial_has_interrupt_mask(port))
			__clear_bit(S3C64XX_UINTM_TXD,
				portaddrl(port, S3C64XX_UINTM));
		else
			enable_irq(ourport->tx_irq);
		tx_enabled(port) = 1;
	}
}

static void s3c24xx_serial_stop_rx(struct uart_port *port)
{
	struct s3c24xx_uart_port *ourport = to_ourport(port);
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
	struct exynos_uart_dma *uart_dma = &ourport->uart_dma;
#endif

	if (rx_enabled(port)) {
		dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
		if (s3c24xx_serial_has_interrupt_mask(port))
			__set_bit(S3C64XX_UINTM_RXD,
				portaddrl(port, S3C64XX_UINTM));
		else
			disable_irq_nosync(ourport->rx_irq);
		rx_enabled(port) = 0;
	}
#ifdef CONFIG_SERIAL_SAMSUNG_DMA
	uart_dma->ops->stop(uart_dma->rx.ch);
#endif
}

#ifdef CONFIG_SERIAL_SAMSUNG_DMA
static void uart_rx_dma_request(struct s3c24xx_uart_port *ourport)
{
	struct uart_port *port = &ourport->port;
	struct exynos_uart_dma *uart_dma = &ourport->uart_dma;

	uart_dma->rx_dst_addr = dma_map_single(port->dev,
			uart_dma->rx_buff,
			uart_dma->rx.req_size,
			DMA_FROM_DEVICE);
	if (dma_mapping_error(port->dev, uart_dma->rx_dst_addr))
		pr_err("Rx DMA mapping error!!!\n");

	/* prepare rx dma mode */
	prepare_dma(&uart_dma->rx,
			uart_dma->rx.req_size, uart_dma->rx_dst_addr);
}

static void callback_uart_rx_dma(void *data)
{
	struct exynos_uart_dma *uart_dma = container_of(data,
						struct exynos_uart_dma, rx);
	struct s3c24xx_uart_port *ourport = container_of(uart_dma,
					struct s3c24xx_uart_port, uart_dma);
	struct uart_port *port = &ourport->port;
	struct tty_struct *tty = port->state->port.tty;
	unsigned int uerstat = ourport->err_occurred;
	unsigned int received_size = 0;
	dma_addr_t src_addr = 0;
	dma_addr_t dst_addr = 0;

	uart_dma->ops->getposition(uart_dma->rx.ch, &src_addr, &dst_addr);
	received_size = dst_addr - (unsigned int)uart_dma->rx_dst_addr;

	dma_unmap_single(port->dev, uart_dma->rx_dst_addr,
			received_size, DMA_FROM_DEVICE);

	/* Error check after DMA transfer */
	if (uerstat != 0) {
		pr_err("UART Rx DMA Error(0x%x)!!!\n", uerstat);

		if (uerstat & S3C2410_UERSTAT_BREAK) {
			dbg("break!\n");
			port->icount.brk += received_size;
		}

		if (uerstat & S3C2410_UERSTAT_FRAME)
			port->icount.frame += received_size;
		if (uerstat & S3C2410_UERSTAT_OVERRUN)
			port->icount.overrun += received_size;

		uerstat &= port->read_status_mask;

		ourport->err_occurred = 0;

	}

	tty_insert_flip_string(tty, uart_dma->rx_buff, received_size);
	tty_flip_buffer_push(tty);

	if (uart_dma->rx.busy == 1)
		uart_rx_dma_request(ourport);
}

static void enable_rx_dma(struct uart_port *port)
{
	unsigned long flags;
	unsigned int ufcon, ucon;

	spin_lock_irqsave(&port->lock, flags);

	ufcon = rd_regl(port, S3C2410_UFCON);
	ufcon &= ~(0x7 << 4);
	ufcon |= (0x1 << 2) | (0x1 << 1) | S5PV210_UFCON_RXTRIG64;
	wr_regl(port, S3C2410_UFCON, ufcon);

	/* set Rx mode to DMA mode */
	ucon = rd_regl(port, S3C2410_UCON);
	ucon &= ~((0x7 << UCON_RXBURST_SZ) |
			(0x1 << UCON_TIMEOUT_VAL) |
			(0x1 << UCON_EMPTYINT_EN) |
			(0x1 << UCON_DMASUS_EN) |
			UCON_RXMODE_CL);
	ucon |= (BURST_1BYTE << UCON_RXBURST_SZ) |
			(0xf << UCON_TIMEOUT_VAL) |
			(0x1 << UCON_EMPTYINT_EN) |
			(0x1 << UCON_DMASUS_EN) |
			(0x1 << UCON_TIMEOUT_EN) |
			UCON_RXDMA_MODE;
	wr_regl(port, S3C2410_UCON, ucon);

	spin_unlock_irqrestore(&port->lock, flags);

}

static void callback_uart_tx_dma(void *data)
{
	struct exynos_uart_dma *uart_dma;
	struct s3c24xx_uart_port *ourport;
	struct uart_port *port;
	struct circ_buf *xmit;
	unsigned long ucon, uintm;

	uart_dma = container_of(data, struct exynos_uart_dma, tx);
	ourport = container_of(uart_dma, struct s3c24xx_uart_port, uart_dma);
	port = &ourport->port;

	dbg("callback_uart_dma\n");
	dma_unmap_single(port->dev, uart_dma->tx_src_addr,
			uart_dma->tx.req_size, DMA_TO_DEVICE);

	xmit = &port->state->xmit;
	xmit->tail = (xmit->tail + uart_dma->tx.req_size)&(UART_XMIT_SIZE - 1);
	port->icount.tx += uart_dma->tx.req_size;

	/* Set Tx mode to interrupt mode */
	ucon = rd_regl(port, S3C2410_UCON);
	ucon &= ~((0x7 << UCON_TXBURST_SZ) | UCON_TXMODE_CL);
	ucon |= UCON_TXCPU_MODE;
	wr_regl(port,  S3C2410_UCON, ucon);

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(port);

	if (uart_circ_empty(xmit)) {
		s3c24xx_serial_stop_tx(port);
	} else {
		/* Unmask tx DMA */
		uintm = rd_regl(port, S3C64XX_UINTM);
		uintm &= ~(0x1 << 2);
		wr_regl(port, S3C64XX_UINTM, uintm);
	}
}

static void prepare_dma(struct uart_dma_data *dma,
					unsigned len, dma_addr_t buf)
{
	struct exynos_uart_dma *uart_dma;
	struct samsung_dma_prep info;
	struct samsung_dma_config config;

	info.cap = DMA_SLAVE;
	info.len = len;


	info.fp = callback_uart_tx_dma;
	info.fp_param = dma;
	info.direction = dma->direction;
	info.buf = buf;

	config.direction = dma->direction;
	config.fifo = dma->fifo_base;
	/* burst size = 1byte (1, 4, 8bytes) */
	config.width = DMA_SLAVE_BUSWIDTH_1_BYTE;

	if (dma->direction == DMA_MEM_TO_DEV) {
		uart_dma = container_of((void *)dma,
				struct exynos_uart_dma, tx);
		info.fp = callback_uart_tx_dma;
	} else {
		uart_dma = container_of((void *)dma,
				struct exynos_uart_dma, rx);
		info.fp = callback_uart_rx_dma;
	}

	uart_dma->ops->config(dma->ch, &co
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值