首页 > 行业资讯 > 正文

Linux RTC 开发指南

1 概述

1.1 编写目的

介绍Linux 内核中RTC 驱动的适配和DEBUG 方法,为RTC 设备的使用者和维护者提供参考。

1.2 适用范围

内核版本 驱动文件 LINUX-4.9 及以上 RTC-SUNXI.C

1.3 相关人员

RTC 驱动及应用层的开发/维护人员。

2 模块介绍

Linux 内核中,RTC 驱动的结构图如下所示, 可以分为三个层次:

Linux RTC开发指南-linux中vi编辑器命令

接口层,负责向用户空间提供操作的结点以及相关接口。 • 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 选项进入下一级配置,如下图所示:

Linux RTC开发指南-linux中vi编辑器命令1

选择Real Time Clock,进入下级配置,如下图所示:

Linux RTC开发指南-linux中vi编辑器命令2

选择Allwinner sunxi RTC,如下图所示:

Linux RTC开发指南-linux中vi编辑器命令3

由于在关机过程中,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选项进入下一级配置,如下图所示:

Linux RTC开发指南-linux中vi编辑器命令4

选择Real Time Clock进入下一级配置,如下图所示:

Linux RTC开发指南-linux中vi编辑器命令5

选择Allwinner sunxi RTC配置,如下图所示。

Linux RTC开发指南-linux中vi编辑器命令6

由于在关机过程中,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 = ; //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 = ; //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 2 #include 3 #include 4 #include

4.2 设置和获取RTC 时间

同样使用标准的ioctl 函数:

1 int ioctl(int d, int request, …);

需要引用头文件:

1 #include 2 #include

5 模块使用范例

此demo 程序是打开一个RTC 设备,然后设置和获取RTC 时间以及设置闹钟功能。

1 #include /*标准输入输出定义*/ 2 #include /*标准函数库定义*/ 3 #include /*Unix 标准函数定义*/ 4 #include 5 #include 6 #include /*文件控制定义*/ 7 #include /*RTC支持的CMD*/ 8 #include /*错误号定义*/ 9 #include 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 failedn”); 28 return -1; 29 } 30 31 /* 获取RTC时间*/ 32 if (ioctl(fd, RTC_RD_TIME, &rtc_tm_temp) < 0) { 33 printf(“RTC_RD_TIME failedn”); 34 return -1; 35 } 36 printf(“RTC_RD_TIME return %04d-%02d-%02d %02d:%02d:%02dn”, 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 failedn”); 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 failedn”); 67 return -1; 68 } 69 70 printf(“RTC_ALM_READ return %04d-%02d-%02d %02d:%02d:%02dn”, 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 failedn”, 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 errorn”); 92 return -EINVAL; 93 } 94 95 /* 设置闹钟*/ 96 ret = set_rtc_alarm(fd); 97 if (ret < 0) { 98 printf(“set rtc alarm errorn”); 99 return -EINVAL; 100 } 101 102 close(fd); 103 return 0; 104 }

6 FAQ

6.1 RTC 时间不准

按照下图RTC 时钟源的路径,确认一下RTC 所使用的时钟源

Linux RTC开发指南-linux中vi编辑器命令7

如果确认使用的时钟源为RC16M,则确认一下有没有启用校准功能,因为RC16M 有正负50% 的偏差。

如果使用外部晶体,则确认一下外部晶体的震荡频率是否正确。

6.2 RTC 时间不走

请查看RTC 时钟源图,确认一下使用的时钟源。

当RTC 时钟源为外部32K 时,请确认一下外部32k 晶体的起振情况。

说明:当使用示波器测量外部32k 晶体起振情况时,有可能会导致32k 晶体起振。

当排查完时钟源,确认时钟源没有问题后,通过以下命令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 确认模块首地址。

审核编辑:汤梓红

猜你喜欢