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.
323 lines
7.7 KiB
323 lines
7.7 KiB
/* |
|
* Copyright (c) 2024 Andrew Featherstone |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/rtc.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/spinlock.h> |
|
|
|
#include <hardware/irq.h> |
|
#include <hardware/rtc.h> |
|
#include <hardware/regs/rtc.h> |
|
|
|
#include "rtc_utils.h" |
|
|
|
#define DT_DRV_COMPAT raspberrypi_pico_rtc |
|
|
|
#define CLK_DRV DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0)) |
|
#define CLK_ID (clock_control_subsys_t) DT_INST_PHA_BY_IDX(0, clocks, 0, clk_id) |
|
|
|
/* struct tm start time: 1st, Jan, 1900 */ |
|
#define TM_YEAR_REF 1900 |
|
/* See section 4.8.1 of the RP2040 datasheet. */ |
|
#define RP2040_RTC_YEAR_MAX 4095 |
|
#ifdef CONFIG_RTC_ALARM |
|
static int rtc_rpi_pico_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
|
struct rtc_time *timeptr); |
|
#endif |
|
struct rtc_rpi_pico_data { |
|
struct k_spinlock lock; |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
struct rtc_time alarm_time; |
|
uint16_t alarm_mask; |
|
rtc_alarm_callback alarm_callback; |
|
void *alarm_user_data; |
|
bool alarm_pending; |
|
#endif /* CONFIG_RTC_ALARM */ |
|
}; |
|
|
|
static struct rtc_rpi_pico_data rtc_data; |
|
|
|
LOG_MODULE_REGISTER(rtc_rpi, CONFIG_RTC_LOG_LEVEL); |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
static void rtc_rpi_isr(const struct device *dev) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
|
|
rtc_alarm_callback callback; |
|
void *user_data; |
|
|
|
rtc_disable_alarm(); |
|
|
|
K_SPINLOCK(&data->lock) { |
|
callback = data->alarm_callback; |
|
user_data = data->alarm_user_data; |
|
} |
|
|
|
if (callback != NULL) { |
|
callback(dev, 0, user_data); |
|
} else { |
|
data->alarm_pending = true; |
|
} |
|
/* re-enable the alarm. */ |
|
rtc_enable_alarm(); |
|
} |
|
#endif |
|
|
|
static int rtc_rpi_pico_init(const struct device *dev) |
|
{ |
|
int ret; |
|
#ifdef CONFIG_RTC_ALARM |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
#endif |
|
|
|
ret = clock_control_on(CLK_DRV, CLK_ID); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
data->alarm_mask = 0; |
|
data->alarm_callback = NULL; |
|
data->alarm_pending = false; |
|
|
|
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_rpi_isr, DEVICE_DT_INST_GET(0), |
|
0); |
|
irq_enable(DT_INST_IRQN(0)); |
|
#endif |
|
rtc_init(); |
|
return 0; |
|
} |
|
|
|
static int rtc_rpi_pico_set_time(const struct device *dev, const struct rtc_time *timeptr) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
int err = 0; |
|
|
|
if (timeptr->tm_year + TM_YEAR_REF > RP2040_RTC_YEAR_MAX) { |
|
return -EINVAL; |
|
} |
|
|
|
if (timeptr->tm_wday == -1) { |
|
/* day of the week is expected */ |
|
return -EINVAL; |
|
} |
|
|
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
datetime_t dt = { |
|
.year = timeptr->tm_year + TM_YEAR_REF, |
|
.month = timeptr->tm_mon + 1, |
|
.day = timeptr->tm_mday, |
|
.dotw = timeptr->tm_wday, |
|
.hour = timeptr->tm_hour, |
|
.min = timeptr->tm_min, |
|
.sec = timeptr->tm_sec, |
|
}; |
|
/* Use the validation in the Pico SDK. */ |
|
if (!rtc_set_datetime(&dt)) { |
|
err = -EINVAL; |
|
} |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return err; |
|
} |
|
|
|
static int rtc_rpi_pico_get_time(const struct device *dev, struct rtc_time *timeptr) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
datetime_t dt; |
|
int err = 0; |
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
if (!rtc_get_datetime(&dt)) { |
|
err = -ENODATA; |
|
} |
|
|
|
timeptr->tm_sec = dt.sec; |
|
timeptr->tm_min = dt.min; |
|
timeptr->tm_hour = dt.hour; |
|
timeptr->tm_mday = dt.day; |
|
timeptr->tm_mon = dt.month - 1; |
|
timeptr->tm_year = dt.year - TM_YEAR_REF; |
|
timeptr->tm_wday = dt.dotw; |
|
/* unknown values */ |
|
timeptr->tm_yday = -1; |
|
timeptr->tm_isdst = -1; |
|
timeptr->tm_nsec = 0; |
|
k_spin_unlock(&data->lock, key); |
|
|
|
return err; |
|
} |
|
|
|
#if defined(CONFIG_RTC_ALARM) |
|
static int rtc_rpi_pico_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
|
uint16_t *supported_fields) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
*supported_fields = RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | |
|
RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_WEEKDAY | |
|
RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_MONTH | |
|
RTC_ALARM_TIME_MASK_YEAR; |
|
|
|
return 0; |
|
} |
|
|
|
static int rtc_rpi_pico_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
|
const struct rtc_time *alarm) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
int err = 0; |
|
uint16_t mask_available; |
|
|
|
(void)rtc_rpi_pico_alarm_get_supported_fields(NULL, 0, &mask_available); |
|
|
|
if (mask & ~mask_available) { |
|
return -EINVAL; |
|
} |
|
|
|
if (!rtc_utils_validate_rtc_time(alarm, mask)) { |
|
return -EINVAL; |
|
} |
|
|
|
LOG_INF("Setting alarm"); |
|
|
|
rtc_disable_alarm(); |
|
if (mask == 0) { |
|
/* Disable the alarm */ |
|
data->alarm_mask = 0; |
|
} |
|
k_spinlock_key_t key = k_spin_lock(&data->lock); |
|
|
|
/* Clear before updating. */ |
|
rtc_hw->irq_setup_0 = 0; |
|
rtc_hw->irq_setup_1 = 0; |
|
|
|
/* Set the match enable bits for things we care about */ |
|
if (mask & RTC_ALARM_TIME_MASK_YEAR) { |
|
hw_set_bits(&rtc_hw->irq_setup_0, |
|
RTC_IRQ_SETUP_0_YEAR_ENA_BITS | |
|
((alarm->tm_year + TM_YEAR_REF) << RTC_IRQ_SETUP_0_YEAR_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_MONTH) { |
|
hw_set_bits(&rtc_hw->irq_setup_0, |
|
RTC_IRQ_SETUP_0_MONTH_ENA_BITS | |
|
((alarm->tm_mon + 1) << RTC_IRQ_SETUP_0_MONTH_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
|
hw_set_bits(&rtc_hw->irq_setup_0, |
|
RTC_IRQ_SETUP_0_DAY_ENA_BITS | |
|
(alarm->tm_mday << RTC_IRQ_SETUP_0_DAY_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) { |
|
hw_set_bits(&rtc_hw->irq_setup_1, |
|
RTC_IRQ_SETUP_1_DOTW_ENA_BITS | |
|
(alarm->tm_wday << RTC_IRQ_SETUP_1_DOTW_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
|
hw_set_bits(&rtc_hw->irq_setup_1, |
|
RTC_IRQ_SETUP_1_HOUR_ENA_BITS | |
|
(alarm->tm_hour << RTC_IRQ_SETUP_1_HOUR_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
|
hw_set_bits(&rtc_hw->irq_setup_1, |
|
RTC_IRQ_SETUP_1_MIN_ENA_BITS | |
|
(alarm->tm_min << RTC_IRQ_SETUP_1_MIN_LSB)); |
|
} |
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
|
hw_set_bits(&rtc_hw->irq_setup_1, |
|
RTC_IRQ_SETUP_1_SEC_ENA_BITS | |
|
(alarm->tm_sec << RTC_IRQ_SETUP_1_SEC_LSB)); |
|
} |
|
data->alarm_time = *alarm; |
|
data->alarm_mask = mask; |
|
k_spin_unlock(&data->lock, key); |
|
|
|
/* Enable the IRQ at the peri */ |
|
rtc_hw->inte = RTC_INTE_RTC_BITS; |
|
|
|
rtc_enable_alarm(); |
|
|
|
return err; |
|
} |
|
|
|
static int rtc_rpi_pico_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
|
struct rtc_time *timeptr) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
K_SPINLOCK(&data->lock) { |
|
*timeptr = data->alarm_time; |
|
*mask = data->alarm_mask; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int rtc_rpi_pico_alarm_is_pending(const struct device *dev, uint16_t id) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
int ret = 0; |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
K_SPINLOCK(&data->lock) { |
|
ret = data->alarm_pending ? 1 : 0; |
|
data->alarm_pending = false; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int rtc_rpi_pico_alarm_set_callback(const struct device *dev, uint16_t id, |
|
rtc_alarm_callback callback, void *user_data) |
|
{ |
|
struct rtc_rpi_pico_data *data = dev->data; |
|
|
|
if (id != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
K_SPINLOCK(&data->lock) { |
|
data->alarm_callback = callback; |
|
data->alarm_user_data = user_data; |
|
if ((callback == NULL) && (user_data == NULL)) { |
|
rtc_disable_alarm(); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#endif /* CONFIG_RTC_ALARM */ |
|
|
|
static DEVICE_API(rtc, rtc_rpi_pico_driver_api) = { |
|
.set_time = rtc_rpi_pico_set_time, |
|
.get_time = rtc_rpi_pico_get_time, |
|
#if defined(CONFIG_RTC_ALARM) |
|
.alarm_get_supported_fields = rtc_rpi_pico_alarm_get_supported_fields, |
|
.alarm_set_time = rtc_rpi_pico_alarm_set_time, |
|
.alarm_get_time = rtc_rpi_pico_alarm_get_time, |
|
.alarm_is_pending = rtc_rpi_pico_alarm_is_pending, |
|
.alarm_set_callback = rtc_rpi_pico_alarm_set_callback, |
|
#endif /* CONFIG_RTC_ALARM */ |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, &rtc_rpi_pico_init, NULL, &rtc_data, NULL, POST_KERNEL, |
|
CONFIG_RTC_INIT_PRIORITY, &rtc_rpi_pico_driver_api);
|
|
|