RTC驱动源码分析

来源:互联网 发布:淘宝买运动鞋哪家店好 编辑:程序博客网 时间:2024/05/22 01:41

1.模块加载函数和卸载函数

static int __init rtc_init(void)

 

{

 

   /*RTC注册成平台设备驱动*/

 

   returnplatform_driver_register(&rtc_driver);

 

}

 

static void __exit rtc_exit(void)

 

{

 

   /*注销RTC平台设备驱动*/

 

   platform_driver_unregister(&rtc_driver);

 

}

2. RTC平台驱动结构体

/*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,

 

   },

 

};

3. RTC探测函数rtc_probe

/*定义了一个用来保存RTCIO端口占用的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;

 

   }

 

 

 

   /*申请RTCIO端口资源所占用的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;

 

   }

 

 

 

   /*RTCIO端口占用的这段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的节拍时间计数寄存器TICNT0-6位进行操作,即:节拍时间计数值的设定*/

 

 

 

   /*device_init_wakeup该函数定义在pm_wakeup.h中,定义如下:

 

   static inline voiddevice_init_wakeup(struct device *dev, int val){

 

       dev->power.can_wakeup= dev->power.should_wakeup = !!val;

 

   }

 

   显然这个函数是让驱动支持电源管理的,这里只要知道,can_wakeup1时表明这个设备可以被唤醒,设备驱动为了支持

 

   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的宏,如下:

 

    #defineplatform_set_drvdata(_dev,data)   dev_set_drvdata(&(_dev)->dev, (data))

 

    dev_set_drvdata又被定义在include/linux/device.h中,如下:

 

     static inline voiddev_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)

    可以控制CPURTC之间的所有接口(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的节拍时间计数寄存器TICNT0-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;

}

 

4.RTC设备类的操作。在这一步中,才是对RTC硬件的各种寄存器进行操作,代码如下:

#include <linux/interrupt.h>

#include <linux/bcd.h>

 

 

/*rtc_class_opsRTC设备类在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_alarmnoRTC探测函数rtc_probe中已经获取得,

    这里使用的是快速中断:IRQF_DISABLED。中断服务程序为:rtc_alarmirq,将RTC设备类rtc_dev做参数传递过去了*/

   ret =request_irq(rtc_alarmno, rtc_alarmirq, IRQF_DISABLED, "my2440-rtcalarm", rtc_dev);

   if (ret)

   {

       dev_err(dev,"IRQ%d error %d\n", rtc_alarmno, ret);

       return ret;

   }

 

 

   /*同上面一样,这里申请的是RTCTICK节拍时间中断服务,服务程序是:rtc_tickirq*/

   ret =request_irq(rtc_tickno, rtc_tickirq, IRQF_DISABLED, "my2440-rtctick", 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;

}

 

 

/*RTCTICK节拍时间中断服务*/

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;

}

 

 

/*读取RTCBCD数中的:分、时、日期、月、年、秒*/

static int rtc_gettime(struct device *dev, struct rtc_time*rtc_tm)

{

   unsigned inthave_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功能相反,将更改后的分、时、日期、月、年、秒写入RTCBCD数中*/

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);

}

5.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_tstate)

{

   

   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中处理的。

原创粉丝点击