| /* |
| * 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 const struct counter_driver_api 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) |