历史上的今天
返回首页

历史上的今天

今天是:2025年07月21日(星期一)

正在发生

2021年07月21日 | 22.Linux-RTC驱动分析及使用

2021-07-21 来源:eefocus

    linux中的rtc驱动位于drivers/rtc下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24xx为主,所以它的RTC驱动为rtc-s3c.c


1.进入./drivers/rtc/rtc-s3c.c

    还是首先进入入口函数,如下图所示:

在这里插入图片描述

    这里注册了一个“s3c2410-rtc”名称的平台设备驱动


    而“s3c2410-rtc”的平台设备,在./arch/arm/plat-s3c24xx/dev.c里定义了,只不过这里没有注册,如下图所示:

在这里插入图片描述

    当内核匹配到有与它名称同名的平台设备,就会调用.probe函数,接下来我们便进入s3c2410_rtcdrv->probe函数中看看,做了什么:


static int s3c_rtc_probe(struct platform_device *pdev)

{

struct rtc_device *rtc;           //rtc设备结构体

struct resource *res;

int ret;


s3c_rtc_tickno = platform_get_irq(pdev, 1);          //获取IRQ_TICK节拍中断资源

s3c_rtc_alarmno = platform_get_irq(pdev, 0);        //获取IRQ_RTC闹钟中断资源

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //获取内存资源


s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//申请内存资源


s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);     //对内存进行重映射



s3c_rtc_enable(pdev, 1);          //设置硬件相关设置,使能RTC寄存器


s3c_rtc_setfreq(s3c_rtc_freq);      //设置TICONT寄存器,使能节拍中断,设置节拍计数值


/*1.注册RTC设备*/

rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);  


rtc->max_user_freq = 128;

platform_set_drvdata(pdev, rtc);

      return 0;

}


    显然最终会调用rtc_device_register()函数来向内核注册rtc_device设备,注册成功会返回一个已注册好的rtc_device,


而s3c_rtcops是一个rtc_class_ops结构体,里面就是保存如何操作这个rtc设备的函数,比如读写RTC时间,读写闹钟时间等,注册后,会保存在rtc_device->ops里


    该函数在drivers/rtc/Class.c文件内被定义。Class.c文件主要定义了RTC子系统,而内核初始化,便会进入Class.c,进入rtc_init()->rtc_dev_init(),来注册字符设备:


 err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");   

        // RTC_DEV_MAX=16,表示只注册0~15个次设备号,设备编号保存在rtc_devt中 


2.它与rtc_device_register()函数注册RTC设备,会有什么关系?

    接下来便来看rtc_device_register(),代码如下:


struct rtc_device *rtc_device_register(const char *name, struct device *dev,const struct rtc_class_ops *ops,struct module *owner)

{

       struct rtc_device *rtc;    //定义一个rtc_device结构体

       ... ...

       rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);  //分配rtc_device结构体为全局变量


 

       /*设置rtc_device*/

    rtc->id = id;

       rtc->ops = ops;            //将s3c_rtcops保存在rtc_device->ops里

       rtc->owner = owner;

       rtc->max_user_freq = 64;

       rtc->dev.parent = dev;

       rtc->dev.class = rtc_class;

       rtc->dev.release = rtc_device_release;

       ... ...


       rtc_dev_prepare(rtc);                   //1.做提前准备,初始化cdev结构体

       ... ...

       rtc_dev_add_device(rtc);               //2.在/dev下创建rtc相关文件,将cdev添加到系统中


       rtc_sysfs_add_device(rtc);             //在/sysfs下创建rtc相关文件

       rtc_proc_add_device(rtc);             //在/proc下创建rtc相关文件

       ... ...

    return rtc;

}


    上面的rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)主要做了以下两个(位于./drivers/rtc/rtc-dev.c):


cdev_init(&rtc->char_dev, &rtc_dev_fops);          //绑定file_operations  


cdev_add(&rtc->char_dev, rtc->dev.devt, 1);    //注册rtc->char_dev字符设备,添加一个从设备到系统中


    显然这里的注册字符设备,和我们上节讲的一摸一样的流程.

所以“s3c2410-rtc”平台设备驱动的.probe主要做了以下几件事:


    1.设置RTC相关寄存器

    2.分配rtc_device结构体

    3.设置rtc_device结构体

-> 3.1 将struct rtc_class_ops s3c_rtcops放入rtc_device->ops,实现对RTC读写时间等操作

4. 注册rtc->char_dev字符设备,且该字符设备的操作结构体为: struct file_operations rtc_dev_fops


3.上面的file_operations操作结构体rtc_dev_fops 的成员,如下图所示:

在这里插入图片描述

3.1当我们应用层open(”/dev/rtcXX”)时,就会调用rtc_dev_fops-> rtc_dev_open(),我们来看看如何open的:

static int rtc_dev_open(struct inode *inode, struct file *file)

{

   struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//获取对应的rtc_device

   const struct rtc_class_ops *ops = rtc->ops;                            //最终等于s3c_rtcops


   file->private_data = rtc;                     //设置file结构体的私有成员等于rtc_device,再次执行ioctl等函数时,直接就可以提取file->private_data即可


   err = ops->open ? ops->open(rtc->dev.parent) : 0;  //调用s3c_rtcops->open


   mutex_unlock(&rtc->char_lock);

   return err;

}

    显然最终还是调用rtc_device下的s3c_rtcops->open:

在这里插入图片描述

    而s3c_rtc_open()函数里主要是申请了两个中断,一个闹钟中断,一个计时中断:


static int s3c_rtc_open(struct device *dev)

{     

 struct platform_device *pdev = to_platform_device(dev);    

 struct rtc_device *rtc_dev = platform_get_drvdata(pdev);      

 int ret;


 ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED,  "s3c2410-rtc alarm", rtc_dev);        //申请闹钟中断                      

              if (ret) {

              dev_err(dev, "IRQ%d error %dn", s3c_rtc_alarmno, ret);

              return ret;

       }


 


 ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);//申请计时中断   

       if (ret) {

              dev_err(dev, "IRQ%d error %dn", s3c_rtc_tickno, ret);

              goto tick_err;

       }


       return ret;


 tick_err:

       free_irq(s3c_rtc_alarmno, rtc_dev);

       return ret;

}


3.2 当我们应用层open后,使用 ioctl(int fd, unsigned long cmd, …)时,就会调用rtc_dev_fops-> rtc_dev_ioctl ():

static int rtc_dev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)

{

struct rtc_device *rtc = file->private_data;  //提取rtc_device

 void __user *uarg = (void __user *) arg;

  ... ...


 switch (cmd) {

       case RTC_EPOCH_SET:

       case RTC_SET_TIME:      //设置时间

              if (!capable(CAP_SYS_TIME))

                     return -EACCES;

              break;

       case RTC_IRQP_SET:   //改变中断触发速度

       ... ...}

       ... ...

       switch (cmd) {

       case RTC_ALM_READ:    //读闹钟时间

              err = rtc_read_alarm(rtc, &alarm);              //调用s3c_rtcops-> read_alarm

              if (err < 0)

                     return err;


              if (copy_to_user(uarg, &alarm.time, sizeof(tm)))  //长传时间数据

                     return -EFAULT;

                     break;


       case RTC_ALM_SET:              //设置闹钟时间 , 调用s3c_rtcops-> set_alarm

              ... ...


       case RTC_RD_TIME:              //读RTC时间, 调用s3c_rtcops-> read_alarm

              ... ...


       case RTC_SET_TIME:      //写RTC时间,调用s3c_rtcops-> set_time

              ... ...


       case RTC_IRQP_SET:      //改变中断触发频率,调用s3c_rtcops-> irq_set_freq

              ... ...


}


    最终还是调用s3c_rtcops下的成员函数,我们以s3c_rtcops-> read_alarm()函数为例,看看如何读出时间的:


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

{

       unsigned int have_retried = 0;

       void __iomem *base = s3c_rtc_base;    //获取RTC相关寄存器基地址



retry_get_time:


       /*获取年,月,日,时,分,秒寄存器*/

       rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);     

       rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);

       rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);

       rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);

       rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);

       rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);


      

       /*  判断秒寄存器中是0,则表示过去了一分钟,那么小时,天,月,等寄存器中的值都可能已经变化,需要重新读取这些寄存器的值*/

if (rtc_tm->tm_sec == 0 && !have_retried) {

              have_retried = 1;

              goto retry_get_time;

       }


       /*将获取的寄存器值,转换为真正的时间数据*/

       BCD_TO_BIN(rtc_tm->tm_sec);

       BCD_TO_BIN(rtc_tm->tm_min);

       BCD_TO_BIN(rtc_tm->tm_hour);

       BCD_TO_BIN(rtc_tm->tm_mday);

       BCD_TO_BIN(rtc_tm->tm_mon);

       BCD_TO_BIN(rtc_tm->tm_year);


    rtc_tm->tm_year += 100;    //存储器中存放的是从1900年开始的时间,所以加上100 

    rtc_tm->tm_mon -= 1;

    return 0;

}


    同样, 在s3c_rtcops-> set_time()函数里,也是向相关寄存器写入RTC时间

推荐阅读

史海拾趣

Celduc Relais公司的发展小趣事

Celduc Relais公司自创立之初,便以技术创新为核心驱动力。在公司的早期发展阶段,研发团队成功开发了一款具有颠覆性的继电器产品,该产品在性能、稳定性和寿命等方面均达到了行业领先水平。这一突破性的技术创新不仅为公司赢得了市场的广泛认可,也为后续的产品线扩展和技术升级奠定了坚实基础。

Chemi-Con公司的发展小趣事

随着电子行业的快速发展,Chemi-Con公司不断加大对研发的投入,致力于技术创新和产品升级。公司研发团队在铝电解电容器的基础上,不断推出性能更优、体积更小、寿命更长的产品,满足了市场对于高效、可靠电容器的迫切需求。此外,Chemi-Con还积极拓展产品线,涉足了多层陶瓷电容器、薄膜电容器等多个领域,为客户提供更加丰富的选择。

DIOTEC公司的发展小趣事

随着中国经济的快速崛起,电子市场需求不断增长。为了抓住这一机遇,DIOTEC于2005年在中国上海设立了分公司,即德欧泰克半导体(上海)有限公司。这家分公司凭借DIOTEC在全球的技术和品质优势,迅速在中国市场打开了局面。如今,德欧泰克半导体(上海)有限公司已经成为中国电子行业的重要供应商之一。

Emulation Technology Inc公司的发展小趣事

面对电子行业的快速发展和市场的不断变化,Emulation始终保持着对技术的持续创新。公司不断投入研发资源,推出了一系列具有领先性能的模拟和仿真产品,满足了客户对于高精度、高效率仿真工具的需求。这些产品不仅提高了工程师们的设计效率,还促进了整个电子行业的发展。

High Tech Chips Inc公司的发展小趣事

为了进一步扩大市场份额,Emulation积极实施国际化战略。公司先后在北美、欧洲和亚洲等地设立了分支机构,与当地合作伙伴建立了紧密的合作关系。这些分支机构不仅为Emulation提供了更多的市场机会,还帮助公司更好地了解当地市场需求和竞争态势。

Hong Kong X'Tals Ltd公司的发展小趣事
由于环境温度和元件参数的变化,实际定时时间可能会有所偏差。在设计时需要考虑这一因素,并留出适当的裕量。

问答坊 | AI 解惑

PIC单片机教程 西安电子科技大学

PIC单片机教程      西安电子科技大学 网上找的觉得不错,传上来,让大家一起分享!…

查看全部问答>

wince 日文EUC编码

wince帮助中好像只有日文JIS编码没有日文EUC编码。wince日文系统怎样支持日文EUC编码啊?…

查看全部问答>

请教,如何做FPGA对CF卡读写

有做过FPGA对CF卡读写的吗?不知道要如何开始,请指点下 我看NIOS2里有CF外设,可是不知道要如何做 例如要学习些什么?能给个开发的步骤吗?…

查看全部问答>

PB编译错误2

刚才删除了build.dat可以编译了,现在编译错误信息: BUILD: [01:0000000138:ERRORE] e:\\WINCE500\\PLATFORM\\SMDK2440\\DRIVERS\\Nandflsh\\FMD\\ecc.c(14) : fatal error C1083: Cannot open include file: \'windows.h\': No such file or dir ...…

查看全部问答>

正负电源推挽放大电路中偏置电阻如何计算问题

哪位高手能讲解下附件中的问题? 在此多多感谢…

查看全部问答>

MSP430的高阻状态

MSP430的I/O口置输入时,应是高阻状态,这个电阻有多大?内部属于啥电路? 比如:在I/O口通过一个10K电阻和0.1的电容接地,先将I/O口置输出高,对电容充电,足够长时间后(确保电容充满),将I/O口置为输入,电容上的电量能否保持?…

查看全部问答>

SPS-2000焊锡搅拌机 (MALCOM)

 SPS-2000焊锡搅拌机(MALCOM)特长:无铅焊锡搅拌时温度等的关系是重要的因素MALCOM <SPS-2000>设定了温度管理、实现搅拌自动停止机能,无论是刚从冰箱拿出的锡膏,工作人员只需按一下按钮就可以搅拌出最佳的状态的锡膏机器。公转约 ...…

查看全部问答>

嵌入式Linux系统走向成熟之路

目前,对嵌入式Linux系统的开发正在蓬勃兴起,并已形成了很大的 市场。但就目前的技术而言,嵌入式Linux的研究成果与市场的真正需求还 有一些距离,因此,嵌入式Linux系统走向成熟还需要在以下几个方面有所 发展。下面就由福州卓跃教育具体介绍 ...…

查看全部问答>

帮我分析下设计波形发生器的方案,万分感谢!!!

      最近有个创新项目,内容如下:       用DDS(Direct Digital Synthesizer,直接数字频率合成器) 的AD9833芯片(如果其它芯片更好也可用)和单片机(最好是51系列,其它系列如果简单也可以) ...…

查看全部问答>