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 <baa@trackunit.com>
This commit is contained in:
Bjarki Arge Andreasen 2023-03-11 19:48:51 +01:00 committed by Carles Cufí
commit ae36da516a
8 changed files with 601 additions and 33 deletions

View file

@ -31,6 +31,7 @@
spi-0 = &spi0; spi-0 = &spi0;
led0 = &led0; led0 = &led0;
kscan0 = &sdl_kscan; kscan0 = &sdl_kscan;
rtc = &rtc;
}; };
leds { leds {
@ -200,4 +201,10 @@
sample-point = <875>; sample-point = <875>;
bus-speed = <125000>; bus-speed = <125000>;
}; };
rtc: rtc {
status = "okay";
compatible = "zephyr,rtc-emul";
alarms-count = <2>;
};
}; };

View file

@ -63,4 +63,52 @@ API Reference
RTC device driver test suite 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 <your board> 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 <your board> -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.

View file

@ -5,4 +5,3 @@ zephyr_library()
zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c)
zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c) zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c)

View file

@ -23,4 +23,6 @@ config RTC_CALIBRATION
This is an option which enables driver support for RTC clock This is an option which enables driver support for RTC clock
calibration. calibration.
source "drivers/rtc/Kconfig.emul"
endif # RTC endif # RTC

9
drivers/rtc/Kconfig.emul Normal file
View file

@ -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.

526
drivers/rtc/rtc_emul.c Normal file
View file

@ -0,0 +1,526 @@
/*
* Copyright (c) 2022 Bjarki Arge Andreasen
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_rtc_emul
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/rtc.h>
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);

View file

@ -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

View file

@ -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``.