Linux驱动开发: Linux下RTC实时时钟驱动
Posted DS小龙哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发: Linux下RTC实时时钟驱动相关的知识,希望对你有一定的参考价值。
Linux内核版本: 3.5
1.1 Linux下RTC时间的读写分析
1.1.1 系统时间与RTC实时时钟时间
Linux系统下包含两个时间:系统时间和RTC时间。
系统时间:是由主芯片的定时器进行维护的时间,一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是掉电后不保存。
RTC时间:是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行。
每次Linux系统启动后在启动过程中会检测和挂载RTC驱动,在挂载后会自动从RTC芯片中读取时间并设置到系统时间中去。此后如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。
linux命令中的date和time等命令都是用来设置系统时间的,而hwclock命令是用来设置和读写RTC时间的。
1.1.2 Linux内核RTC实时时钟配置查看与选择:
进入到内核根目录下,输入: make menuconfig 进入到内核配置菜单:
根据选项进入到RTC实时驱动菜单:
Device Drivers ---> [*] Real Time Clock ---> --- Real Time Clock │ │ │ │ [*] Set system time from RTC on startup and resume │ │ │ │ (rtc0) RTC used to set the system time │ │ │ │ [ ] RTC debug support │ │ │ │ *** RTC interfaces *** │ │ │ │ [*] /sys/class/rtc/rtcN (sysfs) [*] /proc/driver/rtc (procfs for rtc0) │ │ │ │ [*] /dev/rtcN (character devices) │ │ │ │ [ ] RTC UIE emulation on dev interface │ │ │ │ < > Test driver/device │ │ │ │ *** I2C RTC drivers *** │ │ │ │ < > Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025 < > Dallas/Maxim DS1374 │ │ │ │ < > Dallas/Maxim DS1672 │ │ │ │ < > Dallas/Maxim DS3232 │ │ │ │ < > Maxim MAX6900 │ │ │ │ < > Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A < > Intersil ISL1208 │ │ │ │ < > Intersil ISL12022 │ │ │ │ < > Xicor/Intersil X1205 │ │ │ │ < > Philips PCF8563/Epson RTC8564 │ │ │ │ < > Philips PCF8583 │ │ │ │ < > ST M41T62/65/M41T80/81/82/83/84/85/87 < > TI BQ32000 │ │ │ │ < > Seiko Instruments S-35390A │ │ │ │ < > Ramtron FM3130 │ │ │ │ < > Epson RX-8581 │ │ │ │ < > Epson RX-8025SA/NB │ │ │ │ < > EM Microelectronic EM3027 < > Micro Crystal RTC │ │ │ │ *** SPI RTC drivers *** │ │ │ │ < > ST M41T93 │ │ │ │ < > ST M41T94 │ │ │ │ < > Dallas/Maxim DS1305/DS1306 │ │ │ │ < > Dallas/Maxim DS1390/93/94 *** Platform RTC drivers *** │ │ │ │ < > PC-style 'CMOS' │ │ │ │ < > Dallas DS1286 │ │ │ │ < > Dallas DS1511 │ │ │ │ < > Maxim/Dallas DS1553 *** on-CPU RTC drivers *** │ │ │ │ <*> Samsung S3C series SoC RTC │ │ │ │ < > ARM AMBA PL030 RTC │ │ │ │ < > ARM AMBA PL031 RTC │ │ │ └────────────────────────────── |
根据内核的配置得知3个信息(红色选中的配置选项):
1. 系统时间默认从RTC0里获取时间进行设置。
(rtc0表示/dev下第一个rtc驱动,如果安装了第二个RTC驱动,就以rtc1表示,依次类推)
2. 使用proc查看RTC信息,默认只能从rtc0节点里获取(系统里的第一个rtc驱动)
[root@XiaoLong /]#cat /proc/driver/rtc |
3. 内核默认选择CPU本身自带的RTC作为系统实时时钟。
驱动源码\\linux-3.5\\drivers\\rtc\\ rtc-s3c.c是三星公司编写的RTC驱动。
1.1.3 date命令使用介绍
date是用来显示或设定系统的日期与时间的命令。
命令使用格式: date [参数]... [+格式]
命令可以的参数如下:
使用示例: date '+%A' 必要参数: %H 小时(以00-23来表示)。 %I 小时(以01-12来表示)。 %K 小时(以0-23来表示)。 %l 小时(以0-12来表示)。 %M 分钟(以00-59来表示)。 %P AM或PM。 %r 时间(含时分秒,小时以12小时AM/PM来表示)。 %s 总秒数。起算时间为1970-01-01 00:00:00 UTC。 %S 秒(以本地的惯用法来表示)。 %T 时间(含时分秒,小时以24小时制来表示)。 %X 时间(以本地的惯用法来表示)。 %Z 市区。 %a 星期的缩写。 %A 星期的完整名称。 %b 月份英文名的缩写。 %B 月份的完整英文名称。 %c 日期与时间。只输入date指令也会显示同样的结果。 %d 日期(以01-31来表示)。 %D 日期(含年月日)。 %j 该年中的第几天。 %m 月份(以01-12来表示)。 %U 该年中的周数。 %w 该周的天数,0代表周日,1代表周一,异词类推。 %x 日期(以本地的惯用法来表示)。 %y 年份(以00-99来表示)。 %Y 年份(以四位数来表示)。 %n 在显示时,插入新的一行。 %t 在显示时,插入tab。 MM 月份(必要) DD 日期(必要) hh 小时(必要) mm 分钟(必要) ss 秒(选择性) 选择参数: -d<字符串> 显示字符串所指的日期与时间。字符串前后必须加上双引号。 -s<字符串> 根据字符串来设置日期与时间。字符串前后必须加上双引号。 -u 显示GMT。 --help 在线帮助。 --version 显示版本信息 |
系统时间的方式
[root@XiaoLong /]# date -s "2018-07-28 14:21:22" //设置全部日期与时间 [root@XiaoLong /]# date -s "14:30:22" //只设置时间 [root@XiaoLong /]# date -s "2017-07-28" //设置日期,时间默认为00:00:00 |
格式示例
[root@XiaoLong /]# date -r app // -r选项可以打印出指定文件的最后修改时间 Fri Apr 29 05:17:34 UTC 2016 [root@XiaoLong /]# date -d 23:39:00 //打印出指定格式时间(只是打印效果没有其他效果) Sat Apr 30 23:39:00 UTC 2016 [root@XiaoLong /]# date -s 12:20:30 //设置系统时间为12点20分30秒 Sat Apr 30 12:20:30 UTC 2016 [root@XiaoLong /]# date -s 2016.04.30-23:20:10 //设置系统时间为2016年4月30日23点20分10秒 Sat Apr 30 23:20:10 UTC 2016 |
1.1.4 系统RTC实时时钟时间的获取与设置
1. 将RTC时间同步到系统时间
[root@XiaoLong /]# hwclock -s |
为了在启动时自动执行RTC时间同步到系统时间,可以把hwclock -s命令加入到profile或者rcS文件中。
2. 获取显示RTC时间
[root@XiaoLong /]# hwclock -r Sun May 1 00:09:36 2016 0.000000 seconds |
3. 将系统时间同步到RTC,用于设置时间
[root@XiaoLong /]# hwclock -w |
4. 查看RTC的信息
[root@XiaoLong /]# cat /proc/driver/rtc rtc_time : 00:09:27 rtc_date : 2016-05-01 alrm_time : 23:24:07 alrm_date : 2016-05-01 alarm_IRQ : no alrm_pending : no update IRQ enabled : no periodic IRQ enabled : no periodic IRQ frequency : 1 max user IRQ frequency : 32768 24hr : yes periodic_IRQ : no |
1.2 Linux内核RTC子系统结构
1.2.1 RTC框架相关的核心文件
1. /drivers/rtc/class.c 这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口
2. /drivers/rtc/rtc-dev.c 这个文件定义了基本的设备文件操作函数,如:open,read等
3. /drivers/rtc/interface.c 顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数
4. /drivers/rtc/rtc-sysfs.c 与sysfs有关
5. /drivers/rtc/rtc-proc.c 与proc文件系统有关
6. /include/linux/rtc.h 定义了与RTC有关的数据结构
Linux内核源码自带的RTC驱动代码存放位置:
\\linux-3.5\\drivers\\rtc\\目录下全是RTC驱动示例代码 其中:rtc-s3c.c 是三星公司编写的RTC驱动 |
1.2.2 内核提供的rtc底层注册与注销函数
1. RTC框架注册函数
struct rtc_device *rtc_device_register( const char *name, //RTC时钟名称 struct device *dev, //设备指针。该指针需要需要通过平台设备获取。 const struct rtc_class_ops *ops, //rtc文件操作集合 struct module *owner) //驱动所有者。填: THIS_MODULE |
使用示例: rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
使用rtc_device_register函数注册成功之后,在/dev/下可以看到rtcx的设备节点(x是rtc的顺序编号)。
2. RTC框架注销函数
void rtc_device_unregister(struct rtc_device *rtc) |
经过RTC注册函数形参分析,RTC子系统的注册需要通过平台设备框架完成,在平台设备的驱动端的probe函数里进行rtc注册,remove函数里进行注销,在rtc设备端向驱动端传递RTC硬件需要的一些信息。
1.2.3 文件操作集合接口
rtc_class_ops 这个结构是RTC驱动程序要实现的基本操作函数。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。这个驱动接口与应用层的hwclock命令关联在一起,可以通过hwclock命令调用底层RTC这些函数。
struct rtc_class_ops { int (*open)(struct device *); //打开 void (*release)(struct device *); int (*ioctl)(struct device *, unsigned int, unsigned long); /*ioctl函数*/ int (*read_time)(struct device *, struct rtc_time *); //读取时间 int (*set_time)(struct device *, struct rtc_time *); //设置时间 int (*read_alarm)(struct device *, struct rtc_wkalrm *); //读取闹钟 int (*set_alarm)(struct device *, struct rtc_wkalrm *); //设置闹钟 int (*proc)(struct device *, struct seq_file *); //proc接口 int (*set_mmss)(struct device *, unsigned long secs); //设置秒单位 int (*read_callback)(struct device *, int data); //回调函数 int (*alarm_irq_enable)(struct device *, unsigned int enabled); //闹钟中断使能 }; |
RTC子系统里驱动一般只需要实现设置时间和获取时间的函数接口即可,用户可以在应用层通过ioctl函数传入对应的命令调用驱动层的接口,实现时间获取与设置。
常用的两个命令:
#define RTC_RD_TIME _IOR(RTC_MAGIC, 0x09, struct rtc_time) /* Read RTC time. */ #define RTC_SET_TIME _IOW(RTC_MAGIC, 0x0a, struct rtc_time) /* Set RTC time. */ //支持的全部命令 在interface.c文件中有使用范例。 这些命令在用户自己写应用层代码时可以用到 RTC_ALM_READ rtc_read_alarm 读取闹钟时间 RTC_ALM_SET rtc_set_alarm 设置闹钟时间 RTC_RD_TIME rtc_read_time 读取时间与日期 RTC_SET_TIME rtc_set_time 设置时间与日期 RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 开关RTC全局中断的函数 RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC闹钟中断 RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中断 RTC_IRQP_SET rtc_irq_set_freq 设置中断的频率 |
1.2.4 RTC时间结构
rtc_time代表了RTC记录的时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中。
struct rtc_time { int tm_sec; //秒 int tm_min; //分钟 int tm_hour; //小时 int tm_mday; //天 int tm_mon; //月 int tm_year; //年 int tm_wday; //一周中的某一天 int tm_yday; //一年中的某一天 int tm_isdst; //夏令时有效 }; |
1.2.5 闹钟结构
struct rtc_wkalrm { unsigned char enabled; /* 闹钟使能开关 0 = alarm disabled, 1 = alarm enabled */ unsigned char pending; /* 闹钟信号处理状态 0 = alarm not pending 未产生, 1 = alarm pending 产生了闹钟信号*/ struct rtc_time time; /* 闹钟设置的时间 */ }; |
1.3 编写RTC驱动代码
1.3.1 准备工作
要测试自己的编写的RTC驱动,提前需要将内核自带的RTC驱动先去除掉,再重新编译烧写内核,再安装测试。
以tiny4412开发板为例,去除掉自带的rtc驱动。
1. 进入到内核配置菜单: make menuconfig
Device Drivers --->
[*] Real Time Clock --->
2. 重新编译内核,再重新烧写内核到SD或者EMMC:
[root@wbyq boot]# ./123.sh 记录了9288+1 的读入 记录了9288+1 的写出 4755752字节(4.8 MB)已复制,33.2798 秒,143 kB/秒 |
默认没有RTC驱动的情况下,获取系统时间是从1970年开始的:
1.3.2 RTC驱动代码编写—框架示例
以下代码只是演示了RTC驱动的注册框架。
1. RTC设备端代码:
#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
* device 设备端
*/
//释放平台总线
static void pdev_release(struct device *dev)
{
printk("rtc_pdev:the rtc_pdev is close!!!\\n");
}
/*设备端结构体*/
struct platform_device rtc_pdev= /*设备结构体,设备名字很重要!*/
{
.name = "tiny4412rtc", /*设备名*/
.id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
.dev = /*驱动卸载时调用*/
{
.release = pdev_release,/*释放资源*/
},
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
platform_device_register(&rtc_pdev);/*注册平台设备端*/
return 0;
}
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");
2. RTC驱动端代码
#include <linux/module.h> /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h> /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h> /*中断相关头文件*/
#include <linux/irq.h> /*中断相关头文件*/
#include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h> /*内核定时器*/
#include <asm-generic/poll.h>
#include <linux/poll.h> /* poll机制*/
#include <linux/platform_device.h> /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
printk("获取时间成功\\n");
return 0;
}
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
printk("设置时间成功\\n");
return 0;
}
static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
printk("getalarm调用成功\\n");
return 0;
}
static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
printk("getalarm调用成功\\n");
return 0;
}
static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq)
{
printk("proc调用成功\\n");
return 0;
}
static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
printk("alarm_irq_enable调用成功\\n");
return 0;
}
static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
printk("ioctl调用成功\\n");
return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops tiny4412_rtcops = {
.read_time = tiny4412_rtc_gettime,
.set_time = tiny4412_rtc_settime,
.read_alarm = tiny4412_rtc_getalarm,
.set_alarm = tiny4412_rtc_setalarm,
.proc = tiny4412_rtc_proc,
.alarm_irq_enable = tiny4412_rtc_alarm_irq_enable,
.ioctl = tiny4412_rtc_ioctl,
};
struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{
rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
if(rtc==NULL)
printk("RTC驱动注册失败\\n");
else
{
printk("RTC驱动注册成功\\n");
}
return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
rtc_device_unregister(rtc);
printk("RTC驱动卸载成功\\n");
return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver drv=
{
.probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/
.remove = drv_remove, /*创建一个remove函数,用于设备退出*/
.driver =
{
.name = "tiny4412rtc", /*设备名称,用来与设备端匹配(非常重要)*/
},
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
platform_driver_register(&drv);/*注册平台驱动*/
return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init); /*驱动模块的入口*/
module_exit(plat_drv_exit); /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/
3. 安装RTC驱动
4. 查看生成的RTC的设备节点
5. 查看rtc信息
查看/proc/driver/rtc文件时,底层驱动函数接口也相继被调用,只不过刚才写的RTC驱动没有完善,所以获取的信息不正确,是默认值。
6. 设置RTC时间相关的命令测试
通过命令测试,设置时间和获取时间都调用了底层的RTC函数接口,剩下的工作就是完善驱动代码了。
1.3.3 完善RTC驱动
上一步完成了RTC驱动代码框架编写,这一步就先不添加RTC硬件代码,使用软件方式模拟时间传递给应用层。
注意: 内核里RTC时间换算的时间是从: 1900年开始计算的,月份是从0开始的。
在给rtc结构赋值时,在正常的年份上需要减去1900,月份再减去1
赋值示例:
//此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
rtc_tm->tm_year=2018-1900; //年
rtc_tm->tm_mon=8-1; //月
rtc_tm->tm_mday=18; //日
rtc_tm->tm_hour=18; //时
rtc_tm->tm_min=18; //分
rtc_tm->tm_sec=18;//秒
printk("从RTC底层获取时间成功!\\n");
return 0;
}
应用层获取的时间如下:
完善过后的RTC设备驱动端代码
#include <linux/module.h> /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h> /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h> /*中断相关头文件*/
#include <linux/irq.h> /*中断相关头文件*/
#include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h> /*内核定时器*/
#include <asm-generic/poll.h>
#include <linux/poll.h> /* poll机制*/
#include <linux/platform_device.h> /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
//此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
rtc_tm->tm_year=2018-1900; //年
rtc_tm->tm_mon=8-1; //月
rtc_tm->tm_mday=18; //日
rtc_tm->tm_hour=18; //时
rtc_tm->tm_min=18; //分
rtc_tm->tm_sec=18;//秒
printk("从RTC底层获取时间成功!\\n");
return 0;
}
//此函数可以通过应用层的ioctl的RTC_SET_TIME命令进行调用
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
printk("RTC收到的时间为:%d-%d-%d %d-%d-%d\\n",1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
return 0;
}
//获取闹钟时间
static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
alrm->enabled=0; //默认闹钟处于关闭状态
alrm->time.tm_year=2018-1900; //年
alrm->time.tm_mon=8-1; //月
alrm->time.tm_mday=18; //日
alrm->time.tm_hour=18; //时
alrm->time.tm_min=18; //分
alrm->time.tm_sec=18; //秒
printk("从RTC底层获取闹钟时间成功!\\n");
return 0;
}
//设置闹钟时间
static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
printk("RTC闹钟设置成功\\n");
return 0;
}
//proc接口调用
static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq)
{
printk("proc调用成功\\n");
return 0;
}
//闹钟中断使能
static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
printk("alarm_irq_enable调用成功\\n");
return 0;
}
//可以实现用户自定义的命令
static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
printk("ioctl调用成功\\n");
return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops tiny4412_rtcops = {
.read_time = tiny4412_rtc_gettime,
.set_time = tiny4412_rtc_settime,
.read_alarm = tiny4412_rtc_getalarm,
.set_alarm = tiny4412_rtc_setalarm,
.proc = tiny4412_rtc_proc,
.alarm_irq_enable = tiny4412_rtc_alarm_irq_enable,
.ioctl = tiny4412_rtc_ioctl,
};
struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{
rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
if(rtc==NULL)
printk("RTC驱动注册失败\\n");
else
{
printk("RTC驱动注册成功\\n");
}
return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
rtc_device_unregister(rtc);
printk("RTC驱动卸载成功\\n");
return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver drv=
{
.probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/
.remove = drv_remove, /*创建一个remove函数,用于设备退出*/
.driver =
{
.name = "tiny4412rtc", /*设备名称,用来与设备端匹配(非常重要)*/
},
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
platform_driver_register(&drv);/*注册平台驱动*/
return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init); /*驱动模块的入口*/
module_exit(plat_drv_exit); /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/
安装测试结果:
[root@XiaoLong /code]# date
Thu Jan 1 00:00:13 UTC 1970
[root@XiaoLong /code]# insmod plat_rtc_device.ko
[root@XiaoLong /code]# insmod plat_rtc_drver.ko
[ 24.350000] 从RTC底层获取时间成功!
[ 24.350000] 从RTC底层获取闹钟时间成功!
[ 24.350000] 从RTC底层获取时间成功!
[ 24.350000] tiny4412rtc tiny4412rtc: rtc core: registered tiny4412_rtc as rtc0
[ 24.350000] RTC驱动注册成功
[root@XiaoLong /code]# cat /proc/driver/rtc
[ 37.085000] 从RTC底层获取时间成功!
[ 37.085000] proc调用成功
rtc_time : 18:18:18
rtc_date : 2018-08-18
alrm_time : 18:18:18
alrm_date : 2018-08-18
alarm_IRQ : no
alrm_pending : no
update IRQ enabled : no
periodic IRQ enabled : no
periodic IRQ frequency : 1
max user IRQ frequency : 64
24hr : yes
[root@XiaoLong /code]#
[root@XiaoLong /code]# hwclock -s
[ 58.600000] 从RTC底层获取时间成功!
[root@XiaoLong /code]# hwclock -r
[ 61.020000] 从RTC底层获取时间成功!
Sat Aug 18 18:18:18 2018 0.000000 seconds
[root@XiaoLong /code]# hwclock -w
[ 62.920000] RTC收到的时间为:2018-7-18 18-18-22
[ 62.920000] 从RTC底层获取时间成功!
[ 62.920000] alarm_irq_enable调用成功
[root@XiaoLong /code]# date
Sat Aug 18 18:18:24 UTC 2018
1.3.4 RTC应用层代码
应用层想要与RTC驱动交互,可以使用ioctl函数特定的一些命令进行。
RTC子系统常用的ioctl命令如下:
//支持的全部命令 在interface.c文件中有使用范例。 这些命令在用户自己写应用层代码时可以用到
RTC_ALM_READ rtc_read_alarm 读取闹钟时间
RTC_ALM_SET rtc_set_alarm 设置闹钟时间
RTC_RD_TIME rtc_read_time 读取时间与日期
RTC_SET_TIME rtc_set_time 设置时间与日期
RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 开关RTC全局中断的函数
RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC闹钟中断
RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中断
RTC_IRQP_SET rtc_irq_set_freq 设置中断的频率
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
struct rtc_time time; //保存时间值
int main(int argc,char **argv)
{
if(argc!=2)
{
printf("传参格式:/dev/rtc\\r\\n");
return;
}
int fd=open(argv[1],O_RDWR); // 2==O_RDWR
if(fd<0)
{
printf("驱动设备文件打开失败!\\r\\n");
return 0;
}
time.tm_year=2017;
time.tm_mon=10;
time.tm_mday=13;
time.tm_hour=21;
time.tm_min=10;
time.tm_sec=10;
//注意:年月日必须填写正常,否则会导致底层函数无法调用成功
ioctl(fd,RTC_SET_TIME,&time); //底层自己实现了ioctl函数,设置RTC时间
while(1)
{
ioctl(fd,RTC_RD_TIME,&time);
printf("%d-%d-%d %d:%d:%d\\r\\n",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec);
sleep(1);
}
}
1.3.5 标准时间到秒单位时间转换函数
硬件上有些RTC实时时钟设置只计算秒数,不提供年月日时分秒格式的时间设置,这时候就需要自己对标准时间进行转换。
将标准时间转为秒单位时间:
/*
* 自01-01-1970就是将公历日期转换为秒。
*/
int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time)
{
*time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
return 0;
}
1.3.6 DS1302时钟芯片驱动编写示例
上面代码都是模拟时钟,学习RTC框架的用法,下面的的代码就加入了实际的RTC硬件,实现完整的RTC计时。
DS1302驱动端代码:
#include <linux/module.h> /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h> /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h> /*中断相关头文件*/
#include <linux/irq.h> /*中断相关头文件*/
#include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h> /*内核定时器*/
#include <asm-generic/poll.h>
#include <linux/poll.h> /* poll机制*/
#include <linux/platform_device.h> /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
/*--------------------------------DS1302相关操作代码---------------------------------------------*/
static unsigned char RTC_bin2bcd(unsigned val)
{
return ((val/10)<<4)+val%10;
}
static unsigned RTC_bcd2bin(unsigned char val)
{
return (val&0x0f)+(val>>4)*10;
}
/*
函数功能:DS1302初始化
Tiny4412硬件连接:
CLK :GPB_4
DAT :GPB_5
RST :GPB_6
*/
void DS1302IO_Init(void)
{
/*1. 注册GPIO*/
gpio_request(EXYNOS4_GPB(4), "DS1302_CLK");
gpio_request(EXYNOS4_GPB(5), "DS1302_DAT");
gpio_request(EXYNOS4_GPB(6), "DS1302_RST");
/*2. 配置GPIO口模式*/
s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT); //时钟
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //数据
// s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_INPUT); //输入模式
s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_OUTPUT); //复位
/*3. 上拉GPIO口*/
gpio_set_value(EXYNOS4_GPB(4), 1); //CLK
gpio_set_value(EXYNOS4_GPB(5), 1); //DAT
gpio_set_value(EXYNOS4_GPB(6), 1); //RST
gpio_set_value(EXYNOS4_GPB(6), 0); //RST脚置低
gpio_set_value(EXYNOS4_GPB(4), 0); //SCK脚置低
}
//#define RTC_CMD_READ 0x81 /* Read command */
//#define RTC_CMD_WRITE 0x80 /* Write command */
//#define RTC_ADDR_RAM0 0x20 /* Address of RAM0 */
//#define RTC_ADDR_TCR 0x08 /* Address of trickle charge register */
//#define RTC_ADDR_YEAR 0x06 /* Address of year register */
//#define RTC_ADDR_DAY 0x05 /* Address of day of week register */
//#define RTC_ADDR_MON 0x04 /* Address of month register */
//#define RTC_ADDR_DATE 0x03 /* Address of day of month register */
//#define RTC_ADDR_HOUR 0x02 /* Address of hour register */
//#define RTC_ADDR_MIN 0x01 /* Address of minute register */
//#define RTC_ADDR_SEC 0x00 /* Address of second register */
//DS1302地址定义
#define ds1302_sec_add 0x80 //秒数据地址
#define ds1302_min_add 0x82 //分数据地址
#define ds1302_hr_add 0x84 //时数据地址
#define ds1302_date_add 0x86 //日数据地址
#define ds1302_month_add 0x88 //月数据地址
#define ds1302_day_add 0x8a //星期数据地址
#define ds1302_year_add 0x8c //年数据地址
#define ds1302_control_add 0x8e //控制数据地址
#define ds1302_charger_add 0x90
#define ds1302_clkburst_add 0xbe
//初始时间定义
static unsigned char time_buf[8] = {0x20,0x10,0x06,0x01,0x23,0x59,0x55,0x02};//初始时间2010年6月1号23点59分55秒 星期二
static unsigned char readtime[14];//当前时间
static unsigned char sec_buf=0; //秒缓存
static unsigned char sec_flag=0; //秒标志位
//向DS1302写入一字节数据
static void ds1302_write_byte(unsigned char addr, unsigned char d)
{
unsigned char i;
gpio_set_value(EXYNOS4_GPB(6), 1); //启动DS1302总线
//写入目标地址:addr
addr = addr & 0xFE; //最低位置零,寄存器0位为0时写,为1时读
for(i=0;i<8;i++)
{
if(addr&0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
else{gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟
gpio_set_value(EXYNOS4_GPB(4), 0);
addr=addr >> 1;
}
//写入数据:d
for(i=0;i<8;i++)
{
if(d & 0x01) {gpio_set_value(EXYNOS4_GPB(5), 1);}
else {gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟
gpio_set_value(EXYNOS4_GPB(4), 0);
d = d >> 1;
}
gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线
}
//从DS1302读出一字节数据
static unsigned char ds1302_read_byte(unsigned char addr)
{
unsigned char i,temp;
gpio_set_value(EXYNOS4_GPB(6), 1);//启动DS1302总线
//写入目标地址:addr
addr=addr | 0x01; //最低位置高,寄存器0位为0时写,为1时读
for(i=0; i<8; i++)
{
if(addr & 0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
else {gpio_set_value(EXYNOS4_GPB(5), 0);}
gpio_set_value(EXYNOS4_GPB(4), 1);
gpio_set_value(EXYNOS4_GPB(4), 0);
addr=addr >> 1;
}
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_INPUT); //输入模式
//输出数据:temp
for(i=0; i<8; i++)
{
temp=temp>>1;
if(gpio_get_value(EXYNOS4_GPB(5))){temp |= 0x80;}
else{temp&=0x7F;}
gpio_set_value(EXYNOS4_GPB(4), 1);
gpio_set_value(EXYNOS4_GPB(4), 0);
}
s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //输出模式
gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线
return temp;
}
//向DS302写入时钟数据
static void ds1302_write_time(struct rtc_time *time)
{
ds1302_write_byte(ds1302_control_add,0x00); //关闭写保护
ds1302_write_byte(ds1302_sec_add,0x80); //暂停时钟
//ds1302_write_byte(ds1302_charger_add,0xa9); //涓流充电
/*设置RTC时间*/
//因为DS1302的年份只能设置后两位,所有需要使用正常的年份减去2000,得到实际的后两位
ds1302_write_byte(ds1302_year_add,RTC_bin2bcd(time->tm_year-2000)); //年
ds1302_write_byte(ds1302_month_add,RTC_bin2bcd(time->tm_mon)); //月
ds1302_write_byte(ds1302_date_add,RTC_bin2bcd(time->tm_mday)); //日
ds1302_write_byte(ds1302_hr_add,RTC_bin2bcd(time->tm_hour)); //时
ds1302_write_byte(ds1302_min_add,RTC_bin2bcd(time->tm_min)); //分
ds1302_write_byte(ds1302_sec_add,RTC_bin2bcd(time->tm_sec)); //秒
//ds1302_write_byte(ds1302_day_add,RTC_bin2bcd(time->tm_wday)); //周 time->tm_wday一周中的某一天
ds1302_write_byte(ds1302_control_add,0x80); //打开写保护
}
static int DS1302_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
/*设置RTC时间*/
struct rtc_time time;
copy_from_user(&time,(const void __user *)arg,sizeof(struct rtc_time));
ds1302_write_time(&time);
return 0;
}
//此函数通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
rtc_tm->tm_year=RTC_bcd2bin(ds1302_read_byte(ds1302_year_add))+2000; //年
rtc_tm->tm_mon=RTC_bcd2bin(ds1302_read_byte(ds1302_month_add)); //月
rtc_tm->tm_mday=RTC_bcd2bin(ds1302_read_byte(ds1302_date_add)); //日
rtc_tm->tm_hour=RTC_bcd2bin(ds1302_read_byte(ds1302_hr_add)); //时
rtc_tm->tm_min=RTC_bcd2bin(ds1302_read_byte(ds1302_min_add)); //分
rtc_tm->tm_sec=RTC_bcd2bin((ds1302_read_byte(ds1302_sec_add))&0x7f);//秒,屏蔽秒的第7位,避免超出59
//time_buf[7]=ds1302_read_byte(ds1302_day_add); //周
return 0;
}
//此函数通过应用层的ioctl的RTC_SET_TIME命令进行调用
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
ds1302_write_time(tm);
return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops DS1302_rtcops = {
.ioctl=DS1302_rtc_ioctl,
.read_time = tiny4412_rtc_gettime,
.set_time = tiny4412_rtc_settime
};
static struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{
rtc = rtc_device_register("DS1302RTC",&pdev->dev, &DS1302_rtcops,THIS_MODULE);
if(rtc==NULL)
printk("RTC驱动注册失败1\\n");
else
{
printk("RTC驱动注册成功1\\n");
}
/*1. 初始化GPIO口*/
DS1302IO_Init();
msleep(10);
return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
/*释放GPIO口*/
gpio_free(EXYNOS4_GPB(4));
gpio_free(EXYNOS4_GPB(5));
gpio_free(EXYNOS4_GPB(6));
rtc_device_unregister(rtc);
printk("RTC驱动卸载成功\\n");
return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver drv=
{
.probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/
.remove = drv_remove, /*创建一个remove函数,用于设备退出*/
.driver =
{
.name = "DS1302rtc", /*设备名称,用来与设备端匹配(非常重要)*/
},
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
platform_driver_register(&drv);/*注册平台驱动*/
return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init); /*驱动模块的入口*/
module_exit(plat_drv_exit); /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/
DS1320设备端代码
#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
* device 设备端
*/
//释放平台总线
static void pdev_release(struct device *dev)
{
printk("rtc_pdev:the rtc_pdev is close!!!\\n");
}
/*设备端结构体*/
struct platform_device rtc_pdev= /*设备结构体,设备名字很重要!*/
{
.name = "DS1302rtc", /*设备名*/
.id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
.dev = /*驱动卸载时调用*/
{
.release = pdev_release,/*释放资源*/
},
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
platform_device_register(&rtc_pdev);/*注册平台设备端*/
return 0;
}
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");
以上是关于Linux驱动开发: Linux下RTC实时时钟驱动的主要内容,如果未能解决你的问题,请参考以下文章
RK3399驱动开发 | 15 - RTC实时时钟芯片HYM8563S调试(基于linux5.4.32内核)