1概念:
RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间。RTC因为是电池供电的,所以掉电后时间不丢失。Linux内核把RTC用作“离线”的时间与日期维护器
总结rtc的特性只包含两部分:
- 可以将时间写入和读出,因为里面有独立的电池,所以系统在掉电的时候可以准确的读取出时间。
- 毫秒级别的报警器,可以作为定时中断的发生器,作为手机等产品的关机闹钟。
2 特点
RTC 具备以下特点:以海思hi3516EV200为例:
- 内部具有 1 个 16bit 的天计数器,5bit 的小时计数器,6bit 的分计数器,6bit 的秒计数器和 7bit 的 10ms 计数器。
- 计数时钟 100Hz
- 计数初值可配置
- 计数比较值可配置
- 支持超时中断产生
- 支持软复位
- 支持分频参数可配置
- 64bit 用户寄存器,供用户保存数据
功能描述:
由寄存器 RTC_LR_10MS、RTC_LR_S、RTC_LR_M、RTC_LR_H、RTC_LR_D_L、RTC_LR_D_H 载入。在当计数值递加到寄存器与 RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断,然后在下一个计数时钟上升沿,计数器继续递加计数。
根据实际应用需要,可通过配置 RTC_IMSC 使能或者禁止 RTC 产生中断信号。此时,存在以下两种情况:
(1)当禁止产生中断时,RTC 计数器继续递加计数,将不会对外产生中断,在RTC_MSC_INT 中显示屏蔽后中断的状态,在 RTC_RAW_INT 中显示原始中断状态。
(2)当重新开启中断时,RTC 计数器仍然继续递加计数,当计数值递加到与
RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断。
RTC 的计数时钟采用的是 100Hz 时钟,同时提供 16bit 的天计数,便于通过天计数值转换为具体的年、月、日。
寄存器功能描述:
由于rtc有计时与定时的功能,其寄存器也有计数类的寄存器和定时类寄存器
计数或者定时寄存器的值可通过更新设置寄存器的值,然后设置RTC设置值使能加载寄存器为0x01。如果使能中断,当计数值递加到与RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断。
3 RTC驱动框架
Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化rtc_device,最后将 rtc_device 注册到 Linux 内核里面,这样 Linux 内核就有一个 RTC 设的。include/linux/rtc.h
struct rtc_device
{
struct device dev; /* 设备 */
struct module *owner;
int id; /* ID */
char name[RTC_DEVICE_NAME_SIZE]; /* 名字 */
const struct rtc_class_ops *ops; /* RTC 设备底层操作函数 */
struct mutex ops_lock;
struct cdev char_dev; /* 字符设备 */
unsigned long flags;
......
}
重点关注的是 ops 成员变量,这是一个 rtc_class_ops 类型的指针变量,rtc_class_ops为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。
struct rtc_class_ops {
int (*open)(struct device *);
void (*release)(struct device *);
int (*ioctl)(struct device *, unsigned int, unsigned long);
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 *);
int (*set_mmss64)(struct device *, time64_t secs);
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_class_ops 中的这些函数只是最底层的 RTC 设备操作函数,并不是提供给应用层的file_operations 函数操作集。RTC 是个字符设备,那么肯定有字符设备的 file_operations 函数操作集,Linux 内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-dev.c
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};
hctosys
不得不提到的一个驱动就是hctosys(hardware clock to system),顾名思义是将hw clock中的时间向系统同步,此处hw clock指的就是RTC,调用时机为late_initcall,路径/linux-4.4/drivers/rtc/hctosys.c;
static int __init rtc_hctosys(void)
{
int err = -ENODEV;
struct rtc_time tm;
struct timespec64 tv64 = {
.tv_nsec = NSEC_PER_SEC >> 1,
};
struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
if (rtc == NULL) {
pr_info("unable to open rtc device (%s)\n",
CONFIG_RTC_HCTOSYS_DEVICE);
goto err_open;
}
err = rtc_read_time(rtc, &tm);
if (err) {
dev_err(rtc->dev.parent,
"hctosys: unable to read the hardware clock\n");
goto err_read;
}
tv64.tv_sec = rtc_tm_to_time64(&tm);
err = do_settimeofday64(&tv64);
dev_info(rtc->dev.parent,
"setting system clock to "
"%d-%02d-%02d %02d:%02d:%02d UTC (%lld)\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
(long long) tv64.tv_sec);
err_read:
rtc_class_close(rtc);
err_open:
rtc_hctosys_ret = err;
return err;
}
late_initcall(rtc_hctosys);
1 通过interface中接口rtc_class_open()获取系统中注册好的RTC设备,参数的部分是通过编译config配置得到的
$ grep -nr CONFIG_RTC_HCTOSYS_DEVICE .config
3217:CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
2 成功获取得到RTC设备后,调用rtc_read_time获取RTC时间;
3 将得到的时间进行转换,struct rtc_time tm->struct timespec64,最终通过do_settimeofday64函数将时间同步给系统,也就是上文提到的wall time;
4 打印日志,关闭设备。
4 rtc在文件系统中的呈现
4.1、rtc在sysfs
之前曾建立过名为rtc的class:
rtc_class = class_create(THIS_MODULE, "rtc");
查看之:
# ls /sys/class/rtc/
rtc0
# ls -l /sys/class/rtc/
lrwxrwxrwx root root 2014-01-02 16:51 rtc0 -> ../../devices/ff660000.i2c/i2c-2/2-0051/rtc/rtc0
系统中只有一个RTC,所以编号为rtc0。
同时发现rtc0文件为指向/sys/devices/ff660000.i2c/i2c-2/2-0051/rtc/rtc0的符号链接,RTC芯片是I2C接口(SOC外接rtc),所以rtc0挂载在I2C的总线上,总线控制器地址ff660000,控制器编号为2,RTC芯片作为slave端地址为0x51。
rtc0 设备属性:
void __init rtc_sysfs_init(struct class *rtc_class)
{
rtc_class->dev_attrs = rtc_attrs;
}
static struct device_attribute rtc_attrs[] = {
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
rtc_sysfs_set_max_user_freq),
__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
{ },
};
ls -l /sys/class/rtc/rtc0/
-r--r--r-- root root 4096 2014-01-02 16:51 date
-r--r--r-- root root 4096 2014-01-02 16:51 dev
lrwxrwxrwx root root 2014-01-02 16:51 device -> ../../../2-0051
-r--r--r-- root root 4096 2014-01-02 16:51 hctosys
-rw-r--r-- root root 4096 2014-01-02 16:51 max_user_freq
-r--r--r-- root root 4096 2014-01-02 16:51 name
drwxr-xr-x root root 2014-01-02 16:48 power
-r--r--r-- root root 4096 2014-01-02 16:51 since_epoch
lrwxrwxrwx root root 2014-01-02 16:51 subsystem -> ../../../../../../class/rtc
-r--r--r-- root root 4096 2014-01-02 16:51 time
-rw-r--r-- root root 4096 2014-01-02 16:48 uevent
-rw-r--r-- root root 4096 2014-01-02 16:51 wakealarm
4.2、rtc在proc
之前曾rtc0设备加入到了/proc
rtc_proc_fops
-->rtc_proc_open
-->rtc_proc_show
# cat /proc/driver/rtc
rtc_time : 17:19:53
rtc_date : 2014-01-02
24hr : yes
5 rtc相关命令(date hwclock同clock)
Hwclock同clock命令:
Hwclock –r 显示硬件时钟时间
-w 从系统时间设置硬件时钟
-s 从硬件时钟设置系统时间
date -s # 设置当前时间,只有root权限才能设置,其他只能查看
date -s 20120523 # 设置成20120523,这样会把具体时间设置成00:00:00
date -s 01:01:01 # 设置具体时间,不会对日期做更改
date -s "01:01:01 2012-05-23" # 这样可以设置全部时间
date -s "01:01:01 20120523" # 这样可以设置全部时间
date -s "2012-05-23 01:01:01" # 这样可以设置全部时间
date -s "20120523 01:01:01" # 这样可以设置全部时间
Linux中的date日期命令详解_date linux-优快云博客
6 应用代码案例
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定义*/
#include <linux/rtc.h> /*RTC支持的CMD*/
#include <linux/ioctl.h>
#include <errno.h> /*错误号定义*/
#include <string.h>
/*2023 04 09 qxl*/
#define RTC_DEVICE_NAME "/dev/rtc0"
int set_rtc_timer(int fd)
{
struct rtc_time rtc_tm = {0};
struct rtc_time rtc_tm_temp = {0};
rtc_tm.tm_year = 2020 - 1900; /* 需要设置的年份,需要减1900 */
rtc_tm.tm_mon = 11 - 1; /* 需要设置的月份,需要确保在0-11范围*/
rtc_tm.tm_mday = 21; /* 需要设置的日期*/
rtc_tm.tm_hour = 10; /* 需要设置的时间*/
rtc_tm.tm_min = 12; /* 需要设置的分钟时间*/
rtc_tm.tm_sec = 30; /* 需要设置的秒数*/
/* 设置RTC时间*/
if (ioctl(fd, RTC_SET_TIME, &rtc_tm) < 0) {
printf("RTC_SET_TIME failed\n");
return -1;
}
/* 获取RTC时间*/
if (ioctl(fd, RTC_RD_TIME, &rtc_tm_temp) < 0) {
printf("RTC_RD_TIME failed\n");
return -1;
}
printf("RTC_RD_TIME return %04d-%02d-%02d %02d:%02d:%02d\n",
rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,
rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);
return 0;
}
/* ssd202 rtc没有定时功能 */
#if 0
int set_rtc_alarm(int fd)
{
struct rtc_time rtc_tm = {0};
struct rtc_time rtc_tm_temp = {0};
rtc_tm.tm_year = 0; /* 闹钟忽略年设置*/
rtc_tm.tm_mon = 0; /* 闹钟忽略月设置*/
rtc_tm.tm_mday = 0; /* 闹钟忽略日期设置*/
rtc_tm.tm_hour = 10; /* 需要设置的时间*/
rtc_tm.tm_min = 12; /* 需要设置的分钟时间*/
rtc_tm.tm_sec = 30; /* 需要设置的秒数*/
/* set alarm time */
if (ioctl(fd, RTC_ALM_SET, &rtc_tm) < 0) {
printf("RTC_ALM_SET failed\n");
return -1;
}
if (ioctl(fd, RTC_AIE_ON) < 0) {
printf("RTC_AIE_ON failed!\n");
return -1;
}
if (ioctl(fd, RTC_ALM_READ, &rtc_tm_temp) < 0) {
printf("RTC_ALM_READ failed\n");
return -1;
}
printf("RTC_ALM_READ return %04d-%02d-%02d %02d:%02d:%02d\n",
rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,
rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);
return 0;
}
#endif
int main(int argc, char *argv[])
{
int fd;
int ret;
/* open rtc device */
fd = open(RTC_DEVICE_NAME, O_RDWR);
if (fd < 0) {
printf("open rtc device %s failed\n", RTC_DEVICE_NAME);
return -ENODEV;
}
/* 设置RTC时间*/
ret = set_rtc_timer(fd);
if (ret < 0) {
printf("set rtc timer error\n");
close(fd);
return -EINVAL;
}
#if 0
/* 设置闹钟*/
ret = set_rtc_alarm(fd);
if (ret < 0) {
printf("set rtc alarm error\n");
close(fd);
return -EINVAL;
}
#endif
close(fd);
return 0;
}
文章引用:
https://blog.youkuaiyun.com/u013686019/article/details/57126940
https://blog.youkuaiyun.com/spongebob1912/article/details/111174475
Linux RTC 开发指南 - 知乎 (zhihu.com)
Linux中的date日期命令详解_date linux-优快云博客
正点原子驱动开发指南
海思datesheet