| /* |
| * Copyright (c) 2024 Analog Devices, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT adi_max32_rtc_counter |
| |
| #include <zephyr/drivers/counter.h> |
| #include <zephyr/drivers/clock_control/adi_max32_clock_control.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/irq.h> |
| |
| #include <rtc.h> |
| #include <wrap_max32_lp.h> |
| |
| /* Resoultion is 1sec for time of day alarm*/ |
| #define MAX32_RTC_COUNTER_FREQ 1 |
| |
| /* 20bits used for time of day alarm */ |
| #define MAX32_RTC_COUNTER_MAX_VALUE ((1 << 21) - 1) |
| |
| #define MAX32_RTC_COUNTER_INT_FL MXC_RTC_INT_FL_LONG |
| #define MAX32_RTC_COUNTER_INT_EN MXC_RTC_INT_EN_LONG |
| |
| struct max32_rtc_data { |
| counter_alarm_callback_t alarm_callback; |
| counter_top_callback_t top_callback; |
| void *alarm_user_data; |
| void *top_user_data; |
| }; |
| |
| struct max32_rtc_config { |
| struct counter_config_info info; |
| mxc_rtc_regs_t *regs; |
| void (*irq_func)(void); |
| }; |
| |
| static int api_start(const struct device *dev) |
| { |
| /* Ensure that both sec and subsec are reset to 0 */ |
| while (MXC_RTC_Init(0, 0) == E_BUSY) { |
| ; |
| } |
| |
| while (MXC_RTC_Start() == E_BUSY) { |
| ; |
| } |
| |
| while (MXC_RTC_EnableInt(MAX32_RTC_COUNTER_INT_EN) == E_BUSY) { |
| ; |
| } |
| |
| return 0; |
| } |
| |
| static int api_stop(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| while (MXC_RTC_DisableInt(MAX32_RTC_COUNTER_INT_EN) == E_BUSY) { |
| ; |
| } |
| MXC_RTC_Stop(); |
| |
| return 0; |
| } |
| |
| static int api_get_value(const struct device *dev, uint32_t *ticks) |
| { |
| const struct max32_rtc_config *cfg = dev->config; |
| mxc_rtc_regs_t *regs = cfg->regs; |
| uint32_t sec = 0, subsec = 0; |
| |
| /* Read twice incase of glitch */ |
| sec = regs->sec; |
| if (regs->sec != sec) { |
| sec = regs->sec; |
| } |
| |
| /* Read twice incase of glitch */ |
| subsec = regs->ssec; |
| if (regs->ssec != subsec) { |
| subsec = regs->ssec; |
| } |
| |
| *ticks = sec; |
| if (subsec >= (MXC_RTC_MAX_SSEC / 2)) { |
| *ticks += 1; |
| } |
| |
| return 0; |
| } |
| |
| static int api_set_top_value(const struct device *dev, const struct counter_top_cfg *counter_cfg) |
| { |
| const struct max32_rtc_config *cfg = dev->config; |
| struct max32_rtc_data *const data = dev->data; |
| |
| if (counter_cfg->ticks == 0) { |
| return -EINVAL; |
| } |
| |
| if (counter_cfg->ticks != cfg->info.max_top_value) { |
| return -ENOTSUP; |
| } |
| |
| data->top_callback = counter_cfg->callback; |
| data->top_user_data = counter_cfg->user_data; |
| |
| return 0; |
| } |
| |
| static uint32_t api_get_pending_int(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| int flags = MXC_RTC_GetFlags(); |
| |
| if (flags & MAX32_RTC_COUNTER_INT_FL) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t api_get_top_value(const struct device *dev) |
| { |
| const struct max32_rtc_config *cfg = dev->config; |
| |
| return cfg->info.max_top_value; |
| } |
| |
| static int api_set_alarm(const struct device *dev, uint8_t chan, |
| const struct counter_alarm_cfg *alarm_cfg) |
| { |
| int ret; |
| struct max32_rtc_data *data = dev->data; |
| uint32_t ticks = alarm_cfg->ticks; |
| uint32_t current; |
| |
| /* Alarm frequenct is 1Hz so that it seems ticks becomes 0 |
| * some times, in that case system being blocked. |
| * Set it to 1 if ticks is 0 |
| */ |
| if (ticks == 0) { |
| ticks = 1; |
| } |
| |
| if (alarm_cfg->ticks > api_get_top_value(dev)) { |
| return -EINVAL; |
| } |
| |
| if (data->alarm_callback != NULL) { |
| return -EBUSY; |
| } |
| |
| api_stop(dev); |
| |
| api_get_value(dev, ¤t); |
| if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) { |
| ticks += current; |
| } |
| |
| ret = MXC_RTC_SetTimeofdayAlarm(ticks); |
| if (ret == E_BUSY) { |
| ret = -EBUSY; |
| } |
| |
| if (ret == 0) { |
| data->alarm_callback = alarm_cfg->callback; |
| data->alarm_user_data = alarm_cfg->user_data; |
| } |
| |
| api_start(dev); |
| |
| return ret; |
| } |
| |
| static int api_cancel_alarm(const struct device *dev, uint8_t chan) |
| { |
| struct max32_rtc_data *data = dev->data; |
| |
| while (MXC_RTC_DisableInt(MAX32_RTC_COUNTER_INT_EN) == E_BUSY) { |
| ; |
| } |
| data->alarm_callback = NULL; |
| |
| return 0; |
| } |
| |
| static void rtc_max32_isr(const struct device *dev) |
| { |
| struct max32_rtc_data *const data = dev->data; |
| int flags = MXC_RTC_GetFlags(); |
| |
| if (flags & MAX32_RTC_COUNTER_INT_FL) { |
| if (data->alarm_callback) { |
| counter_alarm_callback_t cb; |
| uint32_t current; |
| |
| api_get_value(dev, ¤t); |
| |
| cb = data->alarm_callback; |
| data->alarm_callback = NULL; |
| cb(dev, 0, current, data->alarm_user_data); |
| } |
| } |
| |
| /* Clear all flags */ |
| MXC_RTC_ClearFlags(flags); |
| } |
| |
| static int rtc_max32_init(const struct device *dev) |
| { |
| const struct max32_rtc_config *cfg = dev->config; |
| |
| while (MXC_RTC_Init(0, 0) == E_BUSY) { |
| ; |
| } |
| |
| api_stop(dev); |
| |
| cfg->irq_func(); |
| |
| return 0; |
| } |
| |
| static const struct counter_driver_api counter_rtc_max32_driver_api = { |
| .start = api_start, |
| .stop = api_stop, |
| .get_value = api_get_value, |
| .set_top_value = api_set_top_value, |
| .get_pending_int = api_get_pending_int, |
| .get_top_value = api_get_top_value, |
| .set_alarm = api_set_alarm, |
| .cancel_alarm = api_cancel_alarm, |
| }; |
| |
| #define COUNTER_RTC_MAX32_INIT(_num) \ |
| static struct max32_rtc_data rtc_max32_data_##_num; \ |
| static void max32_rtc_irq_init_##_num(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), rtc_max32_isr, \ |
| DEVICE_DT_INST_GET(_num), 0); \ |
| irq_enable(DT_INST_IRQN(_num)); \ |
| if (DT_INST_PROP(_num, wakeup_source)) { \ |
| MXC_LP_EnableRTCAlarmWakeup(); \ |
| } \ |
| }; \ |
| static const struct max32_rtc_config rtc_max32_config_##_num = { \ |
| .info = \ |
| { \ |
| .max_top_value = MAX32_RTC_COUNTER_MAX_VALUE, \ |
| .freq = MAX32_RTC_COUNTER_FREQ, \ |
| .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
| .channels = 1, \ |
| }, \ |
| .regs = (mxc_rtc_regs_t *)DT_INST_REG_ADDR(_num), \ |
| .irq_func = max32_rtc_irq_init_##_num, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(_num, &rtc_max32_init, NULL, &rtc_max32_data_##_num, \ |
| &rtc_max32_config_##_num, PRE_KERNEL_1, \ |
| CONFIG_COUNTER_INIT_PRIORITY, &counter_rtc_max32_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(COUNTER_RTC_MAX32_INIT) |