You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
12 KiB
397 lines
12 KiB
/* |
|
* Copyright 2024 NXP |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nxp_irtc |
|
|
|
#include <zephyr/devicetree.h> |
|
#include <zephyr/drivers/rtc.h> |
|
#include <zephyr/irq.h> |
|
#include "rtc_utils.h" |
|
|
|
struct nxp_irtc_config { |
|
RTC_Type *base; |
|
void (*irq_config_func)(const struct device *dev); |
|
bool is_output_clock_enabled; |
|
uint8_t clock_src; |
|
uint8_t alarm_match_flag; |
|
}; |
|
|
|
struct nxp_irtc_data { |
|
bool is_dst_enabled; |
|
#ifdef CONFIG_RTC_ALARM |
|
rtc_alarm_callback alarm_callback; |
|
void *alarm_user_data; |
|
uint16_t alarm_mask; |
|
#endif /* CONFIG_RTC_ALARM */ |
|
}; |
|
|
|
/* The IRTC Offset is 2112 instead of 1900 [2112 - 1900] -> [212] */ |
|
#define RTC_NXP_IRTC_YEAR_OFFSET 212 |
|
|
|
#define RTC_NXP_GET_REG_FIELD(_reg, _name, _field) \ |
|
((_reg->_name & RTC_##_name##_##_field##_MASK) >> \ |
|
RTC_##_name##_##_field##_SHIFT) |
|
|
|
/* |
|
* The runtime where this is accessed is unknown so we force a lock on the registers then force an |
|
* unlock to guarantee 2 seconds of write time |
|
*/ |
|
static void nxp_irtc_unlock_registers(RTC_Type *reg) |
|
{ |
|
/* Lock the regsiters */ |
|
while ((reg->STATUS & (uint16_t)RTC_STATUS_WRITE_PROT_EN_MASK) == 0) { |
|
*(uint8_t *)(®->STATUS) |= RTC_STATUS_WE(0x2); |
|
} |
|
/* Unlock the registers */ |
|
while ((reg->STATUS & (int16_t)RTC_STATUS_WRITE_PROT_EN_MASK) != 0) { |
|
/* |
|
* The casting is required for writing only a single Byte to the STATUS Register, |
|
* the pattern here unlocks all RTC registers for writing. When unlocked if a 0x20 |
|
* if written to the STATUS register the RTC registers will lock again and the next |
|
* write will lead to a fault. |
|
*/ |
|
*(volatile uint8_t *)(®->STATUS) = 0x00; |
|
*(volatile uint8_t *)(®->STATUS) = 0x40; |
|
*(volatile uint8_t *)(®->STATUS) = 0xC0; |
|
*(volatile uint8_t *)(®->STATUS) = 0x80; |
|
} |
|
} |
|
|
|
static int nxp_irtc_set_time(const struct device *dev, const struct rtc_time *timeptr) |
|
{ |
|
const struct nxp_irtc_config *config = dev->config; |
|
struct nxp_irtc_data *data = dev->data; |
|
RTC_Type *irtc_reg = config->base; |
|
|
|
if (!timeptr || !rtc_utils_validate_rtc_time(timeptr, 0)) { |
|
return -EINVAL; |
|
} |
|
|
|
int calc_year = timeptr->tm_year - RTC_NXP_IRTC_YEAR_OFFSET; |
|
/* The IRTC Month Index starts at 1 instead of 0 */ |
|
int calc_month = timeptr->tm_mon + 1; |
|
|
|
uint32_t key = irq_lock(); |
|
|
|
nxp_irtc_unlock_registers(irtc_reg); |
|
irtc_reg->SECONDS = RTC_SECONDS_SEC_CNT(timeptr->tm_sec); |
|
|
|
irtc_reg->HOURMIN = RTC_HOURMIN_MIN_CNT(timeptr->tm_min) | |
|
RTC_HOURMIN_HOUR_CNT(timeptr->tm_hour); |
|
|
|
/* 1 is valid for rtc_time.tm_wday property but is out of bounds for IRTC registers */ |
|
irtc_reg->DAYS = RTC_DAYS_DAY_CNT(timeptr->tm_mday) | |
|
(timeptr->tm_wday == -1 ? 0 : RTC_DAYS_DOW(timeptr->tm_wday)); |
|
|
|
irtc_reg->YEARMON = RTC_YEARMON_MON_CNT(calc_month) | RTC_YEARMON_YROFST(calc_year); |
|
|
|
if (timeptr->tm_isdst != -1) { |
|
irtc_reg->CTRL |= RTC_CTRL_DST_EN(timeptr->tm_isdst); |
|
data->is_dst_enabled = true; |
|
} |
|
|
|
irq_unlock(key); |
|
|
|
return 0; |
|
} |
|
|
|
static int nxp_irtc_get_time(const struct device *dev, struct rtc_time *timeptr) |
|
{ |
|
const struct nxp_irtc_config *config = dev->config; |
|
struct nxp_irtc_data *data = dev->data; |
|
RTC_Type *irtc_reg = config->base; |
|
|
|
__ASSERT(timeptr != 0, "timeptr has not been set"); |
|
|
|
timeptr->tm_sec = RTC_NXP_GET_REG_FIELD(irtc_reg, SECONDS, SEC_CNT); |
|
timeptr->tm_min = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, MIN_CNT); |
|
timeptr->tm_hour = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, HOUR_CNT); |
|
timeptr->tm_wday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DOW); |
|
timeptr->tm_mday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DAY_CNT); |
|
timeptr->tm_mon = RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, MON_CNT) - 1; |
|
timeptr->tm_year = (int8_t)RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, YROFST) + |
|
RTC_NXP_IRTC_YEAR_OFFSET; |
|
if (data->is_dst_enabled) { |
|
timeptr->tm_isdst = |
|
((irtc_reg->CTRL & RTC_CTRL_DST_EN_MASK) >> RTC_CTRL_DST_EN_SHIFT); |
|
} |
|
|
|
/* There is no nano second support for IRTC */ |
|
timeptr->tm_nsec = 0; |
|
/* There is no day of the year support for IRTC */ |
|
timeptr->tm_yday = -1; |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_RTC_ALARM) |
|
static int nxp_irtc_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
|
uint16_t *mask) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
*mask = (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 | RTC_ALARM_TIME_MASK_YEAR); |
|
|
|
return 0; |
|
} |
|
|
|
static int nxp_irtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
|
const struct rtc_time *timeptr) |
|
{ |
|
const struct nxp_irtc_config *config = dev->config; |
|
struct nxp_irtc_data *data = dev->data; |
|
RTC_Type *irtc_reg = config->base; |
|
|
|
if (id != 0 || (mask && (timeptr == 0)) || |
|
(timeptr && !rtc_utils_validate_rtc_time(timeptr, mask))) { |
|
return -EINVAL; |
|
} |
|
|
|
uint32_t key = irq_lock(); |
|
|
|
nxp_irtc_unlock_registers(irtc_reg); |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
|
irtc_reg->ALM_SECONDS = RTC_ALM_SECONDS_ALM_SEC(timeptr->tm_sec); |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
|
irtc_reg->ALM_HOURMIN = RTC_ALM_HOURMIN_ALM_MIN(timeptr->tm_min); |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
|
irtc_reg->ALM_HOURMIN |= RTC_ALM_HOURMIN_ALM_HOUR(timeptr->tm_hour); |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
|
irtc_reg->ALM_DAYS = RTC_ALM_DAYS_ALM_DAY(timeptr->tm_mday); |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) { |
|
irtc_reg->ALM_YEARMON = RTC_ALM_YEARMON_ALM_MON(timeptr->tm_mon + 1); |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_YEAR) { |
|
irtc_reg->ALM_YEARMON |= |
|
RTC_ALM_YEARMON_ALM_YEAR(timeptr->tm_year - RTC_NXP_IRTC_YEAR_OFFSET); |
|
} |
|
|
|
/* Clearing out the ALARM Flag Field then setting the correct value */ |
|
irtc_reg->CTRL &= ~(0xC); |
|
switch (mask) { |
|
case 0x0F: |
|
irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x4); |
|
break; |
|
case 0x1F: |
|
irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x8); |
|
break; |
|
case 0x3F: |
|
irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0xC); |
|
break; |
|
default: |
|
irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x0); |
|
} |
|
|
|
/* Enabling Alarm Interrupts */ |
|
irtc_reg->IER |= RTC_ISR_ALM_IS_MASK; |
|
data->alarm_mask = mask; |
|
|
|
irq_unlock(key); |
|
|
|
return 0; |
|
} |
|
|
|
static int nxp_irtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
|
struct rtc_time *timeptr) |
|
{ |
|
const struct nxp_irtc_config *config = dev->config; |
|
struct nxp_irtc_data *data = dev->data; |
|
RTC_Type *irtc_reg = config->base; |
|
uint16_t curr_alarm_mask = data->alarm_mask; |
|
uint16_t return_mask = 0; |
|
|
|
if (id != 0 || !timeptr) { |
|
return -EINVAL; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_SECOND) { |
|
timeptr->tm_sec = RTC_NXP_GET_REG_FIELD(irtc_reg, ALM_SECONDS, ALM_SEC); |
|
return_mask |= RTC_ALARM_TIME_MASK_SECOND; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MINUTE) { |
|
timeptr->tm_min = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, MIN_CNT); |
|
return_mask |= RTC_ALARM_TIME_MASK_MINUTE; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_HOUR) { |
|
timeptr->tm_hour = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, HOUR_CNT); |
|
return_mask |= RTC_ALARM_TIME_MASK_HOUR; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
|
timeptr->tm_mday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DAY_CNT); |
|
return_mask |= RTC_ALARM_TIME_MASK_MONTHDAY; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MONTH) { |
|
timeptr->tm_mon = RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, MON_CNT) - 1; |
|
return_mask |= RTC_ALARM_TIME_MASK_MONTH; |
|
} |
|
|
|
if (curr_alarm_mask & RTC_ALARM_TIME_MASK_YEAR) { |
|
timeptr->tm_year = (int8_t)RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, YROFST) + |
|
RTC_NXP_IRTC_YEAR_OFFSET; |
|
return_mask |= RTC_ALARM_TIME_MASK_YEAR; |
|
} |
|
|
|
*mask = return_mask; |
|
|
|
return 0; |
|
} |
|
|
|
static int nxp_irtc_alarm_is_pending(const struct device *dev, uint16_t id) |
|
{ |
|
struct nxp_irtc_data *data = dev->data; |
|
RTC_Type *irtc_reg = config->base; |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
return RTC_ISR_ALM_IS(0x4); |
|
} |
|
|
|
static int nxp_irtc_alarm_set_callback(const struct device *dev, uint16_t id, |
|
rtc_alarm_callback callback, void *user_data) |
|
{ |
|
struct nxp_irtc_data *data = dev->data; |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
uint32_t key = irq_lock(); |
|
|
|
data->alarm_callback = callback; |
|
data->alarm_user_data = user_data; |
|
|
|
irq_unlock(key); |
|
|
|
return 0; |
|
} |
|
|
|
#endif /* CONFIG_RTC_ALARM */ |
|
#if defined(CONFIG_RTC_UPDATE) |
|
|
|
static int nxp_irtc_update_set_callback(const struct device *dev, rtc_update_callback callback, |
|
void *user_data) |
|
{ |
|
ARG_UNUSED(dev); |
|
ARG_UNUSED(calibration); |
|
ARG_UNUSED(user_data); |
|
return -ENOTSUP; |
|
} |
|
|
|
#endif /* CONFIG_RTC_UPDATE */ |
|
#if defined(CONFIG_RTC_CALIBRATION) |
|
|
|
static int nxp_irtc_set_calibration(const struct device *dev, int32_t calibration) |
|
{ |
|
ARG_UNUSED(dev); |
|
ARG_UNUSED(calibration); |
|
return -ENOTSUP; |
|
} |
|
|
|
static int nxp_irtc_get_calibration(const struct device *dev, int32_t *calibration) |
|
{ |
|
ARG_UNUSED(dev); |
|
ARG_UNUSED(calibration); |
|
return -ENOTSUP; |
|
} |
|
|
|
#endif /* CONFIG_RTC_CALIBRATION */ |
|
|
|
static int nxp_irtc_init(const struct device *dev) |
|
{ |
|
const struct nxp_irtc_config *config = dev->config; |
|
RTC_Type *irtc_reg = config->base; |
|
|
|
nxp_irtc_unlock_registers(irtc_reg); |
|
|
|
/* set the control register bits */ |
|
irtc_reg->CTRL = RTC_CTRL_CLK_SEL(config->clock_src) | |
|
RTC_CTRL_CLKO_DIS(!config->is_output_clock_enabled); |
|
|
|
config->irq_config_func(dev); |
|
|
|
return 0; |
|
} |
|
|
|
static void nxp_irtc_isr(const struct device *dev) |
|
{ |
|
#ifdef CONFIG_RTC_ALARM |
|
const struct nxp_irtc_config *config = dev->config; |
|
RTC_Type *irtc_reg = config->base; |
|
struct nxp_irtc_data *data = dev->data; |
|
uint32_t key = irq_lock(); |
|
|
|
nxp_irtc_unlock_registers(irtc_reg); |
|
/* Clearing ISR Register since w1c */ |
|
irtc_reg->ISR = irtc_reg->ISR; |
|
|
|
if (data->alarm_callback) { |
|
data->alarm_callback(dev, 0, data->alarm_user_data); |
|
} |
|
irq_unlock(key); |
|
#endif /* CONFIG_RTC_ALARM */ |
|
} |
|
|
|
static DEVICE_API(rtc, rtc_nxp_irtc_driver_api) = { |
|
.set_time = nxp_irtc_set_time, |
|
.get_time = nxp_irtc_get_time, |
|
#if defined(CONFIG_RTC_ALARM) |
|
.alarm_get_supported_fields = nxp_irtc_alarm_get_supported_fields, |
|
.alarm_set_time = nxp_irtc_alarm_set_time, |
|
.alarm_get_time = nxp_irtc_alarm_get_time, |
|
.alarm_is_pending = nxp_irtc_alarm_is_pending, |
|
.alarm_set_callback = nxp_irtc_alarm_set_callback, |
|
#endif /* CONFIG_RTC_ALARM */ |
|
#if defined(CONFIG_RTC_UPDATE) |
|
.update_set_callback = nxp_irtc_update_set_callback, |
|
#endif /* CONFIG_RTC_UPDATE */ |
|
#if defined(CONFIG_RTC_CALIBRATION) |
|
.set_calibration = nxp_irtc_set_calibration, |
|
.get_calibration = nxp_irtc_get_calibration, |
|
#endif /* CONFIG_RTC_CALIBRATION */ |
|
}; |
|
|
|
#define RTC_NXP_IRTC_DEVICE_INIT(n) \ |
|
static void nxp_irtc_config_func_##n(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), nxp_irtc_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} \ |
|
static const struct nxp_irtc_config nxp_irtc_config_##n = { \ |
|
.base = (RTC_Type *)DT_INST_REG_ADDR(n), \ |
|
.clock_src = DT_INST_PROP(n, clock_src), \ |
|
.is_output_clock_enabled = DT_INST_PROP(n, output_clk_en), \ |
|
.irq_config_func = nxp_irtc_config_func_##n, \ |
|
}; \ |
|
\ |
|
static struct nxp_irtc_data nxp_irtc_data_##n; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, nxp_irtc_init, NULL, &nxp_irtc_data_##n, &nxp_irtc_config_##n, \ |
|
PRE_KERNEL_1, CONFIG_RTC_INIT_PRIORITY, &rtc_nxp_irtc_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RTC_NXP_IRTC_DEVICE_INIT)
|
|
|