blob: 7d8f282cc1b9e00361c4625cbf08a30d51f0fd92 [file] [log] [blame]
/*
* Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT xlnx_xps_timer_1_00_a
#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/irq.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(xlnx_axi_timer, CONFIG_COUNTER_LOG_LEVEL);
/* AXI Timer v2.0 registers offsets (See Xilinx PG079 for details) */
#define TCSR0_OFFSET 0x00
#define TLR0_OFFSET 0x04
#define TCR0_OFFSET 0x08
#define TCSR1_OFFSET 0x10
#define TLR1_OFFSET 0x14
#define TCR1_OFFSET 0x18
/* TCSRx bit definitions */
#define TCSR_MDT BIT(0)
#define TCSR_UDT BIT(1)
#define TCSR_GENT BIT(2)
#define TCSR_CAPT BIT(3)
#define TCSR_ARHT BIT(4)
#define TCSR_LOAD BIT(5)
#define TCSR_ENIT BIT(6)
#define TCSR_ENT BIT(7)
#define TCSR_TINT BIT(8)
#define TCSR_PWMA BIT(9)
#define TCSR_ENALL BIT(10)
#define TCSR_CASC BIT(11)
/* 1st timer used as main timer in auto-reload, count-down. generate mode */
#define TCSR0_DEFAULT (TCSR_ENIT | TCSR_ARHT | TCSR_GENT | TCSR_UDT)
/* 2nd timer (if available) used as alarm timer in count-down, generate mode */
#define TCSR1_DEFAULT (TCSR_ENIT | TCSR_GENT | TCSR_UDT)
struct xlnx_axi_timer_config {
struct counter_config_info info;
mm_reg_t base;
void (*irq_config_func)(const struct device *dev);
};
struct xlnx_axi_timer_data {
counter_top_callback_t top_callback;
void *top_user_data;
counter_alarm_callback_t alarm_callback;
void *alarm_user_data;
};
static inline uint32_t xlnx_axi_timer_read32(const struct device *dev,
mm_reg_t offset)
{
const struct xlnx_axi_timer_config *config = dev->config;
return sys_read32(config->base + offset);
}
static inline void xlnx_axi_timer_write32(const struct device *dev,
uint32_t value,
mm_reg_t offset)
{
const struct xlnx_axi_timer_config *config = dev->config;
sys_write32(value, config->base + offset);
}
static int xlnx_axi_timer_start(const struct device *dev)
{
const struct xlnx_axi_timer_data *data = dev->data;
uint32_t tcsr = TCSR0_DEFAULT | TCSR_ENT;
LOG_DBG("starting timer");
if (data->alarm_callback) {
/* Start both timers synchronously */
tcsr |= TCSR_ENALL;
}
xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET);
return 0;
}
static int xlnx_axi_timer_stop(const struct device *dev)
{
const struct xlnx_axi_timer_config *config = dev->config;
unsigned int key;
LOG_DBG("stopping timer");
key = irq_lock();
/* The timers cannot be stopped synchronously */
if (config->info.channels > 0) {
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET);
}
xlnx_axi_timer_write32(dev, TCSR0_DEFAULT, TCSR0_OFFSET);
irq_unlock(key);
return 0;
}
static int xlnx_axi_timer_get_value(const struct device *dev, uint32_t *ticks)
{
*ticks = xlnx_axi_timer_read32(dev, TCR0_OFFSET);
return 0;
}
static int xlnx_axi_timer_set_alarm(const struct device *dev, uint8_t chan_id,
const struct counter_alarm_cfg *cfg)
{
struct xlnx_axi_timer_data *data = dev->data;
unsigned int key;
uint32_t tcsr;
ARG_UNUSED(chan_id);
if (cfg->callback == NULL) {
return -EINVAL;
}
if (data->alarm_callback != NULL) {
return -EBUSY;
}
if (cfg->ticks > xlnx_axi_timer_read32(dev, TLR0_OFFSET)) {
return -EINVAL;
}
if (cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) {
/*
* Since two different timers (with the same clock signal) are
* used for main timer and alarm timer we cannot support
* absolute alarms in a reliable way.
*/
return -ENOTSUP;
}
LOG_DBG("triggering alarm in 0x%08x ticks", cfg->ticks);
/* Load alarm timer */
xlnx_axi_timer_write32(dev, cfg->ticks, TLR1_OFFSET);
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | TCSR_LOAD, TCSR1_OFFSET);
key = irq_lock();
data->alarm_callback = cfg->callback;
data->alarm_user_data = cfg->user_data;
/* Enable alarm timer only if main timer already enabled */
tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET);
tcsr &= TCSR_ENT;
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | tcsr, TCSR1_OFFSET);
irq_unlock(key);
return 0;
}
static int xlnx_axi_timer_cancel_alarm(const struct device *dev,
uint8_t chan_id)
{
struct xlnx_axi_timer_data *data = dev->data;
ARG_UNUSED(chan_id);
LOG_DBG("cancelling alarm");
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET);
data->alarm_callback = NULL;
data->alarm_user_data = NULL;
return 0;
}
static int xlnx_axi_timer_set_top_value(const struct device *dev,
const struct counter_top_cfg *cfg)
{
struct xlnx_axi_timer_data *data = dev->data;
bool reload = true;
uint32_t tcsr;
uint32_t now;
if (cfg->ticks == 0) {
return -EINVAL;
}
if (data->alarm_callback) {
return -EBUSY;
}
LOG_DBG("setting top value to 0x%08x", cfg->ticks);
data->top_callback = cfg->callback;
data->top_user_data = cfg->user_data;
if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) {
reload = false;
if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) {
now = xlnx_axi_timer_read32(dev, TCR0_OFFSET);
reload = cfg->ticks < now;
}
}
tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET);
if ((tcsr & TCSR_ENT) == 0U) {
/* Timer not enabled, force reload of new top value */
reload = true;
}
xlnx_axi_timer_write32(dev, cfg->ticks, TLR0_OFFSET);
if (reload) {
xlnx_axi_timer_write32(dev, tcsr | TCSR_LOAD, TCSR0_OFFSET);
xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET);
}
return 0;
}
static uint32_t xlnx_axi_timer_get_pending_int(const struct device *dev)
{
const struct xlnx_axi_timer_config *config = dev->config;
uint32_t pending = 0;
uint32_t tcsr;
tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET);
if (tcsr & TCSR_TINT) {
pending = 1;
}
if (config->info.channels > 0) {
tcsr = xlnx_axi_timer_read32(dev, TCSR1_OFFSET);
if (tcsr & TCSR_TINT) {
pending = 1;
}
}
LOG_DBG("%sinterrupt pending", pending ? "" : "no ");
return pending;
}
static uint32_t xlnx_axi_timer_get_top_value(const struct device *dev)
{
return xlnx_axi_timer_read32(dev, TLR0_OFFSET);
}
static void xlnx_axi_timer_isr(const struct device *dev)
{
struct xlnx_axi_timer_data *data = dev->data;
counter_alarm_callback_t alarm_cb;
uint32_t tcsr;
uint32_t now;
tcsr = xlnx_axi_timer_read32(dev, TCSR1_OFFSET);
if (tcsr & TCSR_TINT) {
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT | TCSR_TINT,
TCSR1_OFFSET);
if (data->alarm_callback) {
now = xlnx_axi_timer_read32(dev, TCR0_OFFSET);
alarm_cb = data->alarm_callback;
data->alarm_callback = NULL;
alarm_cb(dev, 0, now, data->alarm_user_data);
}
}
tcsr = xlnx_axi_timer_read32(dev, TCSR0_OFFSET);
if (tcsr & TCSR_TINT) {
xlnx_axi_timer_write32(dev, tcsr, TCSR0_OFFSET);
if (data->top_callback) {
data->top_callback(dev, data->top_user_data);
}
}
}
static int xlnx_axi_timer_init(const struct device *dev)
{
const struct xlnx_axi_timer_config *config = dev->config;
LOG_DBG("max top value = 0x%08x", config->info.max_top_value);
LOG_DBG("frequency = %d", config->info.freq);
LOG_DBG("channels = %d", config->info.channels);
xlnx_axi_timer_write32(dev, config->info.max_top_value, TLR0_OFFSET);
xlnx_axi_timer_write32(dev, TCSR0_DEFAULT | TCSR_LOAD, TCSR0_OFFSET);
if (config->info.channels > 0) {
xlnx_axi_timer_write32(dev, TCSR1_DEFAULT, TCSR1_OFFSET);
}
config->irq_config_func(dev);
return 0;
}
static const struct counter_driver_api xlnx_axi_timer_driver_api = {
.start = xlnx_axi_timer_start,
.stop = xlnx_axi_timer_stop,
.get_value = xlnx_axi_timer_get_value,
.set_alarm = xlnx_axi_timer_set_alarm,
.cancel_alarm = xlnx_axi_timer_cancel_alarm,
.set_top_value = xlnx_axi_timer_set_top_value,
.get_pending_int = xlnx_axi_timer_get_pending_int,
.get_top_value = xlnx_axi_timer_get_top_value,
};
#define XLNX_AXI_TIMER_INIT(n) \
static void xlnx_axi_timer_config_func_##n(const struct device *dev); \
\
static struct xlnx_axi_timer_config xlnx_axi_timer_config_##n = { \
.info = { \
.max_top_value = \
GENMASK(DT_INST_PROP(n, xlnx_count_width) - 1, 0), \
.freq = DT_INST_PROP(n, clock_frequency), \
.flags = 0, \
.channels = \
COND_CODE_1(DT_INST_PROP(n, xlnx_one_timer_only), \
(0), (1)), \
}, \
.base = DT_INST_REG_ADDR(n), \
.irq_config_func = xlnx_axi_timer_config_func_##n, \
}; \
\
static struct xlnx_axi_timer_data xlnx_axi_timer_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, &xlnx_axi_timer_init, \
NULL, \
&xlnx_axi_timer_data_##n, \
&xlnx_axi_timer_config_##n, \
POST_KERNEL, \
CONFIG_COUNTER_INIT_PRIORITY, \
&xlnx_axi_timer_driver_api); \
\
static void xlnx_axi_timer_config_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
xlnx_axi_timer_isr, \
DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
}
DT_INST_FOREACH_STATUS_OKAY(XLNX_AXI_TIMER_INIT)