嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。
· 共享资源,欢迎转载:http://hbhuanggang.cublog.cn
一、开发环境
· 主 机: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 {
const char * name ; // 设备名称
int id ;
struct device dev ;
u32 num_resources ; // 设备使用各类资源的数量
struct resource * resource ; // 设备使用的资源
struct platform_device_id * id_entry ;
}; |
现在你不必深入理解这个结构体,只要知道在Linux中是用这个结构体来定义一些平台设备的。比如在:arch/arm/plat-s3c24xx/devs.c中就定义了很多平台设备,下面我就只贴出RTC这一种的:
/* RTC */
static struct resource s3c_rtc_resource [] = { // 定义了 RTC 平台设备使用的资源,这些资源在驱动中都会用到
[ 0 ] = { //IO 端口资源范围
. start = S3C24XX_PA_RTC ,
. end = S3C24XX_PA_RTC + 0xff ,
. flags = IORESOURCE_MEM ,
},
[ 1 ] = { //RTC 报警中断资源
. start = IRQ_RTC ,
. end = IRQ_RTC ,
. flags = IORESOURCE_IRQ ,
},
[ 2 ] = { //TICK 节拍时间中断资源
. start = IRQ_TICK ,
. end = IRQ_TICK ,
. flags = IORESOURCE_IRQ
}
};
struct platform_device s3c_device_rtc = { // 定义了 RTC 平台设备
. name = "s3c2410-rtc" , // 设备名称
. id = - 1 ,
. num_resources = ARRAY_SIZE ( s3c_rtc_resource ), // 资源数量
. resource = s3c_rtc_resource , // 引用上面定义的资源
};
EXPORT_SYMBOL ( s3c_device_rtc ); |
好了,定义了平台设备,那系统是怎么来使用他的呢?我们打开:arch/arm/mach-s3c2440/mach-smdk2440.c这个ARM 2440平台的系统入口文件,可以看到在系统初始化函数smdk2440_machine_init中是使用platform_add_devices这个函数将一些平台设备添加到系统中的,如下:(至于系统是如何实现添加平台设备的,这里我们不必研究,这些Linux系统都已经做好了的,我们要研究的是后面平台设备的驱动是如何实现的)
static struct platform_device * smdk2440_devices [] __initdata = {
& s3c_device_usb ,
& s3c_device_lcd ,
& s3c_device_wdt ,
& s3c_device_i2c0 ,
& s3c_device_iis , &s3c_device_rtc, // 这里我们添加上 RTC 平台设备,默认是没添加的
}; // 平台设备列表,也就是说我们要使用一个新的平台设备要先在上面定义,然后加到这个列表中,最后到驱动层去实现该设备的驱动
static void __init smdk2440_machine_init ( void )
{
s3c24xx_fb_set_platdata (& smdk2440_fb_info );
s3c_i2c0_set_platdata ( NULL );
// 将上面列表中的平台设备添加到系统总线中
platform_add_devices ( smdk2440_devices , ARRAY_SIZE ( smdk2440_devices ));
smdk_machine_init ();
} |
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驱动的初始化和退出部分及其他,如下:
# include < linux / kernel . h >
# include < linux / module . h >
# include < linux / init . h >
# include < linux / fs . h >
# include < linux / platform_device . h >
/*RTC 平台驱动结构体,平台驱动结构体定义在 platform_device.h 中,该结构体内的接口函数在第② 、④ 步中实现 */
static struct platform_driver rtc_driver =
{
. probe = rtc_probe , /*RTC 探测函数,在第② 步中实现 */
. remove = __devexit_p ( rtc_remove ), /*RTC 移除函数 , 在第④ 步实现 , 为何使用 __devexit_p, 在该函数实现的地方再讲 */
. suspend = rtc_suspend , /*RTC 挂起函数,在第④ 步中实现 */
. resume = rtc_resume , /*RTC 恢复函数,在第④ 步中实现 */
. driver =
{
/* 注意这里的名称一定要和系统中定义平台设备的地方一致,这样才能把平台设备与该平台设备的驱动关联起来 */
. name = "s3c2410-rtc" ,
. owner = THIS_MODULE ,
},
};
static int __init rtc_init ( void )
{
/* 将 RTC 注册成平台设备驱动 */
return platform_driver_register (& rtc_driver );
}
static void __exit rtc_exit ( void )
{
/* 注销 RTC 平台设备驱动 */
platform_driver_unregister (& rtc_driver );
}
module_init ( rtc_init );
module_exit ( rtc_exit );
MODULE_LICENSE ( "GPL" );
MODULE_AUTHOR ( "Huang Gang" );
MODULE_DESCRIPTION ( "My2440 RTC driver" ); |
②、RTC平台驱动结构中探测函数rtc_probe的实现。探测就意味着在系统总线中去检测设备的存在,然后获取设备有用的相关资源信息,以便我们使用这些信息。代码如下:
RTC驱动程序代码的结构
在上面的各步骤中,我已对RTC驱动程序的每行代码的作用都做了详细的讲述,但到结尾部分后,也许你会有点找不着北的感觉。的确,整个代码太长,而且用文字的方式也确实很难把整个驱动的结构描述清晰。下面,我就用图形的方式来概括上面各步骤之间的关系,使整个驱动程序的结构更加清晰明了。
五、结束语
通过对RTC驱动的实现,我们对平台设备有了进一步的了解,这对我们以后编写I2C、IIS、看门狗等设备的驱动有了很大的帮助。另外,可以看出在对具体硬件操作的时候实际是对该硬件的各种寄存器进行访问读写,所以这就要求我们在编写一个设备驱动之前必须先了解该设备的数据手册,列出所有的寄存器及功能,这样才能在编写驱动时正确的对设备进行访问。
四、回过头再来分析理解具体
# include < asm / irq . h >
# include < asm / io . h >
# include < linux / rtc . h >
# include < linux / ioport . h >
# include < plat / regs - rtc . h >
/* 定义了一个用来保存 RTC 的 IO 端口占用的 IO 空间和经过虚拟映射后的内存地址 */
static struct resource * rtc_mem ;
static void __iomem * rtc_base ;
/* 定义了两个变量来保存 RTC 报警中断号和 TICK 节拍时间中断号, NO_IRQ 宏定义在 irq.h 中 */
static int rtc_alarmno = NO_IRQ ;
static int rtc_tickno = NO_IRQ ;
/* 申明并初始化一个自旋锁 rtc_pie_lock ,对 RTC 资源进行互斥访问 */
static DEFINE_SPINLOCK ( rtc_pie_lock );
/*RTC 平台驱动探测函数,注意这里为什么要使用一个 __devinit ,也到 rtc_remove 实现的地方一起讲 */
static int __devinit rtc_probe ( struct platform_device * pdev )
{
int ret ;
struct rtc_device * rtc ; /* 定义一个 RTC 设备类, rtc_device 定义在 rtc.h 中 */
struct resource * res ; /* 定义一个资源,用来保存获取的 RTC 的资源 */
/* 在系统定义的 RTC 平台设备中获取 RTC 报警中断号
platform_get_irq 定义在 platform_device.h 中 */
rtc_alarmno = platform_get_irq ( pdev , 0 );
if ( rtc_alarmno < 0 )
{
/* 获取 RTC 报警中断号不成功错误处理
dev_err 定义在 device.h 中,在 platform_device.h 中已经引用,所以这里就不需再引用了 */
dev_err (& pdev -> dev , "no irq for alarm/n" );
return - ENOENT ;
}
// 在系统定义的 RTC 平台设备中获取 TICK 节拍时间中断号
rtc_tickno = platform_get_irq ( pdev , 1 );
if ( rtc_tickno < 0 )
{
/* 获取 TICK 节拍时间中断号不成功错误处理 */
dev_err (& pdev -> dev , "no irq for rtc tick/n" );
return - ENOENT ;
}
/* 获取 RTC 平台设备所使用的 IO 端口资源,注意这个 IORESOURCE_MEM 标志和 RTC 平台设备定义中的一致 */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 );
if ( res == NULL )
{
/* 错误处理 */
dev_err (& pdev -> dev , "failed to get memory region resource/n" );
return - ENOENT ;
}
/* 申请 RTC 的 IO 端口资源所占用的 IO 空间 ( 要注意理解 IO 空间和内存空间的区别 ),
request_mem_region 定义在 ioport.h 中 */
rtc_mem = request_mem_region ( res -> start , res -> end - res -> start + 1 , pdev -> name );
if ( rtc_mem == NULL )
{
/* 错误处理 */
dev_err (& pdev -> dev , "failed to reserve memory region/n" );
ret = - ENOENT ;
goto err_nores ;
}
/* 将 RTC 的 IO 端口占用的这段 IO 空间映射到内存的虚拟地址, ioremap 定义在 io.h 中。
注意: IO 空间要映射后才能使用,以后对虚拟地址的操作就是对 IO 空间的操作, */
rtc_base = ioremap ( res -> start , res -> end - res -> start + 1 );
if ( rtc_base == NULL )
{
/* 错误处理 */
dev_err (& pdev -> dev , "failed ioremap()/n" );
ret = - EINVAL ;
goto err_nomap ;
}
/* 好了,通过上面的步骤已经将 RTC 的资源都准备好了,下面就开始使用啦 */
/* 这两个函数开始对 RTC 寄存器操作,定义都在下面 */
rtc_enable ( pdev , 1 ); /* 对 RTC 的实时时钟控制寄存器 RTCCON 进行操作 ( 功能是初始化或者使能 RTC)*/
rtc_setfreq (& pdev -> dev , 1 ); /* 对 RTC 的节拍时间计数寄存器 TICNT 的 0-6 位进行操作,即:节拍时间计数值的设定 */
/*device_init_wakeup 该函数定义在 pm_wakeup.h 中,定义如下:
static inline void device_init_wakeup(struct device *dev, int val){
dev->power.can_wakeup = dev->power.should_wakeup = !!val;
}
显然这个函数是让驱动支持电源管理的 , 这里只要知道 ,can_wakeup 为 1 时表明这个设备可以被唤醒 , 设备驱动为了支持
Linux 中的电源管理 , 有责任调用 device_init_wakeup() 来初始化 can_wakeup ,而 should_wakeup 则是在设备的电源状态
发生变化的时候被 device_may_wakeup() 用来测试 , 测试它该不该变化,因此 can_wakeup 表明的是一种能力,
而 should_wakeup 表明的是有了这种能力以后去不去做某件事。好了,我们没有必要深入研究电源管理的内容了,
要不就扯远了,电源管理以后再讲 */
device_init_wakeup (& pdev -> dev , 1 );
/* 将 RTC 注册为 RTC 设备类, RTC 设备类在 RTC 驱动核心部分中由系统定义好的,
注意 rtcops 这个参数是一个结构体,该结构体的作用和里面的接口函数实现在第③ 步中。
rtc_device_register 函数在 rtc.h 中定义,在 drivers/rtc/class.c 中实现 */
rtc = rtc_device_register ( "my2440" , & pdev -> dev , & rtcops , THIS_MODULE );
if ( IS_ERR ( rtc ))
{
/* 错误处理 */
dev_err (& pdev -> dev , "cannot attach rtc/n" );
ret = PTR_ERR ( rtc );
goto err_nortc ;
}
/* 设置 RTC 节拍时间计数寄存器 TICNT 的节拍时间计数值的用户最大相对值,
这里你可能不理解这句,没关系,等你看到 rtc_setfreq 函数实现后自然就明白了 */
rtc -> max_user_freq = 128 ;
/* 将 RTC 设备类的数据传递给系统平台设备。
platform_set_drvdata 是定义在 platform_device.h 的宏,如下:
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))
而 dev_set_drvdata 又被定义在 include/linux/device.h 中,如下:
static inline void dev_set_drvdata (struct device *dev, void *data){
dev->driver_data = data;
}*/
platform_set_drvdata ( pdev , rtc );
return 0 ;
// 以下是上面错误处理的跳转点
err_nortc :
rtc_enable ( pdev , 0 );
iounmap ( rtc_base );
err_nomap :
release_resource ( rtc_mem );
err_nores :
return ret ;
}
/* 该函数主要是初始化或者使能 RTC ,
以下 RTC 的各种寄存器的宏定义在 arch/arm/plat-s3c/include/plat/regs-rtc.h 中,
各寄存器的用途和设置请参考 S3C2440 数据手册的第十七章实时时钟部分 */
static void rtc_enable ( struct platform_device * pdev , int flag )
{
unsigned int tmp ;
/*RTC 的实时时钟控制寄存器 RTCCON 共有 4 个位,各位的初始值均为 0 ,根据数据手册介绍第 0 位 ( 即: RCTEN 位 )
可以控制 CPU 和 RTC 之间的所有接口 ( 即 RTC 使能功能 ) ,所以在系统复位后应该将 RTCCON 寄存器的第 0 为置为 1 ;
在关闭电源前,又应该将该位清零,以避免无意的写 RTC 寄存器 */
if (! flag )
{
/* 当 flag=0 时 ( 即属于关闭电源前的情况 ) , RTCCON 寄存器清零第一位 */
tmp = readb ( rtc_base + S3C2410_RTCCON ); /* 读取 RTCCON 寄存器的值 */
/* tmp & ~S3C2410_RTCCON_RTCEN = 0 即屏蔽 RTC 使能 */
writeb ( tmp & ~ S3C2410_RTCCON_RTCEN , rtc_base + S3C2410_RTCCON );
tmp = readb ( rtc_base + S3C2410_TICNT ); /* 读取 TICNT 寄存器的值 */
/* tmp & ~S3C2410_TICNT_ENABLE 后第 7 位为 0 ,即屏蔽节拍时间中断使能 */
writeb ( tmp & ~ S3C2410_TICNT_ENABLE , rtc_base + S3C2410_TICNT );
}
else
{
/* 当 flag!=0 时 ( 即属于系统复位后的情况 ) ,使能 RTC*/
if (( readb ( rtc_base + S3C2410_RTCCON ) & S3C2410_RTCCON_RTCEN ) == 0 )
{
dev_info (& pdev -> dev , "rtc disabled, re-enabling/n" );
tmp = readb ( rtc_base + S3C2410_RTCCON );
writeb ( tmp | S3C2410_RTCCON_RTCEN , rtc_base + S3C2410_RTCCON );
}
if (( readb ( rtc_base + S3C2410_RTCCON ) & S3C2410_RTCCON_CNTSEL ))
{
dev_info (& pdev -> dev , "removing RTCCON_CNTSEL/n" );
tmp = readb ( rtc_base + S3C2410_RTCCON );
writeb ( tmp & ~ S3C2410_RTCCON_CNTSEL , rtc_base + S3C2410_RTCCON );
}
if (( readb ( rtc_base + S3C2410_RTCCON ) & S3C2410_RTCCON_CLKRST ))
{
dev_info (& pdev -> dev , "removing RTCCON_CLKRST/n" );
tmp = readb ( rtc_base + S3C2410_RTCCON );
writeb ( tmp & ~ S3C2410_RTCCON_CLKRST , rtc_base + S3C2410_RTCCON );
}
}
}
/* 该函数主要是对 RTC 的节拍时间计数寄存器 TICNT 的 0-6 位进行操作,即:节拍时间计数值的设定 */
static int rtc_setfreq ( struct device * dev , int freq )
{
unsigned int tmp ;
if (! is_power_of_2 ( freq )) /* 对 freq 的值进行检查 */
return - EINVAL ;
spin_lock_irq (& rtc_pie_lock ); /* 获取自旋锁保护临界区资源 */
/* 读取节拍时间计数寄存器 TICNT 的值 */
tmp = readb ( rtc_base + S3C2410_TICNT ) & S3C2410_TICNT_ENABLE ;
/* 看数据手册得知,节拍时间计数值的范围是 1-127 ,
还记得在 rtc_enable 函数中设置的 rtc->max_user_freq=128 吗?所以这里要减 1*/
tmp |= ( 128 / freq ) - 1 ;
/* 将经运算后值写入节拍时间计数寄存器 TICNT 中,这里主要是改变 TICNT 的第 0-6 位的值 */
writeb ( tmp , rtc_base + S3C2410_TICNT );
spin_unlock_irq (& rtc_pie_lock ); /* 释放自旋锁,即解锁 */
return 0 ;
} |
③、RTC设备类的操作。在这一步中,才是对RTC硬件的各种寄存器进行操作,代码如下:
# include < linux / interrupt . h >
# include < linux / bcd . h >
/*rtc_class_ops 是 RTC 设备类在 RTC 驱动核心部分中定义的对 RTC 设备类进行操作的结构体,
类似字符设备在驱动中的 file_operations 对字符设备进行操作的意思。该结构体被定义
在 rtc.h 中,对 RTC 的操作主要有打开、关闭、设置或获取时间、设置或获取报警、设置节拍时间计数值等等,
该结构体内接口函数的实现都在下面 */
static const struct rtc_class_ops rtcops = {
. open = rtc_open ,
. release = rtc_release ,
. irq_set_freq = rtc_setfreq , /* 在第② 步中已实现 */
. irq_set_state = rtc_setpie ,
. read_time = rtc_gettime ,
. set_time = rtc_settime ,
. read_alarm = rtc_getalarm ,
. set_alarm = rtc_setalarm ,
};
/*RTC 设备类打开接口函数 */
static int rtc_open ( struct device * dev )
{
int ret ;
/* 这里主要的目的是从系统平台设备中获取 RTC 设备类的数据,和 RTC 探测函数 rtc_probe 中
的 platform_set_drvdata(pdev, rtc) 的操作刚好相反。这些都定义在 platform_device.h 中 */
struct platform_device * pdev = to_platform_device ( dev );
struct rtc_device * rtc_dev = platform_get_drvdata ( pdev );
/* 申请 RTC 报警中断服务,中断号 rtc_alarmno 在 RTC 探测函数 rtc_probe 中已经获取得,
这里使用的是快速中断 :IRQF_DISABLED 。中断服务程序为 :rtc_alarmirq ,将 RTC 设备类 rtc_dev 做参数传递过去了 */
ret = request_irq ( rtc_alarmno , rtc_alarmirq , IRQF_DISABLED , "my2440-rtc alarm" , rtc_dev );
if ( ret )
{
dev_err ( dev , "IRQ%d error %d/n" , rtc_alarmno , ret );
return ret ;
}
/* 同上面一样,这里申请的是 RTC 的 TICK 节拍时间中断服务,服务程序是 :rtc_tickirq*/
ret = request_irq ( rtc_tickno , rtc_tickirq , IRQF_DISABLED , "my2440-rtc tick" , rtc_dev );
if ( ret )
{
dev_err ( dev , "IRQ%d error %d/n" , rtc_tickno , ret );
goto tick_err ;
}
return ret ;
tick_err : /* 错误处理,注意出现错误后也要释放掉已经申请成功的中断 */
free_irq ( rtc_alarmno , rtc_dev );
return ret ;
}
/*RTC 报警中断服务程序 */
static irqreturn_t rtc_alarmirq ( int irq , void * argv )
{
struct rtc_device * rdev = argv ; /* 接收申请中断时传递过来的 rtc_dev 参数 */
/* 当报警中断到来的时候,去设定 RTC 中报警的相关信息,具体设定的方法, RTC 核心
部分已经在 rtc_update_irq 接口函数中实现,函数定义实现在 interface.c 中 */
rtc_update_irq ( rdev , 1 , RTC_AF | RTC_IRQF );
return IRQ_HANDLED ;
}
/*RTC 的 TICK 节拍时间中断服务 */
static irqreturn_t rtc_tickirq ( int irq , void * argv )
{
struct rtc_device * rdev = argv ; /* 接收申请中断时传递过来的 rtc_dev 参数 */
/* 节拍时间中断到来的时候,去设定 RTC 中节拍时间的相关信息,具体设定的方法, RTC 核心
部分已经在 rtc_update_irq 接口函数中实现,函数定义实现在 interface.c 中 */
rtc_update_irq ( rdev , 1 , RTC_PF | RTC_IRQF );
return IRQ_HANDLED ;
}
/*RTC 设备类关闭接口函数 */
static void rtc_release ( struct device * dev )
{
/* 和 rtc_open 中的作用相同 */
struct platform_device * pdev = to_platform_device ( dev );
struct rtc_device * rtc_dev = platform_get_drvdata ( pdev );
/* 请见 rtc_setpie 接口函数中的解释 */
rtc_setpie ( dev , 0 );
/* 同 rtc_open 中中断的申请相对应,在那里申请中断,这里就释放中断 */
free_irq ( rtc_alarmno , rtc_dev );
free_irq ( rtc_tickno , rtc_dev );
}
/* 该函数主要是对 RTC 的节拍时间计数寄存器 TICNT 的第 7 位进行操作,即:节拍时间计数的使能功能 */
static int rtc_setpie ( struct device * dev , int flag )
{
unsigned int tmp ;
spin_lock_irq (& rtc_pie_lock ); /* 获取自旋锁保护临界区资源 */
/* 读取节拍时间计数寄存器 TICNT 的值 */
tmp = readb ( rtc_base + S3C2410_TICNT ) & ~ S3C2410_TICNT_ENABLE ;
if ( flag )
{
tmp |= S3C2410_TICNT_ENABLE ; /* 根据标志 flag 的值来判断是要使能还是要禁止 */
}
/* 将经运算后值写入节拍时间计数寄存器 TICNT 中,这里主要是改变 TICNT 的第 7 位的值 */
writeb ( tmp , rtc_base + S3C2410_TICNT );
spin_unlock_irq (& rtc_pie_lock ); /* 释放自旋锁,即解锁 */
return 0 ;
}
/* 读取 RTC 中 BCD 数中的:分、时、日期、月、年、秒 */
static int rtc_gettime ( struct device * dev , struct rtc_time * rtc_tm )
{
unsigned int have_retried = 0 ;
retry_get_time :
rtc_tm -> tm_min = readb ( rtc_base + S3C2410_RTCMIN ); /* 读 BCD 分寄存器 RTCMIN*/
rtc_tm -> tm_hour = readb ( rtc_base + S3C2410_RTCHOUR ); /* 读 BCD 时寄存器 RTCHOUR*/
rtc_tm -> tm_mday = readb ( rtc_base + S3C2410_RTCDATE ); /* 读 BCD 日期寄存器 RTCDATE*/
rtc_tm -> tm_mon = readb ( rtc_base + S3C2410_RTCMON ); /* 读 BCD 月寄存器 RTCMON*/
rtc_tm -> tm_year = readb ( rtc_base + S3C2410_RTCYEAR ); /* 读 BCD 年寄存器 RTCYEAR*/
rtc_tm -> tm_sec = readb ( rtc_base + S3C2410_RTCSEC ); /* 读 BCD 秒寄存器 RTCSEC*/
/* 我们知道时间是以 60 为一个周期的,当时、分、秒达到 60 后,他们的上一级会加 1 ,而自身又从 0 开始计数
上面我们最后读的秒,如果读出来的秒刚好是 0 ,那么前面读的分、时等就是上一分钟的,结果就少了一分钟,
所以就要重新读取 */
if ( rtc_tm -> tm_sec == 0 && ! have_retried )
{
have_retried = 1 ;
goto retry_get_time ;
}
/* 将上面读取的时间日期值保存到 RTC 核心定义的时间结构体中,该结构体定义在 rtc.h 中,
这里的 bcd2bin 主要是编译器对返回值相同时进行优化处理,定义在 bcd.h 中 */
rtc_tm -> tm_sec = bcd2bin ( rtc_tm -> tm_sec );
rtc_tm -> tm_min = bcd2bin ( rtc_tm -> tm_min );
rtc_tm -> tm_hour = bcd2bin ( rtc_tm -> tm_hour );
rtc_tm -> tm_mday = bcd2bin ( rtc_tm -> tm_mday );
rtc_tm -> tm_mon = bcd2bin ( rtc_tm -> tm_mon );
rtc_tm -> tm_year = bcd2bin ( rtc_tm -> tm_year );
/* 这里为什么要加 100 年和减 1 月呢,我们查看数据手册得知原来是为了区别 1900 年和 2000 年闰年的因素,
1900 年不是闰年而 2000 年是闰年。这时你或许会问那怎么不考虑 1800 年或 2100 年啊?原因很简单,因为
我们的 RTC 时钟只支持 100 年的时间范围,呵呵!! */
rtc_tm -> tm_year += 100 ;
rtc_tm -> tm_mon -= 1 ;
return 0 ;
}
/* 和上面的 rtc_gettime 功能相反,将更改后的分、时、日期、月、年、秒写入 RTC 中 BCD 数中 */
static int rtc_settime ( struct device * dev , struct rtc_time * tm )
{
/* 这里减 100 年很清楚了吧,因为上面为了区别 1900 年和 2000 年时加了 100 年 */
int year = tm -> tm_year - 100 ;
/*RTC 时钟只支持 100 年的时间范围 */
if ( year < 0 || year >= 100 ) {
dev_err ( dev , "rtc only supports 100 years/n" );
return - EINVAL ;
}
/* 将上面保存到 RTC 核心定义的时间结构体中的时间日期值写入对应的寄存器中 */
writeb ( bin2bcd ( tm -> tm_sec ), rtc_base + S3C2410_RTCSEC );
writeb ( bin2bcd ( tm -> tm_min ), rtc_base + S3C2410_RTCMIN );
writeb ( bin2bcd ( tm -> tm_hour ), rtc_base + S3C2410_RTCHOUR );
writeb ( bin2bcd ( tm -> tm_mday ), rtc_base + S3C2410_RTCDATE );
writeb ( bin2bcd ( tm -> tm_mon + 1 ), rtc_base + S3C2410_RTCMON ); /* 这里加 1 月也明白了吧 */
writeb ( bin2bcd ( year ), rtc_base + S3C2410_RTCYEAR );
return 0 ;
}
/* 读取 RTC 中报警各寄存器的:秒、分、时、月、日期、年的值,保存各值到 rtc_time 结构体中 */
static int rtc_getalarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
unsigned int alm_en ;
struct rtc_time * alm_tm = & alrm -> time ;
alm_tm -> tm_sec = readb ( rtc_base + S3C2410_ALMSEC );
alm_tm -> tm_min = readb ( rtc_base + S3C2410_ALMMIN );
alm_tm -> tm_hour = readb ( rtc_base + S3C2410_ALMHOUR );
alm_tm -> tm_mon = readb ( rtc_base + S3C2410_ALMMON );
alm_tm -> tm_mday = readb ( rtc_base + S3C2410_ALMDATE );
alm_tm -> tm_year = readb ( rtc_base + S3C2410_ALMYEAR );
/* 获取 RTC 报警控制寄存器 RTCALM 的值 */
alm_en = readb ( rtc_base + S3C2410_RTCALM );
/* 判断 RTCALM 值的第 6 位,来设置 RTC 的全局报警使能状态到 RTC 核心定义的报警状态结构体 rtc_wkalrm 中 */
alrm -> enabled = ( alm_en & S3C2410_RTCALM_ALMEN ) ? 1 : 0 ;
/* 判断如果 RTCALM 值的第 0 位的值 ( 秒报警使能 ) 为 1 时,就设置报警秒的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_SECEN )
alm_tm -> tm_sec = bcd2bin ( alm_tm -> tm_sec );
else
alm_tm -> tm_sec = 0xff ;
/* 判断如果 RTCALM 值的第 1 位的值 ( 分报警使能 ) 为 1 时,就设置报警分的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_MINEN )
alm_tm -> tm_min = bcd2bin ( alm_tm -> tm_min );
else
alm_tm -> tm_min = 0xff ;
/* 判断如果 RTCALM 值的第 2 位的值 ( 时报警使能 ) 为 1 时,就设置报警小时的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_HOUREN )
alm_tm -> tm_hour = bcd2bin ( alm_tm -> tm_hour );
else
alm_tm -> tm_hour = 0xff ;
/* 判断如果 RTCALM 值的第 3 位的值 ( 日期报警使能 ) 为 1 时,就设置报警日期的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_DAYEN )
alm_tm -> tm_mday = bcd2bin ( alm_tm -> tm_mday );
else
alm_tm -> tm_mday = 0xff ;
/* 判断如果 RTCALM 值的第 4 位的值 ( 月报警使能 ) 为 1 时,就设置报警月的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_MONEN )
{
alm_tm -> tm_mon = bcd2bin ( alm_tm -> tm_mon );
alm_tm -> tm_mon -= 1 ; /* 这里为什么要递减 1 ,我不是很明白??????? */
}
else
{
alm_tm -> tm_mon = 0xff ;
}
/* 判断如果 RTCALM 值的第 5 位的值 ( 年报警使能 ) 为 1 时,就设置报警年的值到 rtc_time 结构体中 */
if ( alm_en & S3C2410_RTCALM_YEAREN )
alm_tm -> tm_year = bcd2bin ( alm_tm -> tm_year );
else
alm_tm -> tm_year = 0xffff ;
return 0 ;
}
/* 把上面保存到 rtc_time 结构体中各值写入 RTC 中报警各寄存器中 */
static int rtc_setalarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
unsigned int alrm_en ;
struct rtc_time * tm = & alrm -> time ;
/* 读取 RTC 报警控制寄存器 RTCALM 的第 6 位,把全局报警使能的状态保存到 alrm_en 变量中 */
alrm_en = readb ( rtc_base + S3C2410_RTCALM ) & S3C2410_RTCALM_ALMEN ;
/* 把 RTC 报警控制寄存器 RTCALM 的值设为 0 ,即将全局报警使能和其他报警使能全部关闭 */
writeb ( 0x00 , rtc_base + S3C2410_RTCALM );
if ( tm -> tm_sec < 60 && tm -> tm_sec >= 0 )
{
/* 上面的 alrm_en 值只记录了 RTCALM 的第 6 位 ( 全局报警使能的状态 ) ,这里再加上第 0 位 ( 秒报警使能的状态 ) ,
然后将前面保存在 rtc_time 中报警秒的值写入报警秒数据寄存器 ALMSEC 中 */
alrm_en |= S3C2410_RTCALM_SECEN ;
writeb ( bin2bcd ( tm -> tm_sec ), rtc_base + S3C2410_ALMSEC );
}
if ( tm -> tm_min < 60 && tm -> tm_min >= 0 )
{
/* 加上第 1 位 ( 分报警使能的状态 ) ,
然后将前面保存在 rtc_time 中报警分的值写入报警分钟数据寄存器 ALMMIN 中 */
alrm_en |= S3C2410_RTCALM_MINEN ;
writeb ( bin2bcd ( tm -> tm_min ), rtc_base + S3C2410_ALMMIN );
}
if ( tm -> tm_hour < 24 && tm -> tm_hour >= 0 )
{
/* 加上第 2 位 ( 时报警使能的状态 ) ,
然后将前面保存在 rtc_time 中报警小时的值写入报警小时数据寄存器 ALMHOUR 中 */
alrm_en |= S3C2410_RTCALM_HOUREN ;
writeb ( bin2bcd ( tm -> tm_hour ), rtc_base + S3C2410_ALMHOUR );
}
/* 把 alrm_en 修改过后的值重新写入 RTC 报警控制寄存器 RTCALM 中 */
writeb ( alrm_en , rtc_base + S3C2410_RTCALM );
/* 请看下面 rtc_setaie 函数实现部分 */
rtc_setaie ( alrm -> enabled );
/* 根据全局报警使能的状态来决定是唤醒 RTC 报警中断还是睡眠 RTC 报警中断 */
if ( alrm -> enabled )
enable_irq_wake ( rtc_alarmno );
else
disable_irq_wake ( rtc_alarmno );
return 0 ;
}
/* 这里主要还是控制 RTC 报警控制寄存器 RTCALM 的第 6 位 ( 全局报警使能状态 )*/
static void rtc_setaie ( int flag )
{
unsigned int tmp ;
tmp = readb ( rtc_base + S3C2410_RTCALM ) & ~ S3C2410_RTCALM_ALMEN ;
if ( flag ) /* 根据标志 flag 来使能或禁止全局报警 */
tmp |= S3C2410_RTCALM_ALMEN ;
writeb ( tmp , rtc_base + S3C2410_RTCALM );
} |
④、RTC平台驱动的设备移除、挂起和恢复接口函数的实现,代码如下:
/* 注意:这是使用了一个 __devexit ,还记得在第① 步中的 __devexit_p 和第② 步中的 __devinit 吗?
我们还是先来讲讲这个:
在 Linux 内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,
这些宏在 include/linux/init.h 头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,
以减少内存占用和提高内核效率。 __devinit 、 __devexit 就是这些宏之一,在 probe() 和 remove() 函数中
应该使用 __devinit 和 __devexit 宏。又当 remove() 函数使用了 __devexit 宏时,则在驱动结构体中一定要
使用 __devexit_p 宏来引用 remove() ,所以在第① 步中就用 __devexit_p 来引用 rtc_remove*/
static int __devexit rtc_remove ( struct platform_device * dev )
{
/* 从系统平台设备中获取 RTC 设备类的数据 */
struct rtc_device * rtc = platform_get_drvdata ( dev );
platform_set_drvdata ( dev , NULL ); /* 清空平台设备中 RTC 驱动数据 */
rtc_device_unregister ( rtc ); /* 注销 RTC 设备类 */
rtc_setpie (& dev -> dev , 0 ); /* 禁止 RTC 节拍时间计数寄存器 TICNT 的使能功能 */
rtc_setaie ( 0 ); /* 禁止 RTC 报警控制寄存器 RTCALM 的全局报警使能功能 */
iounmap ( rtc_base ); /* 释放 RTC 虚拟地址映射空间 */
release_resource ( rtc_mem ); /* 释放获取的 RTC 平台设备的资源 */
kfree ( rtc_mem ); /* 销毁保存 RTC 平台设备的资源内存空间 */
return 0 ;
}
/* 对 RTC 平台设备驱动电源管理的支持。 CONFIG_PM 这个宏定义在内核中,
当配置内核时选上电源管理,则 RTC 平台驱动的设备挂起和恢复功能均有效,
这时候你应该明白了在第② 步中为什么要有 device_init_wakeup(&pdev->dev, 1) 这句吧!! */
# ifdef CONFIG_PM
static int ticnt_save ; /* 定义一个变量来保存挂起时的 TICNT 值 */
/*RTC 平台驱动的设备挂起接口函数的实现 */
static int rtc_suspend ( struct platform_device * pdev , pm_message_t state )
{
ticnt_save = readb ( rtc_base + S3C2410_TICNT ); /* 以节拍时间计数寄存器 TICNT 的值为挂起点 */
rtc_enable ( pdev , 0 ); /* 挂起了之后就禁止 RTC 控制使能 */
return 0 ;
}
/*RTC 平台驱动的设备恢复接口函数的实现 */
static int rtc_resume ( struct platform_device * pdev )
{
rtc_enable ( pdev , 1 ); /* 恢复之前先使能 RTC 控制 */
writeb ( ticnt_save , rtc_base + S3C2410_TICNT ); /* 恢复挂起时的 TICNT 值, RTC 节拍时间继续计数 */
return 0 ;
}
# else /* 配置内核时没选上电源管理 ,RTC 平台驱动的设备挂起和恢复功能均无效 , 这两个函数也就无需实现了 */
# define rtc_suspend NULL
# define rtc_resume NULL
# endif |
好了,到此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中处理的。