drivers: counter: add driver for NXP QTMR counters

The driver is implemented using the MCUXpresso SDK.

Signed-off-by: Jan Peters <peters@kt-elektronik.de>
This commit is contained in:
Jan Peters 2022-04-29 10:25:12 +02:00 committed by David Leach
commit 253cec5c95
11 changed files with 592 additions and 0 deletions

View file

@ -168,6 +168,12 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev,
break; break;
#endif #endif
#ifdef CONFIG_COUNTER_MCUX_QTMR
case IMX_CCM_QTMR_CLK:
*rate = CLOCK_GetIpgFreq();
break;
#endif
#ifdef CONFIG_I2S_MCUX_SAI #ifdef CONFIG_I2S_MCUX_SAI
case IMX_CCM_SAI1_CLK: case IMX_CCM_SAI1_CLK:
*rate = CLOCK_GetFreq(kCLOCK_AudioPllClk) / 8 *rate = CLOCK_GetFreq(kCLOCK_AudioPllClk) / 8

View file

@ -17,6 +17,7 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM_TC counter_sam_tc.c
zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM0_TC32 counter_sam0_tc32.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM0_TC32 counter_sam0_tc32.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_QTMR counter_mcux_qtmr.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_SNVS counter_mcux_snvs.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_SNVS counter_mcux_snvs.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c)

View file

@ -46,6 +46,8 @@ source "drivers/counter/Kconfig.cmos"
source "drivers/counter/Kconfig.mcux_gpt" source "drivers/counter/Kconfig.mcux_gpt"
source "drivers/counter/Kconfig.mcux_qtmr"
source "drivers/counter/Kconfig.mcux_snvs" source "drivers/counter/Kconfig.mcux_snvs"
source "drivers/counter/Kconfig.xec" source "drivers/counter/Kconfig.xec"

View file

@ -0,0 +1,10 @@
# MCUXpresso SDK QTMR
# Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH
# SPDX-License-Identifier: Apache-2.0
config COUNTER_MCUX_QTMR
bool "MCUX QTMR driver"
depends on HAS_MCUX_QTMR
help
Enable support for mcux Quad Timer (QTMR) driver.

View file

@ -0,0 +1,332 @@
/*
* Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* Counter driver for the Quad Timer through the MCUxpresso SDK. Based mainly on counter_mcux_gpt.c
*
* Each quad timer module has four channels (0-3) that can operate independently, but the Zephyr
* counter-API does not support starting or stopping different channels independently. Hence, each
* channel is represented as an independent counter device.
*/
#include <drivers/counter.h>
#include <drivers/clock_control.h>
#include <fsl_qtmr.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(mcux_qtmr, CONFIG_COUNTER_LOG_LEVEL);
struct mcux_qtmr_config {
/* info must be first element */
struct counter_config_info info;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
TMR_Type *base;
clock_name_t clock_source;
qtmr_channel_selection_t channel;
qtmr_config_t qtmr_config;
qtmr_counting_mode_t mode;
};
struct mcux_qtmr_data {
counter_alarm_callback_t alarm_callback;
counter_top_callback_t top_callback;
void *alarm_user_data;
void *top_user_data;
qtmr_status_flags_t interrupt_mask;
uint32_t freq;
};
/* Only one interrupt per QTMR module. Each of which has four timers. */
#define DT_DRV_COMPAT nxp_imx_qtmr
/**
* @brief ISR for a specific timer channel
*
* @param dev timer channel device
*/
void mcux_qtmr_timer_handler(const struct device *dev, uint32_t status)
{
const struct mcux_qtmr_config *config = dev->config;
struct mcux_qtmr_data *data = dev->data;
uint32_t current = QTMR_GetCurrentTimerCount(config->base, config->channel);
QTMR_ClearStatusFlags(config->base, config->channel, status);
__DSB();
if ((status & kQTMR_Compare1Flag) && data->alarm_callback) {
QTMR_DisableInterrupts(config->base, config->channel,
kQTMR_Compare1InterruptEnable);
data->interrupt_mask &= ~kQTMR_Compare1InterruptEnable;
counter_alarm_callback_t alarm_cb = data->alarm_callback;
data->alarm_callback = NULL;
alarm_cb(dev, config->channel, current, data->alarm_user_data);
}
if ((status & kQTMR_OverflowFlag) && data->top_callback) {
data->top_callback(dev, data->top_user_data);
}
}
/**
* @brief ISR for the QTMR
*
* @param timers array containing the counter devices for each channel of the timer module
*/
static void mcux_qtmr_isr(const struct device *timers[])
{
/* the interrupt can be triggered by any of the four channels of the QTMR. Check status
* of all channels and trigger the ISR for the channel(s) that has/have triggered the
* interrupt.
*/
for (qtmr_channel_selection_t ch = kQTMR_Channel_0; ch <= kQTMR_Channel_3 ; ch++) {
if (timers[ch] != NULL) {
const struct mcux_qtmr_config *config = timers[ch]->config;
struct mcux_qtmr_data *data = timers[ch]->data;
uint32_t channel_status = QTMR_GetStatus(config->base, ch);
if ((channel_status & data->interrupt_mask) != 0) {
mcux_qtmr_timer_handler(timers[ch], channel_status);
}
}
}
}
#define ADD_TIMER(node_id, n) timers_##n[DT_PROP(node_id, channel)] = DEVICE_DT_GET(node_id);
#define QTMR_DEVICE_INIT_MCUX(n) \
static const struct device *timers_##n[4]; \
static int init_irq_##n(const struct device *dev) \
{ \
DT_FOREACH_CHILD_STATUS_OKAY_VARGS(DT_DRV_INST(n), ADD_TIMER, n) \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), mcux_qtmr_isr, \
timers_##n, 0); \
irq_enable(DT_INST_IRQN(n)); \
return 0; \
} \
\
SYS_INIT(init_irq_##n, POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY);
DT_INST_FOREACH_STATUS_OKAY(QTMR_DEVICE_INIT_MCUX)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT nxp_imx_tmr
static int mcux_qtmr_start(const struct device *dev)
{
const struct mcux_qtmr_config *config = dev->config;
QTMR_StartTimer(config->base, config->channel, config->mode);
return 0;
}
static int mcux_qtmr_stop(const struct device *dev)
{
const struct mcux_qtmr_config *config = dev->config;
QTMR_StopTimer(config->base, config->channel);
return 0;
}
static int mcux_qtmr_get_value(const struct device *dev, uint32_t *ticks)
{
const struct mcux_qtmr_config *config = dev->config;
*ticks = QTMR_GetCurrentTimerCount(config->base, config->channel);
return 0;
}
static int mcux_qtmr_set_alarm(const struct device *dev, uint8_t chan_id,
const struct counter_alarm_cfg *alarm_cfg)
{
const struct mcux_qtmr_config *config = dev->config;
struct mcux_qtmr_data *data = dev->data;
uint32_t current;
uint32_t ticks;
if (chan_id != 0) {
LOG_ERR("Invalid channel id");
return -EINVAL;
}
if (data->alarm_callback) {
return -EBUSY;
}
data->alarm_callback = alarm_cfg->callback;
data->alarm_user_data = alarm_cfg->user_data;
current = QTMR_GetCurrentTimerCount(config->base, config->channel);
ticks = alarm_cfg->ticks;
if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) {
ticks += current;
}
/* this timer always counts up. */
config->base->CHANNEL[config->channel].COMP1 = ticks;
data->interrupt_mask |= kQTMR_Compare1InterruptEnable;
QTMR_EnableInterrupts(config->base, config->channel, data->interrupt_mask);
return 0;
}
static int mcux_qtmr_cancel_alarm(const struct device *dev, uint8_t chan_id)
{
const struct mcux_qtmr_config *config = dev->config;
struct mcux_qtmr_data *data = dev->data;
if (chan_id != 0) {
LOG_ERR("Invalid channel id");
return -EINVAL;
}
QTMR_DisableInterrupts(config->base, config->channel, data->interrupt_mask);
data->interrupt_mask &= ~kQTMR_Compare1InterruptEnable;
data->alarm_callback = NULL;
return 0;
}
static uint32_t mcux_qtmr_get_pending_int(const struct device *dev)
{
const struct mcux_qtmr_config *config = dev->config;
return QTMR_GetStatus(config->base, config->channel);
}
static int mcux_qtmr_set_top_value(const struct device *dev,
const struct counter_top_cfg *cfg)
{
const struct mcux_qtmr_config *config = dev->config;
struct mcux_qtmr_data *data = dev->data;
if (cfg->ticks != config->info.max_top_value) {
LOG_ERR("Wrap can only be set to 0x%x",
config->info.max_top_value);
return -ENOTSUP;
}
if ((cfg->flags & COUNTER_TOP_CFG_DONT_RESET) == 0) {
if ((config->base->CHANNEL[config->channel].CTRL & TMR_CTRL_DIR_MASK) != 0U) {
/* counting down, reset to UINT16MAX */
config->base->CHANNEL[config->channel].CNTR = UINT16_MAX;
} else {
/* counting up, reset to 0 */
config->base->CHANNEL[config->channel].CNTR = 0;
}
}
if (cfg->callback != NULL) {
data->top_callback = cfg->callback;
data->top_user_data = cfg->user_data;
data->interrupt_mask |= kQTMR_OverflowInterruptEnable;
QTMR_EnableInterrupts(config->base, config->channel, kQTMR_OverflowInterruptEnable);
}
return 0;
}
static uint32_t mcux_qtmr_get_top_value(const struct device *dev)
{
const struct mcux_qtmr_config *config = dev->config;
return config->info.max_top_value;
}
static uint32_t mcux_qtmr_get_freq(const struct device *dev)
{
struct mcux_qtmr_data *data = dev->data;
return data->freq;
}
/**
* @brief look up table for dividers when using internal clock sources kQTMR_ClockDivide_1 to
* kQTMR_ClockDivide_128
*/
static const uint8_t qtmr_primary_source_divider[] = {1, 2, 4, 8, 16, 32, 64, 128};
static int mcux_qtmr_init(const struct device *dev)
{
const struct mcux_qtmr_config *config = dev->config;
struct mcux_qtmr_data *data = dev->data;
if (config->qtmr_config.primarySource < kQTMR_ClockDivide_1) {
/* for external sources, use the value from the dts (if given) */
data->freq = config->info.freq;
} else {
/* bus clock with divider */
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
&data->freq)) {
return -EINVAL;
}
data->freq /= qtmr_primary_source_divider[config->qtmr_config.primarySource -
kQTMR_ClockDivide_1];
}
QTMR_Init(config->base, config->channel, &config->qtmr_config);
return 0;
}
static const struct counter_driver_api mcux_qtmr_driver_api = {
.start = mcux_qtmr_start,
.stop = mcux_qtmr_stop,
.get_value = mcux_qtmr_get_value,
.set_alarm = mcux_qtmr_set_alarm,
.cancel_alarm = mcux_qtmr_cancel_alarm,
.set_top_value = mcux_qtmr_set_top_value,
.get_pending_int = mcux_qtmr_get_pending_int,
.get_top_value = mcux_qtmr_get_top_value,
.get_freq = mcux_qtmr_get_freq,
};
#define TMR_DEVICE_INIT_MCUX(n) \
static struct mcux_qtmr_data mcux_qtmr_data_ ## n; \
\
static const struct mcux_qtmr_config mcux_qtmr_config_ ## n = { \
.base = (void *)DT_REG_ADDR(DT_INST_PARENT(n)), \
.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(n))), \
.clock_subsys = \
(clock_control_subsys_t)DT_CLOCKS_CELL(DT_INST_PARENT(n), name), \
.info = { \
.max_top_value = UINT16_MAX, \
.freq = DT_INST_PROP_OR(n, freq, 0), \
.channels = 1, \
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
}, \
.channel = DT_INST_PROP(n, channel), \
.qtmr_config = { \
.debugMode = kQTMR_RunNormalInDebug, \
.enableExternalForce = false, \
.enableMasterMode = false, \
.faultFilterCount = DT_INST_PROP_OR(n, filter_count, 0), \
.faultFilterPeriod = DT_INST_PROP_OR(n, filter_count, 0), \
.primarySource = DT_INST_ENUM_IDX(n, primary_source), \
.secondarySource = DT_INST_ENUM_IDX_OR(n, secondary_source, 0), \
}, \
.mode = DT_INST_ENUM_IDX(n, mode), \
}; \
\
DEVICE_DT_INST_DEFINE(n, \
mcux_qtmr_init, \
NULL, \
&mcux_qtmr_data_ ## n, \
&mcux_qtmr_config_ ## n, \
POST_KERNEL, \
CONFIG_COUNTER_INIT_PRIORITY, \
&mcux_qtmr_driver_api); \
DT_INST_FOREACH_STATUS_OKAY(TMR_DEVICE_INIT_MCUX)

View file

@ -120,6 +120,134 @@
label = "GPT2"; label = "GPT2";
}; };
qtmr1: qtmr@401dc000 {
compatible = "nxp,imx-qtmr";
reg = <0x401dc000 0x7a>;
interrupts = <133 0>;
clocks = <&ccm IMX_CCM_QTMR_CLK 0 0>;
label = "QTMR1";
qtmr1_timer0: timer0 {
compatible = "nxp,imx-tmr";
channel = <0>;
label = "QTMR1_TIMER0";
status = "disabled";
};
qtmr1_timer1: timer1 {
compatible = "nxp,imx-tmr";
channel = <1>;
label = "QTMR1_TIMER1";
status = "disabled";
};
qtmr1_timer2: timer2 {
compatible = "nxp,imx-tmr";
channel = <2>;
label = "QTMR1_TIMER2";
status = "disabled";
};
qtmr1_timer3: timer3 {
compatible = "nxp,imx-tmr";
channel = <3>;
label = "QTMR1_TIMER3";
status = "disabled";
};
};
qtmr2: qtmr@401e0000 {
compatible = "nxp,imx-qtmr";
reg = <0x401e0000 0x7a>;
interrupts = <134 0>;
clocks = <&ccm IMX_CCM_QTMR_CLK 0 0>;
label = "QTMR2";
qtmr2_timer0: timer0 {
compatible = "nxp,imx-tmr";
channel = <0>;
label = "QTMR2_TIMER0";
status = "disabled";
};
qtmr2_timer1: timer1 {
compatible = "nxp,imx-tmr";
channel = <1>;
label = "QTMR2_TIMER1";
status = "disabled";
};
qtmr2_timer2: timer2 {
compatible = "nxp,imx-tmr";
channel = <2>;
label = "QTMR2_TIMER2";
status = "disabled";
};
qtmr2_timer3: timer3 {
compatible = "nxp,imx-tmr";
channel = <3>;
label = "QTMR2_TIMER3";
status = "disabled";
};
};
qtmr3: qtmr@401e4000 {
compatible = "nxp,imx-qtmr";
reg = <0x401e4000 0x7a>;
interrupts = <135 0>;
clocks = <&ccm IMX_CCM_QTMR_CLK 0 0>;
label = "QTMR3";
qtmr3_timer0: timer0 {
compatible = "nxp,imx-tmr";
channel = <0>;
label = "QTMR3_TIMER0";
status = "disabled";
};
qtmr3_timer1: timer1 {
compatible = "nxp,imx-tmr";
channel = <1>;
label = "QTMR3_TIMER1";
status = "disabled";
};
qtmr3_timer2: timer2 {
compatible = "nxp,imx-tmr";
channel = <2>;
label = "QTMR3_TIMER2";
status = "disabled";
};
qtmr3_timer3: timer3 {
compatible = "nxp,imx-tmr";
channel = <3>;
label = "QTMR3_TIMER3";
status = "disabled";
};
};
qtmr4: qtmr@401e8000 {
compatible = "nxp,imx-qtmr";
reg = <0x401e8000 0x7a>;
interrupts = <136 0>;
clocks = <&ccm IMX_CCM_QTMR_CLK 0 0>;
label = "QTMR4";
qtmr4_timer0: timer0 {
compatible = "nxp,imx-tmr";
channel = <0>;
label = "QTMR4_TIMER0";
status = "disabled";
};
qtmr4_timer1: timer1 {
compatible = "nxp,imx-tmr";
channel = <1>;
label = "QTMR4_TIMER1";
status = "disabled";
};
qtmr4_timer2: timer2 {
compatible = "nxp,imx-tmr";
channel = <2>;
label = "QTMR4_TIMER2";
status = "disabled";
};
qtmr4_timer3: timer3 {
compatible = "nxp,imx-tmr";
channel = <3>;
label = "QTMR4_TIMER3";
status = "disabled";
};
};
ccm: ccm@400fc000 { ccm: ccm@400fc000 {
compatible = "nxp,imx-ccm"; compatible = "nxp,imx-ccm";
reg = <0x400fc000 0x4000>; reg = <0x400fc000 0x4000>;

View file

@ -0,0 +1,18 @@
# Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH
# SPDX-License-Identifier: Apache-2.0
description: NXP MCUX Quad Timer (QTMR)
compatible: "nxp,imx-qtmr"
include: base.yaml
properties:
reg:
required: true
interrupts:
required: true
label:
required: true

View file

@ -0,0 +1,86 @@
# Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH
# SPDX-License-Identifier: Apache-2.0
description: NXP MCUX Quad Timer Channel. Each channel of each quad timer can operate independently
and hence will be realized as a separate counter device
compatible: "nxp,imx-tmr"
include: base.yaml
properties:
label:
required: true
channel:
type: int
required: true
enum:
- 0
- 1
- 2
- 3
mode:
type: string
required: true
description: counting mode of the timer, see qtmr_counting_mode_t enumerator type
of the MCUXpresso SDK
enum:
- "kQTMR_NoOperation"
- "kQTMR_PriSrcRiseEdge"
- "kQTMR_PriSrcRiseAndFallEdge"
- "kQTMR_PriSrcRiseEdgeSecInpHigh"
- "kQTMR_QuadCountMode"
- "kQTMR_PriSrcRiseEdgeSecDir"
- "kQTMR_SecSrcTrigPriCnt"
- "kQTMR_CascadeCount"
primary_source:
type: string
required: true
description: Primary source of the timer, see qtmr_primary_count_source_t enumerator type
of the MCUXpresso SDK
enum:
- "kQTMR_ClockCounter0InputPin"
- "kQTMR_ClockCounter1InputPin"
- "kQTMR_ClockCounter2InputPin"
- "kQTMR_ClockCounter3InputPin"
- "kQTMR_ClockCounter0Output"
- "kQTMR_ClockCounter1Output"
- "kQTMR_ClockCounter2Output"
- "kQTMR_ClockCounter3Output"
- "kQTMR_ClockDivide_1"
- "kQTMR_ClockDivide_2"
- "kQTMR_ClockDivide_4"
- "kQTMR_ClockDivide_8"
- "kQTMR_ClockDivide_16"
- "kQTMR_ClockDivide_32"
- "kQTMR_ClockDivide_64"
- "kQTMR_ClockDivide_128"
secondary_source:
type: string
required: false
description: Secondary source of the timer, see qtmr_input_source_t enumerator type
of the MCUXpresso SDK
enum:
- "kQTMR_Counter0InputPin"
- "kQTMR_Counter1InputPin"
- "kQTMR_Counter2InputPin"
- "kQTMR_Counter3InputPin"
filter_count:
type: int
required: false
description: Fault filter count (0-255).
filter_period:
type: int
required: false
description: Fault filter period (0-255).
freq:
type: int
required: false
description: clock frequency (only used for external clock sources)

View file

@ -45,4 +45,6 @@
#define IMX_CCM_PWM_CLK 0x0C00UL #define IMX_CCM_PWM_CLK 0x0C00UL
#define IMX_CCM_QTMR_CLK 0x0D00UL
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_IMX_CCM_H_ */ #endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_IMX_CCM_H_ */

View file

@ -136,6 +136,11 @@ config HAS_MCUX_GPT
help help
Set if the general purpose timer (GPT) module is present in the SoC. Set if the general purpose timer (GPT) module is present in the SoC.
config HAS_MCUX_QTMR
bool
help
Set if the quad timer (QTMR) module is present in the SoC.
config HAS_MCUX_GPC config HAS_MCUX_GPC
bool bool
help help

View file

@ -217,6 +217,7 @@ config SOC_MIMXRT1062
select HAS_MCUX_LPSPI select HAS_MCUX_LPSPI
select HAS_MCUX_LPUART select HAS_MCUX_LPUART
select HAS_MCUX_GPT select HAS_MCUX_GPT
select HAS_MCUX_QTMR
select HAS_MCUX_SEMC select HAS_MCUX_SEMC
select HAS_MCUX_TRNG select HAS_MCUX_TRNG
select CPU_HAS_FPU_DOUBLE_PRECISION select CPU_HAS_FPU_DOUBLE_PRECISION
@ -251,6 +252,7 @@ config SOC_MIMXRT1064
select HAS_MCUX_LPSPI select HAS_MCUX_LPSPI
select HAS_MCUX_LPUART select HAS_MCUX_LPUART
select HAS_MCUX_GPT select HAS_MCUX_GPT
select HAS_MCUX_QTMR
select HAS_MCUX_SEMC select HAS_MCUX_SEMC
select HAS_MCUX_SNVS select HAS_MCUX_SNVS
select HAS_MCUX_SRC select HAS_MCUX_SRC