一、开发环境
- 主 机:VMWare–Fedora 9
- 开发板:Mini2440–64MB Nand, Kernel:2.6.30.4
- 编译器:arm-linux-gcc-4.3.2
二、相关概念
1、渠道设备:
通常在Linux中,把SoC体系中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作渠道设备来处理。在Linux顶用platform_device结构体来描绘一个渠道设备,在2.6.30.4内核中界说在:include/linux/platform_device.h中,如下:
struct platform_device_id*id_entry; |
现在你不用深化了解这个结构体,只需知道在Linux中是用这个结构体来界说一些渠道设备的。比方在:arch/arm/plat-s3c24xx/devs.c中就界说了许多渠道设备,下面我就只贴出RTC这一种的:
struct platform_device s3c_device_rtc = {//界说了RTC渠道设备 EXPORT_SYMBOL(s3c_device_rtc); |
好了,界说了渠道设备,那体系是怎样来运用他的呢?咱们翻开:arch/arm/mach-s3c2440/mach-smdk2440.c这个ARM 2440渠道的体系进口文件,能够看到在体系初始化函数smdk2440_machine_init中是运用platform_add_devices这个函数将一些渠道设备增加到体系中的,如下:(至于体系是怎样完成增加渠道设备的,这儿咱们不用研讨,这些Linux体系都现已做好了的,咱们要研讨的是后边渠道设备的驱动是怎样完成的)
static void __init smdk2440_machine_init(void) |
2、渠道设备驱动:
这儿所讲的渠道设备驱动是指详细的某种渠道设备的驱动,比方上面讲的RTC渠道设备,这儿便是指RTC渠道设备驱动。在Linux中,体系还为渠道设备界说了渠道驱动结构体platform_driver,就比方体系为字符设备界说了file_operations相同,但不要把渠道设备跟字符设备、块设备、网络设备搞成了并排的概念,因渠道设备也能够是字符设备等其他设备。留心:在被界说为渠道设备的字符设备的驱动中,除了要完成字符设备驱动中file_operations的open、release、read、write等接口函数外,还要完成渠道设备驱动中platform_driver的probe、remove、suspend、resume等接口函数。好了,在咱们搞了解上面这些后,下面咱们就来详细详细剖析解说RTC渠道设备的驱动实践。
三、实例解说
1、RTC在Linux中的全体结构:
就个人了解,RTC在Linux中全体结构分为两个部分。第一个是部分便是上面所讲的作为渠道设备被挂接到体系总线中,这儿我把他叫做设备层(呵呵,或许不是很精确的叫法);第二部分便是驱动部分,这儿叫做驱动层。在Linux中要使一个驱动在不同的渠道中都能够运用似乎是不或许的,所以咱们先看2.6.30.4内核驱动中的RTC部分是独自的一个文件夹,在文件夹中包含了许多不同体系结构的RTC驱动,当然也有S3C2440的RTC驱动,可是在这些驱动中他们都运用了一组文件晒干的办法,那么这组文件便是RTC的中心(留心这儿的中心不是指对RTC硬件的操作,指的是对RTC操作的办法。对硬件寄存器的操作仍是在详细的驱动中)。好了,咱们仍是用图来阐明这种联系吧!!
2、RTC硬件原理图剖析:以下是S3C2440AL内部集成的RTC模块结构图和一个外部的晶振接口图
咱们从S3C2440内部RTC模块结构图和数据手册得知,RTC在Linux中首要完成两种功用,分别是体系掉电后的时刻日期和谐和时刻日期报警(相似定时器功用)。
①、时刻日期和谐功用:
首要是由RTC实时时钟操控寄存器RTCCON进行功用的使能操控,由节拍时刻计数寄存器TICNT来发生节拍时刻中止来完成实时操作体系功用相关的时刻和实时同步。其间对时刻日期的操作实践上是对BCD码操作,而BCD码则是由一系列的寄存器组成(BCD秒寄存器BCDSEC、BCD分寄存器BCDMIN、BCD小时寄存器BCDHOUR、BCD日期寄存器BCDDATE、BCD日寄存器BCDDAY、BCD月寄存器BCDMON、BCD年寄存器BCDYEAR)。
②、报警功用:
首要由RTC报警操控寄存器RTCALM进行功用使能操控,并发生报警中止。报警时刻日期的设置也是对一系列的寄存器进行操作(报警秒数据寄存器ALMSEC、报警分钟数据寄存器ALMMIN、报警小时数据寄存器ALMHOUR、报警日期数据寄存器ALMDATE、报警月数据寄存器ALMMON、报警年数据寄存器ALMYEAR)。
3、RTC驱动完成过程(树立驱动文件my2440_rtc.c):
留心:在每步中,为了让代码逻辑愈加有条理和简单了解,就没有考虑代码的次序,比方函数要先界说后调用。假如要编译此代码,请严厉依照C言语的标准来调整代码的次序。
①、依然是驱动程序的最基本结构:RTC驱动的初始化和退出部分及其他,如下:
/*RTC渠道驱动结构体,渠道驱动结构体界说在platform_device.h中,该结构体内的接口函数在第②、④步中完成*/ static int __init rtc_init(void) static void __exit rtc_exit(void) module_init(rtc_init); MODULE_LICENSE(“GPL”); |
②、RTC渠道驱动结构中勘探函数rtc_probe的完成。勘探就意味着在体系总线中去检测设备的存在,然后获取设备有用的相关资源信息,以便咱们运用这些信息。代码如下:
/*界说了一个用来保存RTC的IO端口占用的IO空间和经过虚拟映射后的内存地址*/ /*界说了两个变量来保存RTC报警中止号和TICK节拍时刻中止号,NO_IRQ宏界说在irq.h中*/ /*声明并初始化一个自旋锁rtc_pie_lock,对RTC资源进行互斥拜访*/ /*RTC渠道驱动勘探函数,留心这儿为什么要运用一个__devinit,也到rtc_remove完成的当地一同讲*/ /*在体系界说的RTC渠道设备中获取RTC报警中止号 //在体系界说的RTC渠道设备中获取TICK节拍时刻中止号 /*获取RTC渠道设备所运用的IO端口资源,留心这个IORESOURCE_MEM标志和RTC渠道设备界说中的共同*/ /*请求RTC的IO端口资源所占用的IO空间(要留心了解IO空间和内存空间的差异), /*将RTC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap界说在io.h中。 /*好了,经过上面的过程现已将RTC的资源都预备好了,下面就开端运用啦*/ /*这两个函数开端对RTC寄存器操作,界说都在下面*/ /*device_init_wakeup该函数界说在pm_wakeup.h中,界说如下: /*将RTC注册为RTC设备类,RTC设备类在RTC驱动中心部分中由体系界说好的, /*设置RTC节拍时刻计数寄存器TICNT的节拍时刻计数值的用户最大相对值, /*将RTC设备类的数据传递给体系渠道设备。 return 0; //以下是上面过错处理的跳转点 err_nomap: err_nores: /*该函数首要是初始化或许使能RTC, /*RTC的实时时钟操控寄存器RTCCON共有4个位,各位的初始值均为0,依据数据手册介绍第0位(即:RCTEN位) tmp = readb(rtc_base + S3C2410_TICNT); /*读取TICNT寄存器的值*/ if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)) if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)) /*该函数首要是对RTC的节拍时刻计数寄存器TICNT的0-6位进行操作,即:节拍时刻计数值的设定*/ if (!is_power_of_2(freq)) /*对freq的值进行检查*/ spin_lock_irq(&rtc_pie_lock); /*获取自旋锁维护临界区资源*/ /*读取节拍时刻计数寄存器TICNT的值*/ /*看数据手册得知,节拍时刻计数值的规模是1-127, /*将经运算后值写入节拍时刻计数寄存器TICNT中,这儿首要是改动TICNT的第0-6位的值*/ spin_unlock_irq(&rtc_pie_lock);/*开释自旋锁,即解锁*/ return 0; |
③、RTC设备类的操作。在这一步中,才是对RTC硬件的各种寄存器进行操作,代码如下:
/*rtc_class_ops是RTC设备类在RTC驱动中心部分中界说的对RTC设备类进行操作的结构体, /*RTC设备类翻开接口函数*/ /*这儿首要的意图是从体系渠道设备中获取RTC设备类的数据,和RTC勘探函数rtc_probe中 /*请求RTC报警中止服务,中止号rtc_alarmno在RTC勘探函数rtc_probe中现已获获得, /*同上面相同,这儿请求的是RTC的TICK节拍时刻中止服务,服务程序是:rtc_tickirq*/ return ret; tick_err:/*过错处理,留心呈现过错后也要开释掉现已请求成功的中止*/ /*RTC报警中止服务程序*/ /*当报警中止到来的时分,去设定RTC中报警的相关信息,详细设定的办法,RTC中心 /*RTC的TICK节拍时刻中止服务*/ /*节拍时刻中止到来的时分,去设定RTC中节拍时刻的相关信息,详细设定的办法,RTC中心 /*RTC设备类封闭接口函数*/ /*请见rtc_setpie接口函数中的解说*/ /*同rtc_open中中止的请求相对应,在那里请求中止,这儿就开释中止*/ /*该函数首要是对RTC的节拍时刻计数寄存器TICNT的第7位进行操作,即:节拍时刻计数的使能功用*/ spin_lock_irq(&rtc_pie_lock);/*获取自旋锁维护临界区资源*/ /*读取节拍时刻计数寄存器TICNT的值*/ if (flag) /*将经运算后值写入节拍时刻计数寄存器TICNT中,这儿首要是改动TICNT的第7位的值*/ spin_unlock_irq(&rtc_pie_lock);/*开释自旋锁,即解锁*/ return 0; /*读取RTC中BCD数中的:分、时、日期、月、年、秒*/ retry_get_time: /*咱们知道时刻是以60为一个周期的,其时、分、秒到达60后,他们的上一级会加1,而本身又从0开端计数 /*将上面读取的时刻日期值保存到RTC中心界说的时刻结构体中,该结构体界说在rtc.h中, /*这儿为什么要加100年和减1月呢,咱们检查数据手册得知本来是为了差异1900年和2000年闰年的要素, return 0; /*和上面的rtc_gettime功用相反,将更改后的分、时、日期、月、年、秒写入RTC中BCD数中*/ /*RTC时钟只支撑100年的时刻规模*/ /*将上面保存到RTC中心界说的时刻结构体中的时刻日期值写入对应的寄存器中*/ return 0; /*读取RTC中报警各寄存器的:秒、分、时、月、日期、年的值,保存各值到rtc_time结构体中*/ alm_tm->tm_sec = readb(rtc_base + S3C2410_ALMSEC); /*获取RTC报警操控寄存器RTCALM的值*/ /*判别RTCALM值的第6位,来设置RTC的大局报警使能状况到RTC中心界说的报警状况结构体rtc_wkalrm中*/ /*判别假如RTCALM值的第0位的值(秒报警使能)为1时,就设置报警秒的值到rtc_time结构体中*/ /*判别假如RTCALM值的第1位的值(分报警使能)为1时,就设置报警分的值到rtc_time结构体中*/ /*判别假如RTCALM值的第2位的值(时报警使能)为1时,就设置报警小时的值到rtc_time结构体中*/ /*判别假如RTCALM值的第3位的值(日期报警使能)为1时,就设置报警日期的值到rtc_time结构体中*/ /*判别假如RTCALM值的第4位的值(月报警使能)为1时,就设置报警月的值到rtc_time结构体中*/ /*判别假如RTCALM值的第5位的值(年报警使能)为1时,就设置报警年的值到rtc_time结构体中*/ return 0; /*把上面保存到rtc_time结构体中各值写入RTC中报警各寄存器中*/ /*读取RTC报警操控寄存器RTCALM的第6位,把大局报警使能的状况保存到alrm_en变量中*/ /*把RTC报警操控寄存器RTCALM的值设为0,行将大局报警使能和其他报警使能悉数封闭*/ if (tm->tm_sec < 60 && tm->tm_sec >= 0) if (tm->tm_min < 60 && tm->tm_min >= 0) if (tm->tm_hour < 24 && tm->tm_hour >= 0) /*把alrm_en修正往后的值从头写入RTC报警操控寄存器RTCALM中*/ /*请看下面rtc_setaie函数完成部分*/ /*依据大局报警使能的状况来决定是唤醒RTC报警中止仍是睡觉RTC报警中止*/ return 0; /*这儿首要仍是操控RTC报警操控寄存器RTCALM的第6位(大局报警使能状况)*/ tmp = readb(rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN; if (flag)/*依据标志flag来使能或制止大局报警*/ writeb(tmp, rtc_base + S3C2410_RTCALM); |
④、RTC渠道驱动的设备移除、挂起和康复接口函数的完成,代码如下:
platform_set_drvdata(dev, NULL); /*清空渠道设备中RTC驱动数据*/ rtc_setpie(&dev->dev, 0); /*制止RTC节拍时刻计数寄存器TICNT的使能功用*/ iounmap(rtc_base); /*开释RTC虚拟地址映射空间*/ return 0; /*对RTC渠道设备驱动电源办理的支撑。CONFIG_PM这个宏界说在内核中, static int ticnt_save; /*界说一个变量来保存挂起时的TICNT值*/ /*RTC渠道驱动的设备挂起接口函数的完成*/ ticnt_save = readb(rtc_base + S3C2410_TICNT); /*以节拍时刻计数寄存器TICNT的值为挂起点*/ rtc_enable(pdev, 0); /*挂起了之后就制止RTC操控使能*/ return 0; /*RTC渠道驱动的设备康复接口函数的完成*/ writeb(ticnt_save, rtc_base + S3C2410_TICNT); /*康复挂起时的TICNT值,RTC节拍时刻持续计数*/ return 0; #else /*装备内核时没选上电源办理,RTC渠道驱动的设备挂起和康复功用均无效,这两个函数也就无需完成了*/ |
好了,到此RTC驱动程序编写完成了。在这儿不知咱们有没有留心,在前面的概念部分中咱们讲到过,假如把一个字符设备注册成为一个渠道设备,除了要完成渠道设备驱动中platform_driver的接口函数外,还要完成字符设备驱动中file_operations的接口函数,可是从上面的驱动代码中看,这儿并没有对RTC进行file_operations的操作,这是怎样回事啊?本来对RTC进行file_operations的操作在RTC的中心部分现已由体系供给了。在第②步的勘探函数rtc_probe中,首先用rtc_device_register注册为RTC设备类,咱们看rtc_device_register的完成(在class.c中),又调用了rtc_dev_prepare(rtc),其完成在rtc-dev.c中,那么在这儿面才对RTC进行了file_operations操作,对RTC驱动的设备号也在rtc-dev.c中处理的。
四、回过头再来剖析了解详细RTC驱动程序代码的结构
在上面的各过程中,我已对RTC驱动程序的每行代码的效果都做了详细的推荐,但到结束部分后,惹祸你会有点找不着北的感觉。的确,整个代码太长,并且用文字的方法也的确很难把整个驱动的结构描绘明晰。下面,我就用图形的方法来归纳上面各过程之间的联系,使整个驱动程序的结构愈加明晰明晰。
五、结束语
经过对RTC驱动的完成,咱们对渠道设备有了进一步的了解,这对咱们今后编写I2C、IIS、看门狗等设备的驱动有了很大的协助。别的,能够看出在对详细硬件操作的时分实践是对该硬件的各种寄存器进行拜访读写,所以这就要求咱们在编写一个设备驱动之前必须先了解该设备的数据手册,列出一切的寄存器及功用,这样才干在编写驱动时正确的对设备进行拜访。