热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

S3C2440RTC实时时钟驱动分析以及使用(三十)

https:www.cnblogs.comlifexyp7839625.htmlRTC驱动分析总结:drivers\rtc\rtc-s3c.cs3c_rtc_in

https://www.cnblogs.com/lifexy/p/7839625.html

RTC驱动分析总结:

drivers\rtc\rtc-s3c.cs3c_rtc_initplatform_driver_registers3c_rtc_probertc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE)rtc_dev_preparecdev_init(&rtc->char_dev, &rtc_dev_fops);rtc_dev_add_devicecdev_add


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


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

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

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

而"s3c2410-rtc"的平台设备,在./arch/arm/plat-s3c24xx/devs.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_devicertc->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相关文件
}

上面的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字符设备,添加一个从设备到系统中

显然这里的注册字符设备,和我们上节讲的一模一样的流程https://blog.csdn.net/xiaodingqq/article/details/81974606

 

所以"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->chr_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)
{int err;struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//获取对应的rtc_deviceconst struct rtc_class_ops *ops = rtc->ops;//最终等于s3c_rtcops... ...file->private_data = rtc;//设置file结构体的私有成员等于rtc_device,再次执行ioctl等函数时,直接就可以提取file->private_date即可err = ops->open ? ops->open(rtc->dev.parent) : 0;//调用s3c_rtcops->open... ...mutex_unlock(&rtc->char_lock);return err;
}

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


static const struct rtc_class_ops s3c_rtcops = {.open = s3c_rtc_open,.release = s3c_rtc_release,.ioctl = s3c_rtc_ioctl,.read_time = s3c_rtc_gettime,.set_time = s3c_rtc_settime,.read_alarm = s3c_rtc_getalarm,.set_alarm = s3c_rtc_setalarm,.proc = s3c_rtc_proc,
};

则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 %d\n", 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 %d\n", 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)
{int err &#61; 0;struct rtc_device *rtc &#61; file->private_data;提取rtc_device... ...void __user *uarg &#61; (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://改变中断触发速度if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))return -EACCES;break;... ...}.. ...switch (cmd) {case RTC_ALM_READ: //读闹钟时间err &#61; rtc_read_alarm(rtc, &alarm);//调用s3c_rtcops->read_alarmif (err <0)return err;if (copy_to_user(uarg, &alarm.time, sizeof(tm)))//传时间数据return -EFAULT;break;case RTC_ALM_SET://设置闹钟时间&#xff0c;调用s3c_rtcops->set_alarm... ...case RTC_RD_TIME://读RTC时间&#xff0c;调用s3c_rtcops->read_alarm... ...case RTC_SET_TIME://写RTC时间&#xff0c;调用s3c_rtcops->set_time... ...case RTC_IRQP_SET://该败了中断触发频率&#xff0c;调用s3c_rtcops->irq_set_freq... ...... ...
}

最终还是调用s3c_rtcops下的成员函数&#xff0c;我们以s3c_rtcops->raed_alarm()函数为例&#xff0c;看看如何读出时间&#xff1a;

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{unsigned int have_retried &#61; 0;void __iomem *base &#61; s3c_rtc_base;//获取RTC相关寄存器基地址retry_get_time://获取年&#xff0c;月&#xff0c;日&#xff0c;时&#xff0c;分&#xff0c;秒寄存器rtc_tm->tm_min &#61; readb(base &#43; S3C2410_RTCMIN);rtc_tm->tm_hour &#61; readb(base &#43; S3C2410_RTCHOUR);rtc_tm->tm_mday &#61; readb(base &#43; S3C2410_RTCDATE);rtc_tm->tm_mon &#61; readb(base &#43; S3C2410_RTCMON);rtc_tm->tm_year &#61; readb(base &#43; S3C2410_RTCYEAR);rtc_tm->tm_sec &#61; readb(base &#43; S3C2410_RTCSEC);//判断寄存器中是0&#xff0c;则表示过去了一分钟&#xff0c;那么小时&#xff0c;天&#xff0c;月等寄存器中的值都可能已经变化&#xff0c;需要重新读取这些寄存器的值if (rtc_tm->tm_sec &#61;&#61; 0 && !have_retried) {have_retried &#61; 1;goto retry_get_time;}... ...//将获取的寄存器值&#xff0c;转换为真正的时间数据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 &#43;&#61; 100;//存储器中存放的是从1900年开始的时间&#xff0c;所以加上100rtc_tm->tm_mon -&#61; 1;return 0;
}

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

所以&#xff0c;总结如下所示&#xff1a;

rtc_device->char_dev&#xff1a;          字符设备&#xff0c;与应用层、以及更底层的函数打交道

rtc_device->ops&#xff1a;                   更底层的操作函数&#xff0c;直接操作硬件相关的寄存器&#xff0c;被rtc_device->char_dev调用

 

4、修改内核

我们单板上使用ls /dev/rtc*&#xff0c;找不到该字符设备&#xff0c;因为内核里只定义了s3c_device_rtc这个RTC平台设备&#xff0c;没有注册&#xff0c;所以平台驱动没有被匹配上&#xff0c;接下来我们来修改内核里的注册数组

4.1 arch\arm\plat-s3c24xx\common-smdk.c

如下图所示&#xff0c;在smdk_devs[]里&#xff0c;添加RTC的平台设备即可&#xff0c;当内核启动时&#xff0c;就会调用该数组&#xff0c;将里面的platform_device通通注册一遍

然后将PC机上的common-smdk.c 代替 虚拟机的内核目录下的common-smdk.c&#xff0c;重新make uImage编译内核即可

 

5、测试运行

启动后&#xff0c;如下图所示&#xff0c;使用ls /dev/rtc*&#xff0c;就找到了rtc0这个字符设备

5.1 接下来&#xff0c;便开始设置RTC时间

在linux里有两个时钟&#xff1a;

硬件时钟&#xff08;2440里寄存器的时钟&#xff09;、系统时钟&#xff08;内核中的时钟&#xff09;

所以有两个不同的命令&#xff1a;date命令、hwclock命令

5.2 date命令的使用&#xff1a;

输入date查看系统时钟&#xff1a;

如果觉得不方便&#xff0c;也可以指定格式显示日期&#xff0c;需要在字符串前面加“&#43;”

如下图所示&#xff0c;输入了

 

date命令设置时间格式如下&#xff1a;

date 月日时分年.秒

如下图所示&#xff0c;输入&#xff1a;date 082513192018.15&#xff0c;即可设置好系统时钟

 

5.3 hwclock命令使用&#xff1a;

常用参数如下所示&#xff1a;

-r, --show                读取并打印硬件时钟(read hardware clock and print result)

-s, --hctosys           将硬件时钟同步到系统时钟(set the system time from the hardware clock)

-w, --systohc           将系统时钟同步到硬件时钟(set the hardware clock to the current system time)

如下图所示&#xff0c;使用hwclock -w&#xff0c;即可同步硬件时钟

 

然后重启后&#xff0c;使用date命令&#xff0c;看到时间正常

 

 


推荐阅读
  • 本文详细介绍了 Node.js 中 OS 模块的 arch 方法,包括其功能、语法、参数以及返回值,并提供了具体的使用示例。 ... [详细]
  • 本文介绍了一种算法,用于在一个给定的二叉树中找到一个节点,该节点的子树包含最大数量的值小于该节点的节点。如果存在多个符合条件的节点,可以选择任意一个。 ... [详细]
  • 本文详细介绍了Java中的代理模式,包括静态代理、JDK动态代理和Cglib动态代理的实现方式。通过一个火车票销售系统的实例,对比分析了三种代理模式的特点及其应用场景。 ... [详细]
  • 本文探讨了在使用Apache Flink向Kafka发送数据过程中遇到的事务频繁失败问题,并提供了详细的解决方案,包括必要的配置调整和最佳实践。 ... [详细]
  • 拖拉切割直线 ... [详细]
  • 使用Objective-C实现苹果官方NSLayoutConstraint页面布局
    本文详细介绍了如何在iOS开发中使用Objective-C语言通过NSLayoutConstraint来实现页面布局。示例代码展示了如何创建和应用约束,以确保界面元素能够正确地响应不同屏幕尺寸的变化。 ... [详细]
  • Web网络基础
    目录儿1使用HTTP协议访问Web2HTTP的诞生2.1因特网的起源2.2互联网、因特网与万维网2.3万维网与HTTP3网络基础TCPIP3.1TCPIP协议族3.2TCPIP的分 ... [详细]
  • 抽象工厂模式 c++
    抽象工厂模式包含如下角色:AbstractFactory:抽象工厂ConcreteFactory:具体工厂AbstractProduct:抽象产品Product:具体产品https ... [详细]
  • 微信小程序支付官方参数小程序中代码后端发起支付代码支付回调官方参数文档地址:https:developers.weixin.qq.comminiprogramdeva ... [详细]
  • MVC框架下使用DataGrid实现时间筛选与枚举填充
    本文介绍如何在ASP.NET MVC项目中利用DataGrid组件增强搜索功能,具体包括使用jQuery UI的DatePicker插件添加时间筛选条件,并通过枚举数据填充下拉列表。 ... [详细]
  • 为何Compose与Swarm之后仍有Kubernetes的诞生?
    探讨在已有Compose和Swarm的情况下,Kubernetes是如何以其独特的设计理念和技术优势脱颖而出,成为容器编排领域的领航者。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • HTML前端开发:UINavigationController与页面间数据传递详解
    本文详细介绍了如何在HTML前端开发中利用UINavigationController进行页面管理和数据传递,适合初学者和有一定基础的开发者学习。 ... [详细]
  • Java注解(Annotations)简介与应用
    自从Java SE 5.0引入了注解(Annotations)这一特性以来,它就成为了增强代码功能和可读性的重要工具。注解允许开发者在不改变程序逻辑的前提下,在源代码中添加额外的元数据信息。 ... [详细]
  • 从理想主义者的内心深处萌发的技术信仰,推动了云原生技术在全球范围内的快速发展。本文将带你深入了解阿里巴巴在开源领域的贡献与成就。 ... [详细]
author-avatar
SJ曹圭贤V
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有