From ae36da516a16d58bcb2c2d5922016802d4e44867 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Sat, 11 Mar 2023 19:48:51 +0100 Subject: [PATCH] boards/posix/native_posix: Add emulated RTC device driver The emulated RTC device driver is used to emulate a real RTC device. Note that it is not a replacement for the native_rtc module, which is used to control simulated time, get time from the host system, etc. Signed-off-by: Bjarki Arge Andreasen --- boards/posix/native_posix/native_posix.dts | 7 + doc/hardware/peripherals/rtc.rst | 50 +- drivers/rtc/CMakeLists.txt | 1 - drivers/rtc/Kconfig | 2 + drivers/rtc/Kconfig.emul | 9 + drivers/rtc/rtc_emul.c | 526 +++++++++++++++++++++ dts/bindings/rtc/zephyr,rtc-emul.yaml | 8 + tests/drivers/rtc/rtc_api/doc/rtc_test.rst | 31 -- 8 files changed, 601 insertions(+), 33 deletions(-) create mode 100644 drivers/rtc/Kconfig.emul create mode 100644 drivers/rtc/rtc_emul.c create mode 100644 dts/bindings/rtc/zephyr,rtc-emul.yaml delete mode 100644 tests/drivers/rtc/rtc_api/doc/rtc_test.rst diff --git a/boards/posix/native_posix/native_posix.dts b/boards/posix/native_posix/native_posix.dts index aa11130a736..15e442356bf 100644 --- a/boards/posix/native_posix/native_posix.dts +++ b/boards/posix/native_posix/native_posix.dts @@ -31,6 +31,7 @@ spi-0 = &spi0; led0 = &led0; kscan0 = &sdl_kscan; + rtc = &rtc; }; leds { @@ -200,4 +201,10 @@ sample-point = <875>; bus-speed = <125000>; }; + + rtc: rtc { + status = "okay"; + compatible = "zephyr,rtc-emul"; + alarms-count = <2>; + }; }; diff --git a/doc/hardware/peripherals/rtc.rst b/doc/hardware/peripherals/rtc.rst index 0b053f548e4..e6cd52c19f5 100644 --- a/doc/hardware/peripherals/rtc.rst +++ b/doc/hardware/peripherals/rtc.rst @@ -63,4 +63,52 @@ API Reference RTC device driver test suite **************************** -See :ref:`rtc_api_test` +The test suite validates the behavior of the RTC device driver. It +is designed to be portable between boards. It uses the device tree +alias ``rtc`` to designate the RTC device to test. + +This test suite tests the following: + +* Setting and getting the time. +* RTC Time incrementing correctly. +* Alarms if supported by hardware, with and without callback enabled +* Calibration if supported by hardware. + +The calibration test tests a range of values which are printed to the +console to be manually compared. The user must review the set and +gotten values to ensure they are valid. + +By default, only the mandatory Setting and getting time is enabled +for testing. To test the optional alarms, update event callback +and clock calibration, these must be enabled by selecting +``CONFIG_RTC_ALARM``, ``CONFIG_RTC_UPDATE`` and +``CONFIG_RTC_CALIBRATION``. + +To build the test application with default settings for a board which +contains the device tree alias ``rtc``, the following command can be used +for reference: + +:: + + $ west build -p -b zephyr/tests/drivers/rtc/rtc_api/ + +To build the test with additional RTC features enabled, use menuconfig +to enable the additional features. The following command can be used +for reference: + +:: + + $ west build -p -b -t menuconfig zephyr/tests/drivers/rtc/rtc_api/ + +Then build the test application using the following command + +:: + + $ west build + +To run the test suite, flash and run the application on your board, the output will +be printed to the console. + +.. note:: + + The tests take up to 30 seconds each if they are testing real hardware. diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index cca66cf41fc..0af093d4670 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -5,4 +5,3 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c) - diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 0eaf1d1a9e4..dc3d47401f8 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -23,4 +23,6 @@ config RTC_CALIBRATION This is an option which enables driver support for RTC clock calibration. +source "drivers/rtc/Kconfig.emul" + endif # RTC diff --git a/drivers/rtc/Kconfig.emul b/drivers/rtc/Kconfig.emul new file mode 100644 index 00000000000..d006dad7b32 --- /dev/null +++ b/drivers/rtc/Kconfig.emul @@ -0,0 +1,9 @@ +# Copyright (c) 2022 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +config RTC_EMUL + bool "Emulated RTC driver" + default y + depends on DT_HAS_ZEPHYR_RTC_EMUL_ENABLED + help + Enable emulated Real-Time Clock (RTC) driver. diff --git a/drivers/rtc/rtc_emul.c b/drivers/rtc/rtc_emul.c new file mode 100644 index 00000000000..746e99641e1 --- /dev/null +++ b/drivers/rtc/rtc_emul.c @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2022 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_rtc_emul + +#include +#include +#include + +struct rtc_emul_data; + +struct rtc_emul_work_delayable { + struct k_work_delayable dwork; + const struct device *dev; +}; + +struct rtc_emul_alarm { + struct rtc_time datetime; + rtc_alarm_callback callback; + void *user_data; + uint16_t mask; + bool pending; +}; + +struct rtc_emul_data { + bool datetime_set; + + struct rtc_time datetime; + + struct k_mutex lock; + + struct rtc_emul_work_delayable dwork; + +#ifdef CONFIG_RTC_ALARM + struct rtc_emul_alarm *alarms; + uint16_t alarms_count; +#endif /* CONFIG_RTC_ALARM */ + +#ifdef CONFIG_RTC_UPDATE + rtc_update_callback update_callback; + void *update_callback_user_data; +#endif /* CONFIG_RTC_UPDATE */ + +#ifdef CONFIG_RTC_CALIBRATION + int32_t calibration; +#endif /* CONFIG_RTC_CALIBRATION */ +}; + +static const uint8_t rtc_emul_days_in_month[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static const uint8_t rtc_emul_days_in_month_with_leap[12] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static bool rtc_emul_is_leap_year(struct rtc_time *datetime) +{ + if ((datetime->tm_year % 400 == 0) || + (((datetime->tm_year % 100) > 0) && ((datetime->tm_year % 4) == 0))) { + return true; + } + + 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) ? + (rtc_emul_days_in_month_with_leap) : + (rtc_emul_days_in_month); + + return dim[datetime->tm_mon]; +} + +static void rtc_emul_increment_tm(struct rtc_time *datetime) +{ + /* Increment second */ + datetime->tm_sec++; + + /* Validate second limit */ + if (datetime->tm_sec < 60) { + return; + } + + datetime->tm_sec = 0; + + /* Increment minute */ + datetime->tm_min++; + + /* Validate minute limit */ + if (datetime->tm_min < 60) { + return; + } + + datetime->tm_min = 0; + + /* Increment hour */ + datetime->tm_hour++; + + /* Validate hour limit */ + if (datetime->tm_hour < 24) { + return; + } + + datetime->tm_hour = 0; + + /* Increment day */ + datetime->tm_wday++; + datetime->tm_mday++; + datetime->tm_yday++; + + /* Limit week day */ + if (datetime->tm_wday > 6) { + datetime->tm_wday = 0; + } + + /* Validate month limit */ + if (datetime->tm_mday <= rtc_emul_get_days_in_month(datetime)) { + return; + } + + datetime->tm_mday = 1; + + /* Increment month */ + datetime->tm_mon++; + + /* Validate month limit */ + if (datetime->tm_mon < 12) { + return; + } + + /* Increment year */ + datetime->tm_mon = 0; + datetime->tm_yday = 0; + datetime->tm_year++; +} + +#ifdef CONFIG_RTC_ALARM +static void rtc_emul_test_alarms(const struct device *dev) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + struct rtc_emul_alarm *alarm; + + for (uint16_t i = 0; i < data->alarms_count; i++) { + alarm = &data->alarms[i]; + + if (alarm->mask == 0) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_SECOND) && + (alarm->datetime.tm_sec != data->datetime.tm_sec)) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_MINUTE) && + (alarm->datetime.tm_min != data->datetime.tm_min)) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_HOUR) && + (alarm->datetime.tm_hour != data->datetime.tm_hour)) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_MONTHDAY) && + (alarm->datetime.tm_mday != data->datetime.tm_mday)) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_MONTH) && + (alarm->datetime.tm_mon != data->datetime.tm_mon)) { + continue; + } + + if ((alarm->mask & RTC_ALARM_TIME_MASK_WEEKDAY) && + (alarm->datetime.tm_wday != data->datetime.tm_wday)) { + continue; + } + + if (alarm->callback == NULL) { + alarm->pending = true; + + continue; + } + + alarm->callback(dev, i, alarm->user_data); + + alarm->pending = false; + } +} +#endif /* CONFIG_RTC_ALARM */ + +#ifdef CONFIG_RTC_UPDATE +static void rtc_emul_invoke_update_callback(const struct device *dev) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + if (data->update_callback == NULL) { + return; + } + + data->update_callback(dev, data->update_callback_user_data); +} +#endif /* CONFIG_RTC_UPDATE */ + +static void rtc_emul_update(struct k_work *work) +{ + struct rtc_emul_work_delayable *work_delayable = (struct rtc_emul_work_delayable *)work; + const struct device *dev = work_delayable->dev; + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + k_work_schedule(&work_delayable->dwork, K_MSEC(1000)); + + k_mutex_lock(&data->lock, K_FOREVER); + + rtc_emul_increment_tm(&data->datetime); + +#ifdef CONFIG_RTC_ALARM + rtc_emul_test_alarms(dev); +#endif /* CONFIG_RTC_ALARM */ + +#ifdef CONFIG_RTC_UPDATE + rtc_emul_invoke_update_callback(dev); +#endif /* CONFIG_RTC_UPDATE */ + + k_mutex_unlock(&data->lock); +} + +static int rtc_emul_set_time(const struct device *dev, const struct rtc_time *timeptr) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + /* Validate arguments */ + if (timeptr == NULL) { + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + data->datetime = (*timeptr); + data->datetime.tm_isdst = -1; + data->datetime.tm_nsec = 0; + + data->datetime_set = true; + + k_mutex_unlock(&data->lock); + + return 0; +} + +static int rtc_emul_get_time(const struct device *dev, struct rtc_time *timeptr) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + /* Validate arguments */ + if (timeptr == NULL) { + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + /* Validate RTC time is set */ + if (data->datetime_set == false) { + k_mutex_unlock(&data->lock); + + return -ENODATA; + } + + (*timeptr) = data->datetime; + + k_mutex_unlock(&data->lock); + + return 0; +} + +#ifdef CONFIG_RTC_ALARM +static int rtc_emul_alarm_get_supported_fields(const struct device *dev, uint16_t id, + uint16_t *mask) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + if (data->alarms_count <= id) { + 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_WEEKDAY); + + return 0; +} + +static int rtc_emul_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, + const struct rtc_time *timeptr) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + if (data->alarms_count <= id) { + return -EINVAL; + } + + if ((mask > 0) && (timeptr == NULL)) { + return -EINVAL; + } + + if (mask > 0) { + if (rtc_emul_validate_alarm_time(timeptr, mask) == false) { + return -EINVAL; + } + } + + k_mutex_lock(&data->lock, K_FOREVER); + + data->alarms[id].mask = mask; + + if (timeptr != NULL) { + data->alarms[id].datetime = *timeptr; + } + + k_mutex_unlock(&data->lock); + + return 0; +} + +static int rtc_emul_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, + struct rtc_time *timeptr) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + if (data->alarms_count <= id) { + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + (*timeptr) = data->alarms[id].datetime; + (*mask) = data->alarms[id].mask; + + k_mutex_unlock(&data->lock); + + return 0; +} + +static int rtc_emul_alarm_is_pending(const struct device *dev, uint16_t id) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + int ret; + + if (data->alarms_count <= id) { + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = (data->alarms[id].pending == true) ? 1 : 0; + + data->alarms[id].pending = false; + + k_mutex_unlock(&data->lock); + + return ret; +} + +static int rtc_emul_alarm_set_callback(const struct device *dev, uint16_t id, + rtc_alarm_callback callback, void *user_data) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + if (data->alarms_count <= id) { + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + data->alarms[id].callback = callback; + data->alarms[id].user_data = user_data; + + k_mutex_unlock(&data->lock); + + return 0; +} +#endif /* CONFIG_RTC_ALARM */ + +#ifdef CONFIG_RTC_UPDATE +static int rtc_emul_update_set_callback(const struct device *dev, + rtc_update_callback callback, void *user_data) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + data->update_callback = callback; + data->update_callback_user_data = user_data; + + k_mutex_unlock(&data->lock); + + return 0; +} +#endif /* CONFIG_RTC_UPDATE */ + +#ifdef CONFIG_RTC_CALIBRATION +static int rtc_emul_set_calibration(const struct device *dev, int32_t calibration) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + data->calibration = calibration; + + k_mutex_unlock(&data->lock); + + return 0; +} + +static int rtc_emul_get_calibration(const struct device *dev, int32_t *calibration) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + (*calibration) = data->calibration; + + k_mutex_unlock(&data->lock); + + return 0; +} +#endif /* CONFIG_RTC_CALIBRATION */ + +struct rtc_driver_api rtc_emul_driver_api = { + .set_time = rtc_emul_set_time, + .get_time = rtc_emul_get_time, +#ifdef CONFIG_RTC_ALARM + .alarm_get_supported_fields = rtc_emul_alarm_get_supported_fields, + .alarm_set_time = rtc_emul_alarm_set_time, + .alarm_get_time = rtc_emul_alarm_get_time, + .alarm_is_pending = rtc_emul_alarm_is_pending, + .alarm_set_callback = rtc_emul_alarm_set_callback, +#endif /* CONFIG_RTC_ALARM */ +#ifdef CONFIG_RTC_UPDATE + .update_set_callback = rtc_emul_update_set_callback, +#endif /* CONFIG_RTC_UPDATE */ +#ifdef CONFIG_RTC_CALIBRATION + .set_calibration = rtc_emul_set_calibration, + .get_calibration = rtc_emul_get_calibration, +#endif /* CONFIG_RTC_CALIBRATION */ +}; + +int rtc_emul_init(const struct device *dev) +{ + struct rtc_emul_data *data = (struct rtc_emul_data *)dev->data; + + k_mutex_init(&data->lock); + + data->dwork.dev = dev; + k_work_init_delayable(&data->dwork.dwork, rtc_emul_update); + + k_work_schedule(&data->dwork.dwork, K_MSEC(1000)); + + return 0; +} + +#ifdef CONFIG_RTC_ALARM +#define RTC_EMUL_DEVICE_DATA(id) \ + static struct rtc_emul_alarm rtc_emul_alarms_##id[DT_INST_PROP(id, alarms_count)]; \ + \ + struct rtc_emul_data rtc_emul_data_##id = { \ + .alarms = rtc_emul_alarms_##id, \ + .alarms_count = ARRAY_SIZE(rtc_emul_alarms_##id), \ + }; +#else +#define RTC_EMUL_DEVICE_DATA(id) \ + struct rtc_emul_data rtc_emul_data_##id; +#endif /* CONFIG_RTC_ALARM */ + +#define RTC_EMUL_DEVICE(id) \ + RTC_EMUL_DEVICE_DATA(id) \ + \ + DEVICE_DT_INST_DEFINE(id, rtc_emul_init, NULL, &rtc_emul_data_##id, NULL, POST_KERNEL, \ + 99, &rtc_emul_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(RTC_EMUL_DEVICE); diff --git a/dts/bindings/rtc/zephyr,rtc-emul.yaml b/dts/bindings/rtc/zephyr,rtc-emul.yaml new file mode 100644 index 00000000000..0b910f61aa7 --- /dev/null +++ b/dts/bindings/rtc/zephyr,rtc-emul.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2022 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +description: Emulated RTC device + +compatible: "zephyr,rtc-emul" + +include: rtc-device.yaml diff --git a/tests/drivers/rtc/rtc_api/doc/rtc_test.rst b/tests/drivers/rtc/rtc_api/doc/rtc_test.rst deleted file mode 100644 index 7846670f131..00000000000 --- a/tests/drivers/rtc/rtc_api/doc/rtc_test.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _rtc_api_test: - -:Author: Bjarki Arge Andreasen - -RTC Test suite -############### - -Test RTC API implementation for RTC devices. - -Overview -******** - -This suite is design to be portable between boards. It uses the alias -``rtc`` to designate the RTC device to test. - -This test suite tests the following: - -* Setting and getting the time. -* RTC Time incrementing correctly. -* Alarms if supported by hardware, with and without callback enabled -* Calibration if supported by hardware. - -The calibration test tests a range of values which are printed to the -console to be manually compared. The user must review the set and -gotten values to ensure they are valid. - -By default, only the mandatory Setting and getting time is enabled -for testing. To test the optional alarms, update event callback -and clock calibration, these must be enabled by selecting -``CONFIG_RTC_ALARM``, ``CONFIG_RTC_UPDATE`` and -``CONFIG_RTC_CALIBRATION``.