blob: 842cfb8829dc9937cb6375ac0d6a4699fe32ff0a [file] [log] [blame]
Harald Welted1bd5c42019-05-17 16:38:30 +02001/**
2 * \file
3 *
4 * \brief Generic CALENDAR functionality implementation.
5 *
6 * Copyright (c) 2014-2018 Microchip Technology Inc. and its subsidiaries.
7 *
8 * \asf_license_start
9 *
10 * \page License
11 *
12 * Subject to your compliance with these terms, you may use Microchip
13 * software and any derivatives exclusively with Microchip products.
14 * It is your responsibility to comply with third party license terms applicable
15 * to your use of third party software (including open source software) that
16 * may accompany Microchip software.
17 *
18 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
19 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
20 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
21 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
22 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
23 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
24 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
25 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT
26 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
27 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
28 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
29 *
30 * \asf_license_stop
31 *
32 */
33
34#include "hal_calendar.h"
35#include <utils.h>
36#include <utils_assert.h>
37#include <hal_atomic.h>
38
39#define CALENDAR_VERSION 0x00000001u
40#define SECS_IN_LEAP_YEAR 31622400
41#define SECS_IN_NON_LEAP_YEAR 31536000
42#define SECS_IN_31DAYS 2678400
43#define SECS_IN_30DAYS 2592000
44#define SECS_IN_29DAYS 2505600
45#define SECS_IN_28DAYS 2419200
46#define SECS_IN_DAY 86400
47#define SECS_IN_HOUR 3600
48#define SECS_IN_MINUTE 60
49#define DEFAULT_BASE_YEAR 1970
50
51#define SET_ALARM_BUSY 1
52#define PROCESS_ALARM_BUSY 2
53
54/** \brief leap year check
55 * \retval false not leap year.
56 * \retval true leap year.
57 */
58static bool leap_year(uint16_t year)
59{
60 if (year & 3) {
61 return false;
62 } else {
63 return true;
64 }
65}
66
67/** \brief calculate the seconds in specified year/month
68 * \retval 0 month error.
69 */
70static uint32_t get_secs_in_month(uint32_t year, uint8_t month)
71{
72 uint32_t sec_in_month = 0;
73
74 if (leap_year(year)) {
75 switch (month) {
76 case 1:
77 case 3:
78 case 5:
79 case 7:
80 case 8:
81 case 10:
82 case 12:
83 sec_in_month = SECS_IN_31DAYS;
84 break;
85 case 2:
86 sec_in_month = SECS_IN_29DAYS;
87 break;
88 case 4:
89 case 6:
90 case 9:
91 case 11:
92 sec_in_month = SECS_IN_30DAYS;
93 break;
94 default:
95 break;
96 }
97 } else {
98 switch (month) {
99 case 1:
100 case 3:
101 case 5:
102 case 7:
103 case 8:
104 case 10:
105 case 12:
106 sec_in_month = SECS_IN_31DAYS;
107 break;
108 case 2:
109 sec_in_month = SECS_IN_28DAYS;
110 break;
111 case 4:
112 case 6:
113 case 9:
114 case 11:
115 sec_in_month = SECS_IN_30DAYS;
116 break;
117 default:
118 break;
119 }
120 }
121
122 return sec_in_month;
123}
124
125/** \brief convert timestamp to date/time
126 */
127static int32_t convert_timestamp_to_datetime(struct calendar_descriptor *const calendar, uint32_t ts,
128 struct calendar_date_time *dt)
129{
130 uint32_t tmp, sec_in_year, sec_in_month;
131 uint32_t tmp_year = calendar->base_year;
132 uint8_t tmp_month = 1;
133 uint8_t tmp_day = 1;
134 uint8_t tmp_hour = 0;
135 uint8_t tmp_minutes = 0;
136
137 tmp = ts;
138
139 /* Find year */
140 while (true) {
141 sec_in_year = leap_year(tmp_year) ? SECS_IN_LEAP_YEAR : SECS_IN_NON_LEAP_YEAR;
142
143 if (tmp >= sec_in_year) {
144 tmp -= sec_in_year;
145 tmp_year++;
146 } else {
147 break;
148 }
149 }
150 /* Find month of year */
151 while (true) {
152 sec_in_month = get_secs_in_month(tmp_year, tmp_month);
153
154 if (tmp >= sec_in_month) {
155 tmp -= sec_in_month;
156 tmp_month++;
157 } else {
158 break;
159 }
160 }
161 /* Find day of month */
162 while (true) {
163 if (tmp >= SECS_IN_DAY) {
164 tmp -= SECS_IN_DAY;
165 tmp_day++;
166 } else {
167 break;
168 }
169 }
170 /* Find hour of day */
171 while (true) {
172 if (tmp >= SECS_IN_HOUR) {
173 tmp -= SECS_IN_HOUR;
174 tmp_hour++;
175 } else {
176 break;
177 }
178 }
179 /* Find minute in hour */
180 while (true) {
181 if (tmp >= SECS_IN_MINUTE) {
182 tmp -= SECS_IN_MINUTE;
183 tmp_minutes++;
184 } else {
185 break;
186 }
187 }
188
189 dt->date.year = tmp_year;
190 dt->date.month = tmp_month;
191 dt->date.day = tmp_day;
192 dt->time.hour = tmp_hour;
193 dt->time.min = tmp_minutes;
194 dt->time.sec = tmp;
195
196 return ERR_NONE;
197}
198
199/** \brief convert date/time to timestamp
200 * \return timestamp
201 */
202static uint32_t convert_datetime_to_timestamp(struct calendar_descriptor *const calendar, struct calendar_date_time *dt)
203{
204 uint32_t tmp = 0;
205 uint32_t i = 0;
206 uint8_t year, month, day, hour, minutes, seconds;
207
208 year = dt->date.year - calendar->base_year;
209 month = dt->date.month;
210 day = dt->date.day;
211 hour = dt->time.hour;
212 minutes = dt->time.min;
213 seconds = dt->time.sec;
214
215 /* tot up year field */
216 for (i = 0; i < year; ++i) {
217 if (leap_year(calendar->base_year + i)) {
218 tmp += SECS_IN_LEAP_YEAR;
219 } else {
220 tmp += SECS_IN_NON_LEAP_YEAR;
221 }
222 }
223
224 /* tot up month field */
225 for (i = 1; i < month; ++i) {
226 tmp += get_secs_in_month(dt->date.year, i);
227 }
228
229 /* tot up day/hour/minute/second fields */
230 tmp += (day - 1) * SECS_IN_DAY;
231 tmp += hour * SECS_IN_HOUR;
232 tmp += minutes * SECS_IN_MINUTE;
233 tmp += seconds;
234
235 return tmp;
236}
237
238/** \brief calibrate timestamp to make desired timestamp ahead of current timestamp
239 */
240static void calibrate_timestamp(struct calendar_descriptor *const calendar, struct calendar_alarm *alarm,
241 struct calendar_alarm *current_dt)
242{
243 uint32_t alarm_ts;
244 uint32_t current_ts = current_dt->cal_alarm.timestamp;
245
246 alarm_ts = alarm->cal_alarm.timestamp;
247
248 /* calibrate timestamp */
249 switch (alarm->cal_alarm.option) {
250 case CALENDAR_ALARM_MATCH_SEC:
251
252 if (alarm_ts <= current_ts) {
253 alarm_ts += SECS_IN_MINUTE;
254 }
255
256 break;
257 case CALENDAR_ALARM_MATCH_MIN:
258
259 if (alarm_ts <= current_ts) {
260 alarm_ts += SECS_IN_HOUR;
261 }
262
263 break;
264 case CALENDAR_ALARM_MATCH_HOUR:
265
266 if (alarm_ts <= current_ts) {
267 alarm_ts += SECS_IN_DAY;
268 }
269
270 break;
271 case CALENDAR_ALARM_MATCH_DAY:
272
273 if (alarm_ts <= current_ts) {
274 alarm_ts += get_secs_in_month(current_dt->cal_alarm.datetime.date.year,
275 current_dt->cal_alarm.datetime.date.month);
276 }
277
278 break;
279 case CALENDAR_ALARM_MATCH_MONTH:
280
281 if (alarm_ts <= current_ts) {
282 if (leap_year(current_dt->cal_alarm.datetime.date.year)) {
283 alarm_ts += SECS_IN_LEAP_YEAR;
284 } else {
285 alarm_ts += SECS_IN_NON_LEAP_YEAR;
286 }
287 }
288
289 break;
290 /* do nothing for year match */
291 case CALENDAR_ALARM_MATCH_YEAR:
292 default:
293 break;
294 }
295
296 /* desired timestamp after calibration */
297 alarm->cal_alarm.timestamp = alarm_ts;
298}
299
300/** \brief complete alarm to absolute date/time, then fill up the timestamp
301 */
302static void fill_alarm(struct calendar_descriptor *const calendar, struct calendar_alarm *alarm)
303{
304 struct calendar_alarm current_dt;
305 uint32_t tmp, current_ts;
306
307 /* get current date/time */
308 current_ts = _calendar_get_counter(&calendar->device);
309 convert_timestamp_to_datetime(calendar, current_ts, &current_dt.cal_alarm.datetime);
310
311 current_dt.cal_alarm.timestamp = current_ts;
312
313 /* complete alarm */
314 switch (alarm->cal_alarm.option) {
315 case CALENDAR_ALARM_MATCH_SEC:
316 alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year;
317 alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month;
318 alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day;
319 alarm->cal_alarm.datetime.time.hour = current_dt.cal_alarm.datetime.time.hour;
320 alarm->cal_alarm.datetime.time.min = current_dt.cal_alarm.datetime.time.min;
321 break;
322 case CALENDAR_ALARM_MATCH_MIN:
323 alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year;
324 alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month;
325 alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day;
326 alarm->cal_alarm.datetime.time.hour = current_dt.cal_alarm.datetime.time.hour;
327 break;
328 case CALENDAR_ALARM_MATCH_HOUR:
329 alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year;
330 alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month;
331 alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day;
332 break;
333 case CALENDAR_ALARM_MATCH_DAY:
334 alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year;
335 alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month;
336 break;
337 case CALENDAR_ALARM_MATCH_MONTH:
338 alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year;
339 break;
340 case CALENDAR_ALARM_MATCH_YEAR:
341 break;
342 default:
343 break;
344 }
345
346 /* fill up the timestamp */
347 tmp = convert_datetime_to_timestamp(calendar, &alarm->cal_alarm.datetime);
348 alarm->cal_alarm.timestamp = tmp;
349
350 /* calibrate the timestamp */
351 calibrate_timestamp(calendar, alarm, &current_dt);
352 convert_timestamp_to_datetime(calendar, alarm->cal_alarm.timestamp, &alarm->cal_alarm.datetime);
353}
354
355/** \brief add new alarm into the list in ascending order
356 */
357static int32_t calendar_add_new_alarm(struct list_descriptor *list, struct calendar_alarm *alarm)
358{
359 struct calendar_descriptor *calendar = CONTAINER_OF(list, struct calendar_descriptor, alarms);
360 struct calendar_alarm * head, *it, *prev = NULL;
361
362 /*get the head of alarms list*/
363 head = (struct calendar_alarm *)list_get_head(list);
364
365 /*if head is null, insert new alarm as head*/
366 if (!head) {
367 list_insert_as_head(list, alarm);
368 _calendar_set_comp(&calendar->device, alarm->cal_alarm.timestamp);
369 return ERR_NONE;
370 }
371
372 /*insert the new alarm in accending order, the head will be invoked firstly */
373 for (it = head; it; it = (struct calendar_alarm *)list_get_next_element(it)) {
374 if (alarm->cal_alarm.timestamp <= it->cal_alarm.timestamp) {
375 break;
376 }
377
378 prev = it;
379 }
380
381 /*insert new alarm into the list */
382 if (it == head) {
383 list_insert_as_head(list, alarm);
384 /*get the head and set it into register*/
385 _calendar_set_comp(&calendar->device, alarm->cal_alarm.timestamp);
386
387 } else {
388 list_insert_after(prev, alarm);
389 }
390
391 return ERR_NONE;
392}
393
394/** \brief callback for alarm
395 */
396static void calendar_alarm(struct calendar_dev *const dev)
397{
398 struct calendar_descriptor *calendar = CONTAINER_OF(dev, struct calendar_descriptor, device);
399
400 struct calendar_alarm *head, *it, current_dt;
401
402 if ((calendar->flags & SET_ALARM_BUSY) || (calendar->flags & PROCESS_ALARM_BUSY)) {
403 calendar->flags |= PROCESS_ALARM_BUSY;
404 return;
405 }
406
407 /* get current timestamp */
408 current_dt.cal_alarm.timestamp = _calendar_get_counter(dev);
409
410 /* get the head */
411 head = (struct calendar_alarm *)list_get_head(&calendar->alarms);
412 ASSERT(head);
413
414 /* remove all alarms and invoke them*/
415 for (it = head; it; it = (struct calendar_alarm *)list_get_head(&calendar->alarms)) {
416 /* check the timestamp with current timestamp*/
417 if (it->cal_alarm.timestamp <= current_dt.cal_alarm.timestamp) {
418 list_remove_head(&calendar->alarms);
419 it->callback(calendar);
420
421 if (it->cal_alarm.mode == REPEAT) {
422 calibrate_timestamp(calendar, it, &current_dt);
423 convert_timestamp_to_datetime(calendar, it->cal_alarm.timestamp, &it->cal_alarm.datetime);
424 calendar_add_new_alarm(&calendar->alarms, it);
425 }
426 } else {
427 break;
428 }
429 }
430
431 /*if no alarm in the list, register null */
432 if (!it) {
433 _calendar_register_callback(&calendar->device, NULL);
434 return;
435 }
436
437 /*put the new head into register */
438 _calendar_set_comp(&calendar->device, it->cal_alarm.timestamp);
439}
440
441/** \brief Initialize Calendar
442 */
443int32_t calendar_init(struct calendar_descriptor *const calendar, const void *hw)
444{
445 int32_t ret = 0;
446
447 /* Sanity check arguments */
448 ASSERT(calendar);
449
450 if (calendar->device.hw == hw) {
451 /* Already initialized with current configuration */
452 return ERR_NONE;
453 } else if (calendar->device.hw != NULL) {
454 /* Initialized with another configuration */
455 return ERR_ALREADY_INITIALIZED;
456 }
457 calendar->device.hw = (void *)hw;
458 ret = _calendar_init(&calendar->device);
459 calendar->base_year = DEFAULT_BASE_YEAR;
460
461 return ret;
462}
463
464/** \brief Reset the Calendar
465 */
466int32_t calendar_deinit(struct calendar_descriptor *const calendar)
467{
468 /* Sanity check arguments */
469 ASSERT(calendar);
470
471 if (calendar->device.hw == NULL) {
472 return ERR_NOT_INITIALIZED;
473 }
474 _calendar_deinit(&calendar->device);
475 calendar->device.hw = NULL;
476
477 return ERR_NONE;
478}
479
480/** \brief Enable the Calendar
481 */
482int32_t calendar_enable(struct calendar_descriptor *const calendar)
483{
484 /* Sanity check arguments */
485 ASSERT(calendar);
486
487 _calendar_enable(&calendar->device);
488
489 return ERR_NONE;
490}
491
492/** \brief Disable the Calendar
493 */
494int32_t calendar_disable(struct calendar_descriptor *const calendar)
495{
496 /* Sanity check arguments */
497 ASSERT(calendar);
498
499 _calendar_disable(&calendar->device);
500
501 return ERR_NONE;
502}
503
504/** \brief Set base year for calendar
505 */
506int32_t calendar_set_baseyear(struct calendar_descriptor *const calendar, const uint32_t p_base_year)
507{
508 /* Sanity check arguments */
509 ASSERT(calendar);
510
511 calendar->base_year = p_base_year;
512
513 return ERR_NONE;
514}
515
516/** \brief Set time for calendar
517 */
518int32_t calendar_set_time(struct calendar_descriptor *const calendar, struct calendar_time *const p_calendar_time)
519{
520 struct calendar_date_time dt;
521 uint32_t current_ts, new_ts;
522
523 /* Sanity check arguments */
524 ASSERT(calendar);
525
526 /* convert time to timestamp */
527 current_ts = _calendar_get_counter(&calendar->device);
528 convert_timestamp_to_datetime(calendar, current_ts, &dt);
529 dt.time.sec = p_calendar_time->sec;
530 dt.time.min = p_calendar_time->min;
531 dt.time.hour = p_calendar_time->hour;
532
533 new_ts = convert_datetime_to_timestamp(calendar, &dt);
534
535 _calendar_set_counter(&calendar->device, new_ts);
536
537 return ERR_NONE;
538}
539
540/** \brief Set date for calendar
541 */
542int32_t calendar_set_date(struct calendar_descriptor *const calendar, struct calendar_date *const p_calendar_date)
543{
544 struct calendar_date_time dt;
545 uint32_t current_ts, new_ts;
546
547 /* Sanity check arguments */
548 ASSERT(calendar);
549
550 /* convert date to timestamp */
551 current_ts = _calendar_get_counter(&calendar->device);
552 convert_timestamp_to_datetime(calendar, current_ts, &dt);
553 dt.date.day = p_calendar_date->day;
554 dt.date.month = p_calendar_date->month;
555 dt.date.year = p_calendar_date->year;
556
557 new_ts = convert_datetime_to_timestamp(calendar, &dt);
558
559 _calendar_set_counter(&calendar->device, new_ts);
560
561 return ERR_NONE;
562}
563
564/** \brief Get date/time for calendar
565 */
566int32_t calendar_get_date_time(struct calendar_descriptor *const calendar, struct calendar_date_time *const date_time)
567{
568 uint32_t current_ts;
569
570 /* Sanity check arguments */
571 ASSERT(calendar);
572
573 /* convert current timestamp to date/time */
574 current_ts = _calendar_get_counter(&calendar->device);
575 convert_timestamp_to_datetime(calendar, current_ts, date_time);
576
577 return ERR_NONE;
578}
579
580/** \brief Set alarm for calendar
581 */
582int32_t calendar_set_alarm(struct calendar_descriptor *const calendar, struct calendar_alarm *const alarm,
583 calendar_cb_alarm_t callback)
584{
585 struct calendar_alarm *head;
586
587 /* Sanity check arguments */
588 ASSERT(calendar);
589 ASSERT(alarm);
590
591 alarm->callback = callback;
592
593 fill_alarm(calendar, alarm);
594
595 calendar->flags |= SET_ALARM_BUSY;
596
597 head = (struct calendar_alarm *)list_get_head(&calendar->alarms);
598
599 if (head != NULL) {
600 /* already added */
601 if (is_list_element(&calendar->alarms, alarm)) {
602 if (callback == NULL) {
603 /* remove alarm */
604 list_delete_element(&calendar->alarms, alarm);
605
606 if (!list_get_head(&calendar->alarms)) {
607 _calendar_register_callback(&calendar->device, NULL);
608 }
609 } else {
610 /* re-add */
611 list_delete_element(&calendar->alarms, alarm);
612 calendar_add_new_alarm(&calendar->alarms, alarm);
613 }
614 } else if (callback != NULL) {
615 calendar_add_new_alarm(&calendar->alarms, alarm);
616 }
617
618 calendar->flags &= ~SET_ALARM_BUSY;
619
620 if (calendar->flags & PROCESS_ALARM_BUSY) {
621 CRITICAL_SECTION_ENTER()
622 calendar->flags &= ~PROCESS_ALARM_BUSY;
623 _calendar_set_irq(&calendar->device);
624 CRITICAL_SECTION_LEAVE()
625 }
626 } else if (callback != NULL) {
627 /* if head is NULL, Register callback*/
628 _calendar_register_callback(&calendar->device, calendar_alarm);
629 calendar_add_new_alarm(&calendar->alarms, alarm);
630 }
631
632 calendar->flags &= ~SET_ALARM_BUSY;
633
634 return ERR_NONE;
635}
636
637/** \brief Retrieve driver version
638 * \return Current driver version
639 */
640uint32_t calendar_get_version(void)
641{
642 return CALENDAR_VERSION;
643}