drivers: counter: Add Smartbond basic support
This adds support for the TIMER1-4 counter. Each counter has 24bits and can run on LP_CLK (15-32KHz) or DIVN clock (32MHz) with prescaler 1-32. Each counter can have one alarm set. Signed-off-by: Jerzy Kasenberg <jerzy.kasenberg@codecoup.pl>
This commit is contained in:
parent
7e3f6f1290
commit
b896ca5771
|
@ -9,6 +9,7 @@ toolchain:
|
|||
- xtools
|
||||
supported:
|
||||
- arduino_gpio
|
||||
- counter
|
||||
- gpio
|
||||
- watchdog
|
||||
- i2c
|
||||
|
|
|
@ -36,6 +36,7 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_PIT counter_mcux_pit
|
|||
zephyr_library_sources_ifdef(CONFIG_COUNTER_XLNX_AXI_TIMER counter_xlnx_axi_timer.c)
|
||||
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_SMARTBOND_TIMER counter_smartbond_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_MICROCHIP_MCP7940N rtc_mcp7940n.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_ANDES_ATCPIT100 counter_andes_atcpit100.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ACE_V1X_ART_COUNTER counter_ace_v1x_art.c)
|
||||
|
|
|
@ -68,6 +68,8 @@ source "drivers/counter/Kconfig.esp32_tmr"
|
|||
|
||||
source "drivers/counter/Kconfig.esp32_rtc"
|
||||
|
||||
source "drivers/counter/Kconfig.smartbond_timer"
|
||||
|
||||
source "drivers/counter/Kconfig.mcp7940n"
|
||||
|
||||
source "drivers/counter/Kconfig.mcux_ctimer"
|
||||
|
|
11
drivers/counter/Kconfig.smartbond_timer
Normal file
11
drivers/counter/Kconfig.smartbond_timer
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright (c) 2022 Renesas Electronics Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config COUNTER_SMARTBOND_TIMER
|
||||
bool "Renesas SmartBond(tm) counter driver"
|
||||
default y
|
||||
depends on DT_HAS_RENESAS_SMARTBOND_TIMER_ENABLED
|
||||
select CLOCK_CONTROL_SMARTBOND
|
||||
select CLOCK_CONTROL
|
||||
help
|
||||
Enable the counter driver for for Renesas SmartBond(tm) family of processors.
|
381
drivers/counter/counter_smartbond_timer.c
Normal file
381
drivers/counter/counter_smartbond_timer.c
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Renesas Electronics Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT renesas_smartbond_timer
|
||||
|
||||
#include <zephyr/drivers/counter.h>
|
||||
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
|
||||
#include <zephyr/irq.h>
|
||||
#include <zephyr/sys/atomic.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <DA1469xAB.h>
|
||||
|
||||
LOG_MODULE_REGISTER(counter_timer, CONFIG_COUNTER_LOG_LEVEL);
|
||||
|
||||
#define LP_CLK_OSC_RC32K 0
|
||||
#define LP_CLK_OSC_RCX 1
|
||||
#define LP_CLK_OSC_XTAL32K 2
|
||||
|
||||
#define TIMER_TOP_VALUE 0xFFFFFF
|
||||
|
||||
struct counter_smartbond_data {
|
||||
counter_alarm_callback_t callback;
|
||||
void *user_data;
|
||||
uint32_t guard_period;
|
||||
uint32_t freq;
|
||||
};
|
||||
|
||||
struct counter_smartbond_ch_data {
|
||||
counter_alarm_callback_t callback;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
struct counter_smartbond_config {
|
||||
struct counter_config_info info;
|
||||
/* Register set for timer */
|
||||
TIMER2_Type *timer;
|
||||
uint8_t prescaler;
|
||||
/* Timer driven by DIVn if 1 or lp_clk if 0 */
|
||||
uint8_t clock_src_divn;
|
||||
uint8_t irqn;
|
||||
void (*irq_config_func)(const struct device *dev);
|
||||
|
||||
LOG_INSTANCE_PTR_DECLARE(log);
|
||||
};
|
||||
|
||||
static int counter_smartbond_start(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
TIMER2_Type *timer = config->timer;
|
||||
|
||||
/* enable counter in free running mode */
|
||||
timer->TIMER2_CTRL_REG |= TIMER_TIMER_CTRL_REG_TIM_CLK_EN_Msk |
|
||||
TIMER_TIMER_CTRL_REG_TIM_EN_Msk |
|
||||
TIMER_TIMER_CTRL_REG_TIM_FREE_RUN_MODE_EN_Msk;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int counter_smartbond_stop(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
TIMER2_Type *timer = config->timer;
|
||||
|
||||
/* disable counter */
|
||||
timer->TIMER2_CTRL_REG &= ~(TIMER2_TIMER2_CTRL_REG_TIM_EN_Msk |
|
||||
TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk);
|
||||
data->callback = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t counter_smartbond_get_top_value(const struct device *dev)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
return TIMER_TOP_VALUE;
|
||||
}
|
||||
|
||||
static uint32_t counter_smartbond_read(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
TIMER2_Type *timer = config->timer;
|
||||
|
||||
return timer->TIMER2_TIMER_VAL_REG;
|
||||
}
|
||||
|
||||
static int counter_smartbond_get_value(const struct device *dev, uint32_t *ticks)
|
||||
{
|
||||
*ticks = counter_smartbond_read(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int counter_smartbond_set_alarm(const struct device *dev, uint8_t chan,
|
||||
const struct counter_alarm_cfg *alarm_cfg)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
TIMER2_Type *timer = config->timer;
|
||||
volatile uint32_t *timer_clear_irq_reg = ((TIMER_Type *)timer) == TIMER ?
|
||||
&((TIMER_Type *)timer)->TIMER_CLEAR_IRQ_REG :
|
||||
&timer->TIMER2_CLEAR_IRQ_REG;
|
||||
bool absolute = alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE;
|
||||
uint32_t flags = alarm_cfg->flags;
|
||||
uint32_t val = alarm_cfg->ticks;
|
||||
bool irq_on_late;
|
||||
int err = 0;
|
||||
uint32_t max_rel_val;
|
||||
uint32_t now;
|
||||
uint32_t diff;
|
||||
|
||||
if (chan != 0 || alarm_cfg->ticks > counter_smartbond_get_top_value(dev)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (data->callback) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
now = counter_smartbond_read(dev);
|
||||
data->callback = alarm_cfg->callback;
|
||||
data->user_data = alarm_cfg->user_data;
|
||||
|
||||
__ASSERT_NO_MSG(data->guard_period < TIMER_TOP_VALUE);
|
||||
|
||||
if (absolute) {
|
||||
max_rel_val = TIMER_TOP_VALUE - data->guard_period;
|
||||
irq_on_late = flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE;
|
||||
} else {
|
||||
/* If relative value is smaller than half of the counter range
|
||||
* it is assumed that there is a risk of setting value too late
|
||||
* and late detection algorithm must be applied. When late
|
||||
* setting is detected, interrupt shall be triggered for
|
||||
* immediate expiration of the timer. Detection is performed
|
||||
* by limiting relative distance between CC and counter.
|
||||
*
|
||||
* Note that half of counter range is an arbitrary value.
|
||||
*/
|
||||
irq_on_late = val < (TIMER_TOP_VALUE / 2U);
|
||||
/* limit max to detect short relative being set too late. */
|
||||
max_rel_val = irq_on_late ? TIMER_TOP_VALUE / 2U : TIMER_TOP_VALUE;
|
||||
val = (now + val) & TIMER_TOP_VALUE;
|
||||
}
|
||||
timer->TIMER2_RELOAD_REG = val;
|
||||
*timer_clear_irq_reg = 1;
|
||||
/* decrement value to detect also case when val == counter_smartbond_read(dev). Otherwise,
|
||||
* condition would need to include comparing diff against 0.
|
||||
*/
|
||||
diff = ((val - 1U) - counter_smartbond_read(dev)) & TIMER_TOP_VALUE;
|
||||
if (diff > max_rel_val) {
|
||||
if (absolute) {
|
||||
err = -ETIME;
|
||||
}
|
||||
|
||||
/* Interrupt is triggered always for relative alarm and
|
||||
* for absolute depending on the flag.
|
||||
*/
|
||||
if (irq_on_late) {
|
||||
NVIC_SetPendingIRQ(config->irqn);
|
||||
} else {
|
||||
data->callback = NULL;
|
||||
}
|
||||
} else {
|
||||
if (diff == 0) {
|
||||
/* RELOAD value could be set just in time for interrupt
|
||||
* trigger or too late. In any case time is interrupt
|
||||
* should be triggered. No need to enable interrupt
|
||||
* on TIMER just make sure interrupt is pending.
|
||||
*/
|
||||
NVIC_SetPendingIRQ(config->irqn);
|
||||
} else {
|
||||
timer->TIMER2_CTRL_REG |= TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int counter_smartbond_cancel_alarm(const struct device *dev, uint8_t chan)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
TIMER2_Type *timer = config->timer;
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
|
||||
ARG_UNUSED(chan);
|
||||
|
||||
timer->TIMER2_CTRL_REG &= ~TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk;
|
||||
data->callback = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int counter_smartbond_set_top_value(const struct device *dev,
|
||||
const struct counter_top_cfg *cfg)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
if (cfg->ticks != 0xFFFFFF) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t counter_smartbond_get_pending_int(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *config = dev->config;
|
||||
|
||||
/* There is no register to check TIMER peripheral to check for interrupt
|
||||
* pending, check directly in NVIC.
|
||||
*/
|
||||
return NVIC_GetPendingIRQ(config->irqn);
|
||||
}
|
||||
|
||||
static int counter_smartbond_init_timer(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *cfg = dev->config;
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
TIMER2_Type *timer = cfg->timer;
|
||||
TIMER_Type *timer0 = ((TIMER_Type *)cfg->timer) == TIMER ? TIMER : NULL;
|
||||
const struct device *osc_dev;
|
||||
uint32_t osc_freq;
|
||||
uint32_t osc;
|
||||
|
||||
if (cfg->clock_src_divn) {
|
||||
/* Timer clock source is DIVn 32MHz */
|
||||
timer->TIMER2_CTRL_REG = TIMER_TIMER_CTRL_REG_TIM_SYS_CLK_EN_Msk;
|
||||
data->freq = DT_PROP(DT_NODELABEL(divn_clk), clock_frequency) /
|
||||
(cfg->prescaler + 1);
|
||||
} else {
|
||||
osc_dev = DEVICE_DT_GET(DT_NODELABEL(osc));
|
||||
timer->TIMER2_CTRL_REG = 0;
|
||||
switch ((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >>
|
||||
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos) {
|
||||
case LP_CLK_OSC_RC32K:
|
||||
osc = DT_DEP_ORD(DT_NODELABEL(rc32k));
|
||||
break;
|
||||
case LP_CLK_OSC_RCX:
|
||||
osc = DT_DEP_ORD(DT_NODELABEL(rcx));
|
||||
break;
|
||||
default:
|
||||
case LP_CLK_OSC_XTAL32K:
|
||||
osc = DT_DEP_ORD(DT_NODELABEL(xtal32k));
|
||||
break;
|
||||
}
|
||||
clock_control_get_rate(osc_dev, (clock_control_subsys_t *)&osc, &osc_freq);
|
||||
data->freq = osc_freq / (cfg->prescaler + 1);
|
||||
}
|
||||
timer->TIMER2_PRESCALER_REG = cfg->prescaler;
|
||||
timer->TIMER2_RELOAD_REG = counter_get_max_top_value(dev);
|
||||
timer->TIMER2_GPIO1_CONF_REG = 0;
|
||||
timer->TIMER2_GPIO2_CONF_REG = 0;
|
||||
timer->TIMER2_SHOTWIDTH_REG = 0;
|
||||
timer->TIMER2_CAPTURE_GPIO1_REG = 0;
|
||||
timer->TIMER2_CAPTURE_GPIO2_REG = 0;
|
||||
timer->TIMER2_PWM_FREQ_REG = 0;
|
||||
timer->TIMER2_PWM_DC_REG = 0;
|
||||
if (timer0) {
|
||||
timer0->TIMER_CAPTURE_GPIO3_REG = 0;
|
||||
timer0->TIMER_CAPTURE_GPIO4_REG = 0;
|
||||
}
|
||||
|
||||
/* config/enable IRQ */
|
||||
cfg->irq_config_func(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t counter_smartbond_get_guard_period(const struct device *dev, uint32_t flags)
|
||||
{
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
|
||||
ARG_UNUSED(flags);
|
||||
return data->guard_period;
|
||||
}
|
||||
|
||||
static int counter_smartbond_set_guard_period(const struct device *dev, uint32_t guard,
|
||||
uint32_t flags)
|
||||
{
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
|
||||
ARG_UNUSED(flags);
|
||||
__ASSERT_NO_MSG(guard < counter_smartbond_get_top_value(dev));
|
||||
|
||||
data->guard_period = guard;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t counter_smartbond_get_freq(const struct device *dev)
|
||||
{
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
|
||||
return data->freq;
|
||||
}
|
||||
|
||||
static const struct counter_driver_api counter_smartbond_driver_api = {
|
||||
.start = counter_smartbond_start,
|
||||
.stop = counter_smartbond_stop,
|
||||
.get_value = counter_smartbond_get_value,
|
||||
.set_alarm = counter_smartbond_set_alarm,
|
||||
.cancel_alarm = counter_smartbond_cancel_alarm,
|
||||
.set_top_value = counter_smartbond_set_top_value,
|
||||
.get_pending_int = counter_smartbond_get_pending_int,
|
||||
.get_top_value = counter_smartbond_get_top_value,
|
||||
.get_guard_period = counter_smartbond_get_guard_period,
|
||||
.set_guard_period = counter_smartbond_set_guard_period,
|
||||
.get_freq = counter_smartbond_get_freq,
|
||||
};
|
||||
|
||||
void counter_smartbond_irq_handler(const struct device *dev)
|
||||
{
|
||||
const struct counter_smartbond_config *cfg = dev->config;
|
||||
struct counter_smartbond_data *data = dev->data;
|
||||
counter_alarm_callback_t alarm_callback = data->callback;
|
||||
TIMER2_Type *timer = cfg->timer;
|
||||
/* Timer0 has interrupt clear register in other offset */
|
||||
__IOM uint32_t *timer_clear_irq_reg = ((TIMER_Type *)timer) == TIMER ?
|
||||
&((TIMER_Type *)timer)->TIMER_CLEAR_IRQ_REG :
|
||||
&timer->TIMER2_CLEAR_IRQ_REG;
|
||||
|
||||
timer->TIMER2_CTRL_REG &= ~TIMER2_TIMER2_CTRL_REG_TIM_IRQ_EN_Msk;
|
||||
*timer_clear_irq_reg = 1;
|
||||
|
||||
if (alarm_callback != NULL) {
|
||||
data->callback = NULL;
|
||||
alarm_callback(dev, 0, timer->TIMER2_TIMER_VAL_REG,
|
||||
data->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
#define TIMERN(idx) DT_DRV_INST(idx)
|
||||
|
||||
/** TIMERn instance from DT */
|
||||
#define TIM(idx) ((TIMER2_Type *)DT_REG_ADDR(TIMERN(idx)))
|
||||
|
||||
#define COUNTER_DEVICE_INIT(idx) \
|
||||
BUILD_ASSERT(DT_PROP(TIMERN(idx), prescaler) <= 32 && \
|
||||
DT_PROP(TIMERN(idx), prescaler) > 0, \
|
||||
"TIMER prescaler out of range (1..32)"); \
|
||||
\
|
||||
static struct counter_smartbond_data counter##idx##_data; \
|
||||
\
|
||||
static void counter##idx##_smartbond_irq_config(const struct device *dev)\
|
||||
{ \
|
||||
IRQ_CONNECT(DT_IRQN(TIMERN(idx)), \
|
||||
DT_IRQ(TIMERN(idx), priority), \
|
||||
counter_smartbond_irq_handler, \
|
||||
DEVICE_DT_INST_GET(idx), \
|
||||
0); \
|
||||
irq_enable(DT_IRQN(TIMERN(idx))); \
|
||||
} \
|
||||
\
|
||||
static const struct counter_smartbond_config counter##idx##_config = { \
|
||||
.info = { \
|
||||
.max_top_value = 0x00FFFFFF, \
|
||||
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
|
||||
.channels = 1, \
|
||||
}, \
|
||||
.timer = TIM(idx), \
|
||||
.prescaler = DT_PROP(TIMERN(idx), prescaler) - 1, \
|
||||
.clock_src_divn = DT_SAME_NODE(DT_PROP(TIMERN(idx), clock_src), \
|
||||
DT_NODELABEL(divn_clk)) ? 1 : 0, \
|
||||
.irq_config_func = counter##idx##_smartbond_irq_config, \
|
||||
.irqn = DT_IRQN(TIMERN(idx)), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(idx, \
|
||||
counter_smartbond_init_timer, \
|
||||
NULL, \
|
||||
&counter##idx##_data, \
|
||||
&counter##idx##_config, \
|
||||
PRE_KERNEL_1, CONFIG_COUNTER_INIT_PRIORITY, \
|
||||
&counter_smartbond_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(COUNTER_DEVICE_INIT)
|
|
@ -149,6 +149,42 @@
|
|||
status = "okay";
|
||||
};
|
||||
|
||||
timer1: timer@50010200 {
|
||||
compatible = "renesas,smartbond-timer";
|
||||
reg = <0x50010200 0x300>;
|
||||
clock-src = <&lp_clk>;
|
||||
interrupts = <16 0>;
|
||||
prescaler = <1>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
timer2: timer@50010300 {
|
||||
compatible = "renesas,smartbond-timer";
|
||||
reg = <0x50010300 0x300>;
|
||||
clock-src = <&divn_clk>;
|
||||
interrupts = <17 0>;
|
||||
prescaler = <32>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
timer3: timer@50040a00 {
|
||||
compatible = "renesas,smartbond-timer";
|
||||
reg = <0x50040a00 0x300>;
|
||||
clock-src = <&lp_clk>;
|
||||
interrupts = <34 0>;
|
||||
prescaler = <31>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
timer4: timer@50040b00 {
|
||||
compatible = "renesas,smartbond-timer";
|
||||
reg = <0x50040b00 0x300>;
|
||||
clock-src = <&divn_clk>;
|
||||
interrupts = <35 0>;
|
||||
prescaler = <32>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
uart: uart@50020000 {
|
||||
compatible = "renesas,smartbond-uart";
|
||||
reg = <0x50020000 0x100>;
|
||||
|
|
25
dts/bindings/timer/renesas,smartbond-timer.yaml
Normal file
25
dts/bindings/timer/renesas,smartbond-timer.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright (c) 2022 Renesas Electronics Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Renesas SmartBond(tm) general purpose timers
|
||||
|
||||
compatible: "renesas,smartbond-timer"
|
||||
|
||||
include: base.yaml
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
clock-src:
|
||||
required: true
|
||||
type: phandle
|
||||
description: |
|
||||
Timer uses divn_clk or lp_clk
|
||||
|
||||
prescaler:
|
||||
type: int
|
||||
required: true
|
||||
description: |
|
||||
Clock prescaler at the input of the timer
|
||||
Could be in range [0 .. 31] (CLK/(prescaler+1) )
|
Loading…
Reference in a new issue