blob: 9182d824658aa41cd123b09cfce85b6fefa43a64 [file] [log] [blame]
/*
* 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 *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);