介绍Linux 内核中RTC 驱动的适配和DEBUG 方法,为RTC 设备的使用者和维护者提供参考。
内核版本 | 驱动文件 |
---|---|
LINUX-4.9 及以上 | RTC-SUNXI.C |
RTC 驱动及应用层的开发/维护人员。
Linux 内核中,RTC 驱动的结构图如下所示, 可以分为三个层次:
接口层,负责向用户空间提供操作的结点以及相关接口。 • RTC Core, 为rtc 驱动提供了一套API, 完成设备和驱动的注册等。 • RTC 驱动层,负责具体的RTC 驱动实现,如设置时间、闹钟等设置寄存器的操作。
术语 | 解释说明 |
---|---|
Sunxi | 指Allwinner 的一系列SoC 硬件平台 |
RTC | Real Time Clock,实时时钟 |
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
在命令行中进入内核根目录(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驱动才能支持这些扩展功能。
在命令行中进入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 驱动才能支持这些扩展功能。
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
device tree 的源码结构关系如下:
board.dts
└--------sun*.dtsi
|------sun*-pinctrl.dtsi
└------sun*-clk.dtsi
device tree 的源码结构关系如下:
board.dts
└--------sun*.dtsi
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 会工 作异常。因此建议配置上该属性。
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 节点, 节点属性的含义见注释。
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 文件中
RTC 驱动会注册生成串口设备/dev/rtcN,应用层的使用只需遵循Linux 系统中的标准RTC 编程方法即可。
使用标准的文件打开函数:
1 int open(const char *pathname, int flags); 2 int close(int fd);
需要引用头文件:
1 #include 2 #include 3 #include 4 #include
同样使用标准的ioctl 函数:
1 int ioctl(int d, int request, ...);
需要引用头文件:
1 #include 2 #include
此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 }
按照下图RTC 时钟源的路径,确认一下RTC 所使用的时钟源
如果确认使用的时钟源为RC16M,则确认一下有没有启用校准功能,因为RC16M 有正负50% 的偏差。
如果使用外部晶体,则确认一下外部晶体的震荡频率是否正确。
请查看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 确认模块首地址。
全部0条评论
快来发表一下你的评论吧 !