|  | /* | 
|  | * Copyright (c) 2019, Piotr Mienkowski | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT silabs_gecko_rtcc | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <string.h> | 
|  | #include <errno.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <soc.h> | 
|  | #include <em_cmu.h> | 
|  | #include <em_rtcc.h> | 
|  | #include <zephyr/drivers/counter.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(counter_gecko, CONFIG_COUNTER_LOG_LEVEL); | 
|  |  | 
|  | #define RTCC_MAX_VALUE       (_RTCC_CNT_MASK) | 
|  | #define RTCC_ALARM_NUM        2 | 
|  |  | 
|  | struct counter_gecko_config { | 
|  | struct counter_config_info info; | 
|  | void (*irq_config)(void); | 
|  | uint32_t prescaler; | 
|  | }; | 
|  |  | 
|  | struct counter_gecko_alarm_data { | 
|  | counter_alarm_callback_t callback; | 
|  | void *user_data; | 
|  | }; | 
|  |  | 
|  | struct counter_gecko_data { | 
|  | struct counter_gecko_alarm_data alarm[RTCC_ALARM_NUM]; | 
|  | counter_top_callback_t top_callback; | 
|  | void *top_user_data; | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_SOC_GECKO_HAS_ERRATA_RTCC_E201 | 
|  | #define ERRATA_RTCC_E201_MESSAGE \ | 
|  | "Errata RTCC_E201: In case RTCC prescaler != 1 the module does not " \ | 
|  | "reset the counter value on CCV1 compare." | 
|  | #endif | 
|  |  | 
|  | /* Map channel id to CC channel provided by the RTCC module */ | 
|  | static uint8_t chan_id2cc_idx(uint8_t chan_id) | 
|  | { | 
|  | uint8_t cc_idx; | 
|  |  | 
|  | switch (chan_id) { | 
|  | case 0: | 
|  | cc_idx = 2; | 
|  | break; | 
|  | default: | 
|  | cc_idx = 0; | 
|  | break; | 
|  | } | 
|  | return cc_idx; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_start(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | RTCC_Enable(true); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_stop(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | RTCC_Enable(false); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_get_value(const struct device *dev, uint32_t *ticks) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | *ticks = RTCC_CounterGet(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_set_top_value(const struct device *dev, | 
|  | const struct counter_top_cfg *cfg) | 
|  | { | 
|  | struct counter_gecko_data *const dev_data = dev->data; | 
|  | uint32_t ticks; | 
|  | uint32_t flags; | 
|  | int err = 0; | 
|  |  | 
|  | #ifdef CONFIG_SOC_GECKO_HAS_ERRATA_RTCC_E201 | 
|  | const struct counter_gecko_config *const dev_cfg = dev->config; | 
|  |  | 
|  | if (dev_cfg->prescaler != 1) { | 
|  | LOG_ERR(ERRATA_RTCC_E201_MESSAGE); | 
|  | return -EINVAL; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Counter top value can only be changed when all alarms are disabled */ | 
|  | for (int i = 0; i < RTCC_ALARM_NUM; i++) { | 
|  | if (dev_data->alarm[i].callback) { | 
|  | return -EBUSY; | 
|  | } | 
|  | } | 
|  |  | 
|  | RTCC_IntClear(RTCC_IF_CC1); | 
|  |  | 
|  | dev_data->top_callback = cfg->callback; | 
|  | dev_data->top_user_data = cfg->user_data; | 
|  | ticks = cfg->ticks; | 
|  | flags = cfg->flags; | 
|  |  | 
|  | if (!(flags & COUNTER_TOP_CFG_DONT_RESET)) { | 
|  | RTCC_CounterSet(0); | 
|  | } | 
|  |  | 
|  | RTCC_ChannelCCVSet(1, ticks); | 
|  |  | 
|  | LOG_DBG("set top value: %u", ticks); | 
|  |  | 
|  | if ((flags & COUNTER_TOP_CFG_DONT_RESET) && | 
|  | RTCC_CounterGet() > ticks) { | 
|  | err = -ETIME; | 
|  | if (flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { | 
|  | RTCC_CounterSet(0); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Enable the compare interrupt */ | 
|  | RTCC_IntEnable(RTCC_IF_CC1); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static uint32_t counter_gecko_get_top_value(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | return RTCC_ChannelCCVGet(1); | 
|  | } | 
|  |  | 
|  | static int counter_gecko_set_alarm(const struct device *dev, uint8_t chan_id, | 
|  | const struct counter_alarm_cfg *alarm_cfg) | 
|  | { | 
|  | uint32_t count = RTCC_CounterGet(); | 
|  | struct counter_gecko_data *const dev_data = dev->data; | 
|  | uint32_t top_value = counter_gecko_get_top_value(dev); | 
|  | uint32_t ccv; | 
|  |  | 
|  | if ((top_value != 0) && (alarm_cfg->ticks > top_value)) { | 
|  | return -EINVAL; | 
|  | } | 
|  | if (dev_data->alarm[chan_id].callback != NULL) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) != 0) { | 
|  | ccv = alarm_cfg->ticks; | 
|  | } else { | 
|  | if (top_value == 0) { | 
|  | ccv = count + alarm_cfg->ticks; | 
|  | } else { | 
|  | uint64_t ccv64 = count + alarm_cfg->ticks; | 
|  |  | 
|  | ccv = (uint32_t)(ccv64 % top_value); | 
|  | } | 
|  | } | 
|  |  | 
|  | uint8_t cc_idx = chan_id2cc_idx(chan_id); | 
|  |  | 
|  | RTCC_IntClear(RTCC_IF_CC0 << cc_idx); | 
|  |  | 
|  | dev_data->alarm[chan_id].callback = alarm_cfg->callback; | 
|  | dev_data->alarm[chan_id].user_data = alarm_cfg->user_data; | 
|  |  | 
|  | RTCC_ChannelCCVSet(cc_idx, ccv); | 
|  |  | 
|  | LOG_DBG("set alarm: channel %u, count %u", chan_id, ccv); | 
|  |  | 
|  | /* Enable the compare interrupt */ | 
|  | RTCC_IntEnable(RTCC_IF_CC0 << cc_idx); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_cancel_alarm(const struct device *dev, | 
|  | uint8_t chan_id) | 
|  | { | 
|  | struct counter_gecko_data *const dev_data = dev->data; | 
|  |  | 
|  | uint8_t cc_idx = chan_id2cc_idx(chan_id); | 
|  |  | 
|  | /* Disable the compare interrupt */ | 
|  | RTCC_IntDisable(RTCC_IF_CC0 << cc_idx); | 
|  | RTCC_IntClear(RTCC_IF_CC0 << cc_idx); | 
|  |  | 
|  | dev_data->alarm[chan_id].callback = NULL; | 
|  | dev_data->alarm[chan_id].user_data = NULL; | 
|  |  | 
|  | RTCC_ChannelCCVSet(cc_idx, 0); | 
|  |  | 
|  | LOG_DBG("cancel alarm: channel %u", chan_id); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint32_t counter_gecko_get_pending_int(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_gecko_init(const struct device *dev) | 
|  | { | 
|  | const struct counter_gecko_config *const dev_cfg = dev->config; | 
|  |  | 
|  | RTCC_Init_TypeDef rtcc_config = { | 
|  | false,                /* Don't start counting */ | 
|  | false,                /* Disable RTC during debug halt. */ | 
|  | false,                /* Don't wrap prescaler on CCV0 */ | 
|  | true,                 /* Counter wrap on CCV1 */ | 
|  | #if defined(_SILICON_LABS_32B_SERIES_2) | 
|  | (RTCC_CntPresc_TypeDef)(31UL - __CLZ(dev_cfg->prescaler)), | 
|  | #else | 
|  | (RTCC_CntPresc_TypeDef)CMU_DivToLog2(dev_cfg->prescaler), | 
|  | #endif | 
|  | rtccCntTickPresc,     /* Count according to prescaler value */ | 
|  | #if defined(_RTCC_CTRL_BUMODETSEN_MASK) | 
|  | false,                /* Don't store RTCC counter value in | 
|  | * RTCC_CCV2 upon backup mode entry. | 
|  | */ | 
|  | #endif | 
|  | #if defined(_RTCC_CTRL_OSCFDETEN_MASK) | 
|  | false,                /* Don't enable LFXO fail detection */ | 
|  | #endif | 
|  | #if defined (_RTCC_CTRL_CNTMODE_MASK) | 
|  | rtccCntModeNormal,    /* Use RTCC in normal mode */ | 
|  | #endif | 
|  | #if defined (_RTCC_CTRL_LYEARCORRDIS_MASK) | 
|  | false                 /* No leap year correction. */ | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | RTCC_CCChConf_TypeDef rtcc_channel_config = { | 
|  | rtccCapComChModeCompare,    /* Use compare mode */ | 
|  | rtccCompMatchOutActionPulse,/* Don't care */ | 
|  | rtccPRSCh0,                 /* PRS is not used */ | 
|  | rtccInEdgeNone,             /* Capture input is not used */ | 
|  | rtccCompBaseCnt,            /* Compare with base CNT register */ | 
|  | #if defined (_RTCC_CC_CTRL_COMPMASK_MASK) | 
|  | 0,                          /* Compare mask */ | 
|  | #endif | 
|  | #if defined (_RTCC_CC_CTRL_DAYCC_MASK) | 
|  | rtccDayCompareModeMonth,    /* Don't care */ | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | #if defined(cmuClock_CORELE) | 
|  | /* Ensure LE modules are clocked. */ | 
|  | CMU_ClockEnable(cmuClock_CORELE, true); | 
|  | #endif | 
|  |  | 
|  | #if defined(CMU_LFECLKEN0_RTCC) | 
|  | /* Enable LFECLK in CMU (will also enable oscillator if not enabled). */ | 
|  | CMU_ClockSelectSet(cmuClock_LFE, cmuSelect_LFXO); | 
|  | #elif defined(_SILICON_LABS_32B_SERIES_2) | 
|  | CMU_ClockSelectSet(cmuClock_RTCC, cmuSelect_LFXO); | 
|  | #else | 
|  | /* Enable LFACLK in CMU (will also enable oscillator if not enabled). */ | 
|  | CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFXO); | 
|  | #endif | 
|  |  | 
|  | /* Enable RTCC module clock */ | 
|  | CMU_ClockEnable(cmuClock_RTCC, true); | 
|  |  | 
|  | /* Initialize RTCC */ | 
|  | RTCC_Init(&rtcc_config); | 
|  |  | 
|  | /* Set up compare channels */ | 
|  | RTCC_ChannelInit(0, &rtcc_channel_config); | 
|  | RTCC_ChannelInit(1, &rtcc_channel_config); | 
|  | RTCC_ChannelInit(2, &rtcc_channel_config); | 
|  |  | 
|  | /* Disable module's internal interrupt sources */ | 
|  | RTCC_IntDisable(_RTCC_IF_MASK); | 
|  | RTCC_IntClear(_RTCC_IF_MASK); | 
|  |  | 
|  | /* Clear the counter */ | 
|  | RTCC->CNT = 0; | 
|  |  | 
|  | /* Configure & enable module interrupts */ | 
|  | dev_cfg->irq_config(); | 
|  |  | 
|  | LOG_INF("Device %s initialized", dev->name); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct counter_driver_api counter_gecko_driver_api = { | 
|  | .start = counter_gecko_start, | 
|  | .stop = counter_gecko_stop, | 
|  | .get_value = counter_gecko_get_value, | 
|  | .set_alarm = counter_gecko_set_alarm, | 
|  | .cancel_alarm = counter_gecko_cancel_alarm, | 
|  | .set_top_value = counter_gecko_set_top_value, | 
|  | .get_pending_int = counter_gecko_get_pending_int, | 
|  | .get_top_value = counter_gecko_get_top_value, | 
|  | }; | 
|  |  | 
|  | /* RTCC0 */ | 
|  |  | 
|  | ISR_DIRECT_DECLARE(counter_gecko_isr_0) | 
|  | { | 
|  | const struct device *const dev = DEVICE_DT_INST_GET(0); | 
|  | struct counter_gecko_data *const dev_data = dev->data; | 
|  | counter_alarm_callback_t alarm_callback; | 
|  | uint32_t count = RTCC_CounterGet(); | 
|  | uint32_t flags = RTCC_IntGetEnabled(); | 
|  |  | 
|  | RTCC_IntClear(flags); | 
|  |  | 
|  | if (flags & RTCC_IF_CC1) { | 
|  | if (dev_data->top_callback) { | 
|  | dev_data->top_callback(dev, dev_data->top_user_data); | 
|  | } | 
|  | } | 
|  | for (int i = 0; i < RTCC_ALARM_NUM; i++) { | 
|  | uint8_t cc_idx = chan_id2cc_idx(i); | 
|  |  | 
|  | if (flags & (RTCC_IF_CC0 << cc_idx)) { | 
|  | if (dev_data->alarm[i].callback) { | 
|  | alarm_callback = dev_data->alarm[i].callback; | 
|  | dev_data->alarm[i].callback = NULL; | 
|  | alarm_callback(dev, i, count, | 
|  | dev_data->alarm[i].user_data); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | ISR_DIRECT_PM(); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | BUILD_ASSERT((DT_INST_PROP(0, prescaler) > 0U) && | 
|  | (DT_INST_PROP(0, prescaler) <= 32768U)); | 
|  |  | 
|  | static void counter_gecko_0_irq_config(void) | 
|  | { | 
|  | IRQ_DIRECT_CONNECT(DT_INST_IRQN(0), | 
|  | DT_INST_IRQ(0, priority), | 
|  | counter_gecko_isr_0, 0); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  | } | 
|  |  | 
|  | static const struct counter_gecko_config counter_gecko_0_config = { | 
|  | .info = { | 
|  | .max_top_value = RTCC_MAX_VALUE, | 
|  | .freq = DT_INST_PROP(0, clock_frequency) / | 
|  | DT_INST_PROP(0, prescaler), | 
|  | .flags = COUNTER_CONFIG_INFO_COUNT_UP, | 
|  | .channels = RTCC_ALARM_NUM, | 
|  | }, | 
|  | .irq_config = counter_gecko_0_irq_config, | 
|  | .prescaler = DT_INST_PROP(0, prescaler), | 
|  | }; | 
|  |  | 
|  | static struct counter_gecko_data counter_gecko_0_data; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, counter_gecko_init, NULL, | 
|  | &counter_gecko_0_data, &counter_gecko_0_config, | 
|  | PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY, | 
|  | &counter_gecko_driver_api); |