RTC是Real Time Clock的簡稱,它在硬件電路上單獨供電,當系統關機時,CPU和其他外部硬件設備全部掉電,但是RTC仍然繼續工作.
HWCR (Hibernate Wakeup Control Register)是一個控制休眠喚醒的寄存器,如果我們要使用休眠狀態下RTC喚醒的功能,我們需要打開它的第0位ELAM(RTC Alarm Wakeup enable),當ELAM置1時,使能ELAM功能。
RTCSR (RTC Second Registe)是一個32位的寄存器,它的值以1Hz的頻率加1,即每秒自動加1。
RTCSAR (RTC Second Alarm Register)是一個以秒為單位的鬧鍾寄存器,我們可以將設置的格林威治時間轉換成相應的秒數然后寫進這個寄存器,即完成了我們設置的鬧鍾。我們打開HWCR中的ELAM,按power鍵關機,當RTC檢測到RTCSR == RTCSAR的值時,RTC將會喚醒CPU,並從XBOOT開始進行開機啟動。
對於設置RTCSAR的操作,我們可以用一個應用rtc_test.c來完成:
1 #include <stdio.h> 2 #include <linux/rtc.h> 3 #include <sys/ioctl.h> 4 #include <sys/time.h> 5 #include <sys/types.h> 6 #include <fcntl.h> 7 #include <unistd.h> 8 #include <stdlib.h> 9 #include <errno.h> 10 11 /* 12 * This expects the new RTC class driver framework, working with 13 * clocks that will often not be clones of what the PC-AT had. 14 * Use the command line to specify another RTC if you need one. 15 */ 16 static const char default_rtc[] = "/dev/rtc0"; 17 int main(int argc, char **argv) 18 { 19 int i, fd, retval, irqcount = 0, alarm_time = 5; 20 unsigned long tmp, data; 21 struct rtc_time rtc_tm; 22 const char *rtc = default_rtc; 23 switch (argc) 24 { 25 case 2: 26 rtc = argv[1]; 27 /* FALLTHROUGH */ 28 case 1: 29 break; 30 default: 31 fprintf(stderr, "usage: rtctest [rtcdev]\n"); 32 return 1; 33 } 34 fd = open(rtc, O_RDONLY); 35 if (fd == -1) 36 { 37 perror(rtc); 38 exit(errno); 39 } 40 fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n"); 41 42 #if 0 43 /* Turn on update interrupts (one per second) */ 44 retval = ioctl(fd, RTC_UIE_ON, 0); 45 if (retval == -1) 46 { 47 if (errno == ENOTTY) 48 { 49 fprintf(stderr, "\n...Update IRQs not supported.\n"); 50 goto test_READ; 51 } 52 perror("RTC_UIE_ON ioctl"); 53 exit(errno); 54 } 55 fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading %s:\n",rtc); 56 fflush(stderr); 57 for (i=1; i<6; i++) 58 { 59 /* This read will block */ 60 retval = read(fd, &data, sizeof(unsigned long)); 61 if (retval == -1) 62 { 63 perror("read"); 64 exit(errno); 65 } 66 fprintf(stderr, " %d",i); 67 fflush(stderr); 68 irqcount++; 69 } 70 fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:"); 71 fflush(stderr); 72 for (i=1; i<6; i++) 73 { 74 struct timeval tv = {5, 0}; /* 5 second timeout on select */ 75 fd_set readfds; 76 FD_ZERO(&readfds); 77 FD_SET(fd, &readfds); 78 /* The select will wait until an RTC interrupt happens. */ 79 retval = select(fd+1, &readfds, NULL, NULL, &tv); 80 if (retval == -1) 81 { 82 perror("select"); 83 exit(errno); 84 } 85 /* This read won't block unlike the select-less case above. */ 86 retval = read(fd, &data, sizeof(unsigned long)); 87 if (retval == -1) 88 { 89 perror("read"); 90 exit(errno); 91 } 92 fprintf(stderr, " %d",i); 93 fflush(stderr); 94 irqcount++; 95 } 96 /* Turn off update interrupts */ 97 retval = ioctl(fd, RTC_UIE_OFF, 0); 98 if (retval == -1) 99 { 100 perror("RTC_UIE_OFF ioctl"); 101 exit(errno); 102 } 103 #endif 104 //test_READ: 105 /* Read the RTC time/date */ 106 107 printf("Set the alarm time after: "); 108 scanf("%d", &alarm_time); 109 fprintf(stderr, "seconds"); 110 //fprintf(stderr, "Set the alarm time after %d seconds", alarm_time); 111 112 retval = ioctl(fd, RTC_RD_TIME, &rtc_tm); 113 if (retval == -1) 114 { 115 perror("RTC_RD_TIME ioctl"); 116 exit(errno); 117 } 118 fprintf(stderr, "\n\nCurrent RTC date\time is %d-%d-%d, %02d:%02d:%02d.\n",rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); 119 /* Set the alarm to 5 sec in the future, and check for rollover */ 120 rtc_tm.tm_sec += alarm_time; 121 if (rtc_tm.tm_sec >= 60) 122 { 123 rtc_tm.tm_sec %= 60; 124 rtc_tm.tm_min++; 125 } 126 if (rtc_tm.tm_min == 60) 127 { 128 rtc_tm.tm_min = 0; 129 rtc_tm.tm_hour++; 130 } 131 if (rtc_tm.tm_hour == 24) 132 rtc_tm.tm_hour = 0; 133 retval = ioctl(fd, RTC_ALM_SET, &rtc_tm); 134 if (retval == -1) 135 { 136 if (errno == ENOTTY) 137 { 138 fprintf(stderr,"\n...Alarm IRQs not supported.\n"); 139 //goto test_PIE; 140 } 141 perror("RTC_ALM_SET ioctl"); 142 exit(errno); 143 } 144 /* Read the current alarm settings */ 145 retval = ioctl(fd, RTC_ALM_READ, &rtc_tm); 146 if (retval == -1) 147 { 148 perror("RTC_ALM_READ ioctl"); 149 exit(errno); 150 } 151 fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n",rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); 152 fflush(stderr); 153 /* Enable alarm interrupts */ 154 retval = ioctl(fd, RTC_AIE_ON, 0); 155 if (retval == -1) 156 { 157 perror("RTC_AIE_ON ioctl"); 158 exit(errno); 159 } 160 #if 0 161 //fprintf(stderr, "Waiting %d seconds for alarm...", alarm_time); 162 //fflush(stderr); 163 ///* This blocks until the alarm ring causes an interrupt */ 164 //retval = read(fd, &data, sizeof(unsigned long)); 165 //if (retval == -1) 166 //{ 167 // perror("read"); 168 // exit(errno); 169 //} 170 //irqcount++; 171 //fprintf(stderr, " okay. Alarm rang.\n"); 172 /* Disable alarm interrupts */ 173 retval = ioctl(fd, RTC_AIE_OFF, 0); 174 if (retval == -1) 175 { 176 perror("RTC_AIE_OFF ioctl"); 177 exit(errno); 178 } 179 #endif 180 #if 0 181 test_PIE: 182 /* Read periodic IRQ rate */ 183 retval = ioctl(fd, RTC_IRQP_READ, &tmp); 184 if (retval == -1) 185 { 186 /* not all RTCs support periodic IRQs */ 187 if (errno == ENOTTY) 188 { 189 fprintf(stderr, "\nNo periodic IRQ support\n"); 190 goto done; 191 } 192 perror("RTC_IRQP_READ ioctl"); 193 exit(errno); 194 } 195 fprintf(stderr, "\nPeriodic IRQ rate is %ldHz.\n", tmp); 196 fprintf(stderr, "Counting 20 interrupts at:"); 197 fflush(stderr); 198 /* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */ 199 for (tmp=2; tmp<=1024; tmp*=2) 200 { 201 retval = ioctl(fd, RTC_IRQP_SET, tmp); 202 if (retval == -1) 203 { 204 /* not all RTCs can change their periodic IRQ rate */ 205 if (errno == ENOTTY) 206 { 207 fprintf(stderr,"\n...Periodic IRQ rate is fixed\n"); 208 goto done; 209 } 210 perror("RTC_IRQP_SET ioctl"); 211 exit(errno); 212 } 213 fprintf(stderr, "\n%ldHz:\t", tmp); 214 fflush(stderr); 215 /* Enable periodic interrupts */ 216 retval = ioctl(fd, RTC_PIE_ON, 0); 217 if (retval == -1) 218 { 219 perror("RTC_PIE_ON ioctl"); 220 exit(errno); 221 } 222 for (i=1; i<21; i++) 223 { 224 /* This blocks */ 225 retval = read(fd, &data, sizeof(unsigned long)); 226 if (retval == -1) 227 { 228 perror("read"); 229 exit(errno); 230 } 231 fprintf(stderr, " %d",i); 232 fflush(stderr); 233 irqcount++; 234 } 235 /* Disable periodic interrupts */ 236 retval = ioctl(fd, RTC_PIE_OFF, 0); 237 if (retval == -1) 238 { 239 perror("RTC_PIE_OFF ioctl"); 240 exit(errno); 241 } 242 } 243 done: 244 #endif 245 fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n"); 246 close(fd); 247 return 0; 248 }
上面的程序中我只保留了設置RTC ALARM的操作,其他的全注釋掉了。設置ALARM時需要ioctl RTC_ALM_SET和RTC_AIE_ON兩個宏定義,其中RTC_ALM_SET設置的鬧鍾只有在24內有效,我們通過120~132行的代碼也可以看出應用只設置了以時分秒為單位的值,假入我們將天/月/年也設上,133行的
retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);
會調到kernel/drivers/rtc/rtc-dev.c的rtc_dev_ioctl中:
1 case RTC_ALM_SET: 2 mutex_unlock(&rtc->ops_lock); 3 4 if (copy_from_user(&alarm.time, uarg, sizeof(tm))) 5 return -EFAULT; 6 7 alarm.enabled = 0; 8 alarm.pending = 0; 9 alarm.time.tm_wday = -1; 10 alarm.time.tm_yday = -1; 11 alarm.time.tm_isdst = -1; 12 13 /* RTC_ALM_SET alarms may be up to 24 hours in the future. 14 * Rather than expecting every RTC to implement "don't care" 15 * for day/month/year fields, just force the alarm to have 16 * the right values for those fields. 17 * 18 * RTC_WKALM_SET should be used instead. Not only does it 19 * eliminate the need for a separate RTC_AIE_ON call, it 20 * doesn't have the "alarm 23:59:59 in the future" race. 21 * 22 * NOTE: some legacy code may have used invalid fields as 23 * wildcards, exposing hardware "periodic alarm" capabilities. 24 * Not supported here. 25 */ 26 { 27 unsigned long now, then; 28 29 err = rtc_read_time(rtc, &tm); 30 if (err < 0) 31 return err; 32 rtc_tm_to_time(&tm, &now); 33 34 alarm.time.tm_mday = tm.tm_mday; 35 alarm.time.tm_mon = tm.tm_mon; 36 alarm.time.tm_year = tm.tm_year; 37 err = rtc_valid_tm(&alarm.time); 38 if (err < 0) 39 return err; 40 rtc_tm_to_time(&alarm.time, &then); 41 42 /* alarm may need to wrap into tomorrow */ 43 if (then < now) { 44 rtc_time_to_tm(now + 24 * 60 * 60, &tm); 45 alarm.time.tm_mday = tm.tm_mday; 46 alarm.time.tm_mon = tm.tm_mon; 47 alarm.time.tm_year = tm.tm_year; 48 } 49 } 50 51 return rtc_set_alarm(rtc, &alarm);
在rtc-dev.c的rtc_dev_ioctl中,RTC_ALM_SET的case會先將RTC寄存器RTCSR中的時間讀出來,並轉換成格林威治時間,應用設置下來的rtc_tm的天/月/年會被讀取出來的tm中的天/月/年所覆蓋,所以即使應用設置了也沒有用。如果應用需要設置超過24小時以外的鬧鍾可以ioctl RTC_WKALM_SET 的rtc_tm到驅動中。
ioctl RTC_ALM_SET后還需再ioctl一次RTC_AIE_ON。如果應用不ioctl RTC_AIE_ON的話,應用設置的rtc_tm不會被設到rtc驅動中struct rtc_class_ops的.set_alarm成員指針指向的函數,即rtc_tm的值不會被設到RTCSAR寄存器中,鬧鍾設置不會生效。
在Android4.3環境上交叉編譯成rtc_test的可執行文件,rtc_test.c和Android.mk都放在external/test/下,其中Android.mk書寫如下:
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 LOCAL_MODULE_TAGS := optional 5 LOCAL_MODULE := rtc_test 6 LOCAL_SRC_FILES := $(call all-subdir-c-files) 7 LOCAL_C_INCLUDES:= $(LOCAL_C_INCLUDES)\ 8 $(PATH)\ 9 kernel/arch/mips/xburst/soc-4775/include\ 10 kernel/arch/mips/include/asm/mach-generic 11 include $(BUILD_EXECUTABLE)
在項目目錄執行 source build/envsetup.h和lunch后,再到external/test下執行mm,編譯好的rtc_test放在out/target/product/xxxx/system/bin/rtc_test
adb push rtc_test到開發板的/system/bin下,並設置成777權限后即可使用此應用設置RTC的ALARM。
執行rtc_test時需要我們輸入一個距離現在時間的定時鬧鍾,假如我們輸入60,即60s,然后按power鍵關機,待到過了60s(RTCAR == RTCSAR)時,RTC會喚醒CPU啟動開機流程。
由於我們在按power鍵關機后有幾種不同的狀態,比如板子不接USB充電線/板子接USB充電線/板子接USB充電線但隔了一段時間后又拔除,這幾種情況都處於不同的XBOOT流程,所以我們還需在XBOOT中加入處於不同關機狀態的RTC喚醒代碼。