UART驱动
原文地址:http://blog.csdn.net/woshidahuaidan2011/article/details/52054849
1、对UART驱动添加设备信息
对于2440的UART,内核已经对其完整的配置不需要做写入任何的代码,
这里要说明的是,在学习的裸机的时候,我们知道,UART相应的引脚可以配置称为红外IR,这里串口2就被配置成了红外驱动。
对于平台设备,首先要说明的应该是s3c2410_uartcfg结构体,该结构体定义在,Serial_s3c.h(include\linux)文件中
structs3c2410_uartcfg {
unsigned char hwport; /* 硬件端口编号比如UART0 UART1 等等*/
unsigned char unused; //发送和接收使能控制信号
unsigned short flags; //标记号
upf_t uart_flags; /* 默认UART标记号,流量控制标志位*/
unsigned int clk_sel; //时钟选择
unsigned int has_fracval;
unsigned long ucon; /* 对应控制寄存器UCONn*/
unsigned long ulcon; /*对应格式寄存器ULCONn */
unsigned long ufcon; /* 设置缓冲区的寄存器UFCONn */
};
在Mach-smdk2440.c (arch\arm\mach-s3c24xx)文件中,有对其平台设备信息的描述:
static structs3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {
[0] = {
.hwport = 0,
.flags = 0,
.ucon = 0x3c5,//00000011 1100 0101
/**************************************************************************
对应的二进制为0000 0011 1100 0101
查看数据手册可以很直观的看出来,这里是设置含义是:
接收和发送均使用中断或者查询法
将发生数据帧错误时将触发中断
当使用FIFO的时候,接收超时将产生中断,设置为低电平触发中断
UART的时钟选用PCLK
*******************************************************************/
.ulcon = 0x03,
/**************************************************************************
对应的二进制为0000 0000 0000 0011
这里主要设设置帧格式:
8为数据位
1为停止位
无校验位
正常模式(非红外模式)
*******************************************************************/
.ufcon = 0x51,
/**************************************************************************
对应的二进制为0000 00000101 0001
这是设置使用FIFO
使能FIFO
接收FIFO的阈值为8BYTE
接收FIFO的阈值为16BYTE
*******************************************************************/
},
[1] = {
.hwport = 1,
.flags = 0,
.ucon = 0x3c5,
.ulcon = 0x03,
.ufcon = 0x51,
},
/* 设备为红外模式*/
[2] = {
.hwport = 2,
.flags = 0,
.ucon = 0x3c5,
.ulcon = 0x43, //红外模式
.ufcon = 0x51,
}
};
在Mach-smdk2440.c中的smdk2440_map_io函数中有:
s3c24xx_init_uarts(smdk2440_uartcfgs,ARRAY_SIZE(smdk2440_uartcfgs));(该函数定义在Init.c (arch\arm\plat-samsung) 中),干函数又调用:
(cpu->init_uarts)(cfg,no);
cpu->init_uarts在Common.c (arch\arm\mach-s3c24xx)文件中,指向的是s3c244x_init_uarts(该函数定义在S3c244x.c (arch\arm\mach-s3c24xx)),然后s3c244x_init_uarts又调用s3c244x_init_uarts函数:
void __inits3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
s3c24xx_init_uartdevs("s3c2440-uart",s3c2410_uart_resources, cfg, no);
}
也就是说s3c24xx_init_uarts(smdk2440_uartcfgs,ARRAY_SIZE(smdk2440_uartcfgs));最终会被使用成:
s3c24xx_init_uartdevs("s3c2440-uart",s3c2410_uart_resources, cfg, no);
也就是,UART的平台设备定义的名字为:"s3c2440-uart"
可以,应该不容忽视的是,这里内核还定义了系统的资源,s3c2410_uart_resources,该资源在Common.c (arch\arm\mach-s3c24xx)文件中被定义:
static structresource s3c2410_uart0_resource[] = {
[0] = DEFINE_RES_MEM(S3C2410_PA_UART0,SZ_16K),
[1] = DEFINE_RES_NAMED(IRQ_S3CUART_RX0, \
IRQ_S3CUART_ERR0 -IRQ_S3CUART_RX0 + 1, \
NULL, IORESOURCE_IRQ)
};
static structresource s3c2410_uart1_resource[] = {
[0] = DEFINE_RES_MEM(S3C2410_PA_UART1,SZ_16K),
[1] = DEFINE_RES_NAMED(IRQ_S3CUART_RX1, \
IRQ_S3CUART_ERR1 -IRQ_S3CUART_RX1 + 1, \
NULL, IORESOURCE_IRQ)
};
static structresource s3c2410_uart2_resource[] = {
[0] = DEFINE_RES_MEM(S3C2410_PA_UART2,SZ_16K),
[1] = DEFINE_RES_NAMED(IRQ_S3CUART_RX2, \
IRQ_S3CUART_ERR2 -IRQ_S3CUART_RX2 + 1, \
NULL, IORESOURCE_IRQ)
};
在这里,我们以uart0的资源为例来分析一下资源的构成:
static structresource s3c2410_uart0_resource[] = {
[0] = DEFINE_RES_MEM(S3C2410_PA_UART0,SZ_16K),
[1] =DEFINE_RES_NAMED(IRQ_S3CUART_RX0, \
IRQ_S3CUART_ERR0 - IRQ_S3CUART_RX0 + 1, \
NULL, IORESOURCE_IRQ)
};
可以看到对于UART0一共定义了两个资源,其中:
[0] =DEFINE_RES_MEM(S3C2410_PA_UART0, SZ_16K),表明声明的为内存资源,其实,宏DEFINE_RES_MEM被定义为:
{ \
.start = (_start), \
.end = (_start) + (_size) - 1, \
.name = (_name), \
.flags = (_flags), \
}
其中name为NULL;也就是说,UART申请的内存资源为起始物理地址为S3C2410_PA_UART0,大小为SZ_16K,很容易可以查到S3C2410_PA_UART0对应的物理地址就是0x50000000,通过查看数据手册得到0x50000000对应的是ULCON0(UART channel 0 line controlregister,控制帧格式的寄存器)的首地址,那么为什么是SZ_16K呢?继续看数据手册,发现ULCON1的首地址是0x50004000,那么0x50004000-0x50000000=SZ_16K,所以是大小为16kBYTE;.flags = (_flags)这个标号对应的内存资源的标记号,只不过系统为每种资源设定的标记号码而已,驱动找什么类型资源就是对应这个标号找的。
补充一点的是:这里的内存资源是16Kbyte,而且申请的时候为什么只有是一个寄存器的地址,这里是因为对于控制UART0的寄存器组的地址是有规律的,也就是说每个寄存器占有四个字节,上一个寄存器的地址+4就是下一个寄存器的首地址,而ULCON0(0x50000000)就UART0寄存器组的第一个寄存器,可以理解为是该组寄存器的首地址。
查看数据手册,对于UART0来说,看一下其寄存器分配:
ULCON0 0x50000000 R/W UART channel 0 line controlregister
UCON0 0x50000004 R/W UART channel 0 controlregister
UFCON0 0x50000008 R/W UART channel 0 FIFO controlregister
UMCON0 0x5000000C R/W UART channel 0 Modem controlregister
UTRSTAT0 0x50000010 R UART channel 0 Tx/Rx statusregister
UERSTAT0 0x50000014 R UART channel 0 Rx error statusregister
UFSTAT0 0x50000018 R UART channel 0 FIFO statusregister
UMSTAT0 0x5000001C R UART channel 0 modem statusregister
UTXH00x50000020(L)
0x50000023(B) W (by byte) UART channel 0 transmit buffer register
URXH00x50000024(L)
0x50000027(B) R (bybyte) UART channel 0 receive bufferregister
UBRDIV0 0x50000028 R/W Baud rate divisior register 0
接下来看第二个资源:
[1] =DEFINE_RES_NAMED(IRQ_S3CUART_RX0, \
IRQ_S3CUART_ERR0 -IRQ_S3CUART_RX0 + 1, \
NULL, IORESOURCE_IRQ)
首先把宏替换掉就是:
.start = IRQ_S3CUART_RX0, \ //74
.end = RQ_S3CUART_ERR0 -IRQ_S3CUART_RX0 + 1, \ //76-74+1
.name = NULL, \
.flags = IORESOURCE_IRQ,
对于IRQ就比较简单了,内核为每个中断都有对其唯一的IRQ号,这些号码定义在Irqs.h(arch\arm\mach-s3c24xx\include\mach) 文件中(对于中断号的问题,这些东西在裸机接扫寄存器的时候已经写的很详细了,通过中断号可以判定是哪个中断源引起的中断),可以看到:
UART0接收中断 #define IRQ_S3CUART_RX0 74
UART0发送中断 #define IRQ_S3CUART_TX0 75
UART0错误中断 #define IRQ_S3CUART_ERR0 76
在平台设备中定义的这些资源,可以通过在驱动程序通过函数:
platform_get_resource来或许相应的资源。
对于平台设备,暂时分析到这里,后面看驱动分析和测试部分。
2、对UART驱动的测试
假如够细心的话,可以在内核启动的时候看到:
s3c2440-uart.0:ttySAC0 at MMIO 0x50000000 (irq = 74, base_baud = 0) is a S3C2440
console[ttySAC0] enabled
s3c2440-uart.1:ttySAC1 at MMIO 0x50004000 (irq = 77, base_baud = 0) is a S3C2440
s3c2440-uart.2:ttySAC2 at MMIO 0x50008000 (irq = 80, base_baud = 0) is a S3C2440
显然,UART0使能控制台功能。
其设备文件存放在/dev/目录下:
/ # ls -ldev/ttySAC*
crw-rw---- 1 0 0 204, 64 Jan 1 00:00 dev/ttySAC0
crw-rw---- 1 0 0 204, 65 Jan 1 00:00 dev/ttySAC1
crw-rw---- 1 0 0 204, 66 Jan 1 00:00 dev/ttySAC2
可以看到起主设备号码为204,次设备号码分别是64 6566
可以简单的测试:
在终端输入:
echo “hello world” > /dev/ttySAC0
那么结果就会在终端打印出hello world
下面写代码来测试一下串口通信:
在写c代码之前,不得不介绍一个结构体tcgetattr其定义在Termbits.h (include\uapi\asm-generic)文件中:
struct termios {
unsigned intc_iflag; /* input mode flags */
unsigned intc_oflag; /* output mode flags */
unsigned intc_cflag; /* control mode flags */
unsigned intc_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned charc_cc[19]; /* controlcharacters */
};
在对串口编程过程中,可以使用tcgetattr函数得到对应串口的termios结构体。通过tcsetattr函数可以将设置后termios结构体传递给对应的串口。这两个函数定义在交叉编译链的include/termios.h文件中:
/* Put the stateof FD into *TERMIOS_P. */
extern int tcgetattr (int __fd, struct termios*__termios_p) __THROW;
/* Set the stateof FD to *TERMIOS_P. Values forOPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>. */
extern int tcsetattr (int __fd, int __optional_actions,const struct termios *__termios_p) __THROW;
两个函数都是成功返回零;失败返回非零,发生失败接口将设置errno错误标识。
对于设置参数函数tcsetattr的__optional_actions参数,其有三种可能:
1.TCSANOW:设定值马上有效
2.TCSADRAIN:等当前的输出完成后设定值才有效。
3.TCSAFLUSH:等当前的输出完成后设定值才有效,但会丢弃还未从read调用返回的当前的可用的数据。
下面看一下结构体termios 都可怎么配置参数,对于配置参数宏的设定是在内核源码包目录下的Termbits.h (include\uapi\asm-generic) 文件中:
①对于输入模式标志位c_iflag的设置:
IGNBRK | 忽略BREAK键输入 |
BRKINT | 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断 |
IGNPAR | 忽略奇偶校验错误 |
PARMRK | 标识奇偶校验错误 |
INPCK | 允许输入奇偶校验 |
ISTRIP | 去除字符的第8个比特 |
INLCR | 将输入的NL(换行)转换成CR(回车) |
IGNCR | 忽略输入的回车 |
ICRNL | 将输入的回车转化成换行(如果IGNCR未设置的情况下) |
IUCLC | 将输入的大写字符转换成小写字符(非POSIX) |
IXON | 允许输入时对XON/XOFF流进行控制 |
IXANY | 输入任何字符将重启停止的输出 |
IXOFF | 允许输入时对XON/XOFF流进行控制 |
IMAXBEL | 当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置 |
②对于输出模式标志位c_oflag的设置:
键 值 | 说 明 |
处理后输出 | |
OLCUC | 将输入的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输入的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输入的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX) |
NLDLY | 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s) |
CRDLY | 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 |
TABDLY | 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3 |
BSDLY | 空格输出延迟,可以取BS0或BS1 |
VTDLY | 垂直制表符输出延迟,可以取VT0或VT1 |
FFDLY | 换页延迟,可以取FF0或FF1 |
③对于控制模式标志位c_cflag的设置:
键 值 | 说 明 |
CBAUD | 波特率(4+1位)(非POSIX) |
附加波特率(1位)(非POSIX) | |
CSIZE | 字符长度,取值范围为CS5、CS6、CS7或CS8 |
CSTOPB | 设置两个停止位 |
CREAD | 使用接收器 |
PARENB | 使用奇偶校验 |
PARODD | 对输入使用奇偶校验,对输出使用偶校验 |
HUPCL | 关闭设备时挂起 |
CLOCAL | 忽略调制解调器线路状态 |
CRTSCTS | 使用RTS/CTS流控制 |
这里要说明一下串口支持的波特率的表示方式:
#define B0 0000000 /* hang up */
#define B50 0000001 #define B75 0000002 #define B110 0000003
#define B134 0000004 #define B150 0000005 #define B200 0000006
。。。。。。。。。。省略。。。。。。。。。。。。。。。。。。
#define B57600 0010001 #define B115200 0010002 #define B230400 0010003
。。。。。。。。。。。省略。。。。。。。。。。。。。。。。。。。
#define B3500000 0010016 #define B4000000 0010017
对于波特率,还提供几个函数专门处理波特率:
extern speed_tcfgetospeed (const struct termios *__termios_p) __THROW; //返回输出波特率
extern speed_tcfgetispeed (const struct termios *__termios_p) __THROW; //返回输输入波特率
extern int cfsetospeed (struct termios *__termios_p, speed_t__speed) __THROW; //设置输出波特率
extern intcfsetispeed (struct termios *__termios_p, speed_t __speed) __THROW; //设置输入波特率
字符长度设置:
#define CS5 0000000 5位数据位
#define CS6 0000020 6位数据位
#define CS7 0000040 7位数据位
#define CS8 0000060 8位数据位
④对于本地模式标志位c_lflag的设置:
键 值 | 说 明 |
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
XCASE | 在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数) |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
④对于本地模式标志位c_cc的设置:
宏 | 说 明 | 宏 | 说 明 | |
VINTR | Interrupt字符 | VEOL | 附加的End-of-file字符 | |
VQUIT | Quit字符 | VTIME | 非规范模式读取时的超时时间 | |
VERASE | Erase字符 | VSTOP | Stop字符 | |
VKILL | Kill字符 | VSTART | Start字符 | |
VEOF | End-of-file字符 | VSUSP | Suspend字符 | |
VMIN | 非规范模式读取时的最小字符数 |
以上数据表参考博客:http://blog.chinaunix.net/uid-10747583-id-97303.html
下面开始尝试最简单的c代码设置uart:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<termios.h>
#include<errno.h>
#include<string.h>
#define BUFFER_NUM 100
int main(intargc, char **argv) {
int input_number;
int fd;
unsigned char buf[BUFFER_NUM]="input some char,please.Or,input num 1 to exit yhis programe\n"; //存储数组
fd= open("/dev/ttySAC0",O_NOCTTY|O_RDWR );
struct termios parameter;
if (tcgetattr( fd,¶meter) != 0) { //获取termios结构体
perror("tcgetattr error");
return -1;
}
cfsetispeed(¶meter, B115200); //设置接收波特率115200
cfsetospeed(¶meter, B115200); //设置发送波特率115200
parameter.c_cflag |= CS8;//8位数据位
parameter.c_cflag &= ~CSTOPB; //1位停止位
parameter.c_cflag &= ~PARENB; //不使用奇偶校验
parameter.c_iflag &= ~INPCK; //忽略奇偶校验
parameter.c_cflag &= ~CRTSCTS;//不使用RTS/CTS流控制
parameter.c_oflag &= ~OPOST;//没有对数据处理,这里不用设置处理后输出
if (tcsetattr(fd,TCSANOW,¶meter)!= 0) { //设置参数马上有效
perror("com set error!\n");
return -2;
}
write(fd,buf,strlen(buf));// 发送数据
while(1) {
input_number=read(fd,buf,BUFFER_NUM);
if(input_number){ //接收数据
if(buf[0]=='1')
break;
write(fd,buf,input_number);// 发送数据
}
}
printf("\n exit \n");
close(fd);
return 0;
}
该代码的作用是输入相应的输入,然后通过串口接收将相应的字符打印出来,当输出数字1的时候,将自动结束本代码。
3、对UART驱动的分析
在分析之前先对几个名词做出了解:
首先串口和UART的关系:其实两者的都是同一个东西,唯一不同的地方是两者的逻辑电平不一样,串口的电平范围要求是:1:-12V~-6V,0:6V~12V;UART指CPU带的串行端口,电平为,1:5V,0:0V。所以两着之前通信需要电平转换,电平转换可以用RS232,或MAX232。
Linux包含如下几种终端设备:串行端口终端(/dev/ttySn)、伪终端(/dev/pty)、控制终端(/dev/tty)、控制台终端(/dev/ttyn,/dev/conslole)。对于2440串行端口终端使用的设备名为/dev/ttyS0,/dev/ttyS1,/dev/ttyS2等。通过查看/proc/tty/drivers文件可以知道什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同tty驱动的列表,包括驱动名,缺省的节点名,驱动的主编号,驱动的次编号范围,以及tty驱动的类型。
下面来分析一下串口驱动程序的代码,内核的串口驱动的代码在Samsung.c (drivers\tty\serial)文件中:
首先文件中可以看到:
#defineS3C24XX_SERIAL_NAME "ttySAC"
#defineS3C24XX_SERIAL_MAJOR 204
#defineS3C24XX_SERIAL_MINOR 64
这是在定义的UART的名字和设备号。比较简单不在介绍。
接下来看一下驱动的入口函数:
static int__init s3c24xx_serial_modinit(void)
{
int ret;
ret =uart_register_driver(&s3c24xx_uart_drv);
if (ret < 0) {
pr_err("Failed to registerSamsung UART driver\n");
return ret;
}
*************************************************************************************
这里是在注册为UART驱动,这个要分析一个注册的过程:
首先先看传递的参数------s3c24xx_uart_drv
static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.driver_name = "s3c2410_serial",//驱动名,在/proc/tty/driver/目录下将显示的名字
.nr = CONFIG_SERIAL_SAMSUNG_UARTS,
//串口的数量(定义在/include/linux/serial/Kconfig)
.cons = S3C24XX_SERIAL_CONSOLE,
//设置控制台
.dev_name = S3C24XX_SERIAL_NAME,
//设备文件的名字
.major = S3C24XX_SERIAL_MAJOR, //主设备号204
.minor = S3C24XX_SERIAL_MINOR, //次设备号64
};
uart_register_driver定义在Serial_core.c(drivers\tty\serial)文件中,函数原型为:
int uart_register_driver(struct uart_driver *drv)
{
structtty_driver *normal;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache forthis, especially if
* we have a large number of ports to handle.
*/
drv->state= kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if(!drv->state)
gotoout;
normal =alloc_tty_driver(drv->nr);//申请为tty驱动
if (!normal)
gotoout_kfree;
drv->tty_driver= normal;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag= B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed= normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW |TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal,&uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i< drv->nr; i++) {
structuart_state *state = drv->state + i;
structtty_port *port = &state->port;
tty_port_init(port);
port->ops= &uart_port_ops;
port->close_delay = HZ / 2; /* .5 seconds */
port->closing_wait = 30 * HZ;/* 30 seconds */
}
retval =tty_register_driver(normal);//注册tty驱动
if (retval>= 0)
returnretval;
for (i = 0; i< drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return-ENOMEM;
}
为了有更多经历研究后面的代码,这个函数是内核里面的,估计一般写驱动直接调用就可以,不在深入分析,但是该函数的tty_set_operations(normal, &uart_ops);是将驱动代码跟器对应的文件操作的uart_ops结构体,该结构体中定义了对串口的open close write stop start的操作函数。
通过该函数,就会在uart的核心层注册一个驱动,同时也就注册一个tty驱动。
而且该函数会在在/proc/tty/driver/创建"s3c2410_serial"
注册完毕后需要调用uart_add_one_port去添加一个UART。
**************************************************************************/
ret =platform_driver_register(&samsung_serial_driver);//注册平台驱动
if (ret < 0) {
pr_err("Failed to registerplatform driver\n");
uart_unregister_driver(&s3c24xx_uart_drv);
}
return ret;
}
通过上面注册平台驱动函数可以知道,接下要执行的函数是:
static structplatform_driver samsung_serial_driver = {
.probe =s3c24xx_serial_probe,
.remove =s3c24xx_serial_remove,
.id_table =s3c24xx_serial_driver_ids,//平台配对比较
.driver ={
.name = "samsung-uart", //驱动的名字
.owner = THIS_MODULE,
.pm =SERIAL_SAMSUNG_PM_OPS, //电源管理
.of_match_table = of_match_ptr(s3c24xx_uart_dt_match),
//假如没有.id_table就调用此函数比较
},
};
之前一直再说,设备和驱动是通过比较名字来配对的,实际上,比较平台比较配对的方法不止一种(比较函数的优先级不同),看一个平台配对比较的函数platform_match其定义在Platform.c (drivers\base) :
static intplatform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev =to_platform_device(dev);
struct platform_driver *pdrv =to_platform_driver(drv);
/* Attempt anOF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then tryACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try tomatch against the id table */
if (pdrv->id_table)
returnplatform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-backto driver name match */
return (strcmp(pdev->name,drv->name) == 0);
}
函数的注释部分写的已经很详细了,换句话就是说,在struct platform_driver中,假如设备驱动有定义of_match_table就是用of_match_table进行比较,假如定义了id_table就是用id_table,假如没有定义比较函数那么数那么就是用平台驱动和设备的名字进行比较,这里,定义了.id_table =s3c24xx_serial_driver_ids,那么会调用s3c24xx_serial_driver_ids来匹配该驱动有无对应设备。不再使用name来匹配驱动与设备。
s3c24xx_serial_driver_ids的函数原型为:
static structplatform_device_id s3c24xx_serial_driver_ids[] = {
{
.name = "s3c2410-uart",
.driver_data = S3C2410_SERIAL_DRV_DATA,
}, {
.name = "s3c2412-uart",
.driver_data = S3C2412_SERIAL_DRV_DATA,
}, {
.name = "s3c2440-uart",
.driver_data = S3C2440_SERIAL_DRV_DATA,
},
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。省略。。。。。。。。。。
};
在上面对平台设备信息分析的时候,我们知道平台设备的名字是"s3c2440-uart",那么.driver_data =S3C2440_SERIAL_DRV_DATA,可以看到:
static structs3c24xx_serial_drv_data s3c2440_serial_drv_data = {
.info = &(struct s3c24xx_uart_info) {
.name = "Samsung S3C2440 UART",
.type = PORT_S3C2440,
.fifosize = 64,
.has_divslot = 1,
.rx_fifomask = S3C2440_UFSTAT_RXMASK, //接受掩码
.rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, //位移量
.rx_fifofull = S3C2440_UFSTAT_RXFULL,
.tx_fifofull = S3C2440_UFSTAT_TXFULL,
.tx_fifomask = S3C2440_UFSTAT_TXMASK,
.tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
.def_clk_sel = S3C2410_UCON_CLKSEL2,
.num_clks = 4,
.clksel_mask = S3C2412_UCON_CLKMASK,
.clksel_shift = S3C2412_UCON_CLKSHIFT,
},
.def_cfg = &(struct s3c2410_uartcfg) {
.ucon = S3C2410_UCON_DEFAULT,
.ufcon = S3C2410_UFCON_DEFAULT,
},
};
匹配成功后,上面就是定义一下芯片的特性数据,还有一些寄存器的默认数据。这里要注意,
.of_match_table = of_match_ptr(s3c24xx_uart_dt_match),和上面分析的函数功能是一样的。在这里作用优先级是这样的,这是主要是设备树的匹配,这里不多做解释。假如platform没有定义比较配对函数,就调用驱动的比较配对函数。
接下来看一下probe函数:
static ints3c24xx_serial_probe(struct platform_device *pdev)
{
struct s3c24xx_uart_port *ourport;
/**************************************************************************
struct s3c24xx_uart_port {
unsigned char rx_claimed;// 标志是否设置接收函数
unsigned char tx_claimed;//标志是否设置发送函数
unsigned int pm_level; //电源管理
unsigned long baudclk_rate; //波特率
unsigned int rx_irq; //rx中断函数
unsigned int tx_irq; //tx中断函数
structs3c24xx_uart_info *info;//2440的UART的信息,比如fifo缓存器大小等在比较函数中有赋值
struct clk *clk; //UART的时钟
struct clk *baudclk;
structuart_port port;
structs3c24xx_serial_drv_data *drv_data;//驱动的私有数据
/* referenceto platform data */
structs3c2410_uartcfg *cfg; //平台设备设定的参数
#ifdef CONFIG_CPU_FREQ
structnotifier_block freq_transition;
#endif*******************************************************************/
int ret;
dbg("s3c24xx_serial_probe(%p)%d\n", pdev, probe_index); //宏定义就是printk
ourport =&s3c24xx_serial_ports[probe_index];
/**************************************************************************
probe_index是代表UART的个数,假如又三个串口,那么probe_index就就是0 1 2,后面将会出现probe_index++;因为2440有三个串口,所以这里他会probe_index++两次。对于结构体s3c24xx_serial_ports,有定义:
static struct s3c24xx_uart_ports3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[0] = {
.port ={
.lock =__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 0,
}
},
[1] = {
.port ={
.lock =__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 1,
}
},
。。。。。。。。。。。。。。。。。。。。省略
[2] = {
.port ={
.lock =__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype = UPIO_MEM,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 2,
}
CONFIG_SERIAL_SAMSUNG_UARTS是在内核配置菜单中生成的,为3.
该结构体比较关键的是绑定了与没个串口对应的 .ops =&s3c24xx_serial_ops。
*******************************************************************/
ourport->drv_data =s3c24xx_get_driver_data(pdev);
/**************************************************************************
无论怎么都会获得驱动设置的参数
*******************************************************************/
if (!ourport->drv_data) {
dev_err(&pdev->dev,"could not find driver data\n");
return -ENODEV;
}
ourport->baudclk = ERR_PTR(-EINVAL); //保存的错误标记
ourport->info =ourport->drv_data->info;
/**************************************************************************
获得s3c24xx_uart_info,s3c24xx_uart_info是芯片的一些特定信息,比如fifo的大小,时钟分频系数的设定等。
*******************************************************************/
ourport->cfg =(dev_get_platdata(&pdev->dev)) ?
dev_get_platdata(&pdev->dev):
ourport->drv_data->def_cfg;
/**************************************************************************
这里判定是否在平台设备上设置有关的配置信息,假如没有设置的话就是用系统设置的默认值。
*******************************************************************/
ourport->port. fifosize =(ourport->info->fifosize) ? //获取fifosize
ourport->info->fifosize :
ourport->drv_data->fifosize[probe_index];
probe_index++;
dbg("%s: initialising port%p...\n", __func__, ourport);
/**************************************************************************
打印信息 %p 这里的P是指针的意思
__func__,表示本函数的名字,这就是表示s3c24xx_serial_probe
*******************************************************************/
ret = s3c24xx_serial_init_port(ourport,pdev);
/**************************************************************************
这里初始化UART主要的工作获得中断号,设置时钟等
staticint s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
struct platform_device *platdev)
{
struct uart_port *port =&ourport->port;
struct s3c2410_uartcfg *cfg =ourport->cfg;
struct resource *res; //定义资源,一会要获取资源
int ret;
dbg("s3c24xx_serial_init_port:port=%p, platdev=%p\n", port, platdev);
if (platdev == NULL)
return -ENODEV;
if (port->mapbase != 0)//假如之前已经获取过资源,直接退出函数
return 0;
/* setup info for port */
port->dev = &platdev->dev;
/* Startup sequence is different fors3c64xx and higher SoC's */
if(s3c24xx_serial_has_interrupt_mask(port)) //判断是不是s3c64xx或跟高型号芯片
s3c24xx_serial_ops.startup =s3c64xx_serial_startup; //s3c64xx或跟高型号芯片需要执行startup
port->uartclk = 1;
if (cfg->uart_flags &UPF_CONS_FLOW) { //判断是否设置流量控制
dbg("s3c24xx_serial_init_port:enabling flow control\n");
port->flags |= UPF_CONS_FLOW;
}
/* sort our the physical and virtualaddresses for each UART */
res = platform_get_resource(platdev,IORESOURCE_MEM, 0);//获取内存资源
if (res == NULL) {
dev_err(port->dev, "failedto find memory resource for uart\n");
return -EINVAL;
}
dbg("resource %p (%lx..%lx)\n",res, res->start, res->end);
port->membase = devm_ioremap(port->dev,res->start, resource_size(res)); //因为上面获取的是物理地址,假如要使用的需要转换成虚拟地址
if (!port->membase) {
dev_err(port->dev, "failedto remap controller address\n");
return -EBUSY;
}
port->mapbase = res->start;
ret = platform_get_irq(platdev, 0); //获取中断资源
if (ret < 0)
port->irq = 0;
else {
port->irq = ret;
ourport->rx_irq = ret; //接收中断号
ourport->tx_irq = ret + 1; //发送中断号
}
ret = platform_get_irq(platdev, 1); //或许第二个中断资源(实际上UART只有一个中断资源,所以这里返回的应该是负数)
if (ret > 0)
ourport->tx_irq = ret;
ourport->clk = clk_get(&platdev->dev, "uart");
//获取"uart"的时钟(定义在Clock-s3c2410.c (arch\arm\mach-s3c24xx))
if (IS_ERR(ourport->clk)) {
pr_err("%s: Controller clocknot found\n",
dev_name(&platdev->dev));
return PTR_ERR(ourport->clk);
}
ret = clk_prepare_enable(ourport->clk); //对上面获取的时钟使能
if (ret) {
pr_err("uart: clock failed toprepare+enable: %d\n", ret);
clk_put(ourport->clk);
return ret;
}
/* Keep all interrupts masked and cleared*/
if(s3c24xx_serial_has_interrupt_mask(port)) {//判断是不是s3c64xx或跟高型号芯片
wr_regl(port, S3C64XX_UINTM, 0xf);
wr_regl(port, S3C64XX_UINTP, 0xf);
wr_regl(port, S3C64XX_UINTSP, 0xf);
}
dbg("port: map=%08x, mem=%08x, irq=%d(%d,%d), clock=%ld\n",
port->mapbase, port->membase, port->irq,
ourport->rx_irq, ourport->tx_irq, port->uartclk);
/* reset the fifos (and setup the uart) */
s3c24xx_serial_resetport(port, cfg);
/**************************************************************************
staticvoid s3c24xx_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
{
struct s3c24xx_uart_info *info =s3c24xx_port_to_info(port);
unsigned long ucon = rd_regl(port,S3C2410_UCON);
/**************************************************************************
在函数3c24xx_serial_init_port有:
port->membase= devm_ioremap(port->dev, res->start, resource_size(res));
port->membase就是申请寄存器的基地址
宏S3C2410_UCON是UCONn寄存器相对于基地址的偏移量
宏rd_regl(port, S3C2410_UCON)替换一下就是读取的UCONn寄存器的数值其中
port->membase+S3C2410_UCON就是UCONn的寄存器地址
这里对下面几个宏说明一下:
读取8位寄存器:#definerd_regb(port, reg) (__raw_readb(portaddr(port, reg)))
读取32位寄存器:#definerd_regl(port, reg) (__raw_readl(portaddr(port, reg)))
写入8位寄存器(port, reg,val) __raw_writeb(val, portaddr(port, reg))
写入32位寄存器(port, reg,val) __raw_writel(val, portaddr(port, reg))
*******************************************************************/
unsigned int ucon_mask;
ucon_mask = info->clksel_mask;
if (info->type == PORT_S3C2440) //假如是2440,假如使用fclk,这里设置分频系数
ucon_mask |= S3C2440_UCON0_DIVMASK;
ucon &= ucon_mask;
wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); //写入平台设备设置的数据
/* reset both fifos */
wr_regl(port, S3C2410_UFCON, cfg->ufcon| S3C2410_UFCON_RESETBOTH);
wr_regl(port, S3C2410_UFCON,cfg->ufcon); //写入平台设备设置的数据
/* some delay is required after fifo reset*/
udelay(1);
}
*******************************************************************/
return 0;
}
*******************************************************************/
if (ret < 0)
goto probe_err;
dbg("%s: adding port\n",__func__);
uart_add_one_port(&s3c24xx_uart_drv,&ourport->port);
/**************************************************************************
在模块加载的时候曾调用uart_register_driver函数进行注册一个uart的驱动,并绑定文件操作结构体,在注册成功后这里调用uart_add_one_port函数,添加一个串口驱动设备。
*******************************************************************/
platform_set_drvdata(pdev,&ourport->port); //将数据保存起来
/*
*Deactivate the clock enabled in s3c24xx_serial_init_port here,
*so that a potential re-enablement through the pm-callback overlaps
*and keeps the clock enabled in this case.
*/
clk_disable_unprepare(ourport->clk); //disable clk
#ifdefCONFIG_SAMSUNG_CLOCK
ret =device_create_file(&pdev->dev, &dev_attr_clock_source);
//sysfs下创建文件并设置其属性(show和store函数)
if (ret < 0)
dev_err(&pdev->dev,"failed to add clock source attr.\n");
#endif
ret =s3c24xx_serial_cpufreq_register(ourport);//跟节能有关系,申请cpu变频
if (ret < 0)
dev_err(&pdev->dev,"failed to add cpufreq notifier\n");
return 0;
probe_err:
return ret;
}
uart的交给用户写的probe比较简单,看一下uart设备对应文件操作结构体:
static structuart_opss3c24xx_serial_ops ={
.pm =s3c24xx_serial_pm, //电源管理
.tx_empty =s3c24xx_serial_tx_empty,//判断发送缓冲区空
.get_mctrl =s3c24xx_serial_get_mctrl,//得到流控设置参数
.set_mctrl =s3c24xx_serial_set_mctrl, //设置流控参数
.stop_tx =s3c24xx_serial_stop_tx, //设置停止发送
.start_tx =s3c24xx_serial_start_tx, //设置开始发送
.stop_rx =s3c24xx_serial_stop_rx, //设置停止接收
.enable_ms =s3c24xx_serial_enable_ms, //这是个空函数,什么都不做
.break_ctl =s3c24xx_serial_break_ctl,//设置是否发送break信号
.startup =s3c24xx_serial_startup, //启动端口
.shutdown =s3c24xx_serial_shutdown, //关闭端口
.set_termios = s3c24xx_serial_set_termios, //uart通信有关参数的设置
.type =s3c24xx_serial_type, //返回的对应的芯片型号的字符串,这里对应的是"S3C2440";
.release_port = s3c24xx_serial_release_port, //释放申请的端口对应内存资源
.request_port = s3c24xx_serial_request_port, //申请的端口对应内存资源
.config_port = s3c24xx_serial_config_port,//配置端口类型这里的类型就是对应是"S3C2440"
.verify_port = s3c24xx_serial_verify_port, //验证配口类型是否配置正确
#ifdefined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
.poll_get_char =s3c24xx_serial_get_poll_char,//轮询发送数据
.poll_put_char =s3c24xx_serial_put_poll_char,// /轮询接收数据
#endif
};
下面就选取几个有代表性质的函数进行分析。首先分析启动UART要执行的函数:
static int s3c24xx_serial_startup(struct uart_port *port)
{
struct s3c24xx_uart_port*ourport = to_ourport(port);
/**************************************************************************
to_ourport函数调用的是container_of函数,这个函数在按键驱动已经分析过,是得到s3c24xx_uart_port的结构体的首地址指针。
*******************************************************************/
int ret;
dbg("s3c24xx_serial_startup:port=%p (%08lx,%p)\n",
port->mapbase, port->membase);
rx_enabled(port) = 1; //发送使能
ret =request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport);
/**************************************************************************
request_irq申请中断:
request_irq(unsigned int irq, irq_handler_t handler,unsigned long flags,
const char *name, void *dev)
{
returnrequest_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_threaded_irq函数原型为:
int request_threaded_irq(unsigned int irq, irq_handler_thandler,
irq_handler_t thread_fn, unsigned longirqflags,
const char *devname, void *dev_id)
参数的含义为:
unsigned int irq表示要申请的中断线
irq_handler_t handler为中断处理函数
irq_handler_t thread_fn 表示中断函数要使用的线程,假如为空表示不创建任何线程
unsigned long irqflags 为中断类型标记
const char *devname 为申请该中断对应的设备的名字
void *dev_id //共享中断线的是用到的一个判别参数,必须是独一无二的一个数值,该数值一般设备驱动数据存储的首地址
继续看一下ourport->rx_irq就是在3c24xx_serial_init_port函数中获得的中断号
s3c24xx_serial_rx_chars 为接收函数
*******************************************************************/
if (ret != 0) {
dev_err(port->dev,"cannot get irq %d\n", ourport->rx_irq);
return ret;
}
ourport->rx_claimed =1; //标志着已设置完毕接收函数
dbg("requesting txirq...\n");
tx_enabled(port) = 1; //发送使能
ret =request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0, //同上,申请发送中断
s3c24xx_serial_portname(port), ourport);
if (ret) {
dev_err(port->dev,"cannot get irq %d\n", ourport->tx_irq);
goto err;
}
ourport->tx_claimed =1; //标志着已设置完毕发送函数
dbg("s3c24xx_serial_startupok\n");
/* the port reset codeshould have done the correct
* register setup for the port controls */
return ret;
err:
s3c24xx_serial_shutdown(port);//关闭端口
return ret;
}
下面看一下发送和接收函数,先看字符发送函数:
staticirqreturn_t
s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
struct s3c24xx_uart_port *ourport =dev_id;
struct uart_port *port =&ourport->port;
unsigned int ufcon, ch, flag, ufstat,uerstat;
unsigned long flags;
int max_count = 64;
spin_lock_irqsave(&port->lock,flags);
/**************************************************************************
spin_lock_irqsave临时关闭中断然后尝试获得自旋锁,
spin_lock 是不关闭中断然后获取锁,得不到锁的话就在哪里等着获取锁
spin_lock_irqsave对应释放锁的是spin_unlock_irqrestore
spin_lock对应的释放锁是spin_unlock
*********************************************** ********************/
while (max_count-- > 0) { //缓存区的大小为64k ,循环64次
ufcon = rd_regl(port,S3C2410_UFCON); //读取寄存器的数值
ufstat = rd_regl(port, S3C2410_UFSTAT);
if(s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
break;
/**************************************************************************
static int s3c24xx_serial_rx_fifocnt(structs3c24xx_uart_port *ourport,
unsigned long ufstat)
{
structs3c24xx_uart_info *info = ourport->info;
if (ufstat& info->rx_fifofull)
returnourport->port.fifosize;
return (ufstat& info->rx_fifomask) >>info->rx_fifoshift;
}
这个函数是通过UFSTATn的值来判断接受缓冲区的数据的数量。
其中info->rx_fifomask为63表示取值寄出去你的0到5位,
info->rx_fifoshift的值为0。
*********************************************** ********************/
uerstat = rd_regl(port,S3C2410_UERSTAT);//读取错误标志寄存器
ch = rd_regb(port, S3C2410_URXH); //读取接受缓存区的数值
if (port->flags &UPF_CONS_FLOW) { //是否使用流控
int txe =s3c24xx_serial_txempty_nofifo(port);
/**************************************************************************
s3c24xx_serial_txempty_nofifo的函数体为:
static int s3c24xx_serial_txempty_nofifo(struct uart_port*port)
{
returnrd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE;
}
这是是读取UTRSTATn的寄存器的值,当发送缓冲区为0则返回1否则返回0
*******************************************************************/
if (rx_enabled(port)) { //是否接受使能
if (!txe) { //发送缓冲区有数据满足条件
rx_enabled(port)= 0; //接受不使能
continue;
}
} else {
if (txe) { //假如不使能接受且发送缓存区为空
ufcon |=S3C2410_UFCON_RESETRX; //设置重置接收FIFO
wr_regl(port,S3C2410_UFCON, ufcon);//写入寄存器
rx_enabled(port)= 1; //接收使能
spin_unlock_irqrestore(&port->lock, //释放自旋锁,且打开中断
flags);
goto out;
}
continue;
}
}
/* insert the character into thebuffer */
flag = TTY_NORMAL;
port->icount.rx++; //接收数据个数自加
if (unlikely(uerstat& S3C2410_UERSTAT_ANY)){
dbg("rxerr: portch=0x%02x, rxs=0x%08x\n",
ch, uerstat);
/**************************************************************************
uerstat & S3C2410_UERSTAT_ANY是判断是否发出break信号或者是否有帧错误或者是否有溢出错误。
Unlikely没有实际的意义,只是告诉编译器一般情况下uerstat & S3C2410_UERSTAT_ANY是返回false
与之对应的是likely 只是告诉编译器一般情况下得到的是真
*******************************************************************/
/* check for break */
if (uerstat &S3C2410_UERSTAT_BREAK) { //只判断是否发送break
dbg("break!\n");
port->icount.brk++; //break计数器自加
if (uart_handle_break(port)) //魔术键,调试有关
gotoignore_char;
}
if (uerstat &S3C2410_UERSTAT_FRAME) //假如发生帧错误
port->icount.frame++;
if (uerstat &S3C2410_UERSTAT_OVERRUN) //假如发生溢出错误
port->icount.overrun++;
uerstat &=port->read_status_mask; // uerstat是只读寄存器,这是设置无效
if (uerstat &S3C2410_UERSTAT_BREAK) //发生break
flag = TTY_BREAK;
else if (uerstat &S3C2410_UERSTAT_PARITY) //不发生break发生了奇偶错误
flag = TTY_PARITY;
else if (uerstat &(S3C2410_UERSTAT_FRAME |
S3C2410_UERSTAT_OVERRUN))
//不发生break和奇偶错误发生帧、溢出错误
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(port,ch)) //跟错误调试有关
goto ignore_char;
uart_insert_char(port, uerstat,S3C2410_UERSTAT_OVERRUN,
ch, flag);
ignore_char:
continue;
}
spin_unlock_irqrestore(&port->lock,flags); //释放自旋锁,且打开中断
tty_flip_buffer_push(&port->state->port);
out:
return IRQ_HANDLED;
}
上面的介绍的是中断接受函数,接下来看一下中断发送函数:
staticirqreturn_t s3c24xx_serial_tx_chars(int irq,void *id)
{
struct s3c24xx_uart_port *ourport = id;
struct uart_port *port =&ourport->port;
struct circ_buf *xmit =&port->state->xmit; //要发送的字符串所对应的链表结构体
/*********************************************************************************
circ_bufs是个关键性的环形链接的结构体。其定义在Circ_buf.h (include\linux) 文件中:
structcirc_buf {
int head;
int tail;
};
char*buf:指向char类型的数据
inthead:环形缓冲区插入的数据
inttail:环形缓冲区要读取的数据
具体的可参考: Documentation/circular-buffers.txt
http://zh.wikipedia.org/wiki/%E7%92%B0%E5%BD%A2%E7%B7%A9%E8%A1%9D%E5%8D%80
***********************************************************************************/
unsigned long flags;
int count = 256;
spin_lock_irqsave(&port->lock,flags); //申请自旋锁并关中断
if (port->x_char) { //假设发送一个字符
wr_regb(port, S3C2410_UTXH,port->x_char);//写入发送寄存器
port->icount.tx++;
port->x_char = 0; //发送完毕后清零
goto out;
}
/* if there isn't anything more totransmit, or the uart is now
*stopped, disable the uart and exit
*/
if (uart_circ_empty(xmit) ||uart_tx_stopped(port)) { //检测是环形缓冲区数据为空,或者串口停止
s3c24xx_serial_stop_tx(port); //停止发送
goto out;
}
/* try and drain the buffer... */
while (!uart_circ_empty(xmit) &&count-- > 0) { //假如环形缓冲区有需要发送的数据
if (rd_regl(port, S3C2410_UFSTAT)& ourport->info->tx_fifofull)//发送缓冲区是否已满
break;
wr_regb(port, S3C2410_UTXH,xmit->buf[xmit->tail]);//写入一个数据
xmit->tail = (xmit->tail + 1)& (UART_XMIT_SIZE - 1); //指向下一个数据
port->icount.tx++;
}
if (uart_circ_chars_pending(xmit)< WAKEUP_CHARS) {
spin_unlock(&port->lock);
uart_write_wakeup(port);
spin_lock(&port->lock);
}
/*********************************************************************************
uart_circ_chars_pending(xmit)是返回环形缓冲区的剩余的字符数,这是是判定假设环形缓冲区的剩余的字符数小于设定环形的字符数,则唤醒端口急需发送数据。
***********************************************************************************/
if (uart_circ_empty(xmit)) //环形缓冲区为空
s3c24xx_serial_stop_tx(port);
out:
spin_unlock_irqrestore(&port->lock,flags); //解锁开中断
return IRQ_HANDLED;
}
对于UART的驱动比较繁琐,但是同学阅读器代码(部分代码),可以了解其机制及其实现的原理,个人感觉有很高的学习价值。