六 linux UART串口驱动代分析
Posted bigPillow
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了六 linux UART串口驱动代分析相关的知识,希望对你有一定的参考价值。
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(s 以上是关于六 linux UART串口驱动代分析的主要内容,如果未能解决你的问题,请参考以下文章 Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写) Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)