This commit should fix the followings:
1. When an alarm event is initialized, the driver should only
consume the valid alarm fields, based on the given mask.
Otherwise, the driver should use default valid subfield
values.
2. Setting an alarm event should not return with error code
if the timer and/or calendar counters have yet to be
initialized, explicitly. Instead, a log warning should be
issued.
Signed-off-by: Ioannis Karachalios <ioannis.karachalios.px@renesas.com>
664 lines
20 KiB
C
664 lines
20 KiB
C
/*
|
|
* Copyright (c) 2023 Renesas Electronics Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/rtc.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/smartbond_clock_control.h>
|
|
#include <DA1469xAB.h>
|
|
#include <da1469x_config.h>
|
|
#include <da1469x_pdc.h>
|
|
#include "rtc_utils.h"
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(rtc_smartbond, CONFIG_RTC_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT renesas_smartbond_rtc
|
|
|
|
#define SMARTBOND_IRQN DT_INST_IRQN(0)
|
|
#define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority)
|
|
|
|
#define RTC_ALARMS_COUNT DT_PROP(DT_NODELABEL(rtc), alarms_count)
|
|
|
|
#define TM_YEAR_REF 1900
|
|
#define RTC_DIV_DENOM_1000 0
|
|
#define RTC_DIV_DENOM_1024 1
|
|
|
|
#define RTC_SMARTBOND_SUPPORTED_ALARM_FIELDS \
|
|
(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)
|
|
|
|
#define RTC_TIME_REG_SET_FIELD(_field, _var, _val) \
|
|
((_var) = \
|
|
((_var) & ~(RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _U_Msk)) | \
|
|
(((_val) << RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _U_Pos) & \
|
|
(RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _U_Msk)))
|
|
|
|
#define RTC_CALENDAR_REG_SET_FIELD(_field, _var, _val) \
|
|
((_var) = \
|
|
((_var) & ~(RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _U_Msk)) | \
|
|
(((_val) << RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _U_Pos) & \
|
|
(RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _U_Msk)))
|
|
|
|
#define RTC_CALENDAR_ALARM_REG_SET_FIELD(_field, _var, _val) \
|
|
((_var) = \
|
|
((_var) & ~(RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _U_Msk)) | \
|
|
(((_val) << RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _U_Pos) & \
|
|
(RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _U_Msk)))
|
|
|
|
#define RTC_TIME_ALARM_REG_SET_FIELD(_field, _var, _val) \
|
|
((_var) = \
|
|
((_var) & ~(RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _U_Msk)) | \
|
|
(((_val) << RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _U_Pos) & \
|
|
(RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _U_Msk)))
|
|
|
|
#define RTC_TIME_REG_GET_FIELD(_field, _var) \
|
|
(((_var) & (RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _U_Msk)) >> \
|
|
RTC_RTC_TIME_REG_RTC_TIME_ ## _field ## _U_Pos)
|
|
|
|
#define RTC_CALENDAR_REG_GET_FIELD(_field, _var) \
|
|
(((_var) & (RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _U_Msk)) >> \
|
|
RTC_RTC_CALENDAR_REG_RTC_CAL_ ## _field ## _U_Pos)
|
|
|
|
#define RTC_CALENDAR_ALARM_REG_GET_FIELD(_field, _var) \
|
|
(((_var) & (RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _T_Msk | \
|
|
RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _U_Msk)) >> \
|
|
RTC_RTC_CALENDAR_ALARM_REG_RTC_CAL_ ## _field ## _U_Pos)
|
|
|
|
#define RTC_TIME_ALARM_REG_GET_FIELD(_field, _var) \
|
|
(((_var) & (RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _T_Msk | \
|
|
RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _U_Msk)) >> \
|
|
RTC_RTC_TIME_ALARM_REG_RTC_TIME_ ## _field ## _U_Pos)
|
|
|
|
#define CLK_RTCDIV_REG_SET_FIELD(_field, _var, _val) \
|
|
((_var) = \
|
|
((_var) & ~CRG_TOP_CLK_RTCDIV_REG_RTC_DIV_ ## _field ## _Msk) | \
|
|
(((_val) << CRG_TOP_CLK_RTCDIV_REG_RTC_DIV_ ## _field ## _Pos) & \
|
|
CRG_TOP_CLK_RTCDIV_REG_RTC_DIV_ ## _field ## _Msk))
|
|
|
|
struct rtc_smartbond_data {
|
|
struct k_mutex lock;
|
|
bool is_rtc_configured;
|
|
#if defined(CONFIG_RTC_ALARM)
|
|
volatile bool is_alarm_pending;
|
|
rtc_alarm_callback alarm_cb;
|
|
void *alarm_user_data;
|
|
#endif
|
|
#if defined(CONFIG_RTC_UPDATE)
|
|
rtc_update_callback update_cb;
|
|
void *update_user_data;
|
|
#endif
|
|
};
|
|
|
|
#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE)
|
|
static void smartbond_rtc_isr(const struct device *dev)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
/* Exercise which events asserted the RTC IRQ line. Register is cleared upon read. */
|
|
uint32_t rtc_event_flags_reg = RTC->RTC_EVENT_FLAGS_REG;
|
|
/* RTC_EVENT_FLASH_REG will be updated regardless of the interrupt mask. */
|
|
uint32_t rtc_interrupt_mask_reg = RTC->RTC_INTERRUPT_MASK_REG;
|
|
|
|
#if defined(CONFIG_RTC_ALARM)
|
|
if ((rtc_event_flags_reg & RTC_RTC_EVENT_FLAGS_REG_RTC_EVENT_ALRM_Msk) &&
|
|
!(rtc_interrupt_mask_reg & RTC_RTC_INTERRUPT_MASK_REG_RTC_ALRM_INT_MSK_Msk)) {
|
|
if (data->alarm_cb) {
|
|
data->alarm_cb(dev, 0, data->alarm_user_data);
|
|
data->is_alarm_pending = false;
|
|
} else {
|
|
data->is_alarm_pending = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_RTC_UPDATE)
|
|
if ((rtc_event_flags_reg & RTC_RTC_EVENT_FLAGS_REG_RTC_EVENT_SEC_Msk) &&
|
|
!(rtc_interrupt_mask_reg & RTC_RTC_INTERRUPT_MASK_REG_RTC_SEC_INT_MSK_Msk)) {
|
|
if (data->update_cb) {
|
|
data->update_cb(dev, data->update_user_data);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static inline void rtc_smartbond_set_status(bool status)
|
|
{
|
|
if (status) {
|
|
CRG_TOP->CLK_RTCDIV_REG |= CRG_TOP_CLK_RTCDIV_REG_RTC_DIV_ENABLE_Msk;
|
|
RTC->RTC_CONTROL_REG = 0;
|
|
} else {
|
|
RTC->RTC_CONTROL_REG = (RTC_RTC_CONTROL_REG_RTC_CAL_DISABLE_Msk |
|
|
RTC_RTC_CONTROL_REG_RTC_TIME_DISABLE_Msk);
|
|
CRG_TOP->CLK_RTCDIV_REG &= ~CRG_TOP_CLK_RTCDIV_REG_RTC_DIV_ENABLE_Msk;
|
|
}
|
|
}
|
|
|
|
static uint32_t rtc_time_to_bcd(const struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_time_reg = 0;
|
|
|
|
RTC_TIME_REG_SET_FIELD(S, rtc_time_reg, bin2bcd(timeptr->tm_sec)); /*[0, 59]*/
|
|
RTC_TIME_REG_SET_FIELD(M, rtc_time_reg, bin2bcd(timeptr->tm_min)); /*[0, 59]*/
|
|
RTC_TIME_REG_SET_FIELD(HR, rtc_time_reg, bin2bcd(timeptr->tm_hour)); /*[0, 23]*/
|
|
|
|
return rtc_time_reg;
|
|
}
|
|
|
|
static uint32_t rtc_calendar_to_bcd(const struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_calendar_reg = 0;
|
|
|
|
RTC_CALENDAR_REG_SET_FIELD(D, rtc_calendar_reg, bin2bcd(timeptr->tm_mday)); /*[1, 31]*/
|
|
RTC_CALENDAR_REG_SET_FIELD(Y, rtc_calendar_reg,
|
|
bin2bcd((timeptr->tm_year + TM_YEAR_REF) % 100)); /*[year - 1900]*/
|
|
RTC_CALENDAR_REG_SET_FIELD(C, rtc_calendar_reg,
|
|
bin2bcd((timeptr->tm_year + TM_YEAR_REF) / 100));
|
|
RTC_CALENDAR_REG_SET_FIELD(M, rtc_calendar_reg, bin2bcd(timeptr->tm_mon + 1)); /*[0, 11]*/
|
|
|
|
if (timeptr->tm_wday != -1) {
|
|
rtc_calendar_reg |= ((timeptr->tm_wday + 1) &
|
|
RTC_RTC_CALENDAR_REG_RTC_DAY_Msk); /*[0, 6]*/
|
|
}
|
|
|
|
return rtc_calendar_reg;
|
|
}
|
|
|
|
static void bcd_to_rtc_time(struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_time_reg = RTC->RTC_TIME_REG;
|
|
|
|
timeptr->tm_sec = bcd2bin(RTC_TIME_REG_GET_FIELD(S, rtc_time_reg));
|
|
timeptr->tm_min = bcd2bin(RTC_TIME_REG_GET_FIELD(M, rtc_time_reg));
|
|
timeptr->tm_hour = bcd2bin(RTC_TIME_REG_GET_FIELD(HR, rtc_time_reg));
|
|
|
|
timeptr->tm_nsec = 0; /*Unknown*/
|
|
}
|
|
|
|
static void bcd_to_rtc_calendar(struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_calendar_reg = RTC->RTC_CALENDAR_REG;
|
|
|
|
timeptr->tm_mday = bcd2bin(RTC_CALENDAR_REG_GET_FIELD(D, rtc_calendar_reg));
|
|
timeptr->tm_mon = bcd2bin(RTC_CALENDAR_REG_GET_FIELD(M, rtc_calendar_reg)) - 1;
|
|
timeptr->tm_year = bcd2bin(RTC_CALENDAR_REG_GET_FIELD(Y, rtc_calendar_reg)) +
|
|
(bcd2bin(RTC_CALENDAR_REG_GET_FIELD(C, rtc_calendar_reg)) * 100) - TM_YEAR_REF;
|
|
timeptr->tm_wday = (rtc_calendar_reg & RTC_RTC_CALENDAR_REG_RTC_DAY_Msk) - 1;
|
|
|
|
timeptr->tm_yday = timeptr->tm_isdst = -1; /*Unknown*/
|
|
}
|
|
|
|
static int rtc_smartbond_set_time(const struct device *dev, const struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
int ret = 0;
|
|
uint32_t rtc_time_reg, rtc_calendar_reg, rtc_status_reg;
|
|
|
|
if (timeptr == NULL) {
|
|
LOG_ERR("No pointer is provided to set time");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (timeptr->tm_year + TM_YEAR_REF < TM_YEAR_REF) {
|
|
LOG_ERR("RTC time exceeds HW capabilities");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((timeptr->tm_yday != -1) || (timeptr->tm_isdst != -1) || (timeptr->tm_nsec != 0)) {
|
|
LOG_WRN("Unsupported RTC sub-values");
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
rtc_smartbond_set_status(false);
|
|
|
|
/* Store current counter values as it might happen that the requested time is not valid */
|
|
rtc_time_reg = RTC->RTC_TIME_REG;
|
|
rtc_calendar_reg = RTC->RTC_CALENDAR_REG;
|
|
|
|
RTC->RTC_TIME_REG = rtc_time_to_bcd(timeptr);
|
|
RTC->RTC_CALENDAR_REG = rtc_calendar_to_bcd(timeptr);
|
|
|
|
/* Check if the new values were valid, otherwise reset back to the previous ones. */
|
|
rtc_status_reg = RTC->RTC_STATUS_REG;
|
|
if (!(rtc_status_reg & RTC_RTC_STATUS_REG_RTC_VALID_CAL_Msk) ||
|
|
!(rtc_status_reg & RTC_RTC_STATUS_REG_RTC_VALID_TIME_Msk)) {
|
|
RTC->RTC_TIME_REG = rtc_time_reg;
|
|
RTC->RTC_CALENDAR_REG = rtc_calendar_reg;
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* Mark the very first valid RTC configuration; used to check if RTC contains valid data. */
|
|
if (!data->is_rtc_configured && (ret == 0)) {
|
|
data->is_rtc_configured = true;
|
|
}
|
|
|
|
/* It might happen that the very first time RTC is not configured correctly; do not care. */
|
|
rtc_smartbond_set_status(true);
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rtc_smartbond_get_time(const struct device *dev, struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
|
|
if (timeptr == NULL) {
|
|
LOG_ERR("No pointer is provided to store the requested time");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data->is_rtc_configured) {
|
|
LOG_WRN("RTC is not initialized yet");
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
/* Stop RTC counters to obtain coherent data. */
|
|
rtc_smartbond_set_status(false);
|
|
|
|
bcd_to_rtc_time(timeptr);
|
|
bcd_to_rtc_calendar(timeptr);
|
|
|
|
rtc_smartbond_set_status(true);
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_RTC_ALARM)
|
|
BUILD_ASSERT(RTC_ALARMS_COUNT, "At least one alarm event should be supported");
|
|
|
|
/*
|
|
* Parse only the alarm fields indicated by the mask. Default valid values should be assigned
|
|
* to unused fields as it might happen that application has provided with invalid values.
|
|
*/
|
|
static uint32_t alarm_calendar_to_bcd(const struct rtc_time *timeptr, uint16_t mask)
|
|
{
|
|
uint32_t rtc_calendar_alarm_reg = 0x0108;
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
|
RTC_CALENDAR_ALARM_REG_SET_FIELD(D, rtc_calendar_alarm_reg,
|
|
bin2bcd(timeptr->tm_mday));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) {
|
|
RTC_CALENDAR_ALARM_REG_SET_FIELD(M, rtc_calendar_alarm_reg,
|
|
bin2bcd(timeptr->tm_mon + 1));
|
|
}
|
|
|
|
return rtc_calendar_alarm_reg;
|
|
}
|
|
|
|
/*
|
|
* Parse only the alarm fields indicated by the mask. Default valid values should be assigned
|
|
* to unused fields as it might happen that application has provided with invalid values.
|
|
*/
|
|
static inline uint32_t alarm_time_to_bcd(const struct rtc_time *timeptr, uint16_t mask)
|
|
{
|
|
uint32_t rtc_time_alarm_reg = 0;
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
|
/*[0, 59]*/
|
|
RTC_TIME_ALARM_REG_SET_FIELD(S, rtc_time_alarm_reg, bin2bcd(timeptr->tm_sec));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
|
/*[0, 59]*/
|
|
RTC_TIME_ALARM_REG_SET_FIELD(M, rtc_time_alarm_reg, bin2bcd(timeptr->tm_min));
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
|
/*[0, 23]*/
|
|
RTC_TIME_ALARM_REG_SET_FIELD(HR, rtc_time_alarm_reg, bin2bcd(timeptr->tm_hour));
|
|
}
|
|
|
|
return rtc_time_alarm_reg;
|
|
}
|
|
|
|
static void bcd_to_alarm_calendar(struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_calendar_alarm_reg = RTC->RTC_CALENDAR_ALARM_REG;
|
|
|
|
timeptr->tm_mday = bcd2bin(RTC_CALENDAR_ALARM_REG_GET_FIELD(D, rtc_calendar_alarm_reg));
|
|
timeptr->tm_mon = bcd2bin(RTC_CALENDAR_ALARM_REG_GET_FIELD(M, rtc_calendar_alarm_reg)) - 1;
|
|
|
|
timeptr->tm_yday = timeptr->tm_wday = timeptr->tm_isdst = timeptr->tm_year = -1;
|
|
}
|
|
|
|
static void bcd_to_alarm_time(struct rtc_time *timeptr)
|
|
{
|
|
uint32_t rtc_time_alarm_reg = RTC->RTC_TIME_ALARM_REG;
|
|
|
|
timeptr->tm_sec = bcd2bin(RTC_TIME_ALARM_REG_GET_FIELD(S, rtc_time_alarm_reg));
|
|
timeptr->tm_min = bcd2bin(RTC_TIME_ALARM_REG_GET_FIELD(M, rtc_time_alarm_reg));
|
|
timeptr->tm_hour = bcd2bin(RTC_TIME_ALARM_REG_GET_FIELD(HR, rtc_time_alarm_reg));
|
|
|
|
timeptr->tm_nsec = 0;
|
|
}
|
|
|
|
static uint32_t tm_to_rtc_alarm_mask(uint16_t mask)
|
|
{
|
|
uint32_t rtc_alarm_enable_reg = 0;
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
|
rtc_alarm_enable_reg |= RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_SEC_EN_Msk;
|
|
}
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
|
rtc_alarm_enable_reg |= RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_MIN_EN_Msk;
|
|
}
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
|
rtc_alarm_enable_reg |= RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_HOUR_EN_Msk;
|
|
}
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) {
|
|
rtc_alarm_enable_reg |= RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_MNTH_EN_Msk;
|
|
}
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
|
rtc_alarm_enable_reg |= RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_DATE_EN_Msk;
|
|
}
|
|
|
|
return rtc_alarm_enable_reg;
|
|
}
|
|
|
|
static uint16_t rtc_to_tm_alarm_mask(uint32_t rtc_alarm_enable_reg)
|
|
{
|
|
uint16_t mask = 0;
|
|
|
|
if (rtc_alarm_enable_reg & RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_SEC_EN_Msk) {
|
|
mask |= RTC_ALARM_TIME_MASK_SECOND;
|
|
}
|
|
if (rtc_alarm_enable_reg & RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_MIN_EN_Msk) {
|
|
mask |= RTC_ALARM_TIME_MASK_MINUTE;
|
|
}
|
|
if (rtc_alarm_enable_reg & RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_HOUR_EN_Msk) {
|
|
mask |= RTC_ALARM_TIME_MASK_HOUR;
|
|
}
|
|
if (rtc_alarm_enable_reg & RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_MNTH_EN_Msk) {
|
|
mask |= RTC_ALARM_TIME_MASK_MONTH;
|
|
}
|
|
if (rtc_alarm_enable_reg & RTC_RTC_ALARM_ENABLE_REG_RTC_ALARM_DATE_EN_Msk) {
|
|
mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static int rtc_smartbond_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
|
|
const struct rtc_time *timeptr)
|
|
{
|
|
int ret = 0;
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
uint32_t rtc_time_alarm_reg;
|
|
uint32_t rtc_calendar_alarm_reg;
|
|
uint32_t rtc_alarm_enable_reg;
|
|
uint32_t rtc_status_reg;
|
|
|
|
if (id >= RTC_ALARMS_COUNT) {
|
|
LOG_ERR("Alarm id is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mask & ~RTC_SMARTBOND_SUPPORTED_ALARM_FIELDS) {
|
|
LOG_ERR("Invalid alarm mask");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((timeptr == NULL) && (mask != 0)) {
|
|
LOG_ERR("No pointer is provided to set alarm");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!rtc_utils_validate_rtc_time(timeptr, mask)) {
|
|
LOG_ERR("Invalid alarm fields values");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data->is_rtc_configured) {
|
|
LOG_WRN("RTC is not initialized yet");
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
|
|
rtc_alarm_enable_reg = RTC->RTC_ALARM_ENABLE_REG;
|
|
|
|
/* Disable alarm to obtain coherency and/or when the alarm mask is empty */
|
|
RTC->RTC_ALARM_ENABLE_REG = 0;
|
|
RTC->RTC_INTERRUPT_DISABLE_REG = RTC_RTC_INTERRUPT_DISABLE_REG_RTC_ALRM_INT_DIS_Msk;
|
|
|
|
if (mask) {
|
|
/* Store current counter values as it might happen requested alrm is not valid */
|
|
rtc_time_alarm_reg = RTC->RTC_TIME_ALARM_REG;
|
|
rtc_calendar_alarm_reg = RTC->RTC_CALENDAR_ALARM_REG;
|
|
|
|
RTC->RTC_TIME_ALARM_REG = alarm_time_to_bcd(timeptr, mask);
|
|
RTC->RTC_CALENDAR_ALARM_REG = alarm_calendar_to_bcd(timeptr, mask);
|
|
|
|
rtc_status_reg = RTC->RTC_STATUS_REG;
|
|
if (!(rtc_status_reg & RTC_RTC_STATUS_REG_RTC_VALID_CAL_ALM_Msk) ||
|
|
!(rtc_status_reg & RTC_RTC_STATUS_REG_RTC_VALID_TIME_ALM_Msk)) {
|
|
RTC->RTC_TIME_ALARM_REG = rtc_time_alarm_reg;
|
|
RTC->RTC_CALENDAR_ALARM_REG = rtc_calendar_alarm_reg;
|
|
RTC->RTC_ALARM_ENABLE_REG = rtc_alarm_enable_reg;
|
|
ret = -EINVAL;
|
|
} else {
|
|
RTC->RTC_ALARM_ENABLE_REG = tm_to_rtc_alarm_mask(mask);
|
|
}
|
|
|
|
RTC->RTC_INTERRUPT_ENABLE_REG = RTC_RTC_INTERRUPT_ENABLE_REG_RTC_ALRM_INT_EN_Msk;
|
|
}
|
|
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rtc_smartbond_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
|
|
struct rtc_time *timeptr)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
|
|
if (id >= RTC_ALARMS_COUNT) {
|
|
LOG_ERR("Alarm id is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((timeptr == NULL) || (mask == NULL)) {
|
|
LOG_ERR("No pointer is provided to store the requested alarm time/mask");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data->is_rtc_configured) {
|
|
LOG_WRN("RTC is not initialized yet");
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
|
|
bcd_to_alarm_calendar(timeptr);
|
|
bcd_to_alarm_time(timeptr);
|
|
*mask = rtc_to_tm_alarm_mask(RTC->RTC_ALARM_ENABLE_REG);
|
|
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_smartbond_alarm_is_pending(const struct device *dev, uint16_t id)
|
|
{
|
|
unsigned int key;
|
|
int status;
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
|
|
if (id >= RTC_ALARMS_COUNT) {
|
|
LOG_ERR("Alarm id is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Globally disable interrupts as the status flag can be updated within ISR */
|
|
key = DA1469X_IRQ_DISABLE();
|
|
status = data->is_alarm_pending;
|
|
/* After reading, the alarm status should be cleared. */
|
|
data->is_alarm_pending = 0;
|
|
DA1469X_IRQ_ENABLE(key);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int rtc_smartbond_alarm_set_callback(const struct device *dev, uint16_t id,
|
|
rtc_alarm_callback callback, void *user_data)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
|
|
if (id >= RTC_ALARMS_COUNT) {
|
|
LOG_ERR("Alarm id is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
|
|
data->alarm_cb = callback;
|
|
data->alarm_user_data = user_data;
|
|
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtc_smartbond_alarm_get_supported_fields(const struct device *dev, uint16_t id,
|
|
uint16_t *mask)
|
|
{
|
|
if (id >= RTC_ALARMS_COUNT) {
|
|
LOG_ERR("Alarm id is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mask == NULL) {
|
|
LOG_ERR("Pointer to store the mask value is missed");
|
|
return -EINVAL;
|
|
}
|
|
|
|
*mask = (uint16_t)RTC_SMARTBOND_SUPPORTED_ALARM_FIELDS;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_RTC_UPDATE)
|
|
static int rtc_smartbond_update_set_callback(const struct device *dev, rtc_update_callback callback,
|
|
void *user_data)
|
|
{
|
|
struct rtc_smartbond_data *data = dev->data;
|
|
|
|
k_mutex_lock(&data->lock, K_FOREVER);
|
|
|
|
data->update_cb = callback;
|
|
data->update_user_data = user_data;
|
|
|
|
if (data->update_cb) {
|
|
/* Enable asserting the RTC interrupt line when the second counter rolls over. */
|
|
RTC->RTC_INTERRUPT_ENABLE_REG = RTC_RTC_INTERRUPT_ENABLE_REG_RTC_SEC_INT_EN_Msk;
|
|
} else {
|
|
RTC->RTC_INTERRUPT_DISABLE_REG = RTC_RTC_INTERRUPT_DISABLE_REG_RTC_SEC_INT_DIS_Msk;
|
|
}
|
|
|
|
k_mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct rtc_driver_api rtc_smartbond_driver_api = {
|
|
.get_time = rtc_smartbond_get_time,
|
|
.set_time = rtc_smartbond_set_time,
|
|
#if defined(CONFIG_RTC_ALARM)
|
|
.alarm_get_time = rtc_smartbond_alarm_get_time,
|
|
.alarm_set_time = rtc_smartbond_alarm_set_time,
|
|
.alarm_is_pending = rtc_smartbond_alarm_is_pending,
|
|
.alarm_set_callback = rtc_smartbond_alarm_set_callback,
|
|
.alarm_get_supported_fields = rtc_smartbond_alarm_get_supported_fields,
|
|
#endif
|
|
#if defined(CONFIG_RTC_UPDATE)
|
|
.update_set_callback = rtc_smartbond_update_set_callback,
|
|
#endif
|
|
};
|
|
|
|
static void rtc_smartbond_100HZ_clock_cfg(void)
|
|
{
|
|
const struct device * const dev = DEVICE_DT_GET(DT_NODELABEL(osc));
|
|
uint32_t lp_clk_rate;
|
|
uint32_t clk_rtcdiv_reg;
|
|
|
|
if (!device_is_ready(dev)) {
|
|
__ASSERT_MSG_INFO("Clock device is not ready");
|
|
}
|
|
|
|
if (clock_control_get_rate(dev, (clock_control_subsys_t)SMARTBOND_CLK_LP_CLK,
|
|
&lp_clk_rate) < 0) {
|
|
__ASSERT_MSG_INFO("Cannot extract LP clock rate");
|
|
}
|
|
|
|
clk_rtcdiv_reg = CRG_TOP->CLK_RTCDIV_REG;
|
|
CLK_RTCDIV_REG_SET_FIELD(DENOM, clk_rtcdiv_reg, RTC_DIV_DENOM_1000);
|
|
CLK_RTCDIV_REG_SET_FIELD(INT, clk_rtcdiv_reg, lp_clk_rate / 100);
|
|
CLK_RTCDIV_REG_SET_FIELD(FRAC, clk_rtcdiv_reg, (lp_clk_rate % 100) * 10);
|
|
CRG_TOP->CLK_RTCDIV_REG = clk_rtcdiv_reg;
|
|
}
|
|
|
|
static int rtc_smartbond_init(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
/* Wakeup device from RTC events (alarm/roll over) */
|
|
#if CONFIG_PM
|
|
bool is_xtal32m_enabled = DT_NODE_HAS_STATUS(DT_NODELABEL(xtal32m), okay);
|
|
int pdc_idx = da1469x_pdc_add(MCU_PDC_TRIGGER_RTC_ALARM, MCU_PDC_MASTER_M33,
|
|
is_xtal32m_enabled ? MCU_PDC_EN_XTAL : 0);
|
|
|
|
__ASSERT(pdc_idx >= 0, "Failed to add RTC PDC entry");
|
|
da1469x_pdc_set(pdc_idx);
|
|
da1469x_pdc_ack(pdc_idx);
|
|
#endif
|
|
|
|
rtc_smartbond_100HZ_clock_cfg();
|
|
|
|
/* Timer and calendar counters will not reset after SW reset */
|
|
RTC->RTC_KEEP_RTC_REG |= RTC_RTC_KEEP_RTC_REG_RTC_KEEP_Msk;
|
|
|
|
#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE)
|
|
IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_rtc_isr,
|
|
DEVICE_DT_INST_GET(0), 0);
|
|
irq_enable(SMARTBOND_IRQN);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define SMARTBOND_RTC_INIT(inst) \
|
|
BUILD_ASSERT((inst) == 0, "multiple instances are not supported"); \
|
|
\
|
|
static struct rtc_smartbond_data rtc_smartbond_data_ ## inst; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(0, rtc_smartbond_init, NULL, \
|
|
&rtc_smartbond_data_ ## inst, NULL, \
|
|
POST_KERNEL, \
|
|
CONFIG_RTC_INIT_PRIORITY, \
|
|
&rtc_smartbond_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_RTC_INIT)
|