From e2e5c22d9fad593c01bd4108107b0ec91f5efea2 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Wed, 16 Jul 2025 18:22:30 +0800 Subject: [PATCH 1/4] drivers: Counter: LPIT Support on Zephyr 1.Add dts bindings nxp,lpit-channel.yaml and nxp,lpit.yaml 2.Provide counter driver based on lpit driver from NXP mcux-sdk-ng Signed-off-by: Felix Wang --- drivers/counter/CMakeLists.txt | 1 + drivers/counter/Kconfig | 2 + drivers/counter/Kconfig.mcux_lpit | 11 + drivers/counter/counter_mcux_lpit.c | 377 ++++++++++++++++++ dts/bindings/counter/nxp,lpit-channel.yaml | 14 + dts/bindings/counter/nxp,lpit.yaml | 28 ++ .../mcux/mcux-sdk-ng/drivers/drivers.cmake | 1 + 7 files changed, 434 insertions(+) create mode 100644 drivers/counter/Kconfig.mcux_lpit create mode 100644 drivers/counter/counter_mcux_lpit.c create mode 100644 dts/bindings/counter/nxp,lpit-channel.yaml create mode 100644 dts/bindings/counter/nxp,lpit.yaml diff --git a/drivers/counter/CMakeLists.txt b/drivers/counter/CMakeLists.txt index 9de937d7c9b4d..b05a2d242a7cf 100644 --- a/drivers/counter/CMakeLists.txt +++ b/drivers/counter/CMakeLists.txt @@ -33,6 +33,7 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_SNVS counter_mcux_snv zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_TPM counter_mcux_tpm.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_LPIT counter_mcux_lpit.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_NATIVE_SIM counter_native_sim.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE counter_handlers.c) diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index 36e1f0bce3c3c..96f03960336c4 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -70,6 +70,8 @@ source "drivers/counter/Kconfig.xec" source "drivers/counter/Kconfig.mcux_lptmr" +source "drivers/counter/Kconfig.mcux_lpit" + source "drivers/counter/Kconfig.maxim_ds3231" source "drivers/counter/Kconfig.native_sim" diff --git a/drivers/counter/Kconfig.mcux_lpit b/drivers/counter/Kconfig.mcux_lpit new file mode 100644 index 0000000000000..c002d9c23f5b8 --- /dev/null +++ b/drivers/counter/Kconfig.mcux_lpit @@ -0,0 +1,11 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +# MCUXpresso SDK Low Power Periodic Interrupt Timer (LPIT) +config COUNTER_MCUX_LPIT + bool "NXP LPIT driver" + default y + depends on DT_HAS_NXP_LPIT_CHANNEL_ENABLED && \ + DT_HAS_NXP_LPIT_ENABLED + help + Enable support for the NXP Low Power Periodic Interrupt Timer (LPIT). diff --git a/drivers/counter/counter_mcux_lpit.c b/drivers/counter/counter_mcux_lpit.c new file mode 100644 index 0000000000000..552f00da0f71f --- /dev/null +++ b/drivers/counter/counter_mcux_lpit.c @@ -0,0 +1,377 @@ +/* + * Copyright 2023, 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_lpit + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(counter_lpit, CONFIG_COUNTER_LOG_LEVEL); + +/* Device holds a pointer to pointer to data */ +#define LPIT_CHANNEL_DATA(dev) (*(struct mcux_lpit_channel_data *const *const)dev->data) + +/* dev->config->data is an array of data pointers ordered by channel number, + * dev->data is a pointer to one of these pointers in that array, + * so the value of the dev->data - dev->config->data is the channel index + */ +#define LPIT_CHANNEL_ID(dev) \ + (((struct mcux_lpit_channel_data *const *)dev->data) - \ + ((const struct mcux_lpit_config *)dev->config)->data) + +struct mcux_lpit_channel_data { + uint32_t top; /* Top value of the counter */ + counter_top_callback_t top_callback; /* Callback when counter turns around */ + void *top_user_data; /* User data for top callback */ + counter_alarm_callback_t alarm_callback; /* Alarm callback */ + void *alarm_user_data; /* User data for alarm callback */ +}; + +struct mcux_lpit_config { + struct counter_config_info info; + LPIT_Type *base; /* Poniter to LPIT peripheral instance base address*/ + lpit_config_t lpit_config; /* LPIT configuration structure */ + int num_channels; /* Number of channels for one LPIT instance*/ + void (*irq_config_func)(const struct device *dev); + const struct device *clock_dev; + clock_control_subsys_t clock_subsys; + /* Point to array of channels data for this LPIT instance */ + struct mcux_lpit_channel_data *const *data; + /* Point to array of channel devices for this LPIT instance */ + const struct device *const *channels; +}; + +static uint32_t mcux_lpit_get_top_value(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + + /* + * The underlying HAL driver function LPIT_SetTimerPeriod() + * automatically subtracted 1 from the value that ends up in + * TVAL so for reporting purposes we need to add it back in + * here to by consistent. + */ + return (config->base->CHANNEL[channel_id].TVAL + 1U); +} + +static int mcux_lpit_start(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + + LOG_DBG("period is %d", mcux_lpit_get_top_value(dev)); + LPIT_EnableInterrupts(config->base, (1U << channel_id)); + LPIT_StartTimer(config->base, channel_id); + return 0; +} + +static int mcux_lpit_stop(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + + LPIT_StopTimer(config->base, channel_id); + return 0; +} + +static int mcux_lpit_get_value(const struct device *dev, uint32_t *ticks) +{ + const struct mcux_lpit_config *config = dev->config; + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + + *ticks = LPIT_GetCurrentTimerCount(config->base, channel_id); + + return 0; +} + +static int mcux_lpit_set_top_value(const struct device *dev, const struct counter_top_cfg *cfg) +{ + const struct mcux_lpit_config *config = dev->config; + struct mcux_lpit_channel_data *data = LPIT_CHANNEL_DATA(dev); + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + bool running = (config->base->CHANNEL[channel_id].TCTRL & LPIT_TCTRL_T_EN_MASK); + + if (cfg->ticks == 0U || cfg->ticks > config->info.max_top_value) { + return -EINVAL; + } + + data->top_callback = cfg->callback; + data->top_user_data = cfg->user_data; + + if (running) { + /* Timer already enabled, check flags before resetting */ + if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { + return -ENOTSUP; + } + LPIT_StopTimer(config->base, channel_id); + LPIT_SetTimerPeriod(config->base, channel_id, cfg->ticks); + LPIT_StartTimer(config->base, channel_id); + } else { + LPIT_SetTimerPeriod(config->base, channel_id, cfg->ticks); + } + + return 0; +} + +static uint32_t mcux_lpit_get_pending_int(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + uint8_t channel_id = LPIT_CHANNEL_ID(dev); + + return (LPIT_GetStatusFlags(config->base) >> channel_id) & 0x1U; +} + +static uint32_t mcux_lpit_get_frequency(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + uint32_t clock_rate; + + if (clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_rate)) { + LOG_ERR("Failed to get clock rate"); + return 0; + } + + return clock_rate; +} + +static int mcux_lpit_set_alarm(const struct device *dev, uint8_t chan_id, + const struct counter_alarm_cfg *alarm_cfg) +{ + const struct mcux_lpit_config *config = dev->config; + struct mcux_lpit_channel_data *data = LPIT_CHANNEL_DATA(dev); + uint8_t channel = LPIT_CHANNEL_ID(dev); + uint32_t final_ticks = alarm_cfg->ticks; + uint32_t now, max_ticks; + + /* Return error if device does not support requested channel */ + if (chan_id != channel) { + return -ENOTSUP; + } + + now = LPIT_GetCurrentTimerCount(config->base, channel); + + if (alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) { + max_ticks = config->info.max_top_value; + } else { + max_ticks = config->info.max_top_value - now; + } + + if (final_ticks > max_ticks) { + LOG_ERR("Alarm ticks %d exceeds max top value %d", final_ticks, max_ticks); + return -EINVAL; + } + + if (!(alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE)) { + final_ticks += now; + } + + /* The underlying HAL driver function LPIT_SetTimerPeriod() assert(ticks > 2U) */ + if (final_ticks <= 2U) { + return -EINVAL; + } + /* if top value has been set, the alarm ticks should not be bigger than top value*/ + if (data->top_callback && final_ticks > mcux_lpit_get_top_value(dev)) { + return -EINVAL; + } + + data->alarm_callback = alarm_cfg->callback; + data->alarm_user_data = alarm_cfg->user_data; + + /* To abort the current timer cycle and start a timer period with a new value, + * you must disable the timer channel and then enable it again. + */ + LPIT_StopTimer(config->base, channel); + LPIT_SetTimerPeriod(config->base, channel, final_ticks); + LPIT_EnableInterrupts(config->base, (1 << channel)); + LPIT_StartTimer(config->base, channel); + + return 0; +} + +static int mcux_lpit_cancel_alarm(const struct device *dev, uint8_t chan_id) +{ + const struct mcux_lpit_config *config = dev->config; + struct mcux_lpit_channel_data *data = LPIT_CHANNEL_DATA(dev); + uint8_t channel = LPIT_CHANNEL_ID(dev); + + /* Return if device does not support requested channel*/ + if (chan_id != channel) { + return -ENOTSUP; + } + LPIT_DisableInterrupts(config->base, (1 << channel)); + LPIT_StopTimer(config->base, channel); + data->alarm_callback = NULL; + return 0; +} + +static void mcux_lpit_isr(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + + LOG_DBG("lpit counter isr"); + + uint32_t flags = LPIT_GetStatusFlags(config->base); + + for (int channel_index = 0; channel_index < config->num_channels; channel_index++) { + + if (!(flags & (1U << channel_index))) { + continue; + } + + /* Clear interrupt flag */ + LPIT_ClearStatusFlags(config->base, (1U << channel_index)); + + struct mcux_lpit_channel_data *data = + LPIT_CHANNEL_DATA(config->channels[channel_index]); + + if (data->alarm_callback != NULL) { + + counter_alarm_callback_t current_alarm_callback = data->alarm_callback; + /* dsiable alarm callback for next alarm set up */ + data->alarm_callback = NULL; + + uint32_t current_ticks = + LPIT_GetCurrentTimerCount(config->base, channel_index); + /* Pass channel device as callback first parameter, because the + * application use channel api. + */ + current_alarm_callback(config->channels[channel_index], channel_index, + current_ticks, data->alarm_user_data); + } + if (data->top_callback != NULL) { + data->top_callback(config->channels[channel_index], data->top_user_data); + } + } +} + +static int mcux_lpit_init(const struct device *dev) +{ + const struct mcux_lpit_config *config = dev->config; + lpit_config_t lpit_config; + uint32_t clock_rate; + + if (!device_is_ready(config->clock_dev)) { + LOG_ERR("Clock control device not ready"); + return -ENODEV; + } + + LPIT_GetDefaultConfig(&lpit_config); + lpit_config.enableRunInDebug = config->lpit_config.enableRunInDebug; + lpit_config.enableRunInDoze = config->lpit_config.enableRunInDoze; + + LPIT_Init(config->base, &lpit_config); + + clock_rate = mcux_lpit_get_frequency(dev); + + config->irq_config_func(dev); + + for (int channel_index = 0U; channel_index < config->num_channels; channel_index++) { + LPIT_SetTimerPeriod(config->base, channel_index, config->info.max_top_value); + } + + return 0; +} + +static DEVICE_API(counter, mcux_lpit_driver_api) = { + .start = mcux_lpit_start, + .stop = mcux_lpit_stop, + .get_value = mcux_lpit_get_value, + .set_top_value = mcux_lpit_set_top_value, + .get_pending_int = mcux_lpit_get_pending_int, + .get_top_value = mcux_lpit_get_top_value, + .get_freq = mcux_lpit_get_frequency, + .set_alarm = mcux_lpit_set_alarm, + .cancel_alarm = mcux_lpit_cancel_alarm, +}; + +/* Creates a device for a channel (needed for counter API) */ +#define MCUX_LPIT_CHANNEL_DEV_INIT(node, lpit_inst) \ + DEVICE_DT_DEFINE(node, NULL, NULL, \ + (void *)&mcux_lpit_##lpit_inst##_channel_datas[DT_REG_ADDR(node)], \ + &mcux_lpit_##lpit_inst##_config, POST_KERNEL, \ + CONFIG_COUNTER_INIT_PRIORITY, &mcux_lpit_driver_api); + +/* Creates a decleration for each lpit channel */ +#define MCUX_LPIT_CHANNEL_DECLARATIONS(node) \ + static struct mcux_lpit_channel_data mcux_lpit_channel_data_##node; + +/* Initializes an element of the channel data pointer array */ +#define MCUX_LPIT_INSERT_CHANNEL_INTO_ARRAY(node) \ + [DT_REG_ADDR(node)] = &mcux_lpit_channel_data_##node, + +#define MCUX_LPIT_INSERT_CHANNEL_DEVICE_INTO_ARRAY(node) [DT_REG_ADDR(node)] = DEVICE_DT_GET(node), + +#define MCUX_LPIT_IRQ_CONFIG_DECLARATIONS(n) \ + static void mcux_lpit_irq_config_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, 0, irq), DT_INST_IRQ_BY_IDX(n, 0, priority), \ + mcux_lpit_isr, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQN(n)); \ + }; + +#define MCUX_LPIT_SETUP_IRQ_CONFIG(n) MCUX_LPIT_IRQ_CONFIG_DECLARATIONS(n); +#define MCUX_LPIT_SETUP_IRQ_ARRAY(ignored) + +#define COUNTER_MCUX_LPIT_DEVICE_INIT(n) \ + \ + /* Setup the IRQ either for parent irq or per channel irq */ \ + MCUX_LPIT_SETUP_IRQ_CONFIG(n) \ + \ + /* Create channel declarations */ \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, MCUX_LPIT_CHANNEL_DECLARATIONS) \ + \ + /* Array of channel devices */ \ + static struct mcux_lpit_channel_data \ + *const mcux_lpit_##n##_channel_datas[DT_INST_FOREACH_CHILD_SEP_VARGS( \ + n, DT_NODE_HAS_COMPAT, (+), nxp_lpit_channel)] = { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, \ + MCUX_LPIT_INSERT_CHANNEL_INTO_ARRAY)}; \ + \ + /* forward declaration */ \ + static const struct mcux_lpit_config mcux_lpit_##n##_config; \ + \ + /* Create all the channel/counter devices */ \ + DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(n, MCUX_LPIT_CHANNEL_DEV_INIT, n) \ + \ + /* This channel device array is needed by the module device ISR */ \ + const struct device *const mcux_lpit_##n##_channels[DT_INST_FOREACH_CHILD_SEP_VARGS( \ + n, DT_NODE_HAS_COMPAT, (+), nxp_lpit_channel)] = { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(n, MCUX_LPIT_INSERT_CHANNEL_DEVICE_INTO_ARRAY)}; \ + \ + MCUX_LPIT_SETUP_IRQ_ARRAY(n) \ + \ + /* This config struct is shared by all the channels and parent device */ \ + static const struct mcux_lpit_config mcux_lpit_##n##_config = { \ + .info = \ + { \ + .max_top_value = DT_INST_PROP(n, max_load_value), \ + .channels = DT_INST_FOREACH_CHILD_SEP_VARGS( \ + n, DT_NODE_HAS_COMPAT, (+), nxp_lpit_channel), \ + }, \ + .base = (LPIT_Type *)DT_INST_REG_ADDR(n), \ + .lpit_config = \ + { \ + .enableRunInDebug = DT_INST_PROP(n, enable_run_in_debug), \ + .enableRunInDoze = DT_INST_PROP(n, enable_run_in_doze), \ + }, \ + .irq_config_func = mcux_lpit_irq_config_func_##n, \ + .num_channels = DT_INST_FOREACH_CHILD_SEP_VARGS(n, DT_NODE_HAS_COMPAT, (+), \ + nxp_lpit_channel), \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \ + .data = mcux_lpit_##n##_channel_datas, \ + .channels = mcux_lpit_##n##_channels, \ + }; \ + \ + /* Init parent device in order to handle ISR and init. */ \ + DEVICE_DT_INST_DEFINE(n, &mcux_lpit_init, NULL, NULL, &mcux_lpit_##n##_config, \ + POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(COUNTER_MCUX_LPIT_DEVICE_INIT) diff --git a/dts/bindings/counter/nxp,lpit-channel.yaml b/dts/bindings/counter/nxp,lpit-channel.yaml new file mode 100644 index 0000000000000..69f745e30a578 --- /dev/null +++ b/dts/bindings/counter/nxp,lpit-channel.yaml @@ -0,0 +1,14 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier Apache-2.0 + +description: | + Child node for the Low Power Periodic Interrupt Timer node, intended + for an individual timer channel + +compatible: "nxp,lpit-channel" + +include: base.yaml + +properties: + reg: + required: true diff --git a/dts/bindings/counter/nxp,lpit.yaml b/dts/bindings/counter/nxp,lpit.yaml new file mode 100644 index 0000000000000..183ddde20cffc --- /dev/null +++ b/dts/bindings/counter/nxp,lpit.yaml @@ -0,0 +1,28 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP Low Power Periodic Interrupt Timer (PIT) + +compatible: "nxp,lpit" + +include: base.yaml + +properties: + reg: + required: true + + clocks: + required: true + + max-load-value: + type: int + required: true + description: Maximum timeout ticks allowed for timer channels. + + enable-run-in-debug: + type: boolean + description: Set 1 to allows timer channels to continue running when the chip enters Debug mode. + + enable-run-in-doze: + type: boolean + description: Set 1 to allows timer channels to continue running when the chip enters Doze mode. diff --git a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake index b794013ca972c..c76c3c6a1c422 100644 --- a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake +++ b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake @@ -90,6 +90,7 @@ set_variable_ifdef(CONFIG_WDT_MCUX_WDOG CONFIG_MCUX_COMPONENT_driver.wdo set_variable_ifdef(CONFIG_WDT_MCUX_WDOG32 CONFIG_MCUX_COMPONENT_driver.wdog32) set_variable_ifdef(CONFIG_COUNTER_MCUX_GPT CONFIG_MCUX_COMPONENT_driver.gpt) set_variable_ifdef(CONFIG_MCUX_GPT_TIMER CONFIG_MCUX_COMPONENT_driver.gpt) +set_variable_ifdef(CONFIG_COUNTER_MCUX_LPIT CONFIG_MCUX_COMPONENT_driver.lpit) set_variable_ifdef(CONFIG_DISPLAY_MCUX_ELCDIF CONFIG_MCUX_COMPONENT_driver.elcdif) set_variable_ifdef(CONFIG_MCUX_PXP CONFIG_MCUX_COMPONENT_driver.pxp) set_variable_ifdef(CONFIG_LV_USE_GPU_NXP_PXP CONFIG_MCUX_COMPONENT_driver.pxp) From 544ad6f398eaaec2ebc2d4638de1f58ca71482b7 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Wed, 16 Jul 2025 18:49:44 +0800 Subject: [PATCH 2/4] dts: arm: nxp: rt118x: add lpit instances for RT118X Add lpit1, lpit2, lpit3 and all of the channels information Signed-off-by: Felix Wang --- dts/arm/nxp/nxp_rt118x.dtsi | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/dts/arm/nxp/nxp_rt118x.dtsi b/dts/arm/nxp/nxp_rt118x.dtsi index dc25cfb24ecb4..3a299bfab6aa4 100644 --- a/dts/arm/nxp/nxp_rt118x.dtsi +++ b/dts/arm/nxp/nxp_rt118x.dtsi @@ -716,6 +716,112 @@ status = "disabled"; }; + lpit1: lpit@442f0000 { + compatible = "nxp,lpit"; + reg = <0x442f0000 0x1000>; + interrupts = <15 0>; + clocks = <&ccm IMX_CCM_LPIT1_CLK 0x0 0>; + status = "disabled"; + max-load-value = <0xffffffff>; + #address-cells = <1>; + #size-cells = <0>; + + lpit1_channel0: lpit_channel@0 { + compatible = "nxp,lpit-channel"; + reg = <0>; + status = "disabled"; + }; + + lpit1_channel1: lpit_channel@1 { + compatible = "nxp,lpit-channel"; + reg = <1>; + status = "disabled"; + }; + + lpit1_channel2: lpit_channel@2 { + compatible = "nxp,lpit-channel"; + reg = <2>; + status = "disabled"; + }; + + lpit1_channel3: lpit_channel@3 { + compatible = "nxp,lpit-channel"; + reg = <3>; + status = "disabled"; + }; + }; + + lpit2: lpit@424c0000 { + compatible = "nxp,lpit"; + reg = <0x424c0000 0x1000>; + interrupts = <64 0>; + clocks = <&ccm IMX_CCM_LPIT2_CLK 0x0 0>; + status = "disabled"; + max-load-value = <0xffffffff>; + #address-cells = <1>; + #size-cells = <0>; + + lpit2_channel0: lpit_channel@0 { + compatible = "nxp,lpit-channel"; + reg = <0>; + status = "disabled"; + }; + + lpit2_channel1: lpit_channel@1 { + compatible = "nxp,lpit-channel"; + reg = <1>; + status = "disabled"; + }; + + lpit2_channel2: lpit_channel@2 { + compatible = "nxp,lpit-channel"; + reg = <2>; + status = "disabled"; + }; + + lpit2_channel3: lpit_channel@3 { + compatible = "nxp,lpit-channel"; + reg = <3>; + status = "disabled"; + }; + }; + + lpit3: lpit@42cc0000 { + compatible = "nxp,lpit"; + reg = <0x42cc0000 0x1000>; + interrupts = <149 0>; + clocks = <&ccm IMX_CCM_LPIT3_CLK 0x0 0>; + status = "disabled"; + max-load-value = <0xffffffff>; + #address-cells = <1>; + #size-cells = <0>; + + lpit3_channel0: lpit_channel@0 { + compatible = "nxp,lpit-channel"; + reg = <0>; + status = "disabled"; + }; + + lpit3_channel1: lpit_channel@1 { + compatible = "nxp,lpit-channel"; + reg = <1>; + status = "disabled"; + }; + + lpit3_channel2: lpit_channel@2 { + compatible = "nxp,lpit-channel"; + reg = <2>; + status = "disabled"; + }; + + lpit3_channel3: lpit_channel@3 { + compatible = "nxp,lpit-channel"; + reg = <3>; + status = "disabled"; + }; + }; + + lptmr1: lptmr@4300000 { compatible = "nxp,lptmr"; reg = <0x4300000 0x1000>; From e96dca891f513a2341f7d44fbf7b06ca760c205f Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Wed, 16 Jul 2025 18:51:58 +0800 Subject: [PATCH 3/4] soc: nxp: imxrt: clock update for LPIT instances on RT118X 1. Configure clock source for lpit3 for imxrt118x devices 2. Support lpit in clock driver Signed-off-by: Felix Wang --- .../clock_control_mcux_ccm_rev2.c | 20 ++++++++++++++++++- .../zephyr/dt-bindings/clock/imx_ccm_rev2.h | 6 ++++++ soc/nxp/imxrt/imxrt118x/soc.c | 10 ++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/drivers/clock_control/clock_control_mcux_ccm_rev2.c b/drivers/clock_control/clock_control_mcux_ccm_rev2.c index 9eff29cad3ddd..1c2d0363feddf 100644 --- a/drivers/clock_control/clock_control_mcux_ccm_rev2.c +++ b/drivers/clock_control/clock_control_mcux_ccm_rev2.c @@ -256,7 +256,25 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev, clock_root = kCLOCK_Root_Bus + instance; break; #endif - +#ifdef CONFIG_COUNTER_MCUX_LPIT +#if defined(CONFIG_SOC_SERIES_IMXRT118X) + case IMX_CCM_LPIT_CLK: + switch (instance) { + case 0: + clock_root = kCLOCK_Root_Bus_Aon; + break; + case 1: + clock_root = kCLOCK_Root_Bus_Wakeup; + break; + case 2: + clock_root = kCLOCK_Root_Lpit3; + break; + default: + return -EINVAL; + } + break; +#endif +#endif #ifdef CONFIG_ADC_MCUX_LPADC case IMX_CCM_LPADC1_CLK: clock_root = kCLOCK_Root_Adc1 + instance; diff --git a/include/zephyr/dt-bindings/clock/imx_ccm_rev2.h b/include/zephyr/dt-bindings/clock/imx_ccm_rev2.h index e5035584cbec0..6df244712536e 100644 --- a/include/zephyr/dt-bindings/clock/imx_ccm_rev2.h +++ b/include/zephyr/dt-bindings/clock/imx_ccm_rev2.h @@ -149,6 +149,12 @@ #define IMX_CCM_I3C1_CLK 0x2200UL #define IMX_CCM_I3C2_CLK 0x2201UL +/* LPIT */ +#define IMX_CCM_LPIT_CLK 0x2300UL +#define IMX_CCM_LPIT1_CLK 0x2300UL +#define IMX_CCM_LPIT2_CLK 0x2301UL +#define IMX_CCM_LPIT3_CLK 0x2302UL + /* QTMR */ #define IMX_CCM_QTMR_CLK 0x6000UL #define IMX_CCM_QTMR1_CLK 0x6000UL diff --git a/soc/nxp/imxrt/imxrt118x/soc.c b/soc/nxp/imxrt/imxrt118x/soc.c index 5be53080f40fd..28457e781560e 100644 --- a/soc/nxp/imxrt/imxrt118x/soc.c +++ b/soc/nxp/imxrt/imxrt118x/soc.c @@ -596,6 +596,16 @@ __weak void clock_init(void) #endif /* CONFIG_IMX_USDHC */ +#ifdef CONFIG_COUNTER_MCUX_LPIT + /* LPIT1 use BUS_AON, LPIT2 use BUS_WAKEUP, which have been configured */ +#if DT_NODE_HAS_STATUS(DT_NODELABEL(lpit3), okay) + /* Configure LPIT3 using SysPll3Div2 */ + rootCfg.mux = kCLOCK_LPIT3_ClockRoot_MuxSysPll3Div2; + rootCfg.div = 3; + CLOCK_SetRootClock(kCLOCK_Root_Lpit3, &rootCfg); +#endif +#endif /* CONFIG_COUNTER_MCUX_LPIT */ + /* Keep core clock ungated during WFI */ CCM->LPCG[1].LPM0 = 0x33333333; CCM->LPCG[1].LPM1 = 0x33333333; From 4a4fa7e0763aaf4a2cb362cc9da3f488301e8d0c Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Wed, 16 Jul 2025 18:54:10 +0800 Subject: [PATCH 4/4] tests: drivers: counter: Add counter_nxp_lpit_api test case and test on mimxrt1180_evk This test is modified from tests/drivers/counter/counter_basic_api/src/ test_counter.c The original test does not work for LPIT. For example, the hard code channel 0 for counter_set_channel_alarm setting brings error for lpit1_channel1, lpit2_channel3 and so on. And the setting alarm_cfgs.flags=0, which means setting the relative ticks based on current ticks, brings overflow errors, because LPIT is a count down timer which starts from top value. Based on these limitation, In order to avoid modifying the original test and bringing possible side effects to other platforms, a modified new test was prepared here. In this test, COUNTER_ALARM_CFG_ABSOLUTE is set for counter, and all channels specified in edvicetree file are test. This test inclduing following tests: - test_all_channels - test_cancelled_alarm_does_not_expire - test_set_top_value_with_alarm - test_single_shot_alarm_notop - test_single_shot_alarm_top - test_valid_function_without_alarm - test_set_top_value_without_alarm Signed-off-by: Felix Wang --- .../counter_nxp_lpit_api/CMakeLists.txt | 9 + .../mimxrt1180_evk_mimxrt1189_cm33.conf | 7 + .../mimxrt1180_evk_mimxrt1189_cm33.overlay | 65 ++ .../boards/mimxrt1180_evk_mimxrt1189_cm7.conf | 7 + .../mimxrt1180_evk_mimxrt1189_cm7.overlay | 65 ++ .../counter/counter_nxp_lpit_api/prj.conf | 5 + .../counter/counter_nxp_lpit_api/src/main.c | 634 ++++++++++++++++++ .../counter_nxp_lpit_api/testcase.yaml | 10 + 8 files changed, 802 insertions(+) create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/CMakeLists.txt create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.conf create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.overlay create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.conf create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.overlay create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/prj.conf create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/src/main.c create mode 100644 tests/drivers/counter/counter_nxp_lpit_api/testcase.yaml diff --git a/tests/drivers/counter/counter_nxp_lpit_api/CMakeLists.txt b/tests/drivers/counter/counter_nxp_lpit_api/CMakeLists.txt new file mode 100644 index 0000000000000..6f392fd976273 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(counter_nxp_lpit_api) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.conf b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.conf new file mode 100644 index 0000000000000..e196cda4baee9 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.conf @@ -0,0 +1,7 @@ +# +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_COUNTER_MCUX_LPIT=y diff --git a/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.overlay b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.overlay new file mode 100644 index 0000000000000..ff93ec5f10e16 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm33.overlay @@ -0,0 +1,65 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&lpit1 { + status = "okay"; +}; + +&lpit1_channel0 { + status = "okay"; +}; + +&lpit1_channel1 { + status = "okay"; +}; + +&lpit1_channel2 { + status = "okay"; +}; + +&lpit1_channel3 { + status = "okay"; +}; + +&lpit2 { + status = "okay"; +}; + +&lpit2_channel0 { + status = "okay"; +}; + +&lpit2_channel1 { + status = "okay"; +}; + +&lpit2_channel2 { + status = "okay"; +}; + +&lpit2_channel3 { + status = "okay"; +}; + +&lpit3 { + status = "okay"; +}; + +&lpit3_channel0 { + status = "okay"; +}; + +&lpit3_channel1 { + status = "okay"; +}; + +&lpit3_channel2 { + status = "okay"; +}; + +&lpit3_channel3 { + status = "okay"; +}; diff --git a/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.conf b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.conf new file mode 100644 index 0000000000000..e196cda4baee9 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.conf @@ -0,0 +1,7 @@ +# +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_COUNTER_MCUX_LPIT=y diff --git a/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.overlay b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.overlay new file mode 100644 index 0000000000000..ff93ec5f10e16 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/boards/mimxrt1180_evk_mimxrt1189_cm7.overlay @@ -0,0 +1,65 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&lpit1 { + status = "okay"; +}; + +&lpit1_channel0 { + status = "okay"; +}; + +&lpit1_channel1 { + status = "okay"; +}; + +&lpit1_channel2 { + status = "okay"; +}; + +&lpit1_channel3 { + status = "okay"; +}; + +&lpit2 { + status = "okay"; +}; + +&lpit2_channel0 { + status = "okay"; +}; + +&lpit2_channel1 { + status = "okay"; +}; + +&lpit2_channel2 { + status = "okay"; +}; + +&lpit2_channel3 { + status = "okay"; +}; + +&lpit3 { + status = "okay"; +}; + +&lpit3_channel0 { + status = "okay"; +}; + +&lpit3_channel1 { + status = "okay"; +}; + +&lpit3_channel2 { + status = "okay"; +}; + +&lpit3_channel3 { + status = "okay"; +}; diff --git a/tests/drivers/counter/counter_nxp_lpit_api/prj.conf b/tests/drivers/counter/counter_nxp_lpit_api/prj.conf new file mode 100644 index 0000000000000..cc386ea862396 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/prj.conf @@ -0,0 +1,5 @@ +CONFIG_COUNTER=y +CONFIG_BT=n +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y +CONFIG_LOG=y diff --git a/tests/drivers/counter/counter_nxp_lpit_api/src/main.c b/tests/drivers/counter/counter_nxp_lpit_api/src/main.c new file mode 100644 index 0000000000000..310c12677dcf3 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/src/main.c @@ -0,0 +1,634 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor ASA + * Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +LOG_MODULE_REGISTER(test); + +static struct k_sem top_cnt_sem; +static volatile uint32_t top_cnt; +static struct k_sem alarm_cnt_sem; +static volatile uint32_t alarm_cnt; + +static void top_handler(const struct device *dev, void *user_data); + +void *exp_user_data = (void *)199; + +struct counter_alarm_cfg cntr_alarm_cfg; +struct counter_alarm_cfg cntr_alarm_cfg2; + +#define DEVICE_DT_GET_AND_COMMA(node_id) DEVICE_DT_GET(node_id), +/* Generate a list of devices for all instances of the "compat" */ +#define DEVS_FOR_DT_COMPAT(compat) DT_FOREACH_STATUS_OKAY(compat, DEVICE_DT_GET_AND_COMMA) + +static const struct device *const devices[] = { +#ifdef CONFIG_COUNTER_MCUX_LPIT + DEVS_FOR_DT_COMPAT(nxp_lpit_channel) +#endif +}; + +typedef void (*counter_test_func_t)(const struct device *dev); +typedef bool (*counter_capability_func_t)(const struct device *dev); + +static inline uint32_t get_counter_period_us(const struct device *dev) +{ + return 20000; +} + +static void counter_setup_instance(const struct device *dev) +{ + k_sem_reset(&alarm_cnt_sem); + if (!k_is_user_context()) { + alarm_cnt = 0; + } +} + +static void counter_tear_down_instance(const struct device *dev) +{ + int err; + struct counter_top_cfg top_cfg = {.callback = NULL, .user_data = NULL, .flags = 0}; + + top_cfg.ticks = counter_get_max_top_value(dev); + err = counter_set_top_value(dev, &top_cfg); + if (err == -ENOTSUP) { + /* If resetting is not support, attempt without reset. */ + top_cfg.flags = COUNTER_TOP_CFG_DONT_RESET; + err = counter_set_top_value(dev, &top_cfg); + } + zassert_true((err == 0) || (err == -ENOTSUP), "%s: Setting top value to default failed", + dev->name); + + err = counter_stop(dev); + zassert_equal(0, err, "%s: Counter failed to stop", dev->name); +} + +static void test_all_instances(counter_test_func_t func, counter_capability_func_t capability_check) +{ + int devices_skipped = 0; + + zassert_true(ARRAY_SIZE(devices) > 0, "No device found"); + for (int i = 0; i < ARRAY_SIZE(devices); i++) { + counter_setup_instance(devices[i]); + if ((capability_check == NULL) || capability_check(devices[i])) { + TC_PRINT("Testing %s\n", devices[i]->name); + func(devices[i]); + } else { + TC_PRINT("Skipped for %s\n", devices[i]->name); + devices_skipped++; + } + counter_tear_down_instance(devices[i]); + /* Allow logs to be printed. */ + k_sleep(K_MSEC(100)); + } + if (devices_skipped == ARRAY_SIZE(devices)) { + ztest_test_skip(); + } +} + +static bool set_top_value_capable(const struct device *dev) +{ + struct counter_top_cfg cfg = {.ticks = counter_get_top_value(dev) - 1}; + int err; + + err = counter_set_top_value(dev, &cfg); + if (err == -ENOTSUP) { + return false; + } + + cfg.ticks++; + err = counter_set_top_value(dev, &cfg); + if (err == -ENOTSUP) { + return false; + } + + return true; +} + +static void top_handler(const struct device *dev, void *user_data) +{ + zassert_true(user_data == exp_user_data, "%s: Unexpected callback", dev->name); + if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) { + top_cnt++; + + return; + } + + k_sem_give(&top_cnt_sem); +} + +static void test_set_top_value_with_alarm_instance(const struct device *dev) +{ + int err; + uint32_t cnt; + uint32_t top_value; + uint32_t counter_period_us; + uint32_t top_handler_cnt; + struct counter_top_cfg top_cfg = { + .callback = top_handler, .user_data = exp_user_data, .flags = 0}; + + k_sem_reset(&top_cnt_sem); + top_cnt = 0; + + counter_period_us = get_counter_period_us(dev); + top_cfg.ticks = counter_us_to_ticks(dev, counter_period_us); + err = counter_start(dev); + zassert_equal(0, err, "%s: Counter failed to start", dev->name); + + k_busy_wait(5000); + + err = counter_get_value(dev, &cnt); + zassert_true(err == 0, "%s: Counter read failed (err: %d)", dev->name, err); + if (counter_is_counting_up(dev)) { + err = (cnt > 0) ? 0 : 1; + } else { + top_value = counter_get_top_value(dev); + err = (cnt < top_value) ? 0 : 1; + } + zassert_true(err == 0, "%s: Counter should progress", dev->name); + + err = counter_set_top_value(dev, &top_cfg); + zassert_equal(0, err, "%s: Counter failed to set top value (err: %d)", dev->name, err); + + k_busy_wait(5.2 * counter_period_us); + + top_handler_cnt = + IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) ? top_cnt : k_sem_count_get(&top_cnt_sem); + zassert_true(top_handler_cnt == 5U, "%s: Unexpected number of turnarounds (%d).", dev->name, + top_handler_cnt); +} + +ZTEST(counter_basic, test_set_top_value_with_alarm) +{ + test_all_instances(test_set_top_value_with_alarm_instance, set_top_value_capable); +} + +static void test_set_top_value_without_alarm_instance(const struct device *dev) +{ + int err; + uint32_t cnt; + uint32_t top_value; + uint32_t counter_period_us; + struct counter_top_cfg top_cfg = {.callback = NULL, .user_data = NULL, .flags = 0}; + + counter_period_us = get_counter_period_us(dev); + top_cfg.ticks = counter_us_to_ticks(dev, counter_period_us); + err = counter_start(dev); + zassert_equal(0, err, "%s: Counter failed to start", dev->name); + + k_busy_wait(5000); + + err = counter_get_value(dev, &cnt); + zassert_true(err == 0, "%s: Counter read failed (err: %d)", dev->name, err); + if (counter_is_counting_up(dev)) { + err = (cnt > 0) ? 0 : 1; + } else { + top_value = counter_get_top_value(dev); + err = (cnt < top_value) ? 0 : 1; + } + zassert_true(err == 0, "%s: Counter should progress", dev->name); + + err = counter_set_top_value(dev, &top_cfg); + zassert_equal(0, err, "%s: Counter failed to set top value (err: %d)", dev->name, err); + + zassert_true(counter_get_top_value(dev) == top_cfg.ticks, "%s: new top value not in use.", + dev->name); +} + +ZTEST_USER(counter_no_callback, test_set_top_value_without_alarm) +{ + test_all_instances(test_set_top_value_without_alarm_instance, set_top_value_capable); +} + +static void alarm_handler(const struct device *dev, uint8_t chan_id, uint32_t counter, + void *user_data) +{ + /* Arbitrary limit for alarm processing - time between hw expiration + * and read-out from counter in the handler. + */ + static const uint64_t processing_limit_us = 1000; + uint32_t now; + int err; + uint32_t top; + uint32_t diff; + + err = counter_get_value(dev, &now); + zassert_true(err == 0, "%s: Counter read failed (err: %d)", dev->name, err); + + top = counter_get_top_value(dev); + if (counter_is_counting_up(dev)) { + diff = (now < counter) ? (now + top - counter) : (now - counter); + } else { + diff = (now > counter) ? (counter + top - now) : (counter - now); + } + + zassert_true(diff <= counter_us_to_ticks(dev, processing_limit_us), + "Unexpected distance between reported alarm value(%u) " + "and actual counter value (%u), top:%d (processing " + "time limit (%d us) might be exceeded?", + counter, now, top, (int)processing_limit_us); + + if (user_data) { + zassert_true(&cntr_alarm_cfg == user_data, "%s: Unexpected callback", dev->name); + } + + if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) { + alarm_cnt++; + return; + } + zassert_true(k_is_in_isr(), "%s: Expected interrupt context", dev->name); + k_sem_give(&alarm_cnt_sem); +} + +static void test_single_shot_alarm_instance(const struct device *dev, bool set_top) +{ + int err; + uint32_t ticks; + uint32_t cnt; + uint32_t counter_period_us; + uint8_t chan_id = 0, n = 10; + struct counter_top_cfg top_cfg = { + .callback = top_handler, .user_data = exp_user_data, .flags = 0}; + + counter_period_us = get_counter_period_us(dev); + ticks = counter_us_to_ticks(dev, counter_period_us); + top_cfg.ticks = ticks; + + /* This case tests alarm with specific ticks, for LPIT, it counts down from timer value to 0 + * then trigger interrupt, hence it need to count down from ticks, set flag as + * COUNTER_ALARM_CFG_ABSOLUTE + */ + cntr_alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE; + + cntr_alarm_cfg.callback = alarm_handler; + cntr_alarm_cfg.user_data = &cntr_alarm_cfg; + + k_sem_reset(&alarm_cnt_sem); + alarm_cnt = 0; + + if (counter_get_num_of_channels(dev) < 1U) { + /* Counter does not support any alarm */ + return; + } + + err = counter_start(dev); + zassert_equal(0, err, "%s: Counter failed to start", dev->name); + + for (int j = 0; j < n; j++) { + err = counter_cancel_channel_alarm(dev, j); + if (err == 0) { + chan_id = j; + break; + } + } + zassert_not_equal(10, chan_id, "%s: Failed to set an alarm", dev->name); + + if (set_top) { + err = counter_set_top_value(dev, &top_cfg); + + zassert_equal(0, err, "%s: Counter failed to set top value", dev->name); + + cntr_alarm_cfg.ticks = ticks + 1; + err = counter_set_channel_alarm(dev, chan_id, &cntr_alarm_cfg); + zassert_equal(-EINVAL, err, + "%s: Counter should return error because ticks" + " exceeded the limit set alarm", + dev->name); + cntr_alarm_cfg.ticks = ticks - 1; + } + + cntr_alarm_cfg.ticks = ticks; + err = counter_set_channel_alarm(dev, chan_id, &cntr_alarm_cfg); + zassert_equal(0, err, "%s: Counter set alarm failed (err: %d)", dev->name, err); + + k_busy_wait(2 * (uint32_t)counter_ticks_to_us(dev, ticks)); + + cnt = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) ? alarm_cnt : k_sem_count_get(&alarm_cnt_sem); + zassert_equal(1, cnt, "%s: Expecting 1 alarm callback, get %d", dev->name, cnt); + + k_busy_wait(1.5 * counter_ticks_to_us(dev, ticks)); + cnt = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) ? alarm_cnt : k_sem_count_get(&alarm_cnt_sem); + zassert_equal(1, cnt, "%s: Expecting alarm callback", dev->name); + + err = counter_cancel_channel_alarm(dev, chan_id); + zassert_equal(0, err, "%s: Counter disabling alarm failed", dev->name); + + top_cfg.ticks = counter_get_max_top_value(dev); + top_cfg.callback = NULL; + top_cfg.user_data = NULL; + err = counter_set_top_value(dev, &top_cfg); + if (err == -ENOTSUP) { + /* If resetting is not support, attempt without reset. */ + top_cfg.flags = COUNTER_TOP_CFG_DONT_RESET; + err = counter_set_top_value(dev, &top_cfg); + } + zassert_true((err == 0) || (err == -ENOTSUP), "%s: Setting top value to default failed", + dev->name); + + err = counter_stop(dev); + zassert_equal(0, err, "%s: Counter failed to stop", dev->name); +} + +void test_single_shot_alarm_notop_instance(const struct device *dev) +{ + test_single_shot_alarm_instance(dev, false); +} + +void test_single_shot_alarm_top_instance(const struct device *dev) +{ + test_single_shot_alarm_instance(dev, true); +} + +static bool single_channel_alarm_capable(const struct device *dev) +{ + return (counter_get_num_of_channels(dev) > 0); +} + +static bool single_channel_alarm_and_custom_top_capable(const struct device *dev) +{ + return single_channel_alarm_capable(dev) && set_top_value_capable(dev); +} + +ZTEST(counter_basic, test_single_shot_alarm_notop) +{ + test_all_instances(test_single_shot_alarm_notop_instance, single_channel_alarm_capable); +} + +ZTEST(counter_basic, test_single_shot_alarm_top) +{ + test_all_instances(test_single_shot_alarm_top_instance, + single_channel_alarm_and_custom_top_capable); +} + +static void *clbk_data[10]; + +static void alarm_handler2(const struct device *dev, uint8_t chan_id, uint32_t counter, + void *user_data) +{ + if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) { + clbk_data[alarm_cnt] = user_data; + alarm_cnt++; + + return; + } + + clbk_data[k_sem_count_get(&alarm_cnt_sem)] = user_data; + k_sem_give(&alarm_cnt_sem); +} + +static void test_all_channels_instance(const struct device *dev) +{ + int err; + const int n = 10; + uint8_t chan_id = 0; + struct counter_alarm_cfg alarm_cfgs; + uint32_t ticks; + uint32_t cnt; + uint32_t counter_period_us; + + counter_period_us = get_counter_period_us(dev); + ticks = counter_us_to_ticks(dev, counter_period_us); + + /* This case tests alarm with specific ticks, for LPIT, it counts down from timer value to 0 + * then trigger interrupt, hence it need to count down from ticks, set flag as + * COUNTER_ALARM_CFG_ABSOLUTE + */ + alarm_cfgs.flags = COUNTER_ALARM_CFG_ABSOLUTE; + alarm_cfgs.ticks = ticks; + alarm_cfgs.callback = alarm_handler2; + alarm_cfgs.user_data = NULL; + + err = counter_start(dev); + zassert_equal(0, err, "%s: Counter failed to start", dev->name); + + for (int i = 0; i < n; i++) { + err = counter_set_channel_alarm(dev, i, &alarm_cfgs); + if (err == 0) { + chan_id = i; + break; + } + } + zassert_equal(0, err, "%s: Unexpected error on setting alarm", dev->name); + + k_busy_wait(1.5 * counter_ticks_to_us(dev, ticks)); + cnt = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) ? alarm_cnt : k_sem_count_get(&alarm_cnt_sem); + zassert_equal(1, cnt, "%s: Expecting alarm callback %d, actually get: %d", dev->name, + chan_id, cnt); + + err = counter_cancel_channel_alarm(dev, chan_id); + zassert_equal(0, err, "%s: Unexpected error on disabling alarm", dev->name); + + err = counter_cancel_channel_alarm(dev, chan_id + 1); + zassert_equal(-ENOTSUP, err, "%s: Unexpected error on disabling alarm", dev->name); +} + +ZTEST(counter_basic, test_all_channels) +{ + test_all_instances(test_all_channels_instance, single_channel_alarm_capable); +} + +static void test_valid_function_without_alarm(const struct device *dev) +{ + int err; + uint32_t ticks; + uint32_t ticks_expected; + uint32_t tick_current; + uint32_t ticks_tol; + uint32_t wait_for_us; + uint32_t freq = counter_get_frequency(dev); + + /* For timers which cannot count to at least 2 ms before overflow + * the test is skipped by test_all_instances function because sufficient + * accuracy of the test cannot be achieved. + */ + + zassert_true(freq != 0, "%s: counter could not get frequency", dev->name); + + /* Set time of counting based on counter frequency to + * ensure convenient run time and accuracy of the test. + */ + if (freq < 1000) { + /* Ensure to have 1 tick for 1 sec clock */ + wait_for_us = 1100000; + ticks_expected = counter_us_to_ticks(dev, wait_for_us); + } else if (freq < 1000000) { + /* Calculate wait time for convenient ticks count */ + ticks_expected = 1000; + wait_for_us = (ticks_expected * 1000000) / freq; + } else { + /* Wait long enough for high frequency clocks to minimize + * impact of latencies and k_busy_wait function accuracy. + */ + wait_for_us = 1000; + ticks_expected = counter_us_to_ticks(dev, wait_for_us); + } + + /* Set 10% or 2 ticks tolerance, whichever is greater */ + ticks_tol = ticks_expected / 10; + ticks_tol = ticks_tol < 2 ? 2 : ticks_tol; + + err = counter_start(dev); + zassert_equal(0, err, "%s: counter failed to start", dev->name); + + /* counter might not start from 0, use current value as offset */ + counter_get_value(dev, &tick_current); + if (counter_is_counting_up(dev)) { + ticks_expected += tick_current; + } else { + ticks_expected = tick_current - ticks_expected; + } + + k_busy_wait(wait_for_us); + + err = counter_get_value(dev, &ticks); + + zassert_equal(0, err, "%s: could not get counter value", dev->name); + zassert_between_inclusive( + ticks, ticks_expected > ticks_tol ? ticks_expected - ticks_tol : 0, + ticks_expected + ticks_tol, "%s: counter ticks not in tolerance", dev->name); + + /* ticks count is always within ticks_tol for RTC, therefor + * check, if ticks are greater than 0. + */ + zassert_true((ticks > 0), "%s: counter did not count", dev->name); + + err = counter_stop(dev); + zassert_equal(0, err, "%s: counter failed to stop", dev->name); +} + +static bool ms_period_capable(const struct device *dev) +{ + uint32_t freq_khz; + uint32_t max_time_ms; + + /* Assume 2 ms counter periode can be set for frequency below 1 kHz*/ + if (counter_get_frequency(dev) < 1000) { + return true; + } + + freq_khz = counter_get_frequency(dev) / 1000; + max_time_ms = counter_get_top_value(dev) / freq_khz; + + if (max_time_ms >= 2) { + return true; + } + + return false; +} + +ZTEST(counter_basic, test_valid_function_without_alarm) +{ + test_all_instances(test_valid_function_without_alarm, ms_period_capable); +} + +/* Test checks if cancelled alarm does not get triggered when new alarm is + * configured at the point where previous alarm was about to expire. + */ +static void test_cancelled_alarm_does_not_expire_instance(const struct device *dev) +{ + int err; + uint32_t cnt; + uint32_t us = 1000; + uint32_t ticks = counter_us_to_ticks(dev, us); + uint32_t top = counter_get_top_value(dev); + uint8_t chan_id = 0, n = 10; + + us = (uint32_t)counter_ticks_to_us(dev, ticks); + + struct counter_alarm_cfg alarm_cfg = { + .callback = alarm_handler, .flags = COUNTER_ALARM_CFG_ABSOLUTE, .user_data = NULL}; + + k_sem_reset(&alarm_cnt_sem); + alarm_cnt = 0; + + err = counter_start(dev); + zassert_equal(0, err, "%s: Unexpected error", dev->name); + + for (int i = 0; i < us / 2; ++i) { + err = counter_get_value(dev, &(alarm_cfg.ticks)); + zassert_true(err == 0, "%s: Counter read failed (err: %d)", dev->name, err); + + alarm_cfg.ticks += ticks; + alarm_cfg.ticks = alarm_cfg.ticks % top; + + for (int j = 0; j < n; j++) { + err = counter_set_channel_alarm(dev, j, &alarm_cfg); + if (err == 0) { + chan_id = j; + break; + } + } + + zassert_not_equal(10, chan_id, "%s: Failed to set an alarm", dev->name); + + err = counter_cancel_channel_alarm(dev, chan_id); + zassert_equal(0, err, "%s: Failed to cancel an alarm (err: %d)", dev->name, err); + + k_busy_wait(us / 2 + i); + + alarm_cfg.ticks = alarm_cfg.ticks + 2 * ticks; + alarm_cfg.ticks = alarm_cfg.ticks % top; + err = counter_set_channel_alarm(dev, chan_id, &alarm_cfg); + zassert_equal(0, err, "%s: Failed to set an alarm (err: %d)", dev->name, err); + + /* wait to ensure that tick+1 timeout will expire. */ + k_busy_wait(us); + + err = counter_cancel_channel_alarm(dev, chan_id); + zassert_equal(0, err, "%s: Failed to cancel an alarm (err: %d)", dev->name, err); + + cnt = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) ? alarm_cnt + : k_sem_count_get(&alarm_cnt_sem); + zassert_equal(0, cnt, "%s: Expected %d callbacks, got %d (i:%d)\n", dev->name, 0, + cnt, i); + } +} + +static bool reliable_cancel_capable(const struct device *dev) +{ +#ifdef CONFIG_COUNTER_MCUX_LPIT + if (single_channel_alarm_capable(dev)) { + return true; + } +#endif + return false; +} + +ZTEST(counter_basic, test_cancelled_alarm_does_not_expire) +{ + test_all_instances(test_cancelled_alarm_does_not_expire_instance, reliable_cancel_capable); +} + +static void *counter_setup(void) +{ + /* Give required clocks some time to stabilize. In particular, nRF SoCs + * need such delay for the Xtal LF clock source to start and for this + * test to use the correct timing. + */ + k_busy_wait(USEC_PER_MSEC * 300); + + k_sem_init(&top_cnt_sem, 0, UINT_MAX); + k_object_access_grant(&top_cnt_sem, k_current_get()); + + k_sem_init(&alarm_cnt_sem, 0, UINT_MAX); + k_object_access_grant(&alarm_cnt_sem, k_current_get()); + + for (int i = 0; i < ARRAY_SIZE(devices); i++) { + zassert_true(device_is_ready(devices[i]), "Device %s is not ready", + devices[i]->name); + k_object_access_grant(devices[i], k_current_get()); + } + + return NULL; +} + +/* Uses callbacks, run in supervisor mode */ +ZTEST_SUITE(counter_basic, NULL, counter_setup, NULL, NULL, NULL); + +/* No callbacks, run in usermode */ +ZTEST_SUITE(counter_no_callback, NULL, counter_setup, NULL, NULL, NULL); diff --git a/tests/drivers/counter/counter_nxp_lpit_api/testcase.yaml b/tests/drivers/counter/counter_nxp_lpit_api/testcase.yaml new file mode 100644 index 0000000000000..e97a6cb3a18c9 --- /dev/null +++ b/tests/drivers/counter/counter_nxp_lpit_api/testcase.yaml @@ -0,0 +1,10 @@ +tests: + drivers.counter.nxp_lpit_api: + tags: + - drivers + - counter + depends_on: counter + platform_allow: + - mimxrt1180_evk/mimxrt1189/cm33 + - mimxrt1180_evk/mimxrt1189/cm7 + timeout: 600