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] "); 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, " RTC Driver Test Example. "); 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, " ...Update IRQs not supported. "); 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: ",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, " Again, 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, " Current RTC date ime is %d-%d-%d, %02d:%02d:%02d. ",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," ...Alarm IRQs not supported. "); 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. ",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. "); 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, " No periodic IRQ support "); 190 goto done; 191 } 192 perror("RTC_IRQP_READ ioctl"); 193 exit(errno); 194 } 195 fprintf(stderr, " Periodic IRQ rate is %ldHz. ", 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," ...Periodic IRQ rate is fixed "); 208 goto done; 209 } 210 perror("RTC_IRQP_SET ioctl"); 211 exit(errno); 212 } 213 fprintf(stderr, " %ldHz: ", 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, " *** Test complete *** "); 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唤醒代码。