drivers: rtc: add ambiq rtc driver
Add Ambiq RTC driver Signed-off-by: Richard Wheatley <richard.wheatley@ambiq.com>
This commit is contained in:
parent
8f2413fbe2
commit
0a945cbdec
4 changed files with 391 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ zephyr_library_sources(rtc_utils.c)
|
|||
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_RV8263 rtc_rv8263.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_AM1805 rtc_am1805.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_AMBIQ rtc_ambiq.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_DS1307 rtc_ds1307.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ config RTC_SHELL
|
|||
RTC Shell commands
|
||||
|
||||
source "drivers/rtc/Kconfig.am1805"
|
||||
source "drivers/rtc/Kconfig.ambiq"
|
||||
source "drivers/rtc/Kconfig.ds1307"
|
||||
source "drivers/rtc/Kconfig.emul"
|
||||
source "drivers/rtc/Kconfig.fake"
|
||||
|
|
|
|||
10
drivers/rtc/Kconfig.ambiq
Normal file
10
drivers/rtc/Kconfig.ambiq
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2024, Ambiq Micro Inc. <www.ambiq.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config RTC_AMBIQ
|
||||
bool "AMBIQ RTC driver"
|
||||
default y
|
||||
depends on DT_HAS_AMBIQ_RTC_ENABLED
|
||||
select AMBIQ_HAL
|
||||
help
|
||||
Enable the AMBIQ RTC driver.
|
||||
379
drivers/rtc/rtc_ambiq.c
Normal file
379
drivers/rtc/rtc_ambiq.c
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Copyright (c) 2024 Ambiq Micro
|
||||
*/
|
||||
|
||||
|
||||
#include <zephyr/drivers/rtc.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include "rtc_utils.h"
|
||||
|
||||
#define DT_DRV_COMPAT ambiq_rtc
|
||||
|
||||
LOG_MODULE_REGISTER(ambiq_rtc, CONFIG_RTC_LOG_LEVEL);
|
||||
|
||||
#include <am_mcu_apollo.h>
|
||||
|
||||
#define AMBIQ_RTC_ALARM_TIME_MASK \
|
||||
(RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \
|
||||
RTC_ALARM_TIME_MASK_WEEKDAY | RTC_ALARM_TIME_MASK_MONTH | RTC_ALARM_TIME_MASK_MONTHDAY)
|
||||
|
||||
/* struct tm start time: 1st, Jan, 1900 */
|
||||
#define TM_YEAR_REF 1900
|
||||
#define AMBIQ_RTC_YEAR_MAX 2199
|
||||
|
||||
struct ambiq_rtc_config {
|
||||
uint8_t clk_src;
|
||||
};
|
||||
|
||||
struct ambiq_rtc_data {
|
||||
struct k_spinlock lock;
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
struct rtc_time alarm_time;
|
||||
uint16_t alarm_set_mask;
|
||||
rtc_alarm_callback alarm_user_callback;
|
||||
void *alarm_user_data;
|
||||
bool alarm_pending;
|
||||
#endif
|
||||
};
|
||||
|
||||
static void rtc_time_to_ambiq_time_set(const struct rtc_time *tm, am_hal_rtc_time_t *atm)
|
||||
{
|
||||
atm->ui32CenturyBit = ((tm->tm_year <= 99) || (tm->tm_year >= 200));
|
||||
atm->ui32Year = tm->tm_year;
|
||||
if (tm->tm_year > 99) {
|
||||
atm->ui32Year = tm->tm_year % 100;
|
||||
}
|
||||
atm->ui32Weekday = tm->tm_wday;
|
||||
atm->ui32Month = tm->tm_mon + 1;
|
||||
atm->ui32DayOfMonth = tm->tm_mday;
|
||||
atm->ui32Hour = tm->tm_hour;
|
||||
atm->ui32Minute = tm->tm_min;
|
||||
atm->ui32Second = tm->tm_sec;
|
||||
|
||||
/* Nanoseconds times 10mil is hundredths */
|
||||
atm->ui32Hundredths = tm->tm_nsec / 10000000;
|
||||
if (atm->ui32Hundredths > 99) {
|
||||
uint16_t value = atm->ui32Hundredths / 100;
|
||||
|
||||
atm->ui32Second += value;
|
||||
atm->ui32Hundredths -= value*100;
|
||||
}
|
||||
}
|
||||
|
||||
/* To set the timer registers */
|
||||
static void ambiq_time_to_rtc_time_set(const am_hal_rtc_time_t *atm, struct rtc_time *tm)
|
||||
{
|
||||
tm->tm_year = atm->ui32Year;
|
||||
if (atm->ui32CenturyBit == 0)
|
||||
tm->tm_year += 100;
|
||||
else
|
||||
tm->tm_year += 200;
|
||||
tm->tm_wday = atm->ui32Weekday;
|
||||
tm->tm_mon = atm->ui32Month - 1;
|
||||
tm->tm_mday = atm->ui32DayOfMonth;
|
||||
tm->tm_hour = atm->ui32Hour;
|
||||
tm->tm_min = atm->ui32Minute;
|
||||
tm->tm_sec = atm->ui32Second;
|
||||
|
||||
/* Nanoseconds times 10mil is hundredths */
|
||||
tm->tm_nsec = atm->ui32Hundredths * 10000000;
|
||||
}
|
||||
|
||||
static int test_for_rollover(am_hal_rtc_time_t *atm)
|
||||
{
|
||||
if ((atm->ui32Year == 99) &&
|
||||
(atm->ui32Month == 12) &&
|
||||
(atm->ui32DayOfMonth == 31)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* To set the timer registers */
|
||||
static int ambiq_rtc_set_time(const struct device *dev, const struct rtc_time *timeptr)
|
||||
{
|
||||
int err = 0;
|
||||
am_hal_rtc_time_t ambiq_time = {0};
|
||||
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
|
||||
if (timeptr->tm_year + TM_YEAR_REF > AMBIQ_RTC_YEAR_MAX) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
|
||||
LOG_DBG("set time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, "
|
||||
"min = %d, sec = %d",
|
||||
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
||||
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
||||
|
||||
/* Convertn to Ambiq Time */
|
||||
rtc_time_to_ambiq_time_set(timeptr, &ambiq_time);
|
||||
|
||||
if (test_for_rollover(&ambiq_time)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = am_hal_rtc_time_set(&ambiq_time);
|
||||
if (err) {
|
||||
LOG_WRN("Set Timer returned an error - %d!", err);
|
||||
}
|
||||
|
||||
k_spin_unlock(&data->lock, key);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* To get from the timer registers */
|
||||
static int ambiq_rtc_get_time(const struct device *dev, struct rtc_time *timeptr)
|
||||
{
|
||||
int err = 0;
|
||||
am_hal_rtc_time_t ambiq_time = {0};
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
|
||||
err = am_hal_rtc_time_get(&ambiq_time);
|
||||
if (err != 0) {
|
||||
LOG_WRN("Get Timer returned an error - %d!", err);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ambiq_time_to_rtc_time_set(&ambiq_time, timeptr);
|
||||
|
||||
LOG_DBG("get time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, "
|
||||
"min = %d, sec = %d",
|
||||
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
||||
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
||||
|
||||
unlock:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
|
||||
static int ambiq_rtc_alarm_get_supported_fields(const struct device *dev,
|
||||
uint16_t id, uint16_t *mask)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
if (id != 0U) {
|
||||
LOG_ERR("Invalid ID %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*mask = AMBIQ_RTC_ALARM_TIME_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* To get from the alarm registers */
|
||||
static int ambiq_rtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
|
||||
struct rtc_time *timeptr)
|
||||
{
|
||||
|
||||
int err = 0;
|
||||
am_hal_rtc_time_t ambiq_time = {0};
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
|
||||
if (id != 0U) {
|
||||
LOG_ERR("Invalid ID %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
|
||||
err = am_hal_rtc_alarm_get(&ambiq_time, NULL);
|
||||
if (err != 0) {
|
||||
LOG_DBG("Invalid Input Value");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ambiq_time_to_rtc_time_set(&ambiq_time, timeptr);
|
||||
|
||||
*mask = data->alarm_set_mask;
|
||||
|
||||
LOG_DBG("get alarm: wday = %d, mon = %d, mday = %d, hour = %d, min = %d, sec = %d, "
|
||||
"mask = 0x%04x", timeptr->tm_wday, timeptr->tm_mon, timeptr->tm_mday,
|
||||
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, *mask);
|
||||
|
||||
k_spin_unlock(&data->lock, key);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ambiq_rtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
|
||||
const struct rtc_time *timeptr)
|
||||
{
|
||||
int err = 0;
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
am_hal_rtc_time_t ambiq_time = {0};
|
||||
uint16_t mask_available;
|
||||
|
||||
if (id != 0U) {
|
||||
LOG_ERR("Invalid ID %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rtc_utils_validate_rtc_time(timeptr, mask) == false) {
|
||||
LOG_DBG("Invalid Input Value");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
(void)ambiq_rtc_alarm_get_supported_fields(NULL, 0, &mask_available);
|
||||
|
||||
if (mask & ~mask_available) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->alarm_set_mask = mask;
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
|
||||
/* Disable and clear the alarm */
|
||||
am_hal_rtc_interrupt_disable(AM_HAL_RTC_INT_ALM);
|
||||
am_hal_rtc_interrupt_clear(AM_HAL_RTC_INT_ALM);
|
||||
|
||||
/* When mask is 0 */
|
||||
if (mask == 0) {
|
||||
LOG_DBG("The alarm is disabled");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
LOG_DBG("set alarm: second = %d, min = %d, hour = %d, mday = %d, month = %d,"
|
||||
"wday = %d, mask = 0x%04x",
|
||||
timeptr->tm_sec, timeptr->tm_min, timeptr->tm_hour, timeptr->tm_mday,
|
||||
timeptr->tm_mon, timeptr->tm_wday, mask);
|
||||
|
||||
/* Convertn to Ambiq Time */
|
||||
rtc_time_to_ambiq_time_set(timeptr, &ambiq_time);
|
||||
|
||||
/* Set RTC ALARM, Ambiq must have interval != AM_HAL_RTC_ALM_RPT_DIS */
|
||||
if (0 != am_hal_rtc_alarm_set(&ambiq_time, AM_HAL_RTC_ALM_RPT_YR)) {
|
||||
LOG_DBG("Invalid Input Value");
|
||||
err = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
am_hal_rtc_interrupt_enable(AM_HAL_RTC_INT_ALM);
|
||||
|
||||
unlock:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ambiq_rtc_alarm_is_pending(const struct device *dev, uint16_t id)
|
||||
{
|
||||
struct ambiq_rtc_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 void ambiq_rtc_isr(const struct device *dev)
|
||||
{
|
||||
/* Clear the RTC alarm interrupt. 8*/
|
||||
am_hal_rtc_interrupt_clear(AM_HAL_RTC_INT_ALM);
|
||||
|
||||
#if defined(CONFIG_RTC_ALARM)
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
|
||||
if (data->alarm_user_callback) {
|
||||
data->alarm_user_callback(dev, 0, data->alarm_user_data);
|
||||
data->alarm_pending = false;
|
||||
} else {
|
||||
data->alarm_pending = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int ambiq_rtc_alarm_set_callback(const struct device *dev, uint16_t id,
|
||||
rtc_alarm_callback callback, void *user_data)
|
||||
{
|
||||
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
|
||||
if (id != 0U) {
|
||||
LOG_ERR("Invalid ID %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
K_SPINLOCK(&data->lock) {
|
||||
data->alarm_user_callback = callback;
|
||||
data->alarm_user_data = user_data;
|
||||
if ((callback == NULL) && (user_data == NULL)) {
|
||||
am_hal_rtc_interrupt_disable(AM_HAL_RTC_INT_ALM);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ambiq_rtc_init(const struct device *dev)
|
||||
{
|
||||
const struct ambiq_rtc_config *config = dev->config;
|
||||
#
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
struct ambiq_rtc_data *data = dev->data;
|
||||
#endif
|
||||
|
||||
/* Enable the clock for RTC. */
|
||||
am_hal_clkgen_control(config->clk_src, NULL);
|
||||
|
||||
/* Enable the RTC. */
|
||||
am_hal_rtc_osc_enable();
|
||||
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
data->alarm_user_callback = NULL;
|
||||
data->alarm_pending = false;
|
||||
|
||||
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), ambiq_rtc_isr, DEVICE_DT_INST_GET(0),
|
||||
0);
|
||||
irq_enable(DT_INST_IRQN(0));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_driver_api ambiq_rtc_driver_api = {
|
||||
.set_time = ambiq_rtc_set_time,
|
||||
.get_time = ambiq_rtc_get_time,
|
||||
/* RTC_UPDATE not supported */
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
.alarm_get_supported_fields = ambiq_rtc_alarm_get_supported_fields,
|
||||
.alarm_set_time = ambiq_rtc_alarm_set_time,
|
||||
.alarm_get_time = ambiq_rtc_alarm_get_time,
|
||||
.alarm_is_pending = ambiq_rtc_alarm_is_pending,
|
||||
.alarm_set_callback = ambiq_rtc_alarm_set_callback,
|
||||
#endif
|
||||
};
|
||||
|
||||
#define AMBIQ_RTC_INIT(inst) \
|
||||
static const struct ambiq_rtc_config ambiq_rtc_config_##inst = { \
|
||||
.clk_src = DT_INST_ENUM_IDX(inst, clock)}; \
|
||||
\
|
||||
static struct ambiq_rtc_data ambiq_rtc_data##inst; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, &ambiq_rtc_init, NULL, &ambiq_rtc_data##inst, \
|
||||
&ambiq_rtc_config_##inst, POST_KERNEL, \
|
||||
CONFIG_RTC_INIT_PRIORITY, &ambiq_rtc_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(AMBIQ_RTC_INIT)
|
||||
Loading…
Reference in a new issue