1 /*
2 * Copyright (c) 2006-2018, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2012-10-27 heyuanjie87 first version.
9 * 2013-05-17 aozima initial alarm event & mutex in system init.
10 */
11
12 #include <rtthread.h>
13 #include <rtdevice.h>
14
15 #define RT_RTC_YEARS_MAX 137
16 #define RT_ALARM_DELAY 2
17 #define RT_ALARM_STATE_INITED 0x02
18 #define RT_ALARM_STATE_START 0x01
19 #define RT_ALARM_STATE_STOP 0x00
20
21 #if (defined(RT_USING_RTC) && defined(RT_USING_ALARM))
22 static struct rt_alarm_container _container;
23
alarm_mkdaysec(struct tm * time)24 rt_inline rt_uint32_t alarm_mkdaysec(struct tm *time)
25 {
26 rt_uint32_t sec;
27
28 sec = time->tm_sec;
29 sec += time->tm_min * 60;
30 sec += time->tm_hour * 3600;
31
32 return (sec);
33 }
34
alarm_set(struct rt_alarm * alarm)35 static rt_err_t alarm_set(struct rt_alarm *alarm)
36 {
37 rt_device_t device;
38 struct rt_rtc_wkalarm wkalarm;
39 rt_err_t ret;
40
41 device = rt_device_find("rtc");
42 if (device == RT_NULL)
43 {
44 return (RT_ERROR);
45 }
46 if (alarm->flag & RT_ALARM_STATE_START)
47 wkalarm.enable = RT_TRUE;
48 else
49 wkalarm.enable = RT_FALSE;
50
51 wkalarm.tm_sec = alarm->wktime.tm_sec;
52 wkalarm.tm_min = alarm->wktime.tm_min;
53 wkalarm.tm_hour = alarm->wktime.tm_hour;
54
55 ret = rt_device_control(device, RT_DEVICE_CTRL_RTC_SET_ALARM, &wkalarm);
56 if ((ret == RT_EOK) && wkalarm.enable)
57 {
58 ret = rt_device_control(device, RT_DEVICE_CTRL_RTC_GET_ALARM, &wkalarm);
59 if (ret == RT_EOK)
60 {
61 /*
62 some RTC device like RX8025,it's alarms precision is 1 minute.
63 in this case,low level RTC driver should set wkalarm->tm_sec to 0.
64 */
65 alarm->wktime.tm_sec = wkalarm.tm_sec;
66 alarm->wktime.tm_min = wkalarm.tm_min;
67 alarm->wktime.tm_hour = wkalarm.tm_hour;
68 }
69 }
70
71 return (ret);
72 }
73
alarm_wakeup(struct rt_alarm * alarm,struct tm * now)74 static void alarm_wakeup(struct rt_alarm *alarm, struct tm *now)
75 {
76 rt_uint32_t sec_alarm, sec_now;
77 rt_bool_t wakeup = RT_FALSE;
78 time_t timestamp;
79
80 sec_alarm = alarm_mkdaysec(&alarm->wktime);
81 sec_now = alarm_mkdaysec(now);
82
83 if (alarm->flag & RT_ALARM_STATE_START)
84 {
85 switch (alarm->flag & 0xFF00)
86 {
87 case RT_ALARM_ONESHOT:
88 {
89 sec_alarm = mktime(&alarm->wktime);
90 sec_now = mktime(now);
91 if (((sec_now - sec_alarm) <= RT_ALARM_DELAY) && (sec_now >= sec_alarm))
92 {
93 /* stop alarm */
94 alarm->flag &= ~RT_ALARM_STATE_START;
95 alarm_set(alarm);
96 wakeup = RT_TRUE;
97 }
98 }
99 break;
100 case RT_ALARM_DAILY:
101 {
102 if (((sec_now - sec_alarm) <= RT_ALARM_DELAY) && (sec_now >= sec_alarm))
103 wakeup = RT_TRUE;
104 }
105 break;
106 case RT_ALARM_WEEKLY:
107 {
108 /* alarm at wday */
109 sec_alarm += alarm->wktime.tm_wday * 24 * 3600;
110 sec_now += now->tm_wday * 24 * 3600;
111
112 if (((sec_now - sec_alarm) <= RT_ALARM_DELAY) && (sec_now >= sec_alarm))
113 wakeup = RT_TRUE;
114 }
115 break;
116 case RT_ALARM_MONTHLY:
117 {
118 /* monthly someday generate alarm signals */
119 if (alarm->wktime.tm_mday == now->tm_mday)
120 {
121 if ((sec_now - sec_alarm) <= RT_ALARM_DELAY)
122 wakeup = RT_TRUE;
123 }
124 }
125 break;
126 case RT_ALARM_YAERLY:
127 {
128 if ((alarm->wktime.tm_mday == now->tm_mday) && \
129 (alarm->wktime.tm_mon == now->tm_mon))
130 {
131 if ((sec_now - sec_alarm) <= RT_ALARM_DELAY)
132 wakeup = RT_TRUE;
133 }
134 }
135 break;
136 }
137
138 if ((wakeup == RT_TRUE) && (alarm->callback != RT_NULL))
139 {
140 timestamp = time(RT_NULL);
141 alarm->callback(alarm, timestamp);
142 }
143 }
144 }
145
alarm_update(rt_uint32_t event)146 static void alarm_update(rt_uint32_t event)
147 {
148 struct rt_alarm *alm_prev = RT_NULL, *alm_next = RT_NULL;
149 struct rt_alarm *alarm;
150 rt_int32_t sec_now, sec_alarm, sec_tmp;
151 rt_int32_t sec_next = 24 * 3600, sec_prev = 0;
152 time_t timestamp;
153 struct tm now;
154 rt_list_t *next;
155
156 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
157 if (!rt_list_isempty(&_container.head))
158 {
159 /* get time of now */
160 timestamp = time(RT_NULL);
161 localtime_r(×tamp, &now);
162
163 for (next = _container.head.next; next != &_container.head; next = next->next)
164 {
165 alarm = rt_list_entry(next, struct rt_alarm, list);
166 /* check the overtime alarm */
167 alarm_wakeup(alarm, &now);
168 }
169
170 timestamp = time(RT_NULL);
171 localtime_r(×tamp, &now);
172 sec_now = alarm_mkdaysec(&now);
173
174 for (next = _container.head.next; next != &_container.head; next = next->next)
175 {
176 alarm = rt_list_entry(next, struct rt_alarm, list);
177 /* calculate seconds from 00:00:00 */
178 sec_alarm = alarm_mkdaysec(&alarm->wktime);
179
180 if ((alarm->flag & RT_ALARM_STATE_START) && (alarm != _container.current))
181 {
182 sec_tmp = sec_alarm - sec_now;
183 if (sec_tmp > 0)
184 {
185 /* find alarm after now(now to 23:59:59) and the most recent */
186 if (sec_tmp < sec_next)
187 {
188 sec_next = sec_tmp;
189 alm_next = alarm;
190 }
191 }
192 else
193 {
194 /* find alarm before now(00:00:00 to now) and furthest from now */
195 if (sec_tmp < sec_prev)
196 {
197 sec_prev = sec_tmp;
198 alm_prev = alarm;
199 }
200 }
201 }
202 }
203 /* enable the alarm after now first */
204 if (sec_next < 24 * 3600)
205 {
206 if (alarm_set(alm_next) == RT_EOK)
207 _container.current = alm_next;
208 }
209 else if (sec_prev < 0)
210 {
211 /* enable the alarm before now */
212 if (alarm_set(alm_prev) == RT_EOK)
213 _container.current = alm_prev;
214 }
215 }
216 rt_mutex_release(&_container.mutex);
217 }
218
days_of_year_month(int tm_year,int tm_mon)219 static rt_uint32_t days_of_year_month(int tm_year, int tm_mon)
220 {
221 rt_uint32_t ret, year;
222
223 year = tm_year + 1900;
224 if (tm_mon == 1)
225 {
226 ret = 28 + ((!(year % 4) && (year % 100)) || !(year % 400));
227 }
228 else if (((tm_mon <= 6) && (tm_mon % 2 == 0)) || ((tm_mon > 6) && (tm_mon % 2 == 1)))
229 {
230 ret = 31;
231 }
232 else
233 {
234 ret = 30;
235 }
236
237 return (ret);
238 }
239
is_valid_date(struct tm * date)240 static rt_bool_t is_valid_date(struct tm *date)
241 {
242 if ((date->tm_year < 0) || (date->tm_year > RT_RTC_YEARS_MAX))
243 {
244 return (RT_FALSE);
245 }
246
247 if ((date->tm_mon < 0) || (date->tm_mon > 11))
248 {
249 return (RT_FALSE);
250 }
251
252 if ((date->tm_mday < 1) || \
253 (date->tm_mday > days_of_year_month(date->tm_year, date->tm_mon)))
254 {
255 return (RT_FALSE);
256 }
257
258 return (RT_TRUE);
259 }
260
alarm_setup(rt_alarm_t alarm,struct tm * wktime)261 static rt_err_t alarm_setup(rt_alarm_t alarm, struct tm *wktime)
262 {
263 rt_err_t ret = RT_ERROR;
264 time_t timestamp;
265 struct tm *setup, now;
266
267 setup = &alarm->wktime;
268 *setup = *wktime;
269 timestamp = time(RT_NULL);
270 localtime_r(×tamp, &now);
271
272 /* if these are a "don't care" value,we set them to now*/
273 if ((setup->tm_sec > 59) || (setup->tm_sec < 0))
274 setup->tm_sec = now.tm_sec;
275 if ((setup->tm_min > 59) || (setup->tm_min < 0))
276 setup->tm_min = now.tm_min;
277 if ((setup->tm_hour > 23) || (setup->tm_hour < 0))
278 setup->tm_hour = now.tm_hour;
279
280 switch (alarm->flag & 0xFF00)
281 {
282 case RT_ALARM_DAILY:
283 {
284 /* do nothing but needed */
285 }
286 break;
287 case RT_ALARM_ONESHOT:
288 {
289 /* if these are "don't care" value we set them to now */
290 if (setup->tm_year == RT_ALARM_TM_NOW)
291 setup->tm_year = now.tm_year;
292 if (setup->tm_mon == RT_ALARM_TM_NOW)
293 setup->tm_mon = now.tm_mon;
294 if (setup->tm_mday == RT_ALARM_TM_NOW)
295 setup->tm_mday = now.tm_mday;
296 /* make sure the setup is valid */
297 if (!is_valid_date(setup))
298 goto _exit;
299 }
300 break;
301 case RT_ALARM_WEEKLY:
302 {
303 /* if tm_wday is a "don't care" value we set it to now */
304 if ((setup->tm_wday < 0) || (setup->tm_wday > 6))
305 setup->tm_wday = now.tm_wday;
306 }
307 break;
308 case RT_ALARM_MONTHLY:
309 {
310 /* if tm_mday is a "don't care" value we set it to now */
311 if ((setup->tm_mday < 1) || (setup->tm_mday > 31))
312 setup->tm_mday = now.tm_mday;
313 }
314 break;
315 case RT_ALARM_YAERLY:
316 {
317 /* if tm_mon is a "don't care" value we set it to now */
318 if ((setup->tm_mon < 0) || (setup->tm_mon > 11))
319 setup->tm_mon = now.tm_mon;
320
321 if (setup->tm_mon == 1)
322 {
323 /* tm_mon is February */
324
325 /* tm_mday should be 1~29.otherwise,it's a "don't care" value */
326 if ((setup->tm_mday < 1) || (setup->tm_mday > 29))
327 setup->tm_mday = now.tm_mday;
328 }
329 else if (((setup->tm_mon <= 6) && (setup->tm_mon % 2 == 0)) || \
330 ((setup->tm_mon > 6) && (setup->tm_mon % 2 == 1)))
331 {
332 /* Jan,Mar,May,Jul,Aug,Oct,Dec */
333
334 /* tm_mday should be 1~31.otherwise,it's a "don't care" value */
335 if ((setup->tm_mday < 1) || (setup->tm_mday > 31))
336 setup->tm_mday = now.tm_mday;
337 }
338 else
339 {
340 /* tm_mday should be 1~30.otherwise,it's a "don't care" value */
341 if ((setup->tm_mday < 1) || (setup->tm_mday > 30))
342 setup->tm_mday = now.tm_mday;
343 }
344 }
345 break;
346 default:
347 {
348 goto _exit;
349 }
350 }
351
352 if ((setup->tm_hour == 23) && (setup->tm_min == 59) && (setup->tm_sec == 59))
353 {
354 /*
355 for insurance purposes, we will generate an alarm
356 signal two seconds ahead of.
357 */
358 setup->tm_sec = 60 - RT_ALARM_DELAY;
359 }
360 /* set initialized state */
361 alarm->flag |= RT_ALARM_STATE_INITED;
362 ret = RT_EOK;
363
364 _exit:
365
366 return (ret);
367 }
368
369 /** \brief send a rtc alarm event
370 *
371 * \param dev pointer to RTC device(currently unused,you can ignore it)
372 * \param event RTC event(currently unused)
373 * \return none
374 */
rt_alarm_update(rt_device_t dev,rt_uint32_t event)375 void rt_alarm_update(rt_device_t dev, rt_uint32_t event)
376 {
377 rt_event_send(&_container.event, 1);
378 }
379
380 /** \brief modify the alarm setup
381 *
382 * \param alarm pointer to alarm
383 * \param cmd control command
384 * \param arg argument
385 */
rt_alarm_control(rt_alarm_t alarm,int cmd,void * arg)386 rt_err_t rt_alarm_control(rt_alarm_t alarm, int cmd, void *arg)
387 {
388 rt_err_t ret = RT_ERROR;
389
390 RT_ASSERT(alarm != RT_NULL);
391
392 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
393 switch (cmd)
394 {
395 case RT_ALARM_CTRL_MODIFY:
396 {
397 struct rt_alarm_setup *setup;
398
399 RT_ASSERT(arg != RT_NULL);
400 setup = arg;
401 rt_alarm_stop(alarm);
402 alarm->flag = setup->flag & 0xFF00;
403 alarm->wktime = setup->wktime;
404 ret = alarm_setup(alarm, &alarm->wktime);
405 }
406 break;
407 }
408
409 rt_mutex_release(&_container.mutex);
410
411 return (ret);
412 }
413
414 /** \brief start an alarm
415 *
416 * \param alarm pointer to alarm
417 * \return RT_EOK
418 */
rt_alarm_start(rt_alarm_t alarm)419 rt_err_t rt_alarm_start(rt_alarm_t alarm)
420 {
421 rt_int32_t sec_now, sec_old, sec_new;
422 rt_err_t ret = RT_ERROR;
423 time_t timestamp;
424 struct tm now;
425
426 if (alarm == RT_NULL)
427 return (ret);
428 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
429 if (!(alarm->flag & RT_ALARM_STATE_INITED))
430 {
431 if (alarm_setup(alarm, &alarm->wktime) != RT_EOK)
432 goto _exit;
433 }
434 if ((alarm->flag & 0x01) == RT_ALARM_STATE_STOP)
435 {
436 timestamp = time(RT_NULL);
437 localtime_r(×tamp, &now);
438
439 alarm->flag |= RT_ALARM_STATE_START;
440 /* set alarm */
441 if (_container.current == RT_NULL)
442 {
443 ret = alarm_set(alarm);
444 }
445 else
446 {
447 sec_now = alarm_mkdaysec(&now);
448 sec_old = alarm_mkdaysec(&_container.current->wktime);
449 sec_new = alarm_mkdaysec(&alarm->wktime);
450
451 if ((sec_new < sec_old) && (sec_new > sec_now))
452 {
453 ret = alarm_set(alarm);
454 }
455 else if ((sec_new > sec_now) && (sec_old < sec_now))
456 {
457 ret = alarm_set(alarm);
458 }
459 else if ((sec_new < sec_old) && (sec_old < sec_now))
460 {
461 ret = alarm_set(alarm);
462 }
463 else
464 {
465 ret = RT_EOK;
466 goto _exit;
467 }
468 }
469
470 if (ret == RT_EOK)
471 {
472 _container.current = alarm;
473 }
474 }
475
476 _exit:
477 rt_mutex_release(&_container.mutex);
478
479 return (ret);
480 }
481
482 /** \brief stop an alarm
483 *
484 * \param alarm pointer to alarm
485 * \return RT_EOK
486 */
rt_alarm_stop(rt_alarm_t alarm)487 rt_err_t rt_alarm_stop(rt_alarm_t alarm)
488 {
489 rt_err_t ret = RT_ERROR;
490
491 if (alarm == RT_NULL)
492 return (ret);
493 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
494 if (!(alarm->flag & RT_ALARM_STATE_START))
495 goto _exit;
496 /* stop alarm */
497 alarm->flag &= ~RT_ALARM_STATE_START;
498
499 if (_container.current == alarm)
500 {
501 ret = alarm_set(alarm);
502 _container.current = RT_NULL;
503 }
504
505 if (ret == RT_EOK)
506 alarm_update(0);
507
508 _exit:
509 rt_mutex_release(&_container.mutex);
510
511 return (ret);
512 }
513
514 /** \brief delete an alarm
515 *
516 * \param alarm pointer to alarm
517 * \return RT_EOK
518 */
rt_alarm_delete(rt_alarm_t alarm)519 rt_err_t rt_alarm_delete(rt_alarm_t alarm)
520 {
521 rt_err_t ret = RT_ERROR;
522
523 if (alarm == RT_NULL)
524 return (ret);
525 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
526 /* stop the alarm */
527 alarm->flag &= ~RT_ALARM_STATE_START;
528 if (_container.current == alarm)
529 {
530 ret = alarm_set(alarm);
531 _container.current = RT_NULL;
532 /* set new alarm if necessary */
533 alarm_update(0);
534 }
535 rt_list_remove(&alarm->list);
536 rt_free(alarm);
537
538 rt_mutex_release(&_container.mutex);
539
540 return (ret);
541 }
542
543 /** \brief create an alarm
544 *
545 * \param flag set alarm mode e.g: RT_ALARM_DAILY
546 * \param setup pointer to setup infomation
547 */
rt_alarm_create(rt_alarm_callback_t callback,struct rt_alarm_setup * setup)548 rt_alarm_t rt_alarm_create(rt_alarm_callback_t callback, struct rt_alarm_setup *setup)
549 {
550 struct rt_alarm *alarm;
551
552 if (setup == RT_NULL)
553 return (RT_NULL);
554 alarm = rt_malloc(sizeof(struct rt_alarm));
555 if (alarm == RT_NULL)
556 return (RT_NULL);
557 rt_list_init(&alarm->list);
558
559 alarm->wktime = setup->wktime;
560 alarm->flag = setup->flag & 0xFF00;
561 alarm->callback = callback;
562 rt_mutex_take(&_container.mutex, RT_WAITING_FOREVER);
563 rt_list_insert_after(&_container.head, &alarm->list);
564 rt_mutex_release(&_container.mutex);
565
566 return (alarm);
567 }
568
569 /** \brief rtc alarm service thread entry
570 *
571 */
rt_alarmsvc_thread_init(void * param)572 static void rt_alarmsvc_thread_init(void *param)
573 {
574 rt_uint32_t recv;
575
576 _container.current = RT_NULL;
577
578 while (1)
579 {
580 if (rt_event_recv(&_container.event, 0xFFFF,
581 RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
582 RT_WAITING_FOREVER, &recv) == RT_EOK)
583 {
584 alarm_update(recv);
585 }
586 }
587 }
588
589
590 /** \brief initialize alarm service system
591 *
592 * \param none
593 * \return none
594 */
rt_alarm_system_init(void)595 void rt_alarm_system_init(void)
596 {
597 rt_thread_t tid;
598
599 rt_list_init(&_container.head);
600 rt_event_init(&_container.event, "alarmsvc", RT_IPC_FLAG_FIFO);
601 rt_mutex_init(&_container.mutex, "alarmsvc", RT_IPC_FLAG_FIFO);
602
603 tid = rt_thread_create("alarmsvc",
604 rt_alarmsvc_thread_init, RT_NULL,
605 512, 8, 1);
606 if (tid != RT_NULL)
607 rt_thread_startup(tid);
608 }
609 #endif
610