/** * \file * * \brief Generic CALENDAR functionality implementation. * * Copyright (c) 2014-2018 Microchip Technology Inc. and its subsidiaries. * * \asf_license_start * * \page License * * Subject to your compliance with these terms, you may use Microchip * software and any derivatives exclusively with Microchip products. * It is your responsibility to comply with third party license terms applicable * to your use of third party software (including open source software) that * may accompany Microchip software. * * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. * * \asf_license_stop * */ #include "hal_calendar.h" #include #include #include #define CALENDAR_VERSION 0x00000001u #define SECS_IN_LEAP_YEAR 31622400 #define SECS_IN_NON_LEAP_YEAR 31536000 #define SECS_IN_31DAYS 2678400 #define SECS_IN_30DAYS 2592000 #define SECS_IN_29DAYS 2505600 #define SECS_IN_28DAYS 2419200 #define SECS_IN_DAY 86400 #define SECS_IN_HOUR 3600 #define SECS_IN_MINUTE 60 #define DEFAULT_BASE_YEAR 1970 #define SET_ALARM_BUSY 1 #define PROCESS_ALARM_BUSY 2 /** \brief leap year check * \retval false not leap year. * \retval true leap year. */ static bool leap_year(uint16_t year) { if (year & 3) { return false; } else { return true; } } /** \brief calculate the seconds in specified year/month * \retval 0 month error. */ static uint32_t get_secs_in_month(uint32_t year, uint8_t month) { uint32_t sec_in_month = 0; if (leap_year(year)) { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: sec_in_month = SECS_IN_31DAYS; break; case 2: sec_in_month = SECS_IN_29DAYS; break; case 4: case 6: case 9: case 11: sec_in_month = SECS_IN_30DAYS; break; default: break; } } else { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: sec_in_month = SECS_IN_31DAYS; break; case 2: sec_in_month = SECS_IN_28DAYS; break; case 4: case 6: case 9: case 11: sec_in_month = SECS_IN_30DAYS; break; default: break; } } return sec_in_month; } /** \brief convert timestamp to date/time */ static int32_t convert_timestamp_to_datetime(struct calendar_descriptor *const calendar, uint32_t ts, struct calendar_date_time *dt) { uint32_t tmp, sec_in_year, sec_in_month; uint32_t tmp_year = calendar->base_year; uint8_t tmp_month = 1; uint8_t tmp_day = 1; uint8_t tmp_hour = 0; uint8_t tmp_minutes = 0; tmp = ts; /* Find year */ while (true) { sec_in_year = leap_year(tmp_year) ? SECS_IN_LEAP_YEAR : SECS_IN_NON_LEAP_YEAR; if (tmp >= sec_in_year) { tmp -= sec_in_year; tmp_year++; } else { break; } } /* Find month of year */ while (true) { sec_in_month = get_secs_in_month(tmp_year, tmp_month); if (tmp >= sec_in_month) { tmp -= sec_in_month; tmp_month++; } else { break; } } /* Find day of month */ while (true) { if (tmp >= SECS_IN_DAY) { tmp -= SECS_IN_DAY; tmp_day++; } else { break; } } /* Find hour of day */ while (true) { if (tmp >= SECS_IN_HOUR) { tmp -= SECS_IN_HOUR; tmp_hour++; } else { break; } } /* Find minute in hour */ while (true) { if (tmp >= SECS_IN_MINUTE) { tmp -= SECS_IN_MINUTE; tmp_minutes++; } else { break; } } dt->date.year = tmp_year; dt->date.month = tmp_month; dt->date.day = tmp_day; dt->time.hour = tmp_hour; dt->time.min = tmp_minutes; dt->time.sec = tmp; return ERR_NONE; } /** \brief convert date/time to timestamp * \return timestamp */ static uint32_t convert_datetime_to_timestamp(struct calendar_descriptor *const calendar, struct calendar_date_time *dt) { uint32_t tmp = 0; uint32_t i = 0; uint8_t year, month, day, hour, minutes, seconds; year = dt->date.year - calendar->base_year; month = dt->date.month; day = dt->date.day; hour = dt->time.hour; minutes = dt->time.min; seconds = dt->time.sec; /* tot up year field */ for (i = 0; i < year; ++i) { if (leap_year(calendar->base_year + i)) { tmp += SECS_IN_LEAP_YEAR; } else { tmp += SECS_IN_NON_LEAP_YEAR; } } /* tot up month field */ for (i = 1; i < month; ++i) { tmp += get_secs_in_month(dt->date.year, i); } /* tot up day/hour/minute/second fields */ tmp += (day - 1) * SECS_IN_DAY; tmp += hour * SECS_IN_HOUR; tmp += minutes * SECS_IN_MINUTE; tmp += seconds; return tmp; } /** \brief calibrate timestamp to make desired timestamp ahead of current timestamp */ static void calibrate_timestamp(struct calendar_descriptor *const calendar, struct calendar_alarm *alarm, struct calendar_alarm *current_dt) { uint32_t alarm_ts; uint32_t current_ts = current_dt->cal_alarm.timestamp; alarm_ts = alarm->cal_alarm.timestamp; /* calibrate timestamp */ switch (alarm->cal_alarm.option) { case CALENDAR_ALARM_MATCH_SEC: if (alarm_ts <= current_ts) { alarm_ts += SECS_IN_MINUTE; } break; case CALENDAR_ALARM_MATCH_MIN: if (alarm_ts <= current_ts) { alarm_ts += SECS_IN_HOUR; } break; case CALENDAR_ALARM_MATCH_HOUR: if (alarm_ts <= current_ts) { alarm_ts += SECS_IN_DAY; } break; case CALENDAR_ALARM_MATCH_DAY: if (alarm_ts <= current_ts) { alarm_ts += get_secs_in_month(current_dt->cal_alarm.datetime.date.year, current_dt->cal_alarm.datetime.date.month); } break; case CALENDAR_ALARM_MATCH_MONTH: if (alarm_ts <= current_ts) { if (leap_year(current_dt->cal_alarm.datetime.date.year)) { alarm_ts += SECS_IN_LEAP_YEAR; } else { alarm_ts += SECS_IN_NON_LEAP_YEAR; } } break; /* do nothing for year match */ case CALENDAR_ALARM_MATCH_YEAR: default: break; } /* desired timestamp after calibration */ alarm->cal_alarm.timestamp = alarm_ts; } /** \brief complete alarm to absolute date/time, then fill up the timestamp */ static void fill_alarm(struct calendar_descriptor *const calendar, struct calendar_alarm *alarm) { struct calendar_alarm current_dt; uint32_t tmp, current_ts; /* get current date/time */ current_ts = _calendar_get_counter(&calendar->device); convert_timestamp_to_datetime(calendar, current_ts, ¤t_dt.cal_alarm.datetime); current_dt.cal_alarm.timestamp = current_ts; /* complete alarm */ switch (alarm->cal_alarm.option) { case CALENDAR_ALARM_MATCH_SEC: alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year; alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month; alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day; alarm->cal_alarm.datetime.time.hour = current_dt.cal_alarm.datetime.time.hour; alarm->cal_alarm.datetime.time.min = current_dt.cal_alarm.datetime.time.min; break; case CALENDAR_ALARM_MATCH_MIN: alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year; alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month; alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day; alarm->cal_alarm.datetime.time.hour = current_dt.cal_alarm.datetime.time.hour; break; case CALENDAR_ALARM_MATCH_HOUR: alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year; alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month; alarm->cal_alarm.datetime.date.day = current_dt.cal_alarm.datetime.date.day; break; case CALENDAR_ALARM_MATCH_DAY: alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year; alarm->cal_alarm.datetime.date.month = current_dt.cal_alarm.datetime.date.month; break; case CALENDAR_ALARM_MATCH_MONTH: alarm->cal_alarm.datetime.date.year = current_dt.cal_alarm.datetime.date.year; break; case CALENDAR_ALARM_MATCH_YEAR: break; default: break; } /* fill up the timestamp */ tmp = convert_datetime_to_timestamp(calendar, &alarm->cal_alarm.datetime); alarm->cal_alarm.timestamp = tmp; /* calibrate the timestamp */ calibrate_timestamp(calendar, alarm, ¤t_dt); convert_timestamp_to_datetime(calendar, alarm->cal_alarm.timestamp, &alarm->cal_alarm.datetime); } /** \brief add new alarm into the list in ascending order */ static int32_t calendar_add_new_alarm(struct list_descriptor *list, struct calendar_alarm *alarm) { struct calendar_descriptor *calendar = CONTAINER_OF(list, struct calendar_descriptor, alarms); struct calendar_alarm * head, *it, *prev = NULL; /*get the head of alarms list*/ head = (struct calendar_alarm *)list_get_head(list); /*if head is null, insert new alarm as head*/ if (!head) { list_insert_as_head(list, alarm); _calendar_set_comp(&calendar->device, alarm->cal_alarm.timestamp); return ERR_NONE; } /*insert the new alarm in accending order, the head will be invoked firstly */ for (it = head; it; it = (struct calendar_alarm *)list_get_next_element(it)) { if (alarm->cal_alarm.timestamp <= it->cal_alarm.timestamp) { break; } prev = it; } /*insert new alarm into the list */ if (it == head) { list_insert_as_head(list, alarm); /*get the head and set it into register*/ _calendar_set_comp(&calendar->device, alarm->cal_alarm.timestamp); } else { list_insert_after(prev, alarm); } return ERR_NONE; } /** \brief callback for alarm */ static void calendar_alarm(struct calendar_dev *const dev) { struct calendar_descriptor *calendar = CONTAINER_OF(dev, struct calendar_descriptor, device); struct calendar_alarm *head, *it, current_dt; if ((calendar->flags & SET_ALARM_BUSY) || (calendar->flags & PROCESS_ALARM_BUSY)) { calendar->flags |= PROCESS_ALARM_BUSY; return; } /* get current timestamp */ current_dt.cal_alarm.timestamp = _calendar_get_counter(dev); /* get the head */ head = (struct calendar_alarm *)list_get_head(&calendar->alarms); ASSERT(head); /* remove all alarms and invoke them*/ for (it = head; it; it = (struct calendar_alarm *)list_get_head(&calendar->alarms)) { /* check the timestamp with current timestamp*/ if (it->cal_alarm.timestamp <= current_dt.cal_alarm.timestamp) { list_remove_head(&calendar->alarms); it->callback(calendar); if (it->cal_alarm.mode == REPEAT) { calibrate_timestamp(calendar, it, ¤t_dt); convert_timestamp_to_datetime(calendar, it->cal_alarm.timestamp, &it->cal_alarm.datetime); calendar_add_new_alarm(&calendar->alarms, it); } } else { break; } } /*if no alarm in the list, register null */ if (!it) { _calendar_register_callback(&calendar->device, NULL); return; } /*put the new head into register */ _calendar_set_comp(&calendar->device, it->cal_alarm.timestamp); } /** \brief Initialize Calendar */ int32_t calendar_init(struct calendar_descriptor *const calendar, const void *hw) { int32_t ret = 0; /* Sanity check arguments */ ASSERT(calendar); if (calendar->device.hw == hw) { /* Already initialized with current configuration */ return ERR_NONE; } else if (calendar->device.hw != NULL) { /* Initialized with another configuration */ return ERR_ALREADY_INITIALIZED; } calendar->device.hw = (void *)hw; ret = _calendar_init(&calendar->device); calendar->base_year = DEFAULT_BASE_YEAR; return ret; } /** \brief Reset the Calendar */ int32_t calendar_deinit(struct calendar_descriptor *const calendar) { /* Sanity check arguments */ ASSERT(calendar); if (calendar->device.hw == NULL) { return ERR_NOT_INITIALIZED; } _calendar_deinit(&calendar->device); calendar->device.hw = NULL; return ERR_NONE; } /** \brief Enable the Calendar */ int32_t calendar_enable(struct calendar_descriptor *const calendar) { /* Sanity check arguments */ ASSERT(calendar); _calendar_enable(&calendar->device); return ERR_NONE; } /** \brief Disable the Calendar */ int32_t calendar_disable(struct calendar_descriptor *const calendar) { /* Sanity check arguments */ ASSERT(calendar); _calendar_disable(&calendar->device); return ERR_NONE; } /** \brief Set base year for calendar */ int32_t calendar_set_baseyear(struct calendar_descriptor *const calendar, const uint32_t p_base_year) { /* Sanity check arguments */ ASSERT(calendar); calendar->base_year = p_base_year; return ERR_NONE; } /** \brief Set time for calendar */ int32_t calendar_set_time(struct calendar_descriptor *const calendar, struct calendar_time *const p_calendar_time) { struct calendar_date_time dt; uint32_t current_ts, new_ts; /* Sanity check arguments */ ASSERT(calendar); /* convert time to timestamp */ current_ts = _calendar_get_counter(&calendar->device); convert_timestamp_to_datetime(calendar, current_ts, &dt); dt.time.sec = p_calendar_time->sec; dt.time.min = p_calendar_time->min; dt.time.hour = p_calendar_time->hour; new_ts = convert_datetime_to_timestamp(calendar, &dt); _calendar_set_counter(&calendar->device, new_ts); return ERR_NONE; } /** \brief Set date for calendar */ int32_t calendar_set_date(struct calendar_descriptor *const calendar, struct calendar_date *const p_calendar_date) { struct calendar_date_time dt; uint32_t current_ts, new_ts; /* Sanity check arguments */ ASSERT(calendar); /* convert date to timestamp */ current_ts = _calendar_get_counter(&calendar->device); convert_timestamp_to_datetime(calendar, current_ts, &dt); dt.date.day = p_calendar_date->day; dt.date.month = p_calendar_date->month; dt.date.year = p_calendar_date->year; new_ts = convert_datetime_to_timestamp(calendar, &dt); _calendar_set_counter(&calendar->device, new_ts); return ERR_NONE; } /** \brief Get date/time for calendar */ int32_t calendar_get_date_time(struct calendar_descriptor *const calendar, struct calendar_date_time *const date_time) { uint32_t current_ts; /* Sanity check arguments */ ASSERT(calendar); /* convert current timestamp to date/time */ current_ts = _calendar_get_counter(&calendar->device); convert_timestamp_to_datetime(calendar, current_ts, date_time); return ERR_NONE; } /** \brief Set alarm for calendar */ int32_t calendar_set_alarm(struct calendar_descriptor *const calendar, struct calendar_alarm *const alarm, calendar_cb_alarm_t callback) { struct calendar_alarm *head; /* Sanity check arguments */ ASSERT(calendar); ASSERT(alarm); alarm->callback = callback; fill_alarm(calendar, alarm); calendar->flags |= SET_ALARM_BUSY; head = (struct calendar_alarm *)list_get_head(&calendar->alarms); if (head != NULL) { /* already added */ if (is_list_element(&calendar->alarms, alarm)) { if (callback == NULL) { /* remove alarm */ list_delete_element(&calendar->alarms, alarm); if (!list_get_head(&calendar->alarms)) { _calendar_register_callback(&calendar->device, NULL); } } else { /* re-add */ list_delete_element(&calendar->alarms, alarm); calendar_add_new_alarm(&calendar->alarms, alarm); } } else if (callback != NULL) { calendar_add_new_alarm(&calendar->alarms, alarm); } calendar->flags &= ~SET_ALARM_BUSY; if (calendar->flags & PROCESS_ALARM_BUSY) { CRITICAL_SECTION_ENTER() calendar->flags &= ~PROCESS_ALARM_BUSY; _calendar_set_irq(&calendar->device); CRITICAL_SECTION_LEAVE() } } else if (callback != NULL) { /* if head is NULL, Register callback*/ _calendar_register_callback(&calendar->device, calendar_alarm); calendar_add_new_alarm(&calendar->alarms, alarm); } calendar->flags &= ~SET_ALARM_BUSY; return ERR_NONE; } /** \brief Retrieve driver version * \return Current driver version */ uint32_t calendar_get_version(void) { return CALENDAR_VERSION; }