blob: ff0843460c4bf4603339e70ac46904cd970cf8f0 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ----------------------------------------------------------------------------
*/
/** \addtogroup rtc_module Working with RTC
* \section Purpose
* The RTC driver provides the interface to configure and use the RTC
* peripheral.
*
* It manages date, time, and alarms.\n
* This timer is clocked by the 32kHz system clock, and is not impacted by
* power management settings (PMC). To be accurate, it is better to use an
* external 32kHz crystal instead of the internal 32kHz RC.\n
*
* It uses BCD format, and time can be set in AM/PM or 24h mode through a
* configuration bit in the mode register.\n
*
* To update date or time, the user has to follow these few steps :
* <ul>
* <li>Set UPDTIM and/or UPDCAL bit(s) in RTC_CR,</li>
* <li>Polling or IRQ on the ACKUPD bit of RTC_CR,</li>
* <li>Clear ACKUPD bit in RTC_SCCR,</li>
* <li>Update Time and/or Calendar values in RTC_TIMR/RTC_CALR (BCD format),</li>
* <li>Clear UPDTIM and/or UPDCAL bit in RTC_CR.</li>
* </ul>
* An alarm can be set to happen on month, date, hours, minutes or seconds,
* by setting the proper "Enable" bit of each of these fields in the Time and
* Calendar registers.
* This allows a large number of configurations to be available for the user.
* Alarm occurence can be detected even by polling or interrupt.
*
* A check of the validity of the date and time format and values written by the user is automatically done.
* Errors are reported through the Valid Entry Register.
*
* \section Usage
* <ul>
* <li> Enable & disable RTC interrupt using rtc_enable_it() and rtc_disable_it().
* <li> Set RTC data, time, alarm using rtc_set_date(), rtc_set_time(),
* rtc_set_time_alarm() and rtc_set_date_alarm().
* <li> Get RTC data, time using rtc_get_date() and rtc_get_time().
* </li>
* </ul>
*
* For more accurate information, please look at the RTC section of the
* Datasheet.
*
* Related files :\n
* \ref rtc.c\n
* \ref rtc.h.\n
*/
/*@{*/
/*@}*/
/**
* \file
*
* Implementation of Real Time Clock (RTC) controller.
*
*/
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "chip.h"
#include "peripherals/rtc.h"
#include "trace.h"
#include <stdint.h>
#include <assert.h>
/*----------------------------------------------------------------------------
* Local Defines
*----------------------------------------------------------------------------*/
/* The BCD code shift value */
#define BCD_SHIFT 4
/* The BCD code mask value */
#define BCD_MASK 0xfu
/* The BCD mul/div factor value */
#define BCD_FACTOR 10
/*----------------------------------------------------------------------------
* Local Types
*----------------------------------------------------------------------------*/
struct rtc_ppm_lookup {
int8_t tempr;
int16_t ppm;
uint8_t negppm;
uint8_t highppm;
uint16_t correction;
};
//------------------------------------------------------------------------------
// Local constants
//------------------------------------------------------------------------------
static const struct rtc_ppm_lookup ppm_lookup[] = {
{-40, -168, 0, 1, 22},
{-39, -163, 0, 1, 23},
{-38, -158, 0, 1, 24},
{-37, -153, 0, 1, 25},
{-36, -148, 0, 1, 25},
{-35, -143, 0, 1, 26},
{-34, -138, 0, 1, 27},
{-33, -134, 0, 1, 28},
{-32, -129, 0, 1, 29},
{-31, -124, 0, 1, 31},
{-30, -120, 0, 1, 32},
{-29, -116, 0, 1, 33},
{-28, -111, 0, 1, 34},
{-27, -107, 0, 1, 36},
{-26, -103, 0, 1, 37},
{-25, -99, 0, 1, 38},
{-24, -95, 0, 1, 40},
{-23, -91, 0, 1, 42},
{-22, -87, 0, 1, 44},
{-21, -84, 0, 1, 45},
{-20, -80, 0, 1, 48},
{-19, -76, 0, 1, 50},
{-18, -73, 0, 1, 53},
{-17, -70, 0, 1, 55},
{-16, -66, 0, 1, 58},
{-15, -63, 0, 1, 61},
{-14, -60, 0, 1, 64},
{-13, -57, 0, 1, 68},
{-12, -54, 0, 1, 71},
{-11, -51, 0, 1, 76},
{-10, -48, 0, 1, 80},
{-9, -45, 0, 1, 86},
{-8, -43, 0, 1, 90},
{-7, -40, 0, 1, 97},
{-6, -37, 0, 1, 105},
{-5, -35, 0, 1, 111},
{-4, -33, 0, 1, 117},
{-3, -30, 0, 0, 6},
{-2, -28, 0, 0, 6},
{-1, -26, 0, 0, 7},
{0, -24, 0, 0, 7},
{1, -22, 0, 0, 8},
{2, -20, 0, 0, 9},
{3, -18, 0, 0, 10},
{4, -17, 0, 0, 10},
{5, -15, 0, 0, 12},
{6, -13, 0, 0, 14},
{7, -12, 0, 0, 15},
{8, -11, 0, 0, 17},
{9, -9, 0, 0, 21},
{10, -8, 0, 0, 23},
{11, -7, 0, 0, 27},
{12, -6, 0, 0, 32},
{13, -5, 0, 0, 38},
{14, -4, 0, 0, 48},
{15, -3, 0, 0, 64},
{16, -2, 0, 0, 97},
{17, -2, 0, 0, 97},
{18, -1, 0, 0, 127},
{19, 0, 1, 0, 0},
{20, 0, 1, 0, 0},
{21, 0, 1, 0, 0},
{22, 1, 1, 0, 127},
{23, 1, 1, 0, 127},
{24, 1, 1, 0, 127},
{25, 1, 1, 0, 127},
{26, 1, 1, 0, 127},
{27, 1, 1, 0, 127},
{28, 1, 1, 0, 127},
{29, 0, 1, 0, 0},
{30, 0, 1, 0, 0},
{31, 0, 1, 0, 0},
{32, -1, 0, 0, 127},
{33, -2, 0, 0, 97},
{34, -2, 0, 0, 97},
{35, -3, 0, 0, 64},
{36, -4, 0, 0, 48},
{37, -5, 0, 0, 38},
{38, -6, 0, 0, 32},
{39, -7, 0, 0, 27},
{40, -8, 0, 0, 23},
{41, -9, 0, 0, 21},
{42, -11, 0, 0, 17},
{43, -12, 0, 0, 15},
{44, -13, 0, 0, 14},
{45, -15, 0, 0, 12},
{46, -17, 0, 0, 10},
{47, -18, 0, 0, 10},
{48, -20, 0, 0, 9},
{49, -22, 0, 0, 8},
{50, -24, 0, 0, 7},
{51, -26, 0, 0, 7},
{52, -28, 0, 0, 6},
{53, -30, 0, 0, 6},
{54, -33, 0, 1, 117},
{55, -35, 0, 1, 111},
{56, -37, 0, 1, 105},
{57, -40, 0, 1, 97},
{58, -43, 0, 1, 90},
{59, -45, 0, 1, 86},
{60, -48, 0, 1, 80},
{61, -51, 0, 1, 76},
{62, -54, 0, 1, 71},
{63, -57, 0, 1, 68},
{64, -60, 0, 1, 64},
{65, -63, 0, 1, 61},
{66, -66, 0, 1, 58},
{67, -70, 0, 1, 55},
{68, -73, 0, 1, 53},
{69, -76, 0, 1, 50},
{70, -80, 0, 1, 48},
{71, -84, 0, 1, 45},
{72, -87, 0, 1, 44},
{73, -91, 0, 1, 42},
{74, -95, 0, 1, 40},
{75, -99, 0, 1, 38},
{76, -103, 0, 1, 37},
{77, -107, 0, 1, 36},
{78, -111, 0, 1, 34},
{79, -116, 0, 1, 33},
{80, -120, 0, 1, 32},
{81, -124, 0, 1, 31},
{82, -129, 0, 1, 29},
{83, -134, 0, 1, 28},
{84, -138, 0, 1, 27},
{85, -143, 0, 1, 26}
};
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
/**
* \brief Sets the RTC in either 12 or 24 hour mode.
*
* \param dwMode Hour mode.
*/
void rtc_set_hour_mode(uint32_t mode)
{
assert((mode & 0xFFFFFFFE) == 0);
RTC->RTC_MR = mode;
}
extern uint32_t rtc_get_hour_mode(void)
{
uint32_t mode;
mode = RTC->RTC_MR;
mode &= 0xFFFFFFFE;
return mode;
}
void rtc_enable_it(uint32_t sources)
{
assert((sources & (uint32_t) (~0x1F)) == 0);
RTC->RTC_IER = sources;
}
void rtc_disable_it(uint32_t sources)
{
assert((sources & (uint32_t) (~0x1F)) == 0);
RTC->RTC_IDR = sources;
}
uint32_t rtc_set_time(struct _time *time)
{
uint32_t ltime = 0;
uint8_t hour_bcd , min_bcd, sec_bcd;
/* if 12-hour mode, set AMPM bit */
if ((RTC->RTC_MR & RTC_MR_HRMOD) == RTC_MR_HRMOD) {
if (time->hour > 12) {
time->hour -= 12;
ltime |= RTC_TIMR_AMPM;
}
}
hour_bcd = (time->hour % 10) | ((time->hour / 10) << 4);
min_bcd = (time->min % 10) | ((time->min / 10) << 4);
sec_bcd = (time->sec % 10) | ((time->sec / 10) << 4);
/* value overflow */
if ((hour_bcd & (uint8_t) (~RTC_HOUR_BIT_LEN_MASK)) |
(min_bcd & (uint8_t) (~RTC_MIN_BIT_LEN_MASK)) |
(sec_bcd & (uint8_t) (~RTC_SEC_BIT_LEN_MASK))) {
return 1;
}
ltime = sec_bcd | (min_bcd << 8) | (hour_bcd << 16);
RTC->RTC_CR |= RTC_CR_UPDTIM;
while ((RTC->RTC_SR & RTC_SR_ACKUPD) != RTC_SR_ACKUPD) ;
RTC->RTC_SCCR = RTC_SCCR_ACKCLR;
RTC->RTC_TIMR = ltime;
RTC->RTC_CR &= (uint32_t) (~RTC_CR_UPDTIM);
RTC->RTC_SCCR |= RTC_SCCR_SECCLR;
return (uint32_t) (RTC->RTC_VER & RTC_VER_NVTIM);
}
void rtc_get_time(struct _time *time)
{
uint32_t ltime;
/* Get current RTC time */
ltime = RTC->RTC_TIMR;
while (ltime != RTC->RTC_TIMR) {
ltime = RTC->RTC_TIMR;
}
/* Hour */
time->hour = ((ltime & 0x00300000) >> 20) * 10 + ((ltime & 0x000F0000) >> 16);
if ((ltime & RTC_TIMR_AMPM) == RTC_TIMR_AMPM) {
time->hour += 12;
}
/* Minute */
time->min = ((ltime & 0x00007000) >> 12) * 10 + ((ltime & 0x00000F00) >> 8);
/* Second */
time->sec = ((ltime & 0x00000070) >> 4) * 10 + (ltime & 0x0000000F);
}
uint32_t rtc_set_time_alarm(struct _time *time)
{
uint32_t alarm = 0;
/* Hour */
if (time->hour) {
alarm |= RTC_TIMALR_HOUREN | ((time->hour / 10) << 20) | ((time->hour % 10) << 16);
}
/* Minute */
if (time->min) {
alarm |= RTC_TIMALR_MINEN | ((time->min / 10) << 12) | ((time->min % 10) << 8);
}
/* Second */
if (time->sec) {
alarm |= RTC_TIMALR_SECEN | ((time->sec / 10) << 4) | (time->sec % 10);
}
RTC->RTC_TIMALR = alarm;
return (uint32_t) (RTC->RTC_VER & RTC_VER_NVTIMALR);
}
void rtc_get_date(struct _date *date)
{
uint32_t ldate;
/* Get current date (multiple reads are necessary to insure a stable value) */
do {
ldate = RTC->RTC_CALR;
} while (ldate != RTC->RTC_CALR);
/* Retrieve values */
date->year = (((ldate >> 4) & 0x7) * 1000) + ((ldate & 0xF) * 100)
+ (((ldate >> 12) & 0xF) * 10) + ((ldate >> 8) & 0xF);
date->month = (((ldate >> 20) & 1) * 10) + ((ldate >> 16) & 0xF);
date->day = (((ldate >> 28) & 0x3) * 10) + ((ldate >> 24) & 0xF);
date->week = ((ldate >> 21) & 0x7);
}
uint32_t rtc_set_date(struct _date *date)
{
uint32_t ldate;
uint8_t cent_bcd, year_bcd, month_bcd, day_bcd, week_bcd;
cent_bcd = ((date->year / 100) % 10) | ((date->year / 1000) << 4);
year_bcd = (date->year % 10) | (((date->year / 10) % 10) << 4);
month_bcd = ((date->month % 10) | (date->month / 10) << 4);
day_bcd = ((date->day % 10) | (date->day / 10) << 4);
week_bcd = ((date->week % 10) | (date->week / 10) << 4);
/* value over flow */
if ((cent_bcd & (uint8_t) (~RTC_CENT_BIT_LEN_MASK)) |
(year_bcd & (uint8_t) (~RTC_YEAR_BIT_LEN_MASK)) |
(month_bcd & (uint8_t) (~RTC_MONTH_BIT_LEN_MASK)) |
(week_bcd & (uint8_t) (~RTC_WEEK_BIT_LEN_MASK)) |
(day_bcd & (uint8_t) (~RTC_DATE_BIT_LEN_MASK))
) {
return 1;
}
/* Convert values to date register value */
ldate = cent_bcd | (year_bcd << 8) | (month_bcd << 16) | (week_bcd << 21) | (day_bcd << 24);
/* Update calendar register */
RTC->RTC_CR |= RTC_CR_UPDCAL;
while ((RTC->RTC_SR & RTC_SR_ACKUPD) != RTC_SR_ACKUPD) ;
RTC->RTC_SCCR = RTC_SCCR_ACKCLR;
RTC->RTC_CALR = ldate;
RTC->RTC_CR &= (uint32_t) (~RTC_CR_UPDCAL);
RTC->RTC_SCCR |= RTC_SCCR_SECCLR; /* clear SECENV in SCCR */
return (uint32_t) (RTC->RTC_VER & RTC_VER_NVCAL);
}
uint32_t rtc_set_date_alarm(struct _date *date)
{
uint32_t alarm;
alarm = ((date->month) || (date->day)) ? (0) : (0x01010000);
/* Compute alarm field value */
if (date->month) {
alarm |= RTC_CALALR_MTHEN | ((date->month / 10) << 20) | ((date->month % 10) << 16);
}
if (date->day) {
alarm |= RTC_CALALR_DATEEN | ((date->day / 10) << 28) | ((date->day % 10) << 24);
}
/* Set alarm */
RTC->RTC_CALALR = alarm;
return (uint32_t) (RTC->RTC_VER & RTC_VER_NVCALALR);
}
void rtc_clear_sccr(uint32_t mask)
{
/* Clear all flag bits in status clear command register */
mask &= RTC_SCCR_ACKCLR | RTC_SCCR_ALRCLR | RTC_SCCR_SECCLR |
RTC_SCCR_TIMCLR | RTC_SCCR_CALCLR;
RTC->RTC_SCCR = mask;
}
uint32_t rtc_get_sr(uint32_t mask)
{
return (RTC->RTC_SR) & mask;
}
void rtc_get_tamper_time(struct _time *time, uint8_t reg_num)
{
uint32_t ltime, temp;
/* Get current RTC time */
ltime = RTC->RTC_TS[reg_num].RTC_TSTR;
while (ltime != RTC->RTC_TS[reg_num].RTC_TSTR) {
ltime = RTC->RTC_TS[reg_num].RTC_TSTR;
}
/* Hour */
if (time->hour) {
temp = (ltime & RTC_TSTR_HOUR_Msk) >> RTC_TSTR_HOUR_Pos;
time->hour = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
if ((ltime & RTC_TSTR_AMPM) == RTC_TSTR_AMPM) {
time->hour += 12;
}
}
/* Minute */
if (time->min) {
temp = (ltime & RTC_TSTR_MIN_Msk) >> RTC_TSTR_MIN_Pos;
time->min = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
}
/* Second */
if (time->sec) {
temp = (ltime & RTC_TSTR_SEC_Msk) >> RTC_TSTR_SEC_Pos;
time->sec = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
}
}
void rtc_get_tamper_date(struct _date *date, uint8_t reg_num)
{
uint32_t ldate, cent, temp;
/* Get the current date (multiple reads are to insure a stable value). */
ldate = RTC->RTC_TS[reg_num].RTC_TSDR;
while (ldate != RTC->RTC_TS[reg_num].RTC_TSDR) {
ldate = RTC->RTC_TS[reg_num].RTC_TSDR;
}
/* Retrieve year */
temp = (ldate & RTC_TSDR_CENT_Msk) >> RTC_TSDR_CENT_Pos;
cent = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
temp = (ldate & RTC_TSDR_YEAR_Msk) >> RTC_TSDR_YEAR_Pos;
date->year = (cent * BCD_FACTOR * BCD_FACTOR) + (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
/* Retrieve month */
temp = (ldate & RTC_TSDR_MONTH_Msk) >> RTC_TSDR_MONTH_Pos;
date->month = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
/* Retrieve day */
temp = (ldate & RTC_TSDR_DATE_Msk) >> RTC_TSDR_DATE_Pos;
date->day = (temp >> BCD_SHIFT) * BCD_FACTOR + (temp & BCD_MASK);
/* Retrieve week */
date->week= ((ldate & RTC_TSDR_DAY_Msk) >> RTC_TSDR_DAY_Pos);
}
uint32_t rtc_get_tamper_source(uint8_t reg_num)
{
return RTC->RTC_TS[reg_num].RTC_TSSR;
}
uint32_t rtc_get_tamper_event_counter(void)
{
return (RTC->RTC_TS[0].RTC_TSTR & RTC_TSTR_TEVCNT_Msk) >> RTC_TSTR_TEVCNT_Pos;
}
uint8_t rtc_is_tamper_occur_in_backup_mode(uint8_t reg_num)
{
if (RTC->RTC_TS[reg_num].RTC_TSTR & RTC_TSTR_BACKUP) {
return 1;
} else {
return 0;
}
}
void rtc_convert_time_to_hms(struct _time *time, uint32_t count)
{
count = count % 86400;
time->hour = count / 3600;
count -= time->hour * 3600;
time->min = count / 60;
time->sec = count % 60;
}
void rtc_calibration(int32_t current_tempr)
{
uint32_t i, mr;
for (i = 0; i < ARRAY_SIZE(ppm_lookup); i++) {
if (ppm_lookup[i].tempr == current_tempr) {
mr = RTC_MR_CORRECTION(ppm_lookup[i].correction);
mr |= (ppm_lookup[i].highppm << 15);
mr |= (ppm_lookup[i].negppm << 4);
RTC->RTC_MR = mr; // update the calibration value
break;
}
}
}
uint32_t rtc_set_time_event (uint32_t mask)
{
uint32_t reg;
reg = RTC->RTC_CR;
reg &= ~RTC_CR_TIMEVSEL_Msk;
reg |= mask;
RTC->RTC_CR = reg;
return RTC->RTC_CR;
}
uint32_t rtc_set_calendar_event (uint32_t mask)
{
uint32_t reg;
reg = RTC->RTC_CR;
reg &= ~RTC_CR_CALEVSEL_Msk;
reg |= mask;
RTC->RTC_CR = reg;
return RTC->RTC_CR;
}