| /** |
| * \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 <utils.h> |
| #include <utils_assert.h> |
| #include <hal_atomic.h> |
| |
| #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; |
| } |