4c880f4b0a
RTC drivers should validate the `struct rtc_time`'s contents against the provided `mask`. Promote this common code to a new rtc_utils file and modify existing drivers to use this functionality. Extend the test coverage to include verifying this behaviour. This is groundwork ahead of adding support for the RP2040's (as used in the Raspberry Pi Pico) RTC and alarm. Signed-off-by: Andrew Featherstone <andrew.featherstone@gmail.com>
683 lines
16 KiB
C
683 lines
16 KiB
C
/*
|
|
* Copyright (c) 2023 Bjarki Arge Andreasen
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT atmel_sam_rtc
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/drivers/rtc.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
#include <string.h>
|
|
#include <soc.h>
|
|
|
|
#define RTC_SAM_REG_GET_FIELD(value, field) \
|
|
((RTC_##field##_Msk & value) >> RTC_##field##_Pos)
|
|
|
|
#define RTC_SAM_WPMR_DISABLE 0x52544300
|
|
#define RTC_SAM_WPMR_ENABLE 0x52544301
|
|
|
|
#define RTC_SAM_CALIBRATE_PPB_MAX (1950000)
|
|
#define RTC_SAM_CALIBRATE_PPB_MIN (-1950000)
|
|
#define RTC_SAM_CALIBRATE_PPB_QUANTA (1500)
|
|
#define RTC_SAM_CALIBRATE_PPB_LOW_SCALE (30500)
|
|
|
|
#define RTC_SAM_TIME_MASK \
|
|
(RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \
|
|
RTC_ALARM_TIME_MASK_MONTH | RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_YEAR | \
|
|
RTC_ALARM_TIME_MASK_WEEKDAY)
|
|
|
|
typedef void (*rtc_sam_irq_init_fn_ptr)(void);
|
|
|
|
struct rtc_sam_config {
|
|
Rtc *regs;
|
|
uint16_t irq_num;
|
|
rtc_sam_irq_init_fn_ptr irq_init_fn_ptr;
|
|
};
|
|
|
|
struct rtc_sam_data {
|
|
#ifdef CONFIG_RTC_ALARM
|
|
rtc_alarm_callback alarm_callback;
|
|
void *alarm_user_data;
|
|
#endif /* CONFIG_RTC_ALARM */
|
|
#ifdef CONFIG_RTC_UPDATE
|
|
rtc_update_callback update_callback;
|
|
void *update_user_data;
|
|
#endif /* CONFIG_RTC_UPDATE */
|
|
struct k_spinlock lock;
|
|
struct k_sem cr_sec_evt_sem;
|
|
struct k_sem cr_upd_ack_sem;
|
|
};
|
|
|
|
static void rtc_sam_disable_wp(void)
|
|
{
|
|
REG_RTC_WPMR = RTC_SAM_WPMR_DISABLE;
|
|
}
|
|
|
|
static void rtc_sam_enable_wp(void)
|
|
{
|
|
REG_RTC_WPMR = RTC_SAM_WPMR_ENABLE;
|
|
}
|
|
|
|
static uint32_t rtc_sam_timr_from_tm(const struct rtc_time *timeptr)
|
|
{
|
|
uint32_t timr;
|
|
|
|
timr = RTC_TIMR_SEC(bin2bcd(timeptr->tm_sec));
|
|
timr |= RTC_TIMR_MIN(bin2bcd(timeptr->tm_min));
|
|
timr |= RTC_TIMR_HOUR(bin2bcd(timeptr->tm_hour));
|
|
|
|
return timr;
|
|
}
|
|
|
|
static uint32_t rtc_sam_calr_from_tm(const struct rtc_time *timeptr)
|
|
{
|
|
uint32_t calr;
|
|
uint8_t centuries;
|
|
uint8_t years;
|
|
|
|
calr = RTC_CALR_DATE(bin2bcd(timeptr->tm_mday));
|
|
calr |= RTC_CALR_MONTH(bin2bcd(timeptr->tm_mon + 1));
|
|
centuries = (uint8_t)((timeptr->tm_year / 100) + 19);
|
|
years = (uint8_t)((timeptr->tm_year % 100));
|
|
calr |= RTC_CALR_CENT(bin2bcd(centuries));
|
|
calr |= RTC_CALR_YEAR(bin2bcd(years));
|
|
calr |= RTC_CALR_DAY(bin2bcd(timeptr->tm_wday + 1));
|
|
return calr;
|
|
}
|
|
|
|
static int rtc_sam_set_time(const struct device *dev, const struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
if (rtc_utils_validate_rtc_time(timeptr, RTC_SAM_TIME_MASK) == false) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
k_sem_reset(&data->cr_sec_evt_sem);
|
|
k_sem_take(&data->cr_sec_evt_sem, K_MSEC(1100));
|
|
k_sem_reset(&data->cr_upd_ack_sem);
|
|
|
|
/* Enable update acknowledge interrupt */
|
|
regs->RTC_IER = RTC_IER_ACKEN;
|
|
|
|
rtc_sam_disable_wp();
|
|
|
|
/* Request update */
|
|
regs->RTC_CR = (RTC_CR_UPDTIM | RTC_CR_UPDCAL);
|
|
|
|
/* Await update acknowledge */
|
|
if (k_sem_take(&data->cr_upd_ack_sem, K_MSEC(1100)) < 0) {
|
|
regs->RTC_CR = 0;
|
|
|
|
rtc_sam_enable_wp();
|
|
|
|
/* Disable update acknowledge interrupt */
|
|
regs->RTC_IDR = RTC_IDR_ACKDIS;
|
|
|
|
k_spin_unlock(&data->lock, key);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
regs->RTC_TIMR = rtc_sam_timr_from_tm(timeptr);
|
|
regs->RTC_CALR = rtc_sam_calr_from_tm(timeptr);
|
|
regs->RTC_CR = 0;
|
|
rtc_sam_enable_wp();
|
|
regs->RTC_IDR = RTC_IDR_ACKDIS;
|
|
k_spin_unlock(&data->lock, key);
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_sam_get_time(const struct device *dev, struct rtc_time *timeptr)
|
|
{
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
uint32_t timr0;
|
|
uint32_t calr0;
|
|
uint32_t timr1;
|
|
uint32_t calr1;
|
|
|
|
/* Validate time and date */
|
|
if (regs->RTC_VER & (RTC_VER_NVTIM | RTC_VER_NVCAL)) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* Read until synchronized (registers updated async) */
|
|
while (1) {
|
|
timr0 = regs->RTC_TIMR;
|
|
calr0 = regs->RTC_CALR;
|
|
timr1 = regs->RTC_TIMR;
|
|
calr1 = regs->RTC_CALR;
|
|
|
|
if ((timr0 == timr1) && (calr0 == calr1)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
timeptr->tm_sec = bcd2bin(RTC_SAM_REG_GET_FIELD(timr0, TIMR_SEC));
|
|
timeptr->tm_min = bcd2bin(RTC_SAM_REG_GET_FIELD(timr0, TIMR_MIN));
|
|
timeptr->tm_hour = bcd2bin(RTC_SAM_REG_GET_FIELD(timr0, TIMR_HOUR));
|
|
timeptr->tm_mday = bcd2bin(RTC_SAM_REG_GET_FIELD(calr0, CALR_DATE));
|
|
timeptr->tm_mon = bcd2bin(RTC_SAM_REG_GET_FIELD(calr0, CALR_MONTH)) - 1;
|
|
|
|
timeptr->tm_year = bcd2bin(RTC_SAM_REG_GET_FIELD(calr0, CALR_YEAR));
|
|
timeptr->tm_year += ((int)bcd2bin(RTC_SAM_REG_GET_FIELD(calr0, CALR_CENT))) * 100;
|
|
timeptr->tm_year -= 1900;
|
|
|
|
timeptr->tm_wday = bcd2bin(RTC_SAM_REG_GET_FIELD(calr0, CALR_DAY)) - 1;
|
|
timeptr->tm_yday = -1;
|
|
timeptr->tm_isdst = -1;
|
|
timeptr->tm_nsec = 0;
|
|
return 0;
|
|
}
|
|
|
|
static void rtc_sam_isr(const struct device *dev)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
uint32_t sr = regs->RTC_SR;
|
|
|
|
if (sr & RTC_SR_ACKUPD) {
|
|
regs->RTC_SCCR = RTC_SCCR_ACKCLR;
|
|
k_sem_give(&data->cr_upd_ack_sem);
|
|
}
|
|
|
|
#ifdef CONFIG_RTC_ALARM
|
|
if (sr & RTC_SR_ALARM) {
|
|
regs->RTC_SCCR = RTC_SCCR_ALRCLR;
|
|
if (data->alarm_callback != NULL) {
|
|
data->alarm_callback(dev, 0, data->alarm_user_data);
|
|
}
|
|
}
|
|
#endif /* CONFIG_RTC_ALARM */
|
|
|
|
#ifdef CONFIG_RTC_UPDATE
|
|
if (sr & RTC_SR_SEC) {
|
|
regs->RTC_SCCR = RTC_SCCR_SECCLR;
|
|
if (data->update_callback != NULL) {
|
|
data->update_callback(dev, data->update_user_data);
|
|
}
|
|
|
|
k_sem_give(&data->cr_sec_evt_sem);
|
|
}
|
|
#endif /* CONFIG_RTC_UPDATE */
|
|
}
|
|
|
|
#ifdef CONFIG_RTC_ALARM
|
|
static uint16_t rtc_sam_alarm_get_supported_mask(void)
|
|
{
|
|
return (RTC_ALARM_TIME_MASK_SECOND
|
|
| RTC_ALARM_TIME_MASK_MINUTE
|
|
| RTC_ALARM_TIME_MASK_HOUR
|
|
| RTC_ALARM_TIME_MASK_MONTHDAY
|
|
| RTC_ALARM_TIME_MASK_MONTH);
|
|
}
|
|
|
|
static uint32_t rtc_atmel_timalr_from_tm(const struct rtc_time *timeptr, uint32_t mask)
|
|
{
|
|
uint32_t timalr = 0;
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
|
timalr |= RTC_TIMALR_SECEN;
|
|
timalr |= RTC_TIMALR_SEC(bin2bcd(timeptr->tm_sec));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
|
timalr |= RTC_TIMALR_MINEN;
|
|
timalr |= RTC_TIMALR_MIN(bin2bcd(timeptr->tm_min));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
|
timalr |= RTC_TIMALR_HOUREN;
|
|
timalr |= RTC_TIMALR_HOUR(bin2bcd(timeptr->tm_hour));
|
|
}
|
|
|
|
return timalr;
|
|
}
|
|
|
|
static uint32_t rtc_atmel_calalr_from_tm(const struct rtc_time *timeptr, uint32_t mask)
|
|
{
|
|
uint32_t calalr = RTC_CALALR_MONTH(1) | RTC_CALALR_DATE(1);
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) {
|
|
calalr |= RTC_CALALR_MTHEN;
|
|
calalr |= RTC_CALALR_MONTH(bin2bcd(timeptr->tm_mon + 1));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
|
calalr |= RTC_CALALR_DATEEN;
|
|
calalr |= RTC_CALALR_DATE(bin2bcd(timeptr->tm_mday));
|
|
}
|
|
|
|
return calalr;
|
|
}
|
|
|
|
static uint32_t rtc_sam_alarm_mask_from_timalr(uint32_t timalr)
|
|
{
|
|
uint32_t mask = 0;
|
|
|
|
if (timalr & RTC_TIMALR_SECEN) {
|
|
mask |= RTC_ALARM_TIME_MASK_SECOND;
|
|
}
|
|
|
|
if (timalr & RTC_TIMALR_MINEN) {
|
|
mask |= RTC_ALARM_TIME_MASK_MINUTE;
|
|
}
|
|
|
|
if (timalr & RTC_TIMALR_HOUREN) {
|
|
mask |= RTC_ALARM_TIME_MASK_HOUR;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static uint32_t rtc_sam_alarm_mask_from_calalr(uint32_t calalr)
|
|
{
|
|
uint32_t mask = 0;
|
|
|
|
if (calalr & RTC_CALALR_MTHEN) {
|
|
mask |= RTC_ALARM_TIME_MASK_MONTH;
|
|
}
|
|
|
|
if (calalr & RTC_CALALR_DATEEN) {
|
|
mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static void rtc_sam_tm_from_timalr_calalr(struct rtc_time *timeptr, uint32_t mask,
|
|
uint32_t timalr, uint32_t calalr)
|
|
{
|
|
memset(timeptr, 0x00, sizeof(*timeptr));
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
|
timeptr->tm_sec = bcd2bin(RTC_SAM_REG_GET_FIELD(timalr, TIMALR_SEC));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
|
timeptr->tm_min = bcd2bin(RTC_SAM_REG_GET_FIELD(timalr, TIMALR_MIN));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
|
timeptr->tm_hour = bcd2bin(RTC_SAM_REG_GET_FIELD(timalr, TIMALR_HOUR));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
|
timeptr->tm_mday = bcd2bin(RTC_SAM_REG_GET_FIELD(calalr, CALALR_DATE));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) {
|
|
timeptr->tm_mon = bcd2bin(RTC_SAM_REG_GET_FIELD(calalr, CALALR_MONTH)) - 1;
|
|
}
|
|
}
|
|
|
|
static int rtc_sam_alarm_get_supported_fields(const struct device *dev, uint16_t id,
|
|
uint16_t *mask)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(id);
|
|
|
|
*mask = rtc_sam_alarm_get_supported_mask();
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_sam_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
|
|
const struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
uint32_t timalr;
|
|
uint32_t calalr;
|
|
uint32_t mask_supported;
|
|
|
|
mask_supported = rtc_sam_alarm_get_supported_mask();
|
|
|
|
if ((id != 0)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((mask > 0) && (timeptr == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mask & ~mask_supported) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rtc_sam_validate_tm(timeptr, mask) == false) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
timalr = rtc_atmel_timalr_from_tm(timeptr, mask);
|
|
calalr = rtc_atmel_calalr_from_tm(timeptr, mask);
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
irq_disable(config->irq_num);
|
|
rtc_sam_disable_wp();
|
|
|
|
/* Set RTC alarm time */
|
|
regs->RTC_TIMALR = timalr;
|
|
regs->RTC_CALALR = calalr;
|
|
|
|
rtc_sam_enable_wp();
|
|
|
|
/* Clear alarm pending status */
|
|
regs->RTC_SCCR = RTC_SCCR_ALRCLR;
|
|
|
|
irq_enable(config->irq_num);
|
|
k_spin_unlock(&data->lock, key);
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_sam_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
|
|
struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
uint32_t timalr;
|
|
uint32_t calalr;
|
|
|
|
if ((id != 0) || (mask == NULL) || (timeptr == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
timalr = regs->RTC_TIMALR;
|
|
calalr = regs->RTC_CALALR;
|
|
|
|
k_spin_unlock(&data->lock, key);
|
|
|
|
*mask = rtc_sam_alarm_mask_from_timalr(timalr);
|
|
*mask |= rtc_sam_alarm_mask_from_calalr(calalr);
|
|
|
|
rtc_sam_tm_from_timalr_calalr(timeptr, *mask, timalr, calalr);
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_sam_alarm_is_pending(const struct device *dev, uint16_t id)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
if (id != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
if ((regs->RTC_SR & RTC_SR_ALARM) == 0) {
|
|
k_spin_unlock(&data->lock, key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
regs->RTC_SCCR = RTC_SCCR_ALRCLR;
|
|
k_spin_unlock(&data->lock, key);
|
|
return 1;
|
|
}
|
|
|
|
static int rtc_sam_alarm_set_callback(const struct device *dev, uint16_t id,
|
|
rtc_alarm_callback callback, void *user_data)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
if (id != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
irq_disable(config->irq_num);
|
|
data->alarm_callback = callback;
|
|
data->alarm_user_data = user_data;
|
|
|
|
if (data->alarm_callback) {
|
|
regs->RTC_IER = RTC_IER_ALREN;
|
|
} else {
|
|
regs->RTC_IDR = RTC_IDR_ALRDIS;
|
|
}
|
|
|
|
irq_enable(config->irq_num);
|
|
k_spin_unlock(&data->lock, key);
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_RTC_ALARM */
|
|
|
|
#ifdef CONFIG_RTC_UPDATE
|
|
static int rtc_sam_update_set_callback(const struct device *dev, rtc_update_callback callback,
|
|
void *user_data)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
irq_disable(config->irq_num);
|
|
|
|
data->update_callback = callback;
|
|
data->update_user_data = user_data;
|
|
|
|
if (data->update_callback) {
|
|
regs->RTC_IER = RTC_IER_SECEN;
|
|
} else {
|
|
regs->RTC_IDR = RTC_IDR_SECDIS;
|
|
}
|
|
|
|
irq_enable(config->irq_num);
|
|
|
|
k_spin_unlock(&data->lock, key);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_RTC_UPDATE */
|
|
|
|
#ifdef CONFIG_RTC_CALIBRATION
|
|
static int rtc_sam_set_calibration(const struct device *dev, int32_t calibration)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
bool negative_calibration;
|
|
bool high_calibration;
|
|
uint32_t slow_clock_calibration;
|
|
uint32_t mr;
|
|
|
|
if ((calibration < RTC_SAM_CALIBRATE_PPB_MIN) ||
|
|
(calibration > RTC_SAM_CALIBRATE_PPB_MAX)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The value written to the register is absolute */
|
|
if (calibration < 0) {
|
|
negative_calibration = true;
|
|
calibration = -calibration;
|
|
} else {
|
|
negative_calibration = false;
|
|
}
|
|
|
|
/*
|
|
* Formula adapted from
|
|
* Atmel-11157-32-bit-Cortex-M4-Microcontroller-SAM4E16-SAM4E8_Datasheet.pdf
|
|
* section 15.6.2
|
|
*
|
|
* Formula if RTC_MR_HIGHPPM is 0
|
|
*
|
|
* RTC_MR_CORRECTION = (3906 / (20 * ppm)) - 1
|
|
*
|
|
* Formula if RTC_MR_HIGHPPM is 1
|
|
*
|
|
* RTC_MR_CORRECTION = (3906 / ppm) - 1
|
|
*
|
|
* Since we are working with ppb, we adapt the formula by increasing the
|
|
* terms of the fraction by 1000, turning the ppm into ppb
|
|
*
|
|
* Adapted formula if RTC_MR_HIGHPPM is 0
|
|
*
|
|
* RTC_MR_CORRECTION = (3906000 / (20 * ppb)) - 1
|
|
*
|
|
* Adapted formula if RTC_MR_HIGHPPM is 1
|
|
*
|
|
* RTC_MR_CORRECTION = (3906000 / ppb) - 1
|
|
*/
|
|
if (calibration < RTC_SAM_CALIBRATE_PPB_QUANTA) {
|
|
high_calibration = false;
|
|
slow_clock_calibration = 0;
|
|
} else if (calibration < RTC_SAM_CALIBRATE_PPB_LOW_SCALE) {
|
|
high_calibration = false;
|
|
slow_clock_calibration = (uint32_t)((3906000 / (20 * calibration)) - 1);
|
|
} else {
|
|
high_calibration = true;
|
|
slow_clock_calibration = (uint32_t)((3906000 / calibration) - 1);
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
|
|
|
rtc_sam_disable_wp();
|
|
|
|
mr = regs->RTC_MR;
|
|
|
|
if (negative_calibration == true) {
|
|
mr |= RTC_MR_NEGPPM;
|
|
} else {
|
|
mr &= ~RTC_MR_NEGPPM;
|
|
}
|
|
|
|
mr &= ~RTC_MR_CORRECTION_Msk;
|
|
mr |= RTC_MR_CORRECTION(slow_clock_calibration);
|
|
|
|
if (high_calibration == true) {
|
|
mr |= RTC_MR_HIGHPPM;
|
|
} else {
|
|
mr &= ~RTC_MR_HIGHPPM;
|
|
}
|
|
|
|
regs->RTC_MR = mr;
|
|
|
|
rtc_sam_enable_wp();
|
|
|
|
k_spin_unlock(&data->lock, key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_sam_get_calibration(const struct device *dev, int32_t *calibration)
|
|
{
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
uint32_t mr;
|
|
int32_t correction;
|
|
|
|
if (calibration == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
mr = regs->RTC_MR;
|
|
|
|
correction = (int32_t)RTC_SAM_REG_GET_FIELD(mr, MR_CORRECTION);
|
|
|
|
/* Formula documented in rtc_sam_set_calibration() */
|
|
if (correction == 0) {
|
|
*calibration = 0;
|
|
} else if (mr & RTC_MR_HIGHPPM) {
|
|
*calibration = 3906000 / (correction + 1);
|
|
} else {
|
|
*calibration = 3906000 / ((correction + 1) * 20);
|
|
}
|
|
|
|
if (mr & RTC_MR_NEGPPM) {
|
|
*calibration = -*calibration;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_RTC_CALIBRATION */
|
|
|
|
static const struct rtc_driver_api rtc_sam_driver_api = {
|
|
.set_time = rtc_sam_set_time,
|
|
.get_time = rtc_sam_get_time,
|
|
#ifdef CONFIG_RTC_ALARM
|
|
.alarm_get_supported_fields = rtc_sam_alarm_get_supported_fields,
|
|
.alarm_set_time = rtc_sam_alarm_set_time,
|
|
.alarm_get_time = rtc_sam_alarm_get_time,
|
|
.alarm_is_pending = rtc_sam_alarm_is_pending,
|
|
.alarm_set_callback = rtc_sam_alarm_set_callback,
|
|
#endif /* CONFIG_RTC_ALARM */
|
|
#ifdef CONFIG_RTC_UPDATE
|
|
.update_set_callback = rtc_sam_update_set_callback,
|
|
#endif /* CONFIG_RTC_UPDATE */
|
|
#ifdef CONFIG_RTC_CALIBRATION
|
|
.set_calibration = rtc_sam_set_calibration,
|
|
.get_calibration = rtc_sam_get_calibration,
|
|
#endif /* CONFIG_RTC_CALIBRATION */
|
|
};
|
|
|
|
static int rtc_sam_init(const struct device *dev)
|
|
{
|
|
struct rtc_sam_data *data = dev->data;
|
|
const struct rtc_sam_config *config = dev->config;
|
|
Rtc *regs = config->regs;
|
|
|
|
rtc_sam_disable_wp();
|
|
regs->RTC_MR &= ~(RTC_MR_HRMOD | RTC_MR_PERSIAN);
|
|
regs->RTC_CR = 0;
|
|
rtc_sam_enable_wp();
|
|
regs->RTC_IDR = (RTC_IDR_ACKDIS
|
|
| RTC_IDR_ALRDIS
|
|
| RTC_IDR_SECDIS
|
|
| RTC_IDR_TIMDIS
|
|
| RTC_IDR_CALDIS
|
|
| RTC_IDR_TDERRDIS);
|
|
|
|
k_sem_init(&data->cr_sec_evt_sem, 0, 1);
|
|
k_sem_init(&data->cr_upd_ack_sem, 0, 1);
|
|
config->irq_init_fn_ptr();
|
|
irq_enable(config->irq_num);
|
|
return 0;
|
|
}
|
|
|
|
#define RTC_SAM_DEVICE(id) \
|
|
static void rtc_sam_irq_init_##id(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), \
|
|
rtc_sam_isr, DEVICE_DT_INST_GET(id), 0); \
|
|
} \
|
|
\
|
|
static const struct rtc_sam_config rtc_sam_config_##id = { \
|
|
.regs = (Rtc *)DT_INST_REG_ADDR(id), \
|
|
.irq_num = DT_INST_IRQN(id), \
|
|
.irq_init_fn_ptr = rtc_sam_irq_init_##id, \
|
|
}; \
|
|
\
|
|
static struct rtc_sam_data rtc_sam_data_##id; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(id, rtc_sam_init, NULL, \
|
|
&rtc_sam_data_##id, \
|
|
&rtc_sam_config_##id, POST_KERNEL, \
|
|
CONFIG_RTC_INIT_PRIORITY, \
|
|
&rtc_sam_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RTC_SAM_DEVICE);
|