drivers: counter: add snps apb timer
adding driver for snps dw timer Signed-off-by: Balsundar Ponnusamy <balsundar.ponnusamy@intel.com>
This commit is contained in:
parent
a3d6b423c6
commit
e3f0ec6d41
|
@ -45,3 +45,4 @@ zephyr_library_sources_ifdef(CONFIG_ACE_V1X_ART_COUNTER counter_ace_v1x_
|
|||
zephyr_library_sources_ifdef(CONFIG_ACE_V1X_RTC_COUNTER counter_ace_v1x_rtc.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_NXP_S32_SYS_TIMER counter_nxp_s32_sys_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_TIMER_GD32 counter_gd32_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_SNPS_DW counter_dw_timer.c)
|
||||
|
|
|
@ -84,4 +84,6 @@ source "drivers/counter/Kconfig.nxp_s32"
|
|||
|
||||
source "drivers/counter/Kconfig.gd32"
|
||||
|
||||
source "drivers/counter/Kconfig.dw"
|
||||
|
||||
endif # COUNTER
|
||||
|
|
11
drivers/counter/Kconfig.dw
Normal file
11
drivers/counter/Kconfig.dw
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Synopsys DesignWare Timer configuration option
|
||||
|
||||
# Copyright (c) 2023, Intel Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config COUNTER_SNPS_DW
|
||||
bool "Synopsys DesignWare Timer Driver"
|
||||
default y
|
||||
depends on DT_HAS_SNPS_DW_TIMERS_ENABLED
|
||||
help
|
||||
Enable Timer driver
|
403
drivers/counter/counter_dw_timer.c
Normal file
403
drivers/counter/counter_dw_timer.c
Normal file
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* 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 const struct counter_driver_api 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);
|
17
dts/bindings/counter/snps,dw-timers.yaml
Normal file
17
dts/bindings/counter/snps,dw-timers.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (c) 2022-2023, Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
Synopsys Designware timer driver
|
||||
|
||||
compatible: "snps,dw-timers"
|
||||
|
||||
include: [base.yaml, reset-device.yaml]
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
clock-frequency:
|
||||
type: int
|
||||
description: clock frequency information for Timer
|
Loading…
Reference in a new issue