|  | /* | 
|  | * Copyright (c) 2023 Intel Corporation. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT snps_dw_timers | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/counter.h> | 
|  | #include <zephyr/spinlock.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/drivers/reset.h> | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(dw_timer, CONFIG_COUNTER_LOG_LEVEL); | 
|  |  | 
|  | static int counter_dw_timer_get_value(const struct device *timer_dev, uint32_t *ticks); | 
|  |  | 
|  | /* DW APB timer register offsets */ | 
|  | #define LOADCOUNT_OFST           0x0 | 
|  | #define CURRENTVAL_OFST          0x4 | 
|  | #define CONTROLREG_OFST          0x8 | 
|  | #define EOI_OFST                 0xc | 
|  | #define INTSTAT_OFST             0x10 | 
|  |  | 
|  | /* free running mode value */ | 
|  | #define FREE_RUNNING_MODE_VAL 0xFFFFFFFFUL | 
|  |  | 
|  | /* DW APB timer control flags */ | 
|  | #define TIMER_CONTROL_ENABLE_BIT    0 | 
|  | #define TIMER_MODE_BIT              1 | 
|  | #define TIMER_INTR_MASK_BIT         2 | 
|  |  | 
|  | /* DW APB timer modes */ | 
|  | #define USER_DEFINED_MODE       1 | 
|  | #define FREE_RUNNING_MODE       0 | 
|  |  | 
|  | #define DEV_CFG(_dev) ((const struct counter_dw_timer_config *)(_dev)->config) | 
|  | #define DEV_DATA(_dev) ((struct counter_dw_timer_drv_data *const)(_dev)->data) | 
|  |  | 
|  | /* Device Configuration */ | 
|  | struct counter_dw_timer_config { | 
|  | struct counter_config_info info; | 
|  |  | 
|  | DEVICE_MMIO_NAMED_ROM(timer_mmio); | 
|  |  | 
|  | /* clock frequency of timer */ | 
|  | uint32_t freq; | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks) | 
|  | /* clock controller dev instance */ | 
|  | const struct device *clk_dev; | 
|  | /* identifier for timer to get clock freq from clk manager */ | 
|  | clock_control_subsys_t clkid; | 
|  | #endif | 
|  |  | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets) | 
|  | /* reset controller device configuration*/ | 
|  | const struct reset_dt_spec reset; | 
|  | #endif | 
|  |  | 
|  | /* interrupt config function ptr */ | 
|  | void (*irq_config)(void); | 
|  | }; | 
|  |  | 
|  | /* Driver data */ | 
|  | struct counter_dw_timer_drv_data { | 
|  | /* mmio address mapping info */ | 
|  | DEVICE_MMIO_NAMED_RAM(timer_mmio); | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks) | 
|  | /* clock frequency of timer */ | 
|  | uint32_t freq; | 
|  | #endif | 
|  | /* spin lock to protect user data */ | 
|  | struct k_spinlock lock; | 
|  | /* top callback function */ | 
|  | counter_top_callback_t top_cb; | 
|  | /* alarm callback function */ | 
|  | counter_alarm_callback_t alarm_cb; | 
|  | /* private user data */ | 
|  | void *prv_data; | 
|  | }; | 
|  |  | 
|  | static void counter_dw_timer_irq_handler(const struct device *timer_dev) | 
|  | { | 
|  | uint32_t ticks = 0; | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  | k_spinlock_key_t key; | 
|  | counter_alarm_callback_t alarm_cb = data->alarm_cb; | 
|  |  | 
|  | /* read EOI register to clear interrupt flag */ | 
|  | sys_read32(reg_base + EOI_OFST); | 
|  |  | 
|  | counter_dw_timer_get_value(timer_dev, &ticks); | 
|  |  | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | /* In case of alarm, mask interrupt and disable the callback. User | 
|  | * can configure the alarm in same context within callback function. | 
|  | */ | 
|  | if (data->alarm_cb) { | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_INTR_MASK_BIT); | 
|  |  | 
|  | data->alarm_cb = NULL; | 
|  | alarm_cb(timer_dev, 0, ticks, data->prv_data); | 
|  |  | 
|  | } else if (data->top_cb) { | 
|  | data->top_cb(timer_dev, data->prv_data); | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&data->lock, key); | 
|  | } | 
|  |  | 
|  | static int counter_dw_timer_start(const struct device *dev) | 
|  | { | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(dev, timer_mmio); | 
|  |  | 
|  | /* disable timer before starting in free-running mode */ | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  |  | 
|  | /* starting timer in free running mode */ | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_MODE_BIT); | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_INTR_MASK_BIT); | 
|  | sys_write32(FREE_RUNNING_MODE_VAL, reg_base + LOADCOUNT_OFST); | 
|  |  | 
|  | /* enable timer */ | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int counter_dw_timer_disable(const struct device *dev) | 
|  | { | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(dev, timer_mmio); | 
|  |  | 
|  | /* stop timer */ | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint32_t counter_dw_timer_get_top_value(const struct device *timer_dev) | 
|  | { | 
|  | uint32_t top_val = 0; | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  |  | 
|  | /* get the current top value from load count register */ | 
|  | top_val = sys_read32(reg_base + LOADCOUNT_OFST); | 
|  |  | 
|  | return top_val; | 
|  | } | 
|  |  | 
|  | static int counter_dw_timer_get_value(const struct device *timer_dev, uint32_t *ticks) | 
|  | { | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  |  | 
|  | /* current value of the current value register */ | 
|  | *ticks = sys_read32(reg_base + CURRENTVAL_OFST); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_dw_timer_set_top_value(const struct device *timer_dev, | 
|  | const struct counter_top_cfg *top_cfg) | 
|  | { | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  | k_spinlock_key_t key; | 
|  |  | 
|  | if (top_cfg == NULL) { | 
|  | LOG_ERR("Invalid top value configuration"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* top value cannot be updated without reset */ | 
|  | if (top_cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { | 
|  | LOG_ERR("Updating top value without reset is not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | /* top value cannot be updated if the alarm is active */ | 
|  | if (data->alarm_cb) { | 
|  | k_spin_unlock(&data->lock, key); | 
|  | LOG_ERR("Top value cannot be updated, alarm is active!"); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | if (!top_cfg->callback) { | 
|  | /* mask an interrupt if callback is not passed */ | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_INTR_MASK_BIT); | 
|  | } else { | 
|  | /* unmask interrupt if callback is passed */ | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_INTR_MASK_BIT); | 
|  | } | 
|  |  | 
|  | data->top_cb = top_cfg->callback; | 
|  | data->prv_data = top_cfg->user_data; | 
|  |  | 
|  | /* top value can be loaded only when timer is stopped and re-enabled */ | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  |  | 
|  | /* configuring timer in user-defined mode */ | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_MODE_BIT); | 
|  |  | 
|  | /* set new top value */ | 
|  | sys_write32(top_cfg->ticks, reg_base + LOADCOUNT_OFST); | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  |  | 
|  | k_spin_unlock(&data->lock, key); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_dw_timer_set_alarm(const struct device *timer_dev, uint8_t chan_id, | 
|  | const struct counter_alarm_cfg *alarm_cfg) | 
|  | { | 
|  | ARG_UNUSED(chan_id); | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  | k_spinlock_key_t key; | 
|  |  | 
|  | if (alarm_cfg == NULL) { | 
|  | LOG_ERR("Invalid alarm configuration"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Alarm callback is mandatory */ | 
|  | if (!alarm_cfg->callback) { | 
|  | LOG_ERR("Alarm callback function cannot be null"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* absolute alarm is not supported as interrupts are triggered | 
|  | * only when the counter reaches 0(downcounter) | 
|  | */ | 
|  | if (alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) { | 
|  | LOG_ERR("Absolute alarm is not supported"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | /* check if alarm is already active */ | 
|  | if (data->alarm_cb != NULL) { | 
|  | LOG_ERR("Alarm is already active\n"); | 
|  | k_spin_unlock(&data->lock, key); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | data->alarm_cb = alarm_cfg->callback; | 
|  | data->prv_data = alarm_cfg->user_data; | 
|  |  | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  |  | 
|  | /* start timer in user-defined mode */ | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_MODE_BIT); | 
|  | sys_clear_bit(reg_base + CONTROLREG_OFST, TIMER_INTR_MASK_BIT); | 
|  |  | 
|  | sys_write32(alarm_cfg->ticks, reg_base + LOADCOUNT_OFST); | 
|  | sys_set_bit(reg_base + CONTROLREG_OFST, TIMER_CONTROL_ENABLE_BIT); | 
|  |  | 
|  | k_spin_unlock(&data->lock, key); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int counter_dw_timer_cancel_alarm(const struct device *timer_dev, uint8_t chan_id) | 
|  | { | 
|  | ARG_UNUSED(chan_id); | 
|  | uintptr_t reg_base = DEVICE_MMIO_NAMED_GET(timer_dev, timer_mmio); | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  | k_spinlock_key_t key; | 
|  |  | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | sys_write32(0, reg_base + CONTROLREG_OFST); | 
|  |  | 
|  | data->alarm_cb = NULL; | 
|  | data->prv_data = NULL; | 
|  |  | 
|  | k_spin_unlock(&data->lock, key); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | uint32_t counter_dw_timer_get_freq(const struct device *timer_dev) | 
|  | { | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks) | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  |  | 
|  | return data->freq; | 
|  | #else | 
|  | const struct counter_dw_timer_config *config = DEV_CFG(timer_dev); | 
|  |  | 
|  | return config->freq; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static DEVICE_API(counter, dw_timer_driver_api) = { | 
|  | .start = counter_dw_timer_start, | 
|  | .stop = counter_dw_timer_disable, | 
|  | .get_value = counter_dw_timer_get_value, | 
|  | .set_top_value = counter_dw_timer_set_top_value, | 
|  | .get_top_value = counter_dw_timer_get_top_value, | 
|  | .set_alarm = counter_dw_timer_set_alarm, | 
|  | .cancel_alarm = counter_dw_timer_cancel_alarm, | 
|  | .get_freq = counter_dw_timer_get_freq, | 
|  | }; | 
|  |  | 
|  | static int counter_dw_timer_init(const struct device *timer_dev) | 
|  | { | 
|  | DEVICE_MMIO_NAMED_MAP(timer_dev, timer_mmio, K_MEM_CACHE_NONE); | 
|  | const struct counter_dw_timer_config *timer_config = DEV_CFG(timer_dev); | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks) || DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets) | 
|  | int ret; | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * get clock rate from clock_frequency property if valid, | 
|  | * otherwise, get clock rate from clock manager | 
|  | */ | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks) | 
|  | struct counter_dw_timer_drv_data *const data = DEV_DATA(timer_dev); | 
|  |  | 
|  | if (!device_is_ready(timer_config->clk_dev)) { | 
|  | LOG_ERR("clock controller device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  | ret = clock_control_get_rate(timer_config->clk_dev, | 
|  | timer_config->clkid, &data->freq); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("Unable to get clock rate: err:%d", ret); | 
|  | return ret; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Reset timer only if reset controller driver is supported */ | 
|  | #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets) | 
|  | if (timer_config->reset.dev != NULL) { | 
|  | if (!device_is_ready(timer_config->reset.dev)) { | 
|  | LOG_ERR("Reset controller device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | ret = reset_line_toggle(timer_config->reset.dev, timer_config->reset.id); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("Timer reset failed"); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | timer_config->irq_config(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #define DW_SNPS_TIMER_CLOCK_RATE_INIT(inst)							\ | 
|  | COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, clock_frequency),				\ | 
|  | (										\ | 
|  | .freq = DT_INST_PROP(inst, clock_frequency),				\ | 
|  | ),										\ | 
|  | (										\ | 
|  | .freq = 0,								\ | 
|  | .clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)),			\ | 
|  | .clkid = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, clkid),	\ | 
|  | )										\ | 
|  | ) | 
|  |  | 
|  | #define DW_SNPS_TIMER_SNPS_RESET_SPEC_INIT(inst)				\ | 
|  | .reset = RESET_DT_SPEC_INST_GET(inst),					\ | 
|  |  | 
|  | #define CREATE_DW_TIMER_DEV(inst)						\ | 
|  | static void counter_dw_timer_irq_config_##inst(void); \ | 
|  | static struct counter_dw_timer_drv_data timer_data_##inst;		\ | 
|  | static const struct counter_dw_timer_config timer_config_##inst = {	\ | 
|  | DEVICE_MMIO_NAMED_ROM_INIT(timer_mmio, DT_DRV_INST(inst)),	\ | 
|  | DW_SNPS_TIMER_CLOCK_RATE_INIT(inst)				\ | 
|  | .info = {							\ | 
|  | .max_top_value = UINT32_MAX,		\ | 
|  | .channels = 1,				\ | 
|  | },								\ | 
|  | IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, resets),			\ | 
|  | (DW_SNPS_TIMER_SNPS_RESET_SPEC_INIT(inst)))		\ | 
|  | .irq_config = counter_dw_timer_irq_config_##inst,		\ | 
|  | };									\ | 
|  | DEVICE_DT_INST_DEFINE(inst,						\ | 
|  | counter_dw_timer_init,					\ | 
|  | NULL, &timer_data_##inst,				\ | 
|  | &timer_config_##inst, POST_KERNEL,			\ | 
|  | CONFIG_COUNTER_INIT_PRIORITY,				\ | 
|  | &dw_timer_driver_api);					\ | 
|  | static void counter_dw_timer_irq_config_##inst(void)			\ | 
|  | {									\ | 
|  | IRQ_CONNECT(DT_INST_IRQN(inst),					\ | 
|  | DT_INST_IRQ(inst, priority),			\ | 
|  | counter_dw_timer_irq_handler,			\ | 
|  | DEVICE_DT_INST_GET(inst), 0);			\ | 
|  | irq_enable(DT_INST_IRQN(inst));					\ | 
|  | } | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(CREATE_DW_TIMER_DEV); |