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.
276 lines
8.2 KiB
276 lines
8.2 KiB
/* |
|
* Copyright (c) 2025 Henrik Brix Andersen <henrik@brixandersen.dk> |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT neorv32_gptmr |
|
|
|
#include <zephyr/drivers/counter.h> |
|
#include <zephyr/drivers/syscon.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/spinlock.h> |
|
#include <zephyr/sys/sys_io.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
#include <soc.h> |
|
|
|
LOG_MODULE_REGISTER(neorv32_gptmr, CONFIG_COUNTER_LOG_LEVEL); |
|
|
|
/* Registers */ |
|
#define NEORV32_GPTMR_CTRL 0x00 |
|
#define NEORV32_GPTMR_CTRL_EN BIT(0) |
|
#define NEORV32_GPTMR_CTRL_PRSC GENMASK(3, 1) |
|
#define NEORV32_GPTMR_CTRL_IRQ_CLR BIT(30) |
|
#define NEORV32_GPTMR_CTRL_IRQ_PND BIT(31) |
|
|
|
#define NEORV32_GPTMR_THRES 0x04 |
|
#define NEORV32_GPTMR_COUNT 0x08 |
|
|
|
struct neorv32_gptmr_config { |
|
struct counter_config_info info; |
|
const struct device *syscon; |
|
mm_reg_t base; |
|
uint8_t prescaler; |
|
void (*irq_config_func)(void); |
|
}; |
|
|
|
struct neorv32_gptmr_data { |
|
struct k_spinlock lock; |
|
counter_top_callback_t top_callback; |
|
void *top_user_data; |
|
}; |
|
|
|
static inline uint32_t neorv32_gptmr_read(const struct device *dev, uint16_t reg) |
|
{ |
|
const struct neorv32_gptmr_config *config = dev->config; |
|
|
|
return sys_read32(config->base + reg); |
|
} |
|
|
|
static inline void neorv32_gptmr_write(const struct device *dev, uint16_t reg, uint32_t val) |
|
{ |
|
const struct neorv32_gptmr_config *config = dev->config; |
|
|
|
sys_write32(val, config->base + reg); |
|
} |
|
|
|
static int neorv32_gptmr_start(const struct device *dev) |
|
{ |
|
struct neorv32_gptmr_data *data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t ctrl; |
|
|
|
key = k_spin_lock(&data->lock); |
|
|
|
ctrl = neorv32_gptmr_read(dev, NEORV32_GPTMR_CTRL); |
|
ctrl |= NEORV32_GPTMR_CTRL_EN; |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int neorv32_gptmr_stop(const struct device *dev) |
|
{ |
|
struct neorv32_gptmr_data *data = dev->data; |
|
k_spinlock_key_t key; |
|
uint32_t ctrl; |
|
|
|
key = k_spin_lock(&data->lock); |
|
|
|
ctrl = neorv32_gptmr_read(dev, NEORV32_GPTMR_CTRL); |
|
ctrl &= ~NEORV32_GPTMR_CTRL_EN; |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl); |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
return 0; |
|
} |
|
|
|
static int neorv32_gptmr_get_value(const struct device *dev, uint32_t *ticks) |
|
{ |
|
*ticks = neorv32_gptmr_read(dev, NEORV32_GPTMR_COUNT); |
|
|
|
return 0; |
|
} |
|
|
|
static int neorv32_gptmr_set_top_value(const struct device *dev, const struct counter_top_cfg *cfg) |
|
{ |
|
struct neorv32_gptmr_data *data = dev->data; |
|
k_spinlock_key_t key; |
|
bool restart = false; |
|
uint32_t count; |
|
uint32_t ctrl; |
|
int err = 0; |
|
|
|
__ASSERT_NO_MSG(cfg != NULL); |
|
|
|
if (cfg->ticks == 0) { |
|
return -EINVAL; |
|
} |
|
|
|
if ((cfg->flags & ~(COUNTER_TOP_CFG_DONT_RESET | COUNTER_TOP_CFG_RESET_WHEN_LATE)) != 0U) { |
|
LOG_ERR("unsupported flags 0x%08x", cfg->flags); |
|
return -ENOTSUP; |
|
} |
|
|
|
key = k_spin_lock(&data->lock); |
|
|
|
data->top_callback = cfg->callback; |
|
data->top_user_data = cfg->user_data; |
|
|
|
ctrl = neorv32_gptmr_read(dev, NEORV32_GPTMR_CTRL); |
|
count = neorv32_gptmr_read(dev, NEORV32_GPTMR_COUNT); |
|
|
|
if ((ctrl & NEORV32_GPTMR_CTRL_EN) != 0U) { |
|
if ((cfg->flags & COUNTER_TOP_CFG_DONT_RESET) == 0U) { |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl & ~NEORV32_GPTMR_CTRL_EN); |
|
restart = true; |
|
} else if (count >= cfg->ticks) { |
|
if ((cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) != 0U) { |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, |
|
ctrl & ~NEORV32_GPTMR_CTRL_EN); |
|
restart = true; |
|
} |
|
|
|
err = -ETIME; |
|
} |
|
} |
|
|
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_THRES, cfg->ticks); |
|
|
|
if (restart) { |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl); |
|
} |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
return err; |
|
} |
|
|
|
static uint32_t neorv32_gptmr_get_pending_int(const struct device *dev) |
|
{ |
|
uint32_t ctrl; |
|
|
|
ctrl = neorv32_gptmr_read(dev, NEORV32_GPTMR_CTRL); |
|
|
|
return (ctrl & NEORV32_GPTMR_CTRL_IRQ_PND) != 0 ? 1 : 0; |
|
} |
|
|
|
static uint32_t neorv32_gptmr_get_top_value(const struct device *dev) |
|
{ |
|
return neorv32_gptmr_read(dev, NEORV32_GPTMR_THRES); |
|
} |
|
|
|
static uint32_t neorv32_gptmr_get_freq(const struct device *dev) |
|
{ |
|
static const uint16_t prescalers[] = { 2U, 4U, 8U, 64U, 128U, 1024U, 2048U, 4096U }; |
|
const struct neorv32_gptmr_config *config = dev->config; |
|
uint32_t clk; |
|
int err; |
|
|
|
err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_CLK, &clk); |
|
if (err < 0) { |
|
LOG_ERR("failed to determine clock rate (err %d)", err); |
|
return 0U; |
|
} |
|
|
|
return clk / prescalers[config->prescaler]; |
|
} |
|
|
|
static void neorv32_gptmr_isr(const struct device *dev) |
|
{ |
|
struct neorv32_gptmr_data *data = dev->data; |
|
counter_top_callback_t top_callback; |
|
void *top_user_data; |
|
k_spinlock_key_t key; |
|
uint32_t ctrl; |
|
|
|
key = k_spin_lock(&data->lock); |
|
|
|
ctrl = neorv32_gptmr_read(dev, NEORV32_GPTMR_CTRL); |
|
ctrl |= NEORV32_GPTMR_CTRL_IRQ_CLR; |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl); |
|
|
|
top_callback = data->top_callback; |
|
top_user_data = data->top_user_data; |
|
|
|
k_spin_unlock(&data->lock, key); |
|
|
|
if (top_callback != NULL) { |
|
top_callback(dev, top_user_data); |
|
} |
|
} |
|
|
|
static int neorv32_gptmr_init(const struct device *dev) |
|
{ |
|
const struct neorv32_gptmr_config *config = dev->config; |
|
uint32_t features; |
|
uint32_t ctrl; |
|
int err; |
|
|
|
if (!device_is_ready(config->syscon)) { |
|
LOG_ERR("syscon device not ready"); |
|
return -EINVAL; |
|
} |
|
|
|
err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_SOC, &features); |
|
if (err < 0) { |
|
LOG_ERR("failed to determine implemented features (err %d)", err); |
|
return -EIO; |
|
} |
|
|
|
if ((features & NEORV32_SYSINFO_SOC_IO_GPTMR) == 0) { |
|
LOG_ERR("neorv32 gptmr not supported"); |
|
return -ENODEV; |
|
} |
|
|
|
/* Stop timer, set prescaler, clear any pending interrupt */ |
|
ctrl = FIELD_PREP(NEORV32_GPTMR_CTRL_PRSC, config->prescaler) | NEORV32_GPTMR_CTRL_IRQ_CLR; |
|
neorv32_gptmr_write(dev, NEORV32_GPTMR_CTRL, ctrl); |
|
|
|
config->irq_config_func(); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(counter, neorv32_gptmr_driver_api) = { |
|
.start = neorv32_gptmr_start, |
|
.stop = neorv32_gptmr_stop, |
|
.get_value = neorv32_gptmr_get_value, |
|
.set_top_value = neorv32_gptmr_set_top_value, |
|
.get_pending_int = neorv32_gptmr_get_pending_int, |
|
.get_top_value = neorv32_gptmr_get_top_value, |
|
.get_freq = neorv32_gptmr_get_freq, |
|
}; |
|
|
|
#define COUNTER_NEORV32_GPTMR_INIT(n) \ |
|
static void neorv32_gptmr_config_func_##n(void) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), neorv32_gptmr_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} \ |
|
\ |
|
static struct neorv32_gptmr_data neorv32_gptmr_data_##n; \ |
|
\ |
|
static struct neorv32_gptmr_config neorv32_gptmr_config_##n = { \ |
|
.info = { \ |
|
.max_top_value = UINT32_MAX, \ |
|
.freq = 0U, \ |
|
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
|
.channels = 0, \ |
|
}, \ |
|
.syscon = DEVICE_DT_GET(DT_INST_PHANDLE(n, syscon)), \ |
|
.base = DT_INST_REG_ADDR(n), \ |
|
.prescaler = DT_INST_ENUM_IDX(n, prescaler), \ |
|
.irq_config_func = neorv32_gptmr_config_func_##n, \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, &neorv32_gptmr_init, NULL, &neorv32_gptmr_data_##n, \ |
|
&neorv32_gptmr_config_##n, POST_KERNEL, \ |
|
CONFIG_COUNTER_INIT_PRIORITY, &neorv32_gptmr_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(COUNTER_NEORV32_GPTMR_INIT)
|
|
|