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, );      //获取IRQ_TICK节拍中断资源
s3c_rtc_alarmno = platform_get_irq(pdev, );       //获取IRQ_RTC闹钟中断资源
res = platform_get_resource(pdev, IORESOURCE_MEM, ); //获取内存资源 s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+,pdev->name);//申请内存资源 s3c_rtc_base = ioremap(res->start, res->end - res->start + ); //对内存进行重映射 s3c_rtc_enable(pdev, );      //设置硬件相关设置,使能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 = ;
platform_set_drvdata(pdev, rtc);
return ;
}

显然最终会调用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, , 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 = ;
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, );    //注册rtc->char_dev字符设备,添加一个从设备到系统中

显然这里的注册字符设备,和我们上节讲的http://www.cnblogs.com/lifexy/p/7827559.html一摸一样的流程

所以“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) : ; //调用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 %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)
{
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 < )
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 = ;
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 == && !have_retried) {
have_retried = ;
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 += ; //存储器中存放的是从1900年开始的时间,所以加上100
rtc_tm->tm_mon -= ;
return ;
}

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

所以,总结如下所示:

  • rtc_device->char_dev:   字符设备,与应用层、以及更底层的函数打交道
  • rtc_device->ops:    更底层的操作函数,直接操作硬件相关的寄存器,被rtc_device->char_dev调用

4.修改内核

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

4.1进入arch/arm/plat-s3c24xx/Common-smdk.c

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

然后将Common-smdk.c代替虚拟机的内核目录下的Common-smdk.c,重新make uImage编译内核即可

5.测试运行

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

5.1接下来,便开始设置RTC时间

在linux里有两个时钟:

硬件时钟(2440里寄存器的时钟)、系统时钟(内核中的时钟)

所以有两个不同的命令: date命令、hwclock命令

5.2 date命令使用:

输入date查看系统时钟:

如果觉得不方便也可以指定格式显示日期,需要在字符串前面加”+”

如下图所示,输入了  date  "+ %Y/%m/%d %H:%M:%S"

  • %M:表示秒
  • %m:表示月
  • %Y:表示年,当只需要最后两位数字,输入%y即可

date命令设置时间格式如下:

date  月日时分年.秒

如下图所示,输入date 111515292017.20,即可设置好系统时钟

5.3 hwclock命令使用:

常用参数如下所示

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

如下图所示,使用hwclock -w,即可同步硬件时钟

然后重启后,使用date命令,看到时间正常

30.Linux-RTC驱动分析及使用的更多相关文章

  1. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  2. Linux spi驱动分析(二)----SPI核心(bus、device_driver和device)

    一.spi总线注册 这里所说的SPI核心,就是指/drivers/spi/目录下spi.c文件中提供给其他文件的函数,首先看下spi核心的初始化函数spi_init(void).程序如下: 点击(此处 ...

  3. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

  4. 基于335X的Linux网口驱动分析

    基于335X的linux网口驱动分析 一. 系统构成 1.  硬件平台 AM335X 2.  LINUX内核版本 4.4.12 二. 网口驱动构架(mdio部分) mdio网口驱动部分 使用 总线.设 ...

  5. linux RTC 驱动模型分析【转】

    转自:http://blog.csdn.net/yaozhenguo2006/article/details/6824970 RTC(real time clock)实时时钟,主要作用是给Linux系 ...

  6. Linux RTC驱动模型分析之rtc-sysfs.c【转】

    转自:https://blog.csdn.net/longwang155069/article/details/52353408 版权声明:本文为博主原创文章,未经博主允许不得转载. https:// ...

  7. IMX6Q RTC驱动分析

    对于在工作中学习驱动的,讲究的是先使用,再理解.好吧,我们来看看板子里是如何注册的? 在板文件里,它的注册函数是这样的: imx6q_add_imx_snvs_rtc() 好吧,让我们追踪下去: 1 ...

  8. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  9. Linux gadget驱动分析1------驱动加载过程

    为了解决一个问题,简单看了一遍linux gadget驱动的加载流程.做一下记录. 使用的内核为linux 2.6.35 硬件为芯唐NUC950. gadget是在UDC驱动上面的一层,如果要编写ga ...

  10. Linux UART驱动分析

    1. 介绍 8250是IBM PC及兼容机使用的一种串口芯片; 16550是一种带先进先出(FIFO)功能的8250系列串口芯片; 16550A则是16550的升级版本, 修复了FIFO相关BUG, ...

随机推荐

  1. docker镜像文件导入与导出

    工作中经常需要拉取一些国外的镜像,但是网络限制等原因在公司拉取很慢,所以我习惯用亚马逊服务器拉取镜像,导出后下载到本地再导入开发环境 1. 查看镜像id sudo docker images REPO ...

  2. 解决VMware下安装Ubuntu 16.04 不支持1920X1080分辨率的问题

    解决方法: flashmx@ubuntu:~$ cvt # 192.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz Modeline -hsync +vsync fl ...

  3. 高德地图测两点距离android比较精确的

    /////参考资料:高德官方:[http://lbs.amap.com/api/android-location-sdk/guide/android-location/getlocation] 主要三 ...

  4. IntelliJ IDEA 14.1.4设置关闭自动保存和标志改动文件为星号?

    一.设置取消默认的自动保存: 二.设置文件内容改动后为文件标题头部添加*号:

  5. rewrap-ajax.js插件

    很久没有动手写技术的文章,这个过程中间一直在写日记,生活点滴的记录替代了技术文章的编写,可以看出以往的内心是激情或烈火,现在是... 最近写了一个JS插件,用圈内的话说叫造了个轮子,造的好与不好都不是 ...

  6. Latex 去掉行号

    本文主要讲如何去掉Latex的行号 删除\modulolinenumbers删除所有\linenumbers 删除\usepackage{lineno,hyperref} modulolinenumb ...

  7. CMD(SA400 Command)

    一.CMD模糊查询: 命令行键入:CRT,WRK,ADD,CPY,DSP,CHG,CLR,FND,RTV*等. 二.CMD分类查询: 命令行键入:GO CMD xxx eg:GO CMD FILE,G ...

  8. UVa1599,Ideal Path

      说实话,这题参考的: http://blog.csdn.net/u013382399/article/details/38227917 倒着BFS就把我难住了T T,原来这样倒着BFS一遍,遍历完 ...

  9. vue搭建项目前奏曲——vue-cli

    vue-cli是快速构建这个单页应用的脚手架,这个可是官方的.官方给的建议,如果你是初次尝试Vue,哪就老老实实用普通的书写引入js文件,这里牵扯太多的东西,例如webpack.Node.js.npm ...

  10. Tomcat Java.OutOfMemoryError : PermGen Space异常

    背景:前些日子更新公司多年前一个旧平台发布到Tomcat上之后,频繁收到网站许多模块无法正常使用的反汇. 测试过程中发现平台发布一段时间后,访问相关网页出现如下500页面 解决方案:PermGen s ...