Linux RTC 開發(fā)指南
1 概述
1.1 編寫目的
介紹Linux 內(nèi)核中RTC 驅(qū)動的適配和DEBUG 方法,為RTC 設(shè)備的使用者和維護者提供參考。
1.2 適用范圍
內(nèi)核版本 | 驅(qū)動文件 |
---|---|
LINUX-4.9 及以上 | RTC-SUNXI.C |
1.3 相關(guān)人員
RTC 驅(qū)動及應(yīng)用層的開發(fā)/維護人員。
2 模塊介紹
Linux 內(nèi)核中,RTC 驅(qū)動的結(jié)構(gòu)圖如下所示, 可以分為三個層次:
接口層,負責(zé)向用戶空間提供操作的結(jié)點以及相關(guān)接口。 ? RTC Core, 為rtc 驅(qū)動提供了一套API, 完成設(shè)備和驅(qū)動的注冊等。 ? RTC 驅(qū)動層,負責(zé)具體的RTC 驅(qū)動實現(xiàn),如設(shè)置時間、鬧鐘等設(shè)置寄存器的操作。
2.2 相關(guān)術(shù)語介紹
術(shù)語 | 解釋說明 |
---|---|
Sunxi | 指Allwinner 的一系列SoC 硬件平臺 |
RTC | Real Time Clock,實時時鐘 |
2.3 源碼結(jié)構(gòu)介紹
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 版本下
在命令行中進入內(nèi)核根目錄(kernel/linux-4.9),執(zhí)行make ARCH=arm64(arm) menuconfig(32 位系統(tǒng)為make ARCH=arm menuconfig) 進入配置主界面(linux-5.4 內(nèi)核版本在longan 目錄下執(zhí)行:./build.sh menuconfig 進入配置主界面),并按以下步驟操作: 首先,選擇Device Drivers 選項進入下一級配置,如下圖所示:
選擇Real Time Clock,進入下級配置,如下圖所示:
選擇Allwinner sunxi RTC,如下圖所示:
由于在關(guān)機過程中,RTC 一般都是獨立供電的,因此在RTC 電源域中的寄存器不會掉電且RTC寄存器的值也不會恢復(fù)為默認值。利用此特性,Sunxi 平臺支持reboot 命令的一些擴展功能和 假關(guān)機功能,但需要打開support ir fake poweroff 和Sunxi rtc reboot Feature 選項,RTC驅(qū)動才能支持這些擴展功能。
3.1.2 linux-5.4 版本下
在命令行中進入longan 頂層目錄,執(zhí)行./build.sh config,按照提示配置平臺、板型等信息(如果之前已經(jīng)配置過,可跳過此步驟)。 然后執(zhí)行./build.sh menuconfig,進入內(nèi)核圖形化配置界面,并按以下步驟操作: 選擇Device Driver選項進入下一級配置,如下圖所示:
選擇Real Time Clock進入下一級配置,如下圖所示:
選擇Allwinner sunxi RTC配置,如下圖所示。
由于在關(guān)機過程中,RTC 一般都是獨立供電的,因此在RTC 電源域中的寄存器不會掉電且RTC寄存器的值也不會恢復(fù)為默認值。利用此特性,Sunxi 平臺支持reboot 命令的一些擴展功能,但需要打開Sunxi rtc reboot flag和Sunxi rtc general register save bootcount選項,RTC 驅(qū)動才能支持這些擴展功能。
3.2 device tree 源碼結(jié)構(gòu)和路徑
SoC 級設(shè)備樹文件(sun*.dtsi)是針對該SoC 所有方案的通用配置:
? 對于ARM64 CPU 而言,SoC 級設(shè)備樹的路徑為:arch/arm64/boot/dts/sunxi/sun*.dtsi
? 對于ARM32 CPU 而言,SoC 級設(shè)備樹的路徑為:arch/arm/boot/dts/sun*.dtsi
板級設(shè)備樹文件(board.dts)是針對該板型的專用配置:
? 板級設(shè)備樹路徑:device/config/chips/{IC}/configs/{BOARD}/board.dts
板級設(shè)備樹文件(board.dts)是針對該板型的專用配置: ? 板級設(shè)備樹路徑:device/config/chips/{IC}/configs/{BOARD}/board.dts
3.2.1 linux-4.9 版本下
device tree 的源碼結(jié)構(gòu)關(guān)系如下:
board.dts
└--------sun*.dtsi
|------sun*-pinctrl.dtsi
└------sun*-clk.dtsi
3.2.2 linux-5.4 版本下
device tree 的源碼結(jié)構(gòu)關(guān)系如下:
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驅(qū)動
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通用寄存器的個數(shù)
11 gpr_cur_pos = <6>;
12 };
13 }
說明 對于linux-4.9 內(nèi)核,當(dāng)RTC 結(jié)點下配置auto_switch 屬性時,RTC 硬件會自動掃描檢查外部32k 晶體振蕩器的起振情 況。當(dāng)外部晶體振蕩器工作異常時,RTC 硬件會自動切換到內(nèi)部RC16M 時鐘分頻出來的32k 時鐘,從而保證RTC 工作正 常。當(dāng)沒有配置該屬性時,驅(qū)動代碼中直接把RTC 時鐘源設(shè)置為外部32k 晶體的,當(dāng)外部32K 晶體工作異常時,RTC 會工 作異常。因此建議配置上該屬性。
3.3.2 linux-5.4 版本下
1 / {
2 rtc: rtc@7000000 {
3 compatible = "allwinner,sun50iw10p1-rtc"; //用于probe驅(qū)動
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>; //當(dāng)前被用作reboot-flag的通用寄存器的序號
12 };
13 }
在Device Tree 中對每一個RTC 控制器進行配置, 一個RTC 控制器對應(yīng)一個RTC 節(jié)點, 節(jié)點屬性的含義見注釋。
3.4 board.dts 板級配置
board.dts用于保存每個板級平臺的設(shè)備信息(如demo 板、demo2.0 板等等)。board.dts路徑如下:
device/config/chips/{IC}/configs/{BOARD}/boar d.dts
在board.dts中的配置信息如果在*.dtsi(如sun50iw9p1.dtsi等) 中存在,則會存在以下覆蓋規(guī)則:
在board.dts中的配置信息如果在*.dtsi(如sun50iw9p1.dtsi等) 中存在,則會存在以下覆蓋規(guī)則:
相同屬性和結(jié)點,board.dts的配置信息會覆蓋*.dtsi中的配置信息
新增加的屬性和結(jié)點,會添加到編譯生成的dtb 文件中
4 接口描述
RTC 驅(qū)動會注冊生成串口設(shè)備/dev/rtcN,應(yīng)用層的使用只需遵循Linux 系統(tǒng)中的標準RTC 編程方法即可。
4.1 打開/關(guān)閉RTC 設(shè)備
使用標準的文件打開函數(shù):
1 int open(const char *pathname, int flags); 2 int close(int fd);
需要引用頭文件:
1 #include 2 #include 3 #include 4 #include
4.2 設(shè)置和獲取RTC 時間
同樣使用標準的ioctl 函數(shù):
1 int ioctl(int d, int request, ...);
需要引用頭文件:
1 #include 2 #include
5 模塊使用范例
此demo 程序是打開一個RTC 設(shè)備,然后設(shè)置和獲取RTC 時間以及設(shè)置鬧鐘功能。
1 #include /*標準輸入輸出定義*/ 2 #include /*標準函數(shù)庫定義*/ 3 #include /*Unix 標準函數(shù)定義*/ 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; /* 需要設(shè)置的年份,需要減1900 */ 19 rtc_tm.tm_mon = 11 - 1; /* 需要設(shè)置的月份,需要確保在0-11范圍*/ 20 rtc_tm.tm_mday = 21; /* 需要設(shè)置的日期*/ 21 rtc_tm.tm_hour = 10; /* 需要設(shè)置的時間*/ 22 rtc_tm.tm_min = 12; /* 需要設(shè)置的分鐘時間*/ 23 rtc_tm.tm_sec = 30; /* 需要設(shè)置的秒數(shù)*/ 24 25 /* 設(shè)置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; /* 鬧鐘忽略年設(shè)置*/ 48 rtc_tm.tm_mon = 0; /* 鬧鐘忽略月設(shè)置*/ 49 rtc_tm.tm_mday = 0; /* 鬧鐘忽略日期設(shè)置*/ 50 rtc_tm.tm_hour = 10; /* 需要設(shè)置的時間*/ 51 rtc_tm.tm_min = 12; /* 需要設(shè)置的分鐘時間*/ 52 rtc_tm.tm_sec = 30; /* 需要設(shè)置的秒數(shù)*/ 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 /* 設(shè)置RTC時間*/ 89 ret = set_rtc_timer(fd); 90 if (ret < 0) { 91 printf("set rtc timer errorn"); 92 return -EINVAL; 93 } 94 95 /* 設(shè)置鬧鐘*/ 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 所使用的時鐘源
如果確認使用的時鐘源為RC16M,則確認一下有沒有啟用校準功能,因為RC16M 有正負50% 的偏差。
如果使用外部晶體,則確認一下外部晶體的震蕩頻率是否正確。
6.2 RTC 時間不走
請查看RTC 時鐘源圖,確認一下使用的時鐘源。
當(dāng)RTC 時鐘源為外部32K 時,請確認一下外部32k 晶體的起振情況。
說明:當(dāng)使用示波器測量外部32k 晶體起振情況時,有可能會導(dǎo)致32k 晶體起振。
當(dāng)排查完時鐘源,確認時鐘源沒有問題后,通過以下命令dump rtc 相關(guān)寄存器,查看偏移0x0 寄存器的狀態(tài)位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 的模塊首地址是不一樣的,具體根據(jù)spec 或data sheet 確認模塊首地址。
-
模塊
+關(guān)注
關(guān)注
7文章
2658瀏覽量
47294 -
內(nèi)核
+關(guān)注
關(guān)注
3文章
1361瀏覽量
40185 -
Linux
+關(guān)注
關(guān)注
87文章
11212瀏覽量
208724 -
時鐘
+關(guān)注
關(guān)注
10文章
1714瀏覽量
131282 -
RTC
+關(guān)注
關(guān)注
2文章
523瀏覽量
66230
發(fā)布評論請先 登錄
相關(guān)推薦
評論