Linux RTC 开发指南
1 概述
1.1 编写目的
介绍Linux 内核中RTC 驱动的适配和DEBUG 方法,为RTC 设备的使用者和维护者提供参考。
1.2 适用范围
内核版本 | 驱动文件 |
---|---|
LINUX-4.9 及以上 | RTC-SUNXI.C |
1.3 相关人员
RTC 驱动及应用层的开发/维护人员。
2 模块介绍
Linux 内核中,RTC 驱动的结构图如下所示, 可以分为三个层次:
接口层,负责向用户空间提供操作的结点以及相关接口。 • RTC Core, 为rtc 驱动提供了一套API, 完成设备和驱动的注册等。 • RTC 驱动层,负责具体的RTC 驱动实现,如设置时间、闹钟等设置寄存器的操作。
2.2 相关术语介绍
术语 | 解释说明 |
---|---|
Sunxi | 指Allwinner 的一系列SoC 硬件平台 |
RTC | Real Time Clock,实时时钟 |
2.3 源码结构介绍
linux-4.9
└-- drivers
└-- rtc
|-- class.c
|-- hctosys.c
|-- interface.c
|-- rtc-dev.c
|-- rtc-lib.c
|-- rtc-proc.c
|-- rtc-sysfs.c
|-- systohc.c
|-- rtc-core.h
|-- rtc-sunxi.c
└-- rtc-sunxi.h
linux-5.4
└-- drivers
└-- rtc
|-- class.c
|-- hctosys.c
|-- interface.c
|-- dev.c
|-- lib.c
|-- proc.c
|-- sysfs.c
|-- systohc.c
|-- rtc-core.h
|-- rtc-sunxi.c
└-- rtc-sunxi.h
3 模块配置介绍
3.1 kernel menuconfig 配置
3.1.1 linux-4.9 版本下
在命令行中进入内核根目录(kernel/linux-4.9),执行make ARCH=arm64(arm) menuconfig(32 位系统为make ARCH=arm menuconfig) 进入配置主界面(linux-5.4 内核版本在longan 目录下执行:./build.sh menuconfig 进入配置主界面),并按以下步骤操作: 首先,选择Device Drivers 选项进入下一级配置,如下图所示:
选择Real Time Clock,进入下级配置,如下图所示:
选择Allwinner sunxi RTC,如下图所示:
由于在关机过程中,RTC 一般都是独立供电的,因此在RTC 电源域中的寄存器不会掉电且RTC寄存器的值也不会恢复为默认值。利用此特性,Sunxi 平台支持reboot 命令的一些扩展功能和 假关机功能,但需要打开support ir fake poweroff 和Sunxi rtc reboot Feature 选项,RTC驱动才能支持这些扩展功能。
3.1.2 linux-5.4 版本下
在命令行中进入longan 顶层目录,执行./build.sh config,按照提示配置平台、板型等信息(如果之前已经配置过,可跳过此步骤)。 然后执行./build.sh menuconfig,进入内核图形化配置界面,并按以下步骤操作: 选择Device Driver选项进入下一级配置,如下图所示:
选择Real Time Clock进入下一级配置,如下图所示:
选择Allwinner sunxi RTC配置,如下图所示。
由于在关机过程中,RTC 一般都是独立供电的,因此在RTC 电源域中的寄存器不会掉电且RTC寄存器的值也不会恢复为默认值。利用此特性,Sunxi 平台支持reboot 命令的一些扩展功能,但需要打开Sunxi rtc reboot flag和Sunxi rtc general register save bootcount选项,RTC 驱动才能支持这些扩展功能。
3.2 device tree 源码结构和路径
SoC 级设备树文件(sun*.dtsi)是针对该SoC 所有方案的通用配置:
• 对于ARM64 CPU 而言,SoC 级设备树的路径为:arch/arm64/boot/dts/sunxi/sun*.dtsi
• 对于ARM32 CPU 而言,SoC 级设备树的路径为:arch/arm/boot/dts/sun*.dtsi
板级设备树文件(board.dts)是针对该板型的专用配置:
• 板级设备树路径:device/config/chips/{IC}/configs/{BOARD}/board.dts
板级设备树文件(board.dts)是针对该板型的专用配置: • 板级设备树路径:device/config/chips/{IC}/configs/{BOARD}/board.dts
3.2.1 linux-4.9 版本下
device tree 的源码结构关系如下:
board.dts
└--------sun*.dtsi
|------sun*-pinctrl.dtsi
└------sun*-clk.dtsi
3.2.2 linux-5.4 版本下
device tree 的源码结构关系如下:
board.dts
└--------sun*.dtsi
3.3 device tree 对RTC 控制器的通用配置
3.3.1 linux-4.9 版本下
1 / {
2 rtc: rtc@07000000 {
3 compatible = "allwinner,sunxi-rtc"; //用于probe驱动
4 device_type = "rtc";
5 auto_switch; //支持RTC使用的32k时钟源硬件自动切换
6 wakeup-source; //表示RTC是具备休眠唤醒能力的中断唤醒源
7 reg = <0x0 0x07000000 0x0 0x200>; //RTC寄存器基地址和映射范围
8 interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; //RTC硬件中断号
9 gpr_offset = <0x100>; //RTC通用寄存器的偏移
10 gpr_len = <8>; //RTC通用寄存器的个数
11 gpr_cur_pos = <6>;
12 };
13 }
说明 对于linux-4.9 内核,当RTC 结点下配置auto_switch 属性时,RTC 硬件会自动扫描检查外部32k 晶体振荡器的起振情 况。当外部晶体振荡器工作异常时,RTC 硬件会自动切换到内部RC16M 时钟分频出来的32k 时钟,从而保证RTC 工作正 常。当没有配置该属性时,驱动代码中直接把RTC 时钟源设置为外部32k 晶体的,当外部32K 晶体工作异常时,RTC 会工 作异常。因此建议配置上该属性。
3.3.2 linux-5.4 版本下
1 / {
2 rtc: rtc@7000000 {
3 compatible = "allwinner,sun50iw10p1-rtc"; //用于probe驱动
4 device_type = "rtc";
5 wakeup-source; //表示RTC是具备休眠唤醒能力的中断唤醒源
6 reg = <0x0 0x07000000 0x0 0x200>; //RTC寄存器基地址和映射范围
7 interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>; //RTC硬件中断号
8 clocks = <&r_ccu CLK_R_AHB_BUS_RTC>, <&rtc_ccu CLK_RTC_1K>; //RTC所用到的时钟
9 clock-names = "r-ahb-rtc", "rtc-1k"; //上述时钟的名字
10 resets = <&r_ccu RST_R_AHB_BUS_RTC>;
11 gpr_cur_pos = <6>; //当前被用作reboot-flag的通用寄存器的序号
12 };
13 }
在Device Tree 中对每一个RTC 控制器进行配置, 一个RTC 控制器对应一个RTC 节点, 节点属性的含义见注释。
3.4 board.dts 板级配置
board.dts用于保存每个板级平台的设备信息(如demo 板、demo2.0 板等等)。board.dts路径如下:
device/config/chips/{IC}/configs/{BOARD}/boar d.dts
在board.dts中的配置信息如果在*.dtsi(如sun50iw9p1.dtsi等) 中存在,则会存在以下覆盖规则:
在board.dts中的配置信息如果在*.dtsi(如sun50iw9p1.dtsi等) 中存在,则会存在以下覆盖规则:
- 相同属性和结点,board.dts的配置信息会覆盖*.dtsi中的配置信息
- 新增加的属性和结点,会添加到编译生成的dtb 文件中
4 接口描述
RTC 驱动会注册生成串口设备/dev/rtcN,应用层的使用只需遵循Linux 系统中的标准RTC 编程方法即可。
4.1 打开/关闭RTC 设备
使用标准的文件打开函数:
1 int open(const char *pathname, int flags);
2 int close(int fd);
需要引用头文件:
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <unistd.h>
4.2 设置和获取RTC 时间
同样使用标准的ioctl 函数:
1 int ioctl(int d, int request, ...);
需要引用头文件:
1 #include <sys/ioctl.h>
2 #include <linux/rtc.h>
5 模块使用范例
此demo 程序是打开一个RTC 设备,然后设置和获取RTC 时间以及设置闹钟功能。
1 #include <stdio.h> /*标准输入输出定义*/
2 #include <stdlib.h> /*标准函数库定义*/
3 #include <unistd.h> /*Unix 标准函数定义*/
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h> /*文件控制定义*/
7 #include <linux/rtc.h> /*RTC支持的CMD*/
8 #include <errno.h> /*错误号定义*/
9 #include <string.h>
10
11 #define RTC_DEVICE_NAME "/dev/rtc0"
12
13 int set_rtc_timer(int fd)
14 {
15 struct rtc_time rtc_tm = {0};
16 struct rtc_time rtc_tm_temp = {0};
17
18 rtc_tm.tm_year = 2020 - 1900; /* 需要设置的年份,需要减1900 */
19 rtc_tm.tm_mon = 11 - 1; /* 需要设置的月份,需要确保在0-11范围*/
20 rtc_tm.tm_mday = 21; /* 需要设置的日期*/
21 rtc_tm.tm_hour = 10; /* 需要设置的时间*/
22 rtc_tm.tm_min = 12; /* 需要设置的分钟时间*/
23 rtc_tm.tm_sec = 30; /* 需要设置的秒数*/
24
25 /* 设置RTC时间*/
26 if (ioctl(fd, RTC_SET_TIME, &rtc_tm) < 0) {
27 printf("RTC_SET_TIME failed\n");
28 return -1;
29 }
30
31 /* 获取RTC时间*/
32 if (ioctl(fd, RTC_RD_TIME, &rtc_tm_temp) < 0) {
33 printf("RTC_RD_TIME failed\n");
34 return -1;
35 }
36 printf("RTC_RD_TIME return %04d-%02d-%02d %02d:%02d:%02d\n",
37 rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,
38 rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);
39 return 0;
40 }
41
42 int set_rtc_alarm(int fd)
43 {
44 struct rtc_time rtc_tm = {0};
45 struct rtc_time rtc_tm_temp = {0};
46
47 rtc_tm.tm_year = 0; /* 闹钟忽略年设置*/
48 rtc_tm.tm_mon = 0; /* 闹钟忽略月设置*/
49 rtc_tm.tm_mday = 0; /* 闹钟忽略日期设置*/
50 rtc_tm.tm_hour = 10; /* 需要设置的时间*/
51 rtc_tm.tm_min = 12; /* 需要设置的分钟时间*/
52 rtc_tm.tm_sec = 30; /* 需要设置的秒数*/
53
54 /* set alarm time */
55 if (ioctl(fd, RTC_ALM_SET, &rtc_tm) < 0) {
56 printf("RTC_ALM_SET failed\n");
57 return -1;
58 }
59
60 if (ioctl(fd, RTC_AIE_ON) < 0) {
61 printf("RTC_AIE_ON failed!\n");
62 return -1;
63 }
64
65 if (ioctl(fd, RTC_ALM_READ, &rtc_tm_temp) < 0) {
66 printf("RTC_ALM_READ failed\n");
67 return -1;
68 }
69
70 printf("RTC_ALM_READ return %04d-%02d-%02d %02d:%02d:%02d\n",
71 rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,
72 rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);
73 return 0;
74 }
75
76 int main(int argc, char *argv[])
77 {
78 int fd;
79 int ret;
80
81 /* open rtc device */
82 fd = open(RTC_DEVICE_NAME, O_RDWR);
83 if (fd < 0) {
84 printf("open rtc device %s failed\n", RTC_DEVICE_NAME);
85 return -ENODEV;
86 }
87
88 /* 设置RTC时间*/
89 ret = set_rtc_timer(fd);
90 if (ret < 0) {
91 printf("set rtc timer error\n");
92 return -EINVAL;
93 }
94
95 /* 设置闹钟*/
96 ret = set_rtc_alarm(fd);
97 if (ret < 0) {
98 printf("set rtc alarm error\n");
99 return -EINVAL;
100 }
101
102 close(fd);
103 return 0;
104 }
6 FAQ
6.1 RTC 时间不准
- 按照下图RTC 时钟源的路径,确认一下RTC 所使用的时钟源
- 如果确认使用的时钟源为RC16M,则确认一下有没有启用校准功能,因为RC16M 有正负50% 的偏差。
- 如果使用外部晶体,则确认一下外部晶体的震荡频率是否正确。
6.2 RTC 时间不走
- 请查看RTC 时钟源图,确认一下使用的时钟源。
- 当RTC 时钟源为外部32K 时,请确认一下外部32k 晶体的起振情况。
说明:当使用示波器测量外部32k 晶体起振情况时,有可能会导致32k 晶体起振。 3. 当排查完时钟源,确认时钟源没有问题后,通过以下命令dump rtc 相关寄存器,查看偏移0x0 寄存器的状态位bit7 和bit8 是否异常置1 了,如下所示:
/ # echo 0x07000000,0x07000200 > /sys/class/sunxi_dump/dump; cat /sys/class/sunxi_dump/dump
0x0000000007000000: 0x00004010 0x00000004 0x0000000f 0x7a000000
0x0000000007000010: 0x00000001 0x00000023 0x00000000 0x00000000
0x0000000007000020: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000030: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000040: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000050: 0x00000001 0x00000000 0x00000000 0x00000000
0x0000000007000060: 0x00000004 0x00000000 0x00000000 0x00000000
0x0000000007000070: 0x00010003 0x00000000 0x00000000 0x00000000
0x0000000007000080: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000090: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070000f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000100: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000110: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000120: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000130: 0x00000000 0x000030ea 0x04001000 0x00006061
0x0000000007000140: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000150: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000160: 0x083f10f7 0x00000043 0x00000000 0x00000000
0x0000000007000170: 0x00000000 0x00000000 0x00000000 0x00000000
0x0000000007000180: 0x00000000 0x00000000 0x00010001 0x00000000
0x0000000007000190: 0x00000004 0x00000000 0x00000000 0x00000000
0x00000000070001a0: 0x000090ff 0x00000000 0x00000000 0x00000000
0x00000000070001b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070001c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070001d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070001e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000070001f0: 0x00000000 0x00000001 0x00000000 0x00000000
0x0000000007000200: 0x10000000
说明:
每款SoC 的模块首地址是不一样的,具体根据spec 或data sheet 确认模块首地址。
标签:指南,0x00000000,RTC,rtc,--,tm,temp,Linux From: https://blog.51cto.com/weidongshan/6095889