From a2cc4b702f6bf46755fd1244ec8834f7d283dc4e Mon Sep 17 00:00:00 2001 From: Jimmy Zheng Date: Fri, 12 Aug 2022 16:36:47 +0800 Subject: [PATCH] drivers: counter: add Andes atcpit100 counter driver atcpit100 counter driver support 4 32-bit PIT channel, using channel 3 as the default counter. Signed-off-by: Jimmy Zheng --- drivers/counter/CMakeLists.txt | 1 + drivers/counter/Kconfig | 2 + drivers/counter/Kconfig.andes_atcpit100 | 12 + drivers/counter/counter_andes_atcpit100.c | 507 ++++++++++++++++++++++ 4 files changed, 522 insertions(+) create mode 100644 drivers/counter/Kconfig.andes_atcpit100 create mode 100644 drivers/counter/counter_andes_atcpit100.c diff --git a/drivers/counter/CMakeLists.txt b/drivers/counter/CMakeLists.txt index 59bb2f6046..5fde6c2315 100644 --- a/drivers/counter/CMakeLists.txt +++ b/drivers/counter/CMakeLists.txt @@ -29,3 +29,4 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_XLNX_AXI_TIMER counter_xlnx_axi zephyr_library_sources_ifdef(CONFIG_COUNTER_TMR_ESP32 counter_esp32_tmr.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_RTC_ESP32 counter_esp32_rtc.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MICROCHIP_MCP7940N rtc_mcp7940n.c) +zephyr_library_sources_ifdef(CONFIG_COUNTER_ANDES_ATCPIT100 counter_andes_atcpit100.c) diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index 037ed73211..0566858905 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -70,4 +70,6 @@ source "drivers/counter/Kconfig.mcp7940n" source "drivers/counter/Kconfig.mcux_ctimer" +source "drivers/counter/Kconfig.andes_atcpit100" + endif # COUNTER diff --git a/drivers/counter/Kconfig.andes_atcpit100 b/drivers/counter/Kconfig.andes_atcpit100 new file mode 100644 index 0000000000..9a613014a3 --- /dev/null +++ b/drivers/counter/Kconfig.andes_atcpit100 @@ -0,0 +1,12 @@ +# Kconfig Andes ATCPIT100 configuration option +# +# Copyright (c) 2022 Andes Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +config COUNTER_ANDES_ATCPIT100 + bool "Andes ATCPIT100 PIT driver" + default y + depends on DT_HAS_ANDESTECH_ATCPIT100_ENABLED + help + Enable driver for the Andes ATCPIT100 PIT controller. diff --git a/drivers/counter/counter_andes_atcpit100.c b/drivers/counter/counter_andes_atcpit100.c new file mode 100644 index 0000000000..06ef8f6c26 --- /dev/null +++ b/drivers/counter/counter_andes_atcpit100.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2022 Andes Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT andestech_atcpit100 + +/* register definitions */ +#define REG_IDR 0x00 /* ID and Revision Reg. */ +#define REG_CFG 0x10 /* Configuration Reg. */ +#define REG_INTE 0x14 /* Interrupt Enable Reg. */ +#define REG_ISTA 0x18 /* Interrupt Status Reg. */ +#define REG_CHEN 0x1C /* Channel Enable Reg. */ +#define REG_CTRL0 0x20 /* Channel 0 Control Reg. */ +#define REG_RELD0 0x24 /* Channel 0 Reload Reg. */ +#define REG_CNTR0 0x28 /* Channel 0 Counter Reg. */ +#define REG_CTRL1 0x30 /* Channel 1 Control Reg. */ +#define REG_RELD1 0x34 /* Channel 1 Reload Reg. */ +#define REG_CNTR1 0x38 /* Channel 1 Counter Reg. */ +#define REG_CTRL2 0x40 /* Channel 2 Control Reg. */ +#define REG_RELD2 0x44 /* Channel 2 Reload Reg. */ +#define REG_CNTR2 0x48 /* Channel 2 Counter Reg. */ +#define REG_CTRL3 0x50 /* Channel 3 Control Reg. */ +#define REG_RELD3 0x54 /* Channel 3 Reload Reg. */ +#define REG_CNTR3 0x58 /* Channel 3 Counter Reg. */ + +#define PIT_BASE (((const struct atcpit100_config *)(dev)->config)->base) +#define PIT_INTE(dev) (PIT_BASE + REG_INTE) +#define PIT_ISTA(dev) (PIT_BASE + REG_ISTA) +#define PIT_CHEN(dev) (PIT_BASE + REG_CHEN) +#define PIT_CH_CTRL(dev, ch) (PIT_BASE + REG_CTRL0 + (ch << 4)) +#define PIT_CH_RELD(dev, ch) (PIT_BASE + REG_RELD0 + (ch << 4)) +#define PIT_CH_CNTR(dev, ch) (PIT_BASE + REG_CNTR0 + (ch << 4)) + +#define CTRL_CH_SRC_PCLK BIT(3) +#define CTRL_CH_MODE_32BIT BIT(0) + +#define CHANNEL_NUM (4) +#define CH_NUM_PER_COUNTER (CHANNEL_NUM - 1) +#define TIMER0_CHANNEL(ch) BIT(((ch) * CHANNEL_NUM)) + +typedef void (*atcpit100_cfg_func_t)(void); + +struct atcpit100_config { + struct counter_config_info info; + uint32_t base; + uint32_t divider; + uint32_t irq_num; + atcpit100_cfg_func_t cfg_func; +}; + +struct counter_atcpit100_ch_data { + counter_alarm_callback_t alarm_callback; + void *alarm_user_data; +}; + +struct atcpit100_data { + counter_top_callback_t top_callback; + void *top_user_data; + uint32_t guard_period; + struct k_spinlock lock; + struct counter_atcpit100_ch_data ch_data[CH_NUM_PER_COUNTER]; +}; + +static inline uint32_t get_current_tick(const struct device *dev, uint32_t ch) +{ + const struct atcpit100_config *config = dev->config; + uint32_t top, now_cnt; + + /* Preload cycles is reload register + 1 */ + top = sys_read32(PIT_CH_RELD(dev, ch)) + 1; + now_cnt = top - sys_read32(PIT_CH_CNTR(dev, ch)); + + return (now_cnt / config->divider); +} + +static void atcpit100_irq_handler(void *arg) +{ + struct device *dev = (struct device *)arg; + struct atcpit100_data *data = dev->data; + counter_alarm_callback_t cb; + uint32_t int_status, int_enable, ch_enable, cur_ticks; + uint8_t i; + + ch_enable = sys_read32(PIT_CHEN(dev)); + int_enable = sys_read32(PIT_INTE(dev)); + int_status = sys_read32(PIT_ISTA(dev)); + + if (int_status & TIMER0_CHANNEL(3)) { + if (data->top_callback) { + data->top_callback(dev, data->top_user_data); + } + } + + for (i = 0; i < CH_NUM_PER_COUNTER; i++) { + if (int_status & TIMER0_CHANNEL(i)) { + int_enable &= ~TIMER0_CHANNEL(i); + ch_enable &= ~TIMER0_CHANNEL(i); + + cb = data->ch_data[i].alarm_callback; + data->ch_data[i].alarm_callback = NULL; + + cur_ticks = get_current_tick(dev, 3); + cb(dev, i, cur_ticks, data->ch_data[i].alarm_user_data); + } + } + + /* Disable channel and interrupt */ + sys_write32(int_enable, PIT_INTE(dev)); + sys_write32(ch_enable, PIT_CHEN(dev)); + + /* Clear interrupt status */ + sys_write32(int_status, PIT_ISTA(dev)); +} + +static int counter_atcpit100_init(const struct device *dev) +{ + const struct atcpit100_config *config = dev->config; + uint32_t reg; + + /* Disable all channels */ + sys_write32(0, PIT_CHEN(dev)); + + /* Channel 0 ~ 3, 32 bits timer, PCLK source */ + reg = CTRL_CH_MODE_32BIT | CTRL_CH_SRC_PCLK; + sys_write32(reg, PIT_CH_CTRL(dev, 0)); + sys_write32(reg, PIT_CH_CTRL(dev, 1)); + sys_write32(reg, PIT_CH_CTRL(dev, 2)); + sys_write32(reg, PIT_CH_CTRL(dev, 3)); + + /* Disable all interrupt and clear all pending interrupt */ + sys_write32(0, PIT_INTE(dev)); + sys_write32(UINT32_MAX, PIT_ISTA(dev)); + + /* Select channel 3 as default counter and set max top value */ + reg = config->info.max_top_value * config->divider; + + /* Set cycle - 1 to reload register */ + sys_write32((reg - 1), PIT_CH_RELD(dev, 3)); + + config->cfg_func(); + + irq_enable(config->irq_num); + + return 0; +} + +static int atcpit100_start(const struct device *dev) +{ + struct atcpit100_data *data = dev->data; + k_spinlock_key_t key; + uint32_t reg; + + key = k_spin_lock(&data->lock); + + /* Enable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg |= TIMER0_CHANNEL(3); + sys_write32(reg, PIT_CHEN(dev)); + + k_spin_unlock(&data->lock, key); + + return 0; +} + +static int atcpit100_stop(const struct device *dev) +{ + struct atcpit100_data *data = dev->data; + k_spinlock_key_t key; + uint32_t reg; + + key = k_spin_lock(&data->lock); + + /* Disable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg &= ~TIMER0_CHANNEL(3); + sys_write32(reg, PIT_INTE(dev)); + + /* Disable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg &= ~TIMER0_CHANNEL(3); + sys_write32(reg, PIT_CHEN(dev)); + + /* Clear interrupt status */ + sys_write32(TIMER0_CHANNEL(3), PIT_ISTA(dev)); + + k_spin_unlock(&data->lock, key); + + return 0; +} + +static int atcpit100_get_value(const struct device *dev, uint32_t *ticks) +{ + struct atcpit100_data *data = dev->data; + k_spinlock_key_t key; + + key = k_spin_lock(&data->lock); + + *ticks = get_current_tick(dev, 3); + + k_spin_unlock(&data->lock, key); + + return 0; +} + +static int atcpit100_set_alarm(const struct device *dev, uint8_t chan_id, + const struct counter_alarm_cfg *alarm_cfg) +{ + const struct atcpit100_config *config = dev->config; + struct atcpit100_data *data = dev->data; + uint32_t top, now_cnt, remain_cnt, alarm_cnt, flags, reg; + k_spinlock_key_t key; + int err = 0; + + if (chan_id >= CH_NUM_PER_COUNTER) { + return -ENOTSUP; + } + + if (!alarm_cfg->callback) { + return -EINVAL; + } + + if (data->ch_data[chan_id].alarm_callback) { + return -EBUSY; + } + + key = k_spin_lock(&data->lock); + + /* Preload cycles is reload register + 1 */ + top = sys_read32(PIT_CH_RELD(dev, 3)) + 1; + remain_cnt = sys_read32(PIT_CH_CNTR(dev, 3)); + alarm_cnt = alarm_cfg->ticks * config->divider; + + if (alarm_cnt > top) { + err = -EINVAL; + goto out; + } + + flags = alarm_cfg->flags; + data->ch_data[chan_id].alarm_callback = alarm_cfg->callback; + data->ch_data[chan_id].alarm_user_data = alarm_cfg->user_data; + + if (flags & COUNTER_ALARM_CFG_ABSOLUTE) { + uint32_t irq_on_late, max_rel_val; + + now_cnt = top - remain_cnt; + max_rel_val = top - (data->guard_period * config->divider); + irq_on_late = flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE; + + if (now_cnt < alarm_cnt) { + /* Absolute alarm is in this round counting */ + reg = alarm_cnt - now_cnt; + irq_on_late = 0; + } else { + /* Absolute alarm is in the next round counting */ + reg = alarm_cnt + remain_cnt; + } + + if (reg > max_rel_val) { + /* Absolute alarm is in the guard period */ + err = -ETIME; + if (!irq_on_late) { + data->ch_data[chan_id].alarm_callback = NULL; + goto out; + } + } + + if (irq_on_late) { + /* Trigger interrupt immediately */ + reg = 1; + } + } else { + /* Round up decreasing counter to tick boundary */ + now_cnt = remain_cnt + config->divider - 1; + now_cnt = (now_cnt / config->divider) * config->divider; + + /* Adjusting relative alarm counter to tick boundary */ + reg = alarm_cnt - (now_cnt - remain_cnt); + } + + /* Set cycle - 1 to reload register */ + sys_write32((reg - 1), PIT_CH_RELD(dev, chan_id)); + + /* Enable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg |= TIMER0_CHANNEL(chan_id); + sys_write32(reg, PIT_INTE(dev)); + + /* Enable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg |= TIMER0_CHANNEL(chan_id); + sys_write32(reg, PIT_CHEN(dev)); + +out: + k_spin_unlock(&data->lock, key); + + return err; +} + +static int atcpit100_cancel_alarm(const struct device *dev, uint8_t chan_id) +{ + struct atcpit100_data *data = dev->data; + k_spinlock_key_t key; + uint32_t reg; + + if (chan_id >= CH_NUM_PER_COUNTER) { + return -ENOTSUP; + } + + key = k_spin_lock(&data->lock); + + /* Disable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg &= ~TIMER0_CHANNEL(chan_id); + sys_write32(reg, PIT_INTE(dev)); + + /* Disable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg &= ~TIMER0_CHANNEL(chan_id); + sys_write32(reg, PIT_CHEN(dev)); + + /* Clear interrupt status */ + sys_write32(TIMER0_CHANNEL(chan_id), PIT_ISTA(dev)); + + data->ch_data[chan_id].alarm_callback = NULL; + + k_spin_unlock(&data->lock, key); + + return 0; +} + +static int atcpit100_set_top_value(const struct device *dev, + const struct counter_top_cfg *cfg) +{ + const struct atcpit100_config *config = dev->config; + struct atcpit100_data *data = dev->data; + uint32_t ticks, reg, reset_counter = 1; + k_spinlock_key_t key; + int err = 0; + uint8_t i; + + for (i = 0; i < counter_get_num_of_channels(dev); i++) { + if (data->ch_data[i].alarm_callback) { + return -EBUSY; + } + } + + if (cfg->ticks > config->info.max_top_value) { + return -ENOTSUP; + } + + key = k_spin_lock(&data->lock); + + if (cfg->callback) { + /* Disable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg &= ~TIMER0_CHANNEL(3); + sys_write32(reg, PIT_INTE(dev)); + + data->top_callback = cfg->callback; + data->top_user_data = cfg->user_data; + + /* Enable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg |= TIMER0_CHANNEL(3); + sys_write32(reg, PIT_INTE(dev)); + } + + if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { + /* Don't reset counter */ + reset_counter = 0; + ticks = get_current_tick(dev, 3); + if (ticks >= cfg->ticks) { + err = -ETIME; + if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) { + /* Reset counter if current is late */ + reset_counter = 1; + } + } + } + + /* Set cycle - 1 to reload register */ + reg = cfg->ticks * config->divider; + sys_write32((reg - 1), PIT_CH_RELD(dev, 3)); + + if (reset_counter) { + /* Disable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg &= ~TIMER0_CHANNEL(3); + sys_write32(reg, PIT_CHEN(dev)); + + /* Clear interrupt status */ + sys_write32(TIMER0_CHANNEL(3), PIT_ISTA(dev)); + + /* Enable channel interrupt */ + reg = sys_read32(PIT_INTE(dev)); + reg |= TIMER0_CHANNEL(3); + sys_write32(reg, PIT_INTE(dev)); + + /* Enable channel */ + reg = sys_read32(PIT_CHEN(dev)); + reg |= TIMER0_CHANNEL(3); + sys_write32(reg, PIT_CHEN(dev)); + } + + k_spin_unlock(&data->lock, key); + + return err; +} + +static uint32_t atcpit100_get_pending_int(const struct device *dev) +{ + uint32_t reg = sys_read32(PIT_ISTA(dev)); + + reg &= (TIMER0_CHANNEL(0) | TIMER0_CHANNEL(1) | + TIMER0_CHANNEL(2) | TIMER0_CHANNEL(3)); + + return !(!reg); +} + +static uint32_t atcpit100_get_top_value(const struct device *dev) +{ + const struct atcpit100_config *config = dev->config; + uint32_t top = sys_read32(PIT_CH_RELD(dev, 3)) + 1; + + return (top / config->divider); +} + +static uint32_t atcpit100_get_guard_period(const struct device *dev, + uint32_t flags) +{ + struct atcpit100_data *data = dev->data; + + return data->guard_period; +} + +static int atcpit100_set_guard_period(const struct device *dev, + uint32_t ticks, uint32_t flags) +{ + const struct atcpit100_config *config = dev->config; + struct atcpit100_data *data = dev->data; + uint32_t top = sys_read32(PIT_CH_RELD(dev, 3)) + 1; + + if ((ticks * config->divider) > top) { + return -EINVAL; + } + + data->guard_period = ticks; + + return 0; +} + +static const struct counter_driver_api atcpit100_driver_api = { + .start = atcpit100_start, + .stop = atcpit100_stop, + .get_value = atcpit100_get_value, + .set_alarm = atcpit100_set_alarm, + .cancel_alarm = atcpit100_cancel_alarm, + .set_top_value = atcpit100_set_top_value, + .get_pending_int = atcpit100_get_pending_int, + .get_top_value = atcpit100_get_top_value, + .get_guard_period = atcpit100_get_guard_period, + .set_guard_period = atcpit100_set_guard_period, +}; + +#define COUNTER_ATCPIT100_INIT(n) \ + static void counter_atcpit100_cfg_##n(void); \ + static struct atcpit100_data atcpit100_data_##n; \ + \ + static const struct atcpit100_config atcpit100_config_##n = { \ + .info = { \ + .max_top_value = \ + (UINT32_MAX/DT_INST_PROP(n, prescaler)),\ + .freq = (DT_INST_PROP(n, clock_frequency) / \ + DT_INST_PROP(n, prescaler)), \ + .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ + .channels = CH_NUM_PER_COUNTER, \ + }, \ + .base = DT_INST_REG_ADDR(n), \ + .divider = DT_INST_PROP(n, prescaler), \ + .irq_num = DT_INST_IRQN(n), \ + .cfg_func = counter_atcpit100_cfg_##n, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, \ + counter_atcpit100_init, \ + NULL, \ + &atcpit100_data_##n, \ + &atcpit100_config_##n, \ + POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &atcpit100_driver_api); \ + \ + static void counter_atcpit100_cfg_##n(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), \ + DT_INST_IRQ(n, priority), \ + atcpit100_irq_handler, \ + DEVICE_DT_INST_GET(n), \ + 0); \ + } + +DT_INST_FOREACH_STATUS_OKAY(COUNTER_ATCPIT100_INIT)