xref: /nrf52832-nimble/rt-thread/components/drivers/rtc/alarm.c (revision 104654410c56c573564690304ae786df310c91fc)
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(&timestamp, &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(&timestamp, &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(&timestamp, &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(&timestamp, &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