You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
536 lines
15 KiB
536 lines
15 KiB
/* |
|
* Copyright (c) 2022 Renesas Electronics Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT renesas_smartbond_timer |
|
|
|
#include <zephyr/drivers/counter.h> |
|
#include <zephyr/drivers/clock_control/smartbond_clock_control.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/sys/atomic.h> |
|
#include <zephyr/pm/device.h> |
|
#include <zephyr/pm/device_runtime.h> |
|
#include <zephyr/pm/policy.h> |
|
#include <DA1469xAB.h> |
|
#include <da1469x_pdc.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
LOG_MODULE_REGISTER(counter_timer, CONFIG_COUNTER_LOG_LEVEL); |
|
|
|
#define LP_CLK_OSC_RC32K 0 |
|
#define LP_CLK_OSC_RCX 1 |
|
#define LP_CLK_OSC_XTAL32K 2 |
|
|
|
#define TIMER_TOP_VALUE 0xFFFFFF |
|
|
|
#define COUNTER_DT_DEVICE(_idx) DEVICE_DT_GET_OR_NULL(DT_NODELABEL(timer##_idx)) |
|
|
|
#define PDC_XTAL_EN (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(xtal32m)) ? \ |
|
MCU_PDC_EN_XTAL : MCU_PDC_EN_NONE) |
|
|
|
struct counter_smartbond_data { |
|
counter_alarm_callback_t callback; |
|
void *user_data; |
|
uint32_t guard_period; |
|
uint32_t freq; |
|
#if defined(CONFIG_PM_DEVICE) |
|
uint8_t pdc_idx; |
|
#endif |
|
}; |
|
|
|
struct counter_smartbond_ch_data { |
|
counter_alarm_callback_t callback; |
|
void *user_data; |
|
}; |
|
|
|
struct counter_smartbond_config { |
|
struct counter_config_info info; |
|
/* Register set for timer */ |
|
TIMER2_Type *timer; |
|
uint8_t prescaler; |
|
/* Timer driven by DIVn if 1 or lp_clk if 0 */ |
|
uint8_t clock_src_divn; |
|
uint8_t irqn; |
|
void (*irq_config_func)(const struct device *dev); |
|
|
|
LOG_INSTANCE_PTR_DECLARE(log); |
|
}; |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
static void counter_smartbond_pm_policy_state_lock_get(const struct device *dev) |
|
{ |
|
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
pm_device_runtime_get(dev); |
|
} |
|
|
|
static void counter_smartbond_pm_policy_state_lock_put(const struct device *dev) |
|
{ |
|
pm_device_runtime_put(dev); |
|
if (pm_policy_state_lock_is_active(PM_STATE_STANDBY, PM_ALL_SUBSTATES)) { |
|
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
} |
|
} |
|
|
|
/* |
|
* Routine to check whether the device is allowed to enter the sleep state or not. |
|
* Entering the standby mode should be allowed for TIMER1/2 that are clocked by LP |
|
* clock. Although, TIMER1/2 are powered by a distinct power domain, |
|
* namely PD_TMR which is always enabled (used to generate the sleep tick count), |
|
* the DIVN path which reflects the main crystal, that is XTAL32M, is turned off |
|
* during sleep by PDC. It's worth noting that during sleep the clock source of |
|
* a timer block will automatically be switched from DIVN to LP and vice versa. |
|
*/ |
|
static inline bool counter_smartbond_is_sleep_allowed(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
|
|
return (((dev == COUNTER_DT_DEVICE(1)) || |
|
(dev == COUNTER_DT_DEVICE(2))) && !config->clock_src_divn); |
|
} |
|
|
|
/* Get the PDC trigger associated with the requested counter device */ |
|
static uint8_t counter_smartbond_pdc_trigger_get(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
|
|
switch ((uint32_t)config->timer) { |
|
case (uint32_t)TIMER: |
|
return MCU_PDC_TRIGGER_TIMER; |
|
case (uint32_t)TIMER2: |
|
return MCU_PDC_TRIGGER_TIMER2; |
|
case (uint32_t)TIMER3: |
|
return MCU_PDC_TRIGGER_TIMER3; |
|
case (uint32_t)TIMER4: |
|
return MCU_PDC_TRIGGER_TIMER4; |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
/* |
|
* Add PDC entry so that the application core, which should be turned off during sleep, |
|
* can get notified upon counter events. This routine is called for counter instances |
|
* that are powered by PD_TMR and can operate during sleep. |
|
*/ |
|
static void counter_smartbond_pdc_add(const struct device *dev) |
|
{ |
|
struct counter_smartbond_data *data = dev->data; |
|
uint8_t trigger = counter_smartbond_pdc_trigger_get(dev); |
|
|
|
data->pdc_idx = da1469x_pdc_add(trigger, MCU_PDC_MASTER_M33, PDC_XTAL_EN); |
|
__ASSERT_NO_MSG(data->pdc_idx >= 0); |
|
|
|
da1469x_pdc_set(data->pdc_idx); |
|
da1469x_pdc_ack(data->pdc_idx); |
|
} |
|
|
|
static void counter_smartbond_pdc_del(const struct device *dev) |
|
{ |
|
struct counter_smartbond_data *data = dev->data; |
|
|
|
da1469x_pdc_del(data->pdc_idx); |
|
} |
|
#endif |
|
|
|
static int counter_smartbond_start(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
TIMER2_Type *timer = config->timer; |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
if (!counter_smartbond_is_sleep_allowed(dev)) { |
|
/* |
|
* Power mode constraints should be applied as long as the device |
|
* is up and running. |
|
*/ |
|
counter_smartbond_pm_policy_state_lock_get(dev); |
|
} else { |
|
counter_smartbond_pdc_add(dev); |
|
} |
|
#endif |
|
|
|
/* Enable counter in free running mode */ |
|
timer->TIMER2_CTRL_REG |= (TIMER2_TIMER2_CTRL_REG_TIM_CLK_EN_Msk | |
|
TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk | |
|
TIMER2_TIMER2_CTRL_REG_TIM_FREE_RUN_MODE_EN_Msk); |
|
|
|
return 0; |
|
} |
|
|
|
static int counter_smartbond_stop(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
struct counter_smartbond_data *data = dev->data; |
|
TIMER2_Type *timer = config->timer; |
|
|
|
/* disable counter */ |
|
timer->TIMER2_CTRL_REG &= ~(TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk | |
|
TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk | |
|
TIMER2_TIMER2_CTRL_REG_TIM_CLK_EN_Msk); |
|
data->callback = NULL; |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
if (!counter_smartbond_is_sleep_allowed(dev)) { |
|
counter_smartbond_pm_policy_state_lock_put(dev); |
|
} else { |
|
counter_smartbond_pdc_del(dev); |
|
} |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static uint32_t counter_smartbond_get_top_value(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
return TIMER_TOP_VALUE; |
|
} |
|
|
|
static uint32_t counter_smartbond_read(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
TIMER2_Type *timer = config->timer; |
|
|
|
return timer->TIMER2_TIMER_VAL_REG; |
|
} |
|
|
|
static int counter_smartbond_get_value(const struct device *dev, uint32_t *ticks) |
|
{ |
|
*ticks = counter_smartbond_read(dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int counter_smartbond_set_alarm(const struct device *dev, uint8_t chan, |
|
const struct counter_alarm_cfg *alarm_cfg) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
struct counter_smartbond_data *data = dev->data; |
|
TIMER2_Type *timer = config->timer; |
|
volatile uint32_t *timer_clear_irq_reg = ((TIMER_Type *)timer) == TIMER ? |
|
&((TIMER_Type *)timer)->TIMER_CLEAR_IRQ_REG : |
|
&timer->TIMER2_CLEAR_IRQ_REG; |
|
bool absolute = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE; |
|
uint32_t flags = alarm_cfg->flags; |
|
uint32_t val = alarm_cfg->ticks; |
|
bool irq_on_late; |
|
int err = 0; |
|
uint32_t max_rel_val; |
|
uint32_t now; |
|
uint32_t diff; |
|
|
|
if (chan != 0 || alarm_cfg->ticks > counter_smartbond_get_top_value(dev)) { |
|
return -EINVAL; |
|
} |
|
|
|
if (data->callback) { |
|
return -EBUSY; |
|
} |
|
|
|
now = counter_smartbond_read(dev); |
|
data->callback = alarm_cfg->callback; |
|
data->user_data = alarm_cfg->user_data; |
|
|
|
__ASSERT_NO_MSG(data->guard_period < TIMER_TOP_VALUE); |
|
|
|
if (absolute) { |
|
max_rel_val = TIMER_TOP_VALUE - data->guard_period; |
|
irq_on_late = flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE; |
|
} else { |
|
/* If relative value is smaller than half of the counter range |
|
* it is assumed that there is a risk of setting value too late |
|
* and late detection algorithm must be applied. When late |
|
* setting is detected, interrupt shall be triggered for |
|
* immediate expiration of the timer. Detection is performed |
|
* by limiting relative distance between CC and counter. |
|
* |
|
* Note that half of counter range is an arbitrary value. |
|
*/ |
|
irq_on_late = val < (TIMER_TOP_VALUE / 2U); |
|
/* limit max to detect short relative being set too late. */ |
|
max_rel_val = irq_on_late ? TIMER_TOP_VALUE / 2U : TIMER_TOP_VALUE; |
|
val = (now + val) & TIMER_TOP_VALUE; |
|
} |
|
timer->TIMER2_RELOAD_REG = val; |
|
*timer_clear_irq_reg = 1; |
|
/* decrement value to detect also case when val == counter_smartbond_read(dev). Otherwise, |
|
* condition would need to include comparing diff against 0. |
|
*/ |
|
diff = ((val - 1U) - counter_smartbond_read(dev)) & TIMER_TOP_VALUE; |
|
if (diff > max_rel_val) { |
|
if (absolute) { |
|
err = -ETIME; |
|
} |
|
|
|
/* Interrupt is triggered always for relative alarm and |
|
* for absolute depending on the flag. |
|
*/ |
|
if (irq_on_late) { |
|
NVIC_SetPendingIRQ(config->irqn); |
|
} else { |
|
data->callback = NULL; |
|
} |
|
} else { |
|
if (diff == 0) { |
|
/* RELOAD value could be set just in time for interrupt |
|
* trigger or too late. In any case time is interrupt |
|
* should be triggered. No need to enable interrupt |
|
* on TIMER just make sure interrupt is pending. |
|
*/ |
|
NVIC_SetPendingIRQ(config->irqn); |
|
} else { |
|
timer->TIMER2_CTRL_REG |= TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk; |
|
} |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int counter_smartbond_cancel_alarm(const struct device *dev, uint8_t chan) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
TIMER2_Type *timer = config->timer; |
|
struct counter_smartbond_data *data = dev->data; |
|
|
|
ARG_UNUSED(chan); |
|
|
|
timer->TIMER2_CTRL_REG &= ~TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk; |
|
data->callback = NULL; |
|
|
|
return 0; |
|
} |
|
|
|
static int counter_smartbond_set_top_value(const struct device *dev, |
|
const struct counter_top_cfg *cfg) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
if (cfg->ticks != 0xFFFFFF) { |
|
return -ENOTSUP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static uint32_t counter_smartbond_get_pending_int(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *config = dev->config; |
|
|
|
/* There is no register to check TIMER peripheral to check for interrupt |
|
* pending, check directly in NVIC. |
|
*/ |
|
return NVIC_GetPendingIRQ(config->irqn); |
|
} |
|
|
|
static int counter_smartbond_init_timer(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *cfg = dev->config; |
|
struct counter_smartbond_data *data = dev->data; |
|
TIMER2_Type *timer = cfg->timer; |
|
TIMER_Type *timer0 = ((TIMER_Type *)cfg->timer) == TIMER ? TIMER : NULL; |
|
const struct device *osc_dev; |
|
uint32_t osc_freq; |
|
enum smartbond_clock osc; |
|
|
|
if (cfg->clock_src_divn) { |
|
/* Timer clock source is DIVn 32MHz */ |
|
timer->TIMER2_CTRL_REG = TIMER2_TIMER2_CTRL_REG_TIM_SYS_CLK_EN_Msk; |
|
data->freq = DT_PROP(DT_NODELABEL(divn_clk), clock_frequency) / |
|
(cfg->prescaler + 1); |
|
} else { |
|
osc_dev = DEVICE_DT_GET(DT_NODELABEL(osc)); |
|
timer->TIMER2_CTRL_REG = 0; |
|
switch ((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >> |
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos) { |
|
case LP_CLK_OSC_RC32K: |
|
osc = SMARTBOND_CLK_RC32K; |
|
break; |
|
case LP_CLK_OSC_RCX: |
|
osc = SMARTBOND_CLK_RCX; |
|
break; |
|
default: |
|
case LP_CLK_OSC_XTAL32K: |
|
osc = SMARTBOND_CLK_XTAL32K; |
|
break; |
|
} |
|
clock_control_get_rate(osc_dev, (clock_control_subsys_t)osc, &osc_freq); |
|
data->freq = osc_freq / (cfg->prescaler + 1); |
|
} |
|
timer->TIMER2_PRESCALER_REG = cfg->prescaler; |
|
timer->TIMER2_RELOAD_REG = counter_get_max_top_value(dev); |
|
timer->TIMER2_GPIO1_CONF_REG = 0; |
|
timer->TIMER2_GPIO2_CONF_REG = 0; |
|
timer->TIMER2_SHOTWIDTH_REG = 0; |
|
timer->TIMER2_CAPTURE_GPIO1_REG = 0; |
|
timer->TIMER2_CAPTURE_GPIO2_REG = 0; |
|
timer->TIMER2_PWM_FREQ_REG = 0; |
|
timer->TIMER2_PWM_DC_REG = 0; |
|
if (timer0) { |
|
timer0->TIMER_CAPTURE_GPIO3_REG = 0; |
|
timer0->TIMER_CAPTURE_GPIO4_REG = 0; |
|
} |
|
|
|
/* config/enable IRQ */ |
|
cfg->irq_config_func(dev); |
|
|
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
/* Make sure device state is marked as suspended */ |
|
pm_device_init_suspended(dev); |
|
|
|
return pm_device_runtime_enable(dev); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
static uint32_t counter_smartbond_get_guard_period(const struct device *dev, uint32_t flags) |
|
{ |
|
struct counter_smartbond_data *data = dev->data; |
|
|
|
ARG_UNUSED(flags); |
|
return data->guard_period; |
|
} |
|
|
|
static int counter_smartbond_set_guard_period(const struct device *dev, uint32_t guard, |
|
uint32_t flags) |
|
{ |
|
struct counter_smartbond_data *data = dev->data; |
|
|
|
ARG_UNUSED(flags); |
|
__ASSERT_NO_MSG(guard < counter_smartbond_get_top_value(dev)); |
|
|
|
data->guard_period = guard; |
|
|
|
return 0; |
|
} |
|
|
|
static uint32_t counter_smartbond_get_freq(const struct device *dev) |
|
{ |
|
struct counter_smartbond_data *data = dev->data; |
|
|
|
return data->freq; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
static void counter_smartbond_resume(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *cfg = dev->config; |
|
TIMER2_Type *timer = cfg->timer; |
|
|
|
/* |
|
* Resume only for block instances that are powered by PD_SYS |
|
* and so their register contents should reset after sleep. |
|
*/ |
|
if (!counter_smartbond_is_sleep_allowed(dev)) { |
|
if (cfg->clock_src_divn) { |
|
timer->TIMER2_CTRL_REG = TIMER2_TIMER2_CTRL_REG_TIM_SYS_CLK_EN_Msk; |
|
} else { |
|
timer->TIMER2_CTRL_REG = 0; |
|
} |
|
timer->TIMER2_PRESCALER_REG = cfg->prescaler; |
|
timer->TIMER2_RELOAD_REG = counter_get_max_top_value(dev); |
|
} |
|
} |
|
|
|
static int counter_smartbond_pm_action(const struct device *dev, enum pm_device_action action) |
|
{ |
|
int ret = 0; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_SUSPEND: |
|
break; |
|
case PM_DEVICE_ACTION_RESUME: |
|
counter_smartbond_resume(dev); |
|
break; |
|
default: |
|
ret = -ENOTSUP; |
|
} |
|
|
|
return ret; |
|
} |
|
#endif |
|
|
|
static DEVICE_API(counter, counter_smartbond_driver_api) = { |
|
.start = counter_smartbond_start, |
|
.stop = counter_smartbond_stop, |
|
.get_value = counter_smartbond_get_value, |
|
.set_alarm = counter_smartbond_set_alarm, |
|
.cancel_alarm = counter_smartbond_cancel_alarm, |
|
.set_top_value = counter_smartbond_set_top_value, |
|
.get_pending_int = counter_smartbond_get_pending_int, |
|
.get_top_value = counter_smartbond_get_top_value, |
|
.get_guard_period = counter_smartbond_get_guard_period, |
|
.set_guard_period = counter_smartbond_set_guard_period, |
|
.get_freq = counter_smartbond_get_freq, |
|
}; |
|
|
|
void counter_smartbond_irq_handler(const struct device *dev) |
|
{ |
|
const struct counter_smartbond_config *cfg = dev->config; |
|
struct counter_smartbond_data *data = dev->data; |
|
counter_alarm_callback_t alarm_callback = data->callback; |
|
TIMER2_Type *timer = cfg->timer; |
|
/* Timer0 has interrupt clear register in other offset */ |
|
__IOM uint32_t *timer_clear_irq_reg = ((TIMER_Type *)timer) == TIMER ? |
|
&((TIMER_Type *)timer)->TIMER_CLEAR_IRQ_REG : |
|
&timer->TIMER2_CLEAR_IRQ_REG; |
|
|
|
timer->TIMER2_CTRL_REG &= ~TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk; |
|
*timer_clear_irq_reg = 1; |
|
|
|
if (alarm_callback != NULL) { |
|
data->callback = NULL; |
|
alarm_callback(dev, 0, timer->TIMER2_TIMER_VAL_REG, |
|
data->user_data); |
|
} |
|
} |
|
|
|
#define TIMERN(idx) DT_DRV_INST(idx) |
|
|
|
/** TIMERn instance from DT */ |
|
#define TIM(idx) ((TIMER2_Type *)DT_REG_ADDR(TIMERN(idx))) |
|
|
|
#define COUNTER_DEVICE_INIT(idx) \ |
|
BUILD_ASSERT(DT_PROP(TIMERN(idx), prescaler) <= 32 && \ |
|
DT_PROP(TIMERN(idx), prescaler) > 0, \ |
|
"TIMER prescaler out of range (1..32)"); \ |
|
\ |
|
static struct counter_smartbond_data counter##idx##_data; \ |
|
\ |
|
static void counter##idx##_smartbond_irq_config(const struct device *dev)\ |
|
{ \ |
|
IRQ_CONNECT(DT_IRQN(TIMERN(idx)), \ |
|
DT_IRQ(TIMERN(idx), priority), \ |
|
counter_smartbond_irq_handler, \ |
|
DEVICE_DT_INST_GET(idx), \ |
|
0); \ |
|
irq_enable(DT_IRQN(TIMERN(idx))); \ |
|
} \ |
|
\ |
|
static const struct counter_smartbond_config counter##idx##_config = { \ |
|
.info = { \ |
|
.max_top_value = 0x00FFFFFF, \ |
|
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
|
.channels = 1, \ |
|
}, \ |
|
.timer = TIM(idx), \ |
|
.prescaler = DT_PROP(TIMERN(idx), prescaler) - 1, \ |
|
.clock_src_divn = DT_SAME_NODE(DT_PROP(TIMERN(idx), clock_src), \ |
|
DT_NODELABEL(divn_clk)) ? 1 : 0, \ |
|
.irq_config_func = counter##idx##_smartbond_irq_config, \ |
|
.irqn = DT_IRQN(TIMERN(idx)), \ |
|
}; \ |
|
\ |
|
PM_DEVICE_DT_INST_DEFINE(idx, counter_smartbond_pm_action); \ |
|
DEVICE_DT_INST_DEFINE(idx, \ |
|
counter_smartbond_init_timer, \ |
|
PM_DEVICE_DT_INST_GET(idx), \ |
|
&counter##idx##_data, \ |
|
&counter##idx##_config, \ |
|
PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY, \ |
|
&counter_smartbond_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(COUNTER_DEVICE_INIT)
|
|
|