From 8ce0b2e0b62a3f03727706498583d520705e0e8a Mon Sep 17 00:00:00 2001 From: Andrew Featherstone Date: Sun, 25 Feb 2024 13:12:35 +0000 Subject: [PATCH 1/3] drivers: rtc: Create utility function for time validation 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 --- drivers/rtc/CMakeLists.txt | 2 + drivers/rtc/rtc_emul.c | 41 +------------ drivers/rtc/rtc_sam.c | 42 ++------------ drivers/rtc/rtc_utils.c | 58 +++++++++++++++++++ drivers/rtc/rtc_utils.h | 28 +++++++++ tests/drivers/rtc/rtc_api/src/test_alarm.c | 31 ++++++++++ tests/drivers/rtc/rtc_utils/CMakeLists.txt | 13 +++++ tests/drivers/rtc/rtc_utils/prj.conf | 5 ++ tests/drivers/rtc/rtc_utils/src/main.c | 9 +++ .../rtc/rtc_utils/src/test_rtc_utils.c | 36 ++++++++++++ tests/drivers/rtc/rtc_utils/testcase.yaml | 9 +++ 11 files changed, 200 insertions(+), 74 deletions(-) create mode 100644 drivers/rtc/rtc_utils.c create mode 100644 drivers/rtc/rtc_utils.h create mode 100644 tests/drivers/rtc/rtc_utils/CMakeLists.txt create mode 100644 tests/drivers/rtc/rtc_utils/prj.conf create mode 100644 tests/drivers/rtc/rtc_utils/src/main.c create mode 100644 tests/drivers/rtc/rtc_utils/src/test_rtc_utils.c create mode 100644 tests/drivers/rtc/rtc_utils/testcase.yaml diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 65d822dbe6e088..8bab34ebd31a8e 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -5,6 +5,8 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/rtc.h) zephyr_library() +zephyr_library_sources(rtc_utils.c) + zephyr_library_sources_ifdef(CONFIG_RTC_AM1805 rtc_am1805.c) zephyr_library_sources_ifdef(CONFIG_RTC_DS1307 rtc_ds1307.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) diff --git a/drivers/rtc/rtc_emul.c b/drivers/rtc/rtc_emul.c index 9ce6b5dbc25f39..eb40b9399a4433 100644 --- a/drivers/rtc/rtc_emul.c +++ b/drivers/rtc/rtc_emul.c @@ -10,6 +10,8 @@ #include #include +#include "rtc_utils.h" + struct rtc_emul_data; struct rtc_emul_work_delayable { @@ -67,43 +69,6 @@ static bool rtc_emul_is_leap_year(struct rtc_time *datetime) return false; } -#ifdef CONFIG_RTC_ALARM -static bool rtc_emul_validate_alarm_time(const struct rtc_time *timeptr, uint32_t mask) -{ - if ((mask & RTC_ALARM_TIME_MASK_SECOND) && - (timeptr->tm_sec < 0 || timeptr->tm_sec > 59)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && - (timeptr->tm_min < 0 || timeptr->tm_min > 59)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_HOUR) && - (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MONTH) && - (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && - (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_YEAR) && - (timeptr->tm_year < 0 || timeptr->tm_year > 199)) { - return false; - } - - return true; -} -#endif /* CONFIG_RTC_ALARM */ - static int rtc_emul_get_days_in_month(struct rtc_time *datetime) { const uint8_t *dim = (rtc_emul_is_leap_year(datetime) == true) ? @@ -346,7 +311,7 @@ static int rtc_emul_alarm_set_time(const struct device *dev, uint16_t id, uint16 } if (mask > 0) { - if (rtc_emul_validate_alarm_time(timeptr, mask) == false) { + if (rtc_utils_validate_rtc_time(timeptr, mask) == false) { return -EINVAL; } } diff --git a/drivers/rtc/rtc_sam.c b/drivers/rtc/rtc_sam.c index e50edd94e3c095..c9868e636520e2 100644 --- a/drivers/rtc/rtc_sam.c +++ b/drivers/rtc/rtc_sam.c @@ -26,6 +26,11 @@ #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 { @@ -58,41 +63,6 @@ static void rtc_sam_enable_wp(void) REG_RTC_WPMR = RTC_SAM_WPMR_ENABLE; } -static bool rtc_sam_validate_tm(const struct rtc_time *timeptr, uint32_t mask) -{ - if ((mask & RTC_ALARM_TIME_MASK_SECOND) && - (timeptr->tm_sec < 0 || timeptr->tm_sec > 59)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && - (timeptr->tm_min < 0 || timeptr->tm_min > 59)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_HOUR) && - (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MONTH) && - (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && - (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_YEAR) && - (timeptr->tm_year < 0 || timeptr->tm_year > 199)) { - return false; - } - - return true; -} - static uint32_t rtc_sam_timr_from_tm(const struct rtc_time *timeptr) { uint32_t timr; @@ -126,7 +96,7 @@ static int rtc_sam_set_time(const struct device *dev, const struct rtc_time *tim const struct rtc_sam_config *config = dev->config; Rtc *regs = config->regs; - if (rtc_sam_validate_tm(timeptr, UINT32_MAX) == false) { + if (rtc_utils_validate_rtc_time(timeptr, RTC_SAM_TIME_MASK) == false) { return -EINVAL; } diff --git a/drivers/rtc/rtc_utils.c b/drivers/rtc/rtc_utils.c new file mode 100644 index 00000000000000..20e0e8bb78e522 --- /dev/null +++ b/drivers/rtc/rtc_utils.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Bjarki Arge Andreasen + * Copyright (c) 2024 Andrew Featherstone + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "rtc_utils.h" + +bool rtc_utils_validate_rtc_time(const struct rtc_time *timeptr, uint16_t mask) +{ + if ((mask & RTC_ALARM_TIME_MASK_SECOND) && (timeptr->tm_sec < 0 || timeptr->tm_sec > 59)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && (timeptr->tm_min < 0 || timeptr->tm_min > 59)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_HOUR) && (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_MONTH) && (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && + (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_YEAR) && (timeptr->tm_year < 0 || timeptr->tm_year > 199)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_WEEKDAY) && + (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_YEARDAY) && + (timeptr->tm_yday < 0 || timeptr->tm_yday > 365)) { + return false; + } + + if ((mask & RTC_ALARM_TIME_MASK_NSEC) && + (timeptr->tm_nsec < 0 || timeptr->tm_nsec > 999999999)) { + return false; + } + + return true; +} diff --git a/drivers/rtc/rtc_utils.h b/drivers/rtc/rtc_utils.h new file mode 100644 index 00000000000000..1b0953e6c2f79b --- /dev/null +++ b/drivers/rtc/rtc_utils.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Bjarki Arge Andreasen + * Copyright (c) 2024 Andrew Featherstone + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ +#define ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ + +#include +#include + +#include + +/** + * @brief Validate a datetime with a mask + * + * Ensure that any fields selected by mask contain a valid value. + * + * @param timeptr The time to set + * @param mask Mask of fields to validate + * + * @return true if the required fields are valid. + */ +bool rtc_utils_validate_rtc_time(const struct rtc_time *timeptr, uint16_t mask); + +#endif /* ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ */ diff --git a/tests/drivers/rtc/rtc_api/src/test_alarm.c b/tests/drivers/rtc/rtc_api/src/test_alarm.c index 5751524789cff0..4382208910852d 100644 --- a/tests/drivers/rtc/rtc_api/src/test_alarm.c +++ b/tests/drivers/rtc/rtc_api/src/test_alarm.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -48,6 +49,36 @@ ZTEST(rtc_api, test_alarm) "Failed to clear and disable alarm callback"); } + /* Every supported alarm field should reject invalid values. */ + for (uint16_t i = 0; i < alarms_count; i++) { + ret = rtc_alarm_get_supported_fields(rtc, i, &alarm_time_mask_supported); + + zassert_ok(ret, "Failed to get supported alarm fields"); + + int *fields[] = { + &alarm_time_set.tm_sec, &alarm_time_set.tm_min, &alarm_time_set.tm_hour, + &alarm_time_set.tm_mday, &alarm_time_set.tm_mon, &alarm_time_set.tm_year, + &alarm_time_set.tm_wday, &alarm_time_set.tm_yday, &alarm_time_set.tm_nsec}; + uint16_t masks[] = {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, + RTC_ALARM_TIME_MASK_WEEKDAY, RTC_ALARM_TIME_MASK_YEARDAY, + RTC_ALARM_TIME_MASK_NSEC}; + int bad_values[] = {70, 70, 25, 35, 15, 8000, 8, 370, INT32_MAX}; + + ARRAY_FOR_EACH(fields, j) + { + if (masks[j] & alarm_time_mask_supported) { + *fields[j] = bad_values[j]; + ret = rtc_alarm_set_time(rtc, i, masks[j], &alarm_time_set); + zassert_equal( + -EINVAL, ret, + "%s: RTC should reject invalid alarm time in field %zu.", + rtc->name, j); + } + } + } + /* Validate alarms supported fields */ for (uint16_t i = 0; i < alarms_count; i++) { ret = rtc_alarm_get_supported_fields(rtc, i, &alarm_time_mask_supported); diff --git a/tests/drivers/rtc/rtc_utils/CMakeLists.txt b/tests/drivers/rtc/rtc_utils/CMakeLists.txt new file mode 100644 index 00000000000000..aac91f6c281d8e --- /dev/null +++ b/tests/drivers/rtc/rtc_utils/CMakeLists.txt @@ -0,0 +1,13 @@ +# # Copyright (c) 2024 Andrew Featherstone +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(rtc_utils) + +target_sources(app PRIVATE + src/main.c + src/test_rtc_utils.c +) diff --git a/tests/drivers/rtc/rtc_utils/prj.conf b/tests/drivers/rtc/rtc_utils/prj.conf new file mode 100644 index 00000000000000..64646c7aaf42e2 --- /dev/null +++ b/tests/drivers/rtc/rtc_utils/prj.conf @@ -0,0 +1,5 @@ +# Copyright (c) 2024 Andrew Featherstone +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_RTC=y +CONFIG_ZTEST=y diff --git a/tests/drivers/rtc/rtc_utils/src/main.c b/tests/drivers/rtc/rtc_utils/src/main.c new file mode 100644 index 00000000000000..fe22c136ab1c1c --- /dev/null +++ b/tests/drivers/rtc/rtc_utils/src/main.c @@ -0,0 +1,9 @@ +/* + * # Copyright (c) 2024 Andrew Featherstone + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +ZTEST_SUITE(rtc_utils, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/rtc/rtc_utils/src/test_rtc_utils.c b/tests/drivers/rtc/rtc_utils/src/test_rtc_utils.c new file mode 100644 index 00000000000000..88c6da3e2d76d2 --- /dev/null +++ b/tests/drivers/rtc/rtc_utils/src/test_rtc_utils.c @@ -0,0 +1,36 @@ +/* + * # Copyright (c) 2024 Andrew Featherstone + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "../../../../drivers/rtc/rtc_utils.h" + +ZTEST(rtc_utils, test_rtc_utils_validate_rtc_time) +{ + /* Arbitrary out-out-range values. */ + const struct rtc_time alarm_time = { + .tm_sec = 70, + .tm_min = 70, + .tm_hour = 25, + .tm_mday = 35, + .tm_mon = 15, + .tm_year = 8000, + .tm_wday = 8, + .tm_yday = 370, + .tm_nsec = INT32_MAX, + }; + uint16_t masks[] = {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, + RTC_ALARM_TIME_MASK_WEEKDAY, RTC_ALARM_TIME_MASK_YEARDAY, + RTC_ALARM_TIME_MASK_NSEC}; + ARRAY_FOR_EACH(masks, j) + { + bool ret = rtc_utils_validate_rtc_time(&alarm_time, masks[j]); + + zassert_false(ret, "RTC should reject invalid alarm time in field %zu.", j); + } +} diff --git a/tests/drivers/rtc/rtc_utils/testcase.yaml b/tests/drivers/rtc/rtc_utils/testcase.yaml new file mode 100644 index 00000000000000..992fac141b86fe --- /dev/null +++ b/tests/drivers/rtc/rtc_utils/testcase.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Andrew Featherstone +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.rtc.rtc_utils: + tags: + - drivers + - rtc + depends_on: rtc From 0e785037014d88f15c734afac9192c46462c1df2 Mon Sep 17 00:00:00 2001 From: Andrew Featherstone Date: Tue, 7 Nov 2023 16:14:09 +0000 Subject: [PATCH 2/3] drivers: rtc: rpi_pico: Add support for the Raspberry Pi Pico RTC This adds the minimal get_time/set_time support for the rp2040 and enables support by default on the Pico boards. This doesn't support configuring the clock source or alarm interrupts yet. Signed-off-by: Andrew Featherstone --- .../raspberrypi/rpi_pico/rpi_pico-common.dtsi | 6 + drivers/rtc/CMakeLists.txt | 1 + drivers/rtc/Kconfig | 5 +- drivers/rtc/Kconfig.rpi_pico | 9 ++ drivers/rtc/rtc_rpi_pico.c | 112 ++++++++++++++++++ dts/arm/rpi_pico/rp2040.dtsi | 8 ++ dts/bindings/rtc/raspberrypi,pico-rtc.yaml | 12 ++ modules/hal_rpi_pico/CMakeLists.txt | 5 + modules/hal_rpi_pico/Kconfig | 5 + 9 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 drivers/rtc/Kconfig.rpi_pico create mode 100644 drivers/rtc/rtc_rpi_pico.c create mode 100644 dts/bindings/rtc/raspberrypi,pico-rtc.yaml diff --git a/boards/raspberrypi/rpi_pico/rpi_pico-common.dtsi b/boards/raspberrypi/rpi_pico/rpi_pico-common.dtsi index 95a27d3a0090c9..9c7f1da181707a 100644 --- a/boards/raspberrypi/rpi_pico/rpi_pico-common.dtsi +++ b/boards/raspberrypi/rpi_pico/rpi_pico-common.dtsi @@ -24,6 +24,7 @@ }; aliases { + rtc = &rtc; watchdog0 = &wdt0; }; @@ -133,6 +134,11 @@ status = "okay"; }; +&rtc { + clocks = <&clocks RPI_PICO_CLKID_CLK_RTC>; + status = "okay"; +}; + &adc { status = "okay"; pinctrl-0 = <&adc_default>; diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 8bab34ebd31a8e..26ea1a56567e68 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -19,3 +19,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_SHELL rtc_shell.c) zephyr_library_sources_ifdef(CONFIG_RTC_FAKE rtc_fake.c) zephyr_library_sources_ifdef(CONFIG_RTC_SMARTBOND rtc_smartbond.c) zephyr_library_sources_ifdef(CONFIG_RTC_ATMEL_SAM rtc_sam.c) +zephyr_library_sources_ifdef(CONFIG_RTC_RPI_PICO rtc_rpi_pico.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 10b228ceeabc7f..3b44235b75c809 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -45,11 +45,12 @@ source "drivers/rtc/Kconfig.am1805" source "drivers/rtc/Kconfig.ds1307" source "drivers/rtc/Kconfig.emul" source "drivers/rtc/Kconfig.fake" +source "drivers/rtc/Kconfig.mc146818" source "drivers/rtc/Kconfig.pcf8523" source "drivers/rtc/Kconfig.pcf8563" -source "drivers/rtc/Kconfig.mc146818" +source "drivers/rtc/Kconfig.rpi_pico" source "drivers/rtc/Kconfig.sam" -source "drivers/rtc/Kconfig.stm32" source "drivers/rtc/Kconfig.smartbond" +source "drivers/rtc/Kconfig.stm32" endif # RTC diff --git a/drivers/rtc/Kconfig.rpi_pico b/drivers/rtc/Kconfig.rpi_pico new file mode 100644 index 00000000000000..24a872258ff74d --- /dev/null +++ b/drivers/rtc/Kconfig.rpi_pico @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Andrew Featherstone +# SPDX-License-Identifier: Apache-2.0 + +config RTC_RPI_PICO + bool "Raspberry Pi Pico RTC driver" + default y + depends on DT_HAS_RASPBERRYPI_PICO_RTC_ENABLED + select PICOSDK_USE_RTC + depends on RESET diff --git a/drivers/rtc/rtc_rpi_pico.c b/drivers/rtc/rtc_rpi_pico.c new file mode 100644 index 00000000000000..f628d2e9708ef6 --- /dev/null +++ b/drivers/rtc/rtc_rpi_pico.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Andrew Featherstone + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#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 +struct rtc_rpi_pico_data { + struct k_spinlock lock; +}; + +LOG_MODULE_REGISTER(rtc_rpi, CONFIG_RTC_LOG_LEVEL); + +static int rtc_rpi_pico_init(const struct device *dev) +{ + int ret; + + ret = clock_control_on(CLK_DRV, CLK_ID); + if (ret < 0) { + return ret; + } + + 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; +} + +static const struct rtc_driver_api rtc_rpi_pico_driver_api = { + .set_time = rtc_rpi_pico_set_time, + .get_time = rtc_rpi_pico_get_time, +}; + +static struct rtc_rpi_pico_data rtc_data; + +DEVICE_DT_INST_DEFINE(0, &rtc_rpi_pico_init, NULL, &rtc_data, NULL, POST_KERNEL, + CONFIG_RTC_INIT_PRIORITY, &rtc_rpi_pico_driver_api); diff --git a/dts/arm/rpi_pico/rp2040.dtsi b/dts/arm/rpi_pico/rp2040.dtsi index 72c71bfcfdcc25..4635aabd7526a6 100644 --- a/dts/arm/rpi_pico/rp2040.dtsi +++ b/dts/arm/rpi_pico/rp2040.dtsi @@ -399,6 +399,14 @@ resets = <&reset RPI_PICO_RESETS_RESET_PIO1>; status = "disabled"; }; + + rtc: rtc@4005c000 { + compatible = "raspberrypi,pico-rtc"; + reg = <0x4005c000 DT_SIZE_K(4)>; + interrupts = <25 RPI_PICO_DEFAULT_IRQ_PRIORITY>; + resets = <&reset RPI_PICO_RESETS_RESET_RTC>; + status = "disabled"; + }; }; pinctrl: pin-controller { diff --git a/dts/bindings/rtc/raspberrypi,pico-rtc.yaml b/dts/bindings/rtc/raspberrypi,pico-rtc.yaml new file mode 100644 index 00000000000000..b947a9fc6d60c6 --- /dev/null +++ b/dts/bindings/rtc/raspberrypi,pico-rtc.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024 Andrew Featherstone +# SPDX-License-Identifier: Apache-2.0 + +description: RaspberryPi Pico RTC + +compatible: "raspberrypi,pico-rtc" + +include: [rtc.yaml, reset-device.yaml] + +properties: + reg: + required: true diff --git a/modules/hal_rpi_pico/CMakeLists.txt b/modules/hal_rpi_pico/CMakeLists.txt index 6474b18055e173..b68c2efdb37ee0 100644 --- a/modules/hal_rpi_pico/CMakeLists.txt +++ b/modules/hal_rpi_pico/CMakeLists.txt @@ -113,6 +113,11 @@ if(CONFIG_HAS_RPI_PICO) zephyr_include_directories_ifdef(CONFIG_PICOSDK_USE_CLAIM ${rp2_common_dir}/hardware_claim/include) + zephyr_library_sources_ifdef(CONFIG_PICOSDK_USE_RTC + ${rp2_common_dir}/hardware_rtc/rtc.c) + zephyr_include_directories_ifdef(CONFIG_PICOSDK_USE_RTC + ${rp2_common_dir}/hardware_rtc/include) + # Some flash driver functions must be executed from the RAM. # Originally pico-sdk places them in the RW data section, so this # implementation does the same. diff --git a/modules/hal_rpi_pico/Kconfig b/modules/hal_rpi_pico/Kconfig index 56f92ea427ec7e..3c37c84f4c0109 100644 --- a/modules/hal_rpi_pico/Kconfig +++ b/modules/hal_rpi_pico/Kconfig @@ -49,3 +49,8 @@ config PICOSDK_USE_TIMER bool help Use the TIMER driver from pico-sdk + +config PICOSDK_USE_RTC + bool + help + Use the RTC driver from pico-sdk From 37ae8818af9a0cb50b666e6a06f3fcdbcba01b23 Mon Sep 17 00:00:00 2001 From: Andrew Featherstone Date: Thu, 16 Nov 2023 22:35:22 +0000 Subject: [PATCH 3/3] drivers: rtc: rpi_pico: Add alarm support to RPi Pico RTC driver This adds support for the alarm functionality of the RPi Pico RTC. Signed-off-by: Andrew Featherstone --- drivers/rtc/rtc_rpi_pico.c | 215 ++++++++++++++++++++- dts/arm/rpi_pico/rp2040.dtsi | 2 + dts/bindings/rtc/raspberrypi,pico-rtc.yaml | 2 +- tests/drivers/rtc/rtc_api/src/test_alarm.c | 20 +- 4 files changed, 228 insertions(+), 11 deletions(-) diff --git a/drivers/rtc/rtc_rpi_pico.c b/drivers/rtc/rtc_rpi_pico.c index f628d2e9708ef6..84a0d2a4dec29f 100644 --- a/drivers/rtc/rtc_rpi_pico.c +++ b/drivers/rtc/rtc_rpi_pico.c @@ -11,7 +11,11 @@ #include #include +#include #include +#include + +#include "rtc_utils.h" #define DT_DRV_COMPAT raspberrypi_pico_rtc @@ -22,21 +26,72 @@ #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; } @@ -101,12 +156,168 @@ static int rtc_rpi_pico_get_time(const struct device *dev, struct rtc_time *time 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 << 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 + 1) << 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 const struct rtc_driver_api 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 */ }; -static struct rtc_rpi_pico_data rtc_data; - DEVICE_DT_INST_DEFINE(0, &rtc_rpi_pico_init, NULL, &rtc_data, NULL, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, &rtc_rpi_pico_driver_api); diff --git a/dts/arm/rpi_pico/rp2040.dtsi b/dts/arm/rpi_pico/rp2040.dtsi index 4635aabd7526a6..e04af802277018 100644 --- a/dts/arm/rpi_pico/rp2040.dtsi +++ b/dts/arm/rpi_pico/rp2040.dtsi @@ -404,7 +404,9 @@ compatible = "raspberrypi,pico-rtc"; reg = <0x4005c000 DT_SIZE_K(4)>; interrupts = <25 RPI_PICO_DEFAULT_IRQ_PRIORITY>; + interrupt-names = "rtc"; resets = <&reset RPI_PICO_RESETS_RESET_RTC>; + alarms-count = <1>; status = "disabled"; }; }; diff --git a/dts/bindings/rtc/raspberrypi,pico-rtc.yaml b/dts/bindings/rtc/raspberrypi,pico-rtc.yaml index b947a9fc6d60c6..cba7a708af8b36 100644 --- a/dts/bindings/rtc/raspberrypi,pico-rtc.yaml +++ b/dts/bindings/rtc/raspberrypi,pico-rtc.yaml @@ -5,7 +5,7 @@ description: RaspberryPi Pico RTC compatible: "raspberrypi,pico-rtc" -include: [rtc.yaml, reset-device.yaml] +include: [rtc-device.yaml, reset-device.yaml] properties: reg: diff --git a/tests/drivers/rtc/rtc_api/src/test_alarm.c b/tests/drivers/rtc/rtc_api/src/test_alarm.c index 4382208910852d..fcaf187dbf6a6d 100644 --- a/tests/drivers/rtc/rtc_api/src/test_alarm.c +++ b/tests/drivers/rtc/rtc_api/src/test_alarm.c @@ -55,21 +55,25 @@ ZTEST(rtc_api, test_alarm) zassert_ok(ret, "Failed to get supported alarm fields"); - int *fields[] = { - &alarm_time_set.tm_sec, &alarm_time_set.tm_min, &alarm_time_set.tm_hour, - &alarm_time_set.tm_mday, &alarm_time_set.tm_mon, &alarm_time_set.tm_year, - &alarm_time_set.tm_wday, &alarm_time_set.tm_yday, &alarm_time_set.tm_nsec}; + alarm_time_set = (struct rtc_time) { + .tm_sec = 70, + .tm_min = 70, + .tm_hour = 25, + .tm_mday = 35, + .tm_mon = 15, + .tm_year = 8000, + .tm_wday = 8, + .tm_yday = 370, + .tm_nsec = INT32_MAX, + }; uint16_t masks[] = {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, RTC_ALARM_TIME_MASK_WEEKDAY, RTC_ALARM_TIME_MASK_YEARDAY, RTC_ALARM_TIME_MASK_NSEC}; - int bad_values[] = {70, 70, 25, 35, 15, 8000, 8, 370, INT32_MAX}; - - ARRAY_FOR_EACH(fields, j) + ARRAY_FOR_EACH(masks, j) { if (masks[j] & alarm_time_mask_supported) { - *fields[j] = bad_values[j]; ret = rtc_alarm_set_time(rtc, i, masks[j], &alarm_time_set); zassert_equal( -EINVAL, ret,