/* * Copyright 2020,2023-2024 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_pit #include #include #include #include #define LOG_MODULE_NAME counter_pit #include LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_COUNTER_LOG_LEVEL); /* Device holds a pointer to pointer to data */ #define PIT_CHANNEL_DATA(dev) \ (*(struct nxp_pit_channel_data *const *const)dev->data) /* Device 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 PIT_CHANNEL_ID(dev) \ (((struct nxp_pit_channel_data *const *)dev->data) - \ ((const struct nxp_pit_config *)dev->config)->data) struct nxp_pit_channel_data { uint32_t top; counter_top_callback_t top_callback; void *top_user_data; }; struct nxp_pit_config { struct counter_config_info info; PIT_Type *base; bool enableRunInDebug; int num_channels; #if DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) void (*irq_config_func)(const struct device *dev); #else void (**irq_config_func)(const struct device *dev); #endif const struct device *clock_dev; clock_control_subsys_t clock_subsys; struct nxp_pit_channel_data *const *data; const struct device *const *channels; }; static uint32_t nxp_pit_get_top_value(const struct device *dev) { const struct nxp_pit_config *config = dev->config; pit_chnl_t channel = PIT_CHANNEL_ID(dev); /* * According to RM, the LDVAL trigger = clock ticks -1 * The underlying HAL driver function PIT_SetTimerPeriod() * automatically subtracted 1 from the value that ends up in * LDVAL so for reporting purposes we need to add it back in * here to by consistent. */ return (config->base->CHANNEL[channel].LDVAL + 1); } static int nxp_pit_start(const struct device *dev) { const struct nxp_pit_config *config = dev->config; int channel_id = PIT_CHANNEL_ID(dev); LOG_DBG("period is %d", nxp_pit_get_top_value(dev)); PIT_EnableInterrupts(config->base, channel_id, kPIT_TimerInterruptEnable); PIT_StartTimer(config->base, channel_id); return 0; } static int nxp_pit_stop(const struct device *dev) { const struct nxp_pit_config *config = dev->config; int channel_id = PIT_CHANNEL_ID(dev); PIT_DisableInterrupts(config->base, channel_id, kPIT_TimerInterruptEnable); PIT_StopTimer(config->base, channel_id); return 0; } static int nxp_pit_get_value(const struct device *dev, uint32_t *ticks) { const struct nxp_pit_config *config = dev->config; int channel_id = PIT_CHANNEL_ID(dev); *ticks = PIT_GetCurrentTimerCount(config->base, channel_id); return 0; } static int nxp_pit_set_top_value(const struct device *dev, const struct counter_top_cfg *cfg) { const struct nxp_pit_config *config = dev->config; struct nxp_pit_channel_data *data = PIT_CHANNEL_DATA(dev); pit_chnl_t channel = PIT_CHANNEL_ID(dev); if (cfg->ticks == 0) { return -EINVAL; } data->top_callback = cfg->callback; data->top_user_data = cfg->user_data; if (config->base->CHANNEL[channel].TCTRL & PIT_TCTRL_TEN_MASK) { /* Timer already enabled, check flags before resetting */ if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) { return -ENOTSUP; } PIT_StopTimer(config->base, channel); PIT_SetTimerPeriod(config->base, channel, cfg->ticks); PIT_StartTimer(config->base, channel); } else { PIT_SetTimerPeriod(config->base, channel, cfg->ticks); } return 0; } static uint32_t nxp_pit_get_pending_int(const struct device *dev) { const struct nxp_pit_config *config = dev->config; uint32_t mask = PIT_TFLG_TIF_MASK; uint32_t flags; int channel_id = PIT_CHANNEL_ID(dev); flags = PIT_GetStatusFlags(config->base, channel_id); return ((flags & mask) == mask); } static uint32_t nxp_pit_get_frequency(const struct device *dev) { const struct nxp_pit_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; } #if DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) static void nxp_pit_isr(const struct device *dev) { const struct nxp_pit_config *config = dev->config; uint32_t flags; LOG_DBG("pit counter isr"); for (int channel_index = 0; channel_index < config->num_channels; channel_index++) { flags = PIT_GetStatusFlags(config->base, channel_index); if (flags) { struct nxp_pit_channel_data *data = PIT_CHANNEL_DATA(config->channels[channel_index]); PIT_ClearStatusFlags(config->base, channel_index, flags); data->top_callback(dev, data->top_user_data); } } } #else static void nxp_pit_isr(const struct device *dev) { const struct nxp_pit_config *config = dev->config; struct nxp_pit_channel_data *data = PIT_CHANNEL_DATA(dev); pit_chnl_t channel = PIT_CHANNEL_ID(dev); uint32_t flags; LOG_DBG("pit counter isr"); flags = PIT_GetStatusFlags(config->base, channel); if (flags) { PIT_ClearStatusFlags(config->base, channel, flags); if (data->top_callback) { data->top_callback(dev, data->top_user_data); } } } #endif /* DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) */ static int nxp_pit_init(const struct device *dev) { const struct nxp_pit_config *config = dev->config; pit_config_t pit_config; uint32_t clock_rate; if (!device_is_ready(config->clock_dev)) { LOG_ERR("Clock control device not ready"); return -ENODEV; } PIT_GetDefaultConfig(&pit_config); pit_config.enableRunInDebug = config->enableRunInDebug; PIT_Init(config->base, &pit_config); clock_rate = nxp_pit_get_frequency(dev); #if DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) config->irq_config_func(dev); for (int channel_index = 0; channel_index < config->num_channels; channel_index++) { PIT_SetTimerPeriod(config->base, channel_index, USEC_TO_COUNT(config->info.max_top_value, clock_rate)); } #else for (int channel_index = 0; channel_index < config->num_channels; channel_index++) { if (config->irq_config_func[channel_index]) { config->irq_config_func[channel_index](dev); PIT_SetTimerPeriod(config->base, channel_index, USEC_TO_COUNT(config->info.max_top_value, clock_rate)); } } #endif /* DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) */ return 0; } static const struct counter_driver_api nxp_pit_driver_api = { .start = nxp_pit_start, .stop = nxp_pit_stop, .get_value = nxp_pit_get_value, .set_top_value = nxp_pit_set_top_value, .get_pending_int = nxp_pit_get_pending_int, .get_top_value = nxp_pit_get_top_value, .get_freq = nxp_pit_get_frequency, }; /* Creates a device for a channel (needed for counter API) */ #define NXP_PIT_CHANNEL_DEV_INIT(node, pit_inst) \ DEVICE_DT_DEFINE(node, NULL, NULL, \ (void *) \ &nxp_pit_##pit_inst##_channel_datas[DT_REG_ADDR(node)], \ &nxp_pit_##pit_inst##_config, \ POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, \ &nxp_pit_driver_api); /* Creates a decleration for each pit channel */ #define NXP_PIT_CHANNEL_DECLARATIONS(node) static struct nxp_pit_channel_data \ nxp_pit_channel_data_##node; /* Initializes an element of the channel data pointer array */ #define NXP_PIT_INSERT_CHANNEL_INTO_ARRAY(node) \ [DT_REG_ADDR(node)] = \ &nxp_pit_channel_data_##node, #define NXP_PIT_INSERT_CHANNEL_DEVICE_INTO_ARRAY(node) \ [DT_REG_ADDR(node)] = DEVICE_DT_GET(node), #if DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts) #define NXP_PIT_IRQ_CONFIG_DECLARATIONS(n) \ static void nxp_pit_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), \ nxp_pit_isr, \ DEVICE_DT_INST_GET(n), 0); \ irq_enable(DT_INST_IRQN(n)); \ }; #define NXP_PIT_SETUP_IRQ_CONFIG(n) NXP_PIT_IRQ_CONFIG_DECLARATIONS(n); #define NXP_PIT_SETUP_IRQ_ARRAY(ignored) #else #define NXP_PIT_IRQ_CONFIG_DECLARATIONS(n) \ static void nxp_pit_irq_config_func_##n(const struct device *dev) \ { \ IRQ_CONNECT(DT_IRQN(n), \ DT_IRQ(n, priority), \ nxp_pit_isr, \ DEVICE_DT_GET(n), 0); \ irq_enable(DT_IRQN(n)); \ }; #define NXP_PIT_SETUP_IRQ_CONFIG(n) \ DT_INST_FOREACH_CHILD_STATUS_OKAY(n, NXP_PIT_IRQ_CONFIG_DECLARATIONS); #define NXP_PIT_INSERT_IRQ_CONFIG_INTO_ARRAY(n) \ [DT_REG_ADDR(n)] = &nxp_pit_irq_config_func_##n, #define NXP_PIT_SETUP_IRQ_ARRAY(n) \ /* Create Array of IRQs -> 1 irq func per channel */ \ void (*nxp_pit_irq_config_array[DT_INST_FOREACH_CHILD_SEP_VARGS(n, \ DT_NODE_HAS_COMPAT, (+), nxp_pit_channel)]) \ (const struct device *dev) = { \ DT_INST_FOREACH_CHILD_STATUS_OKAY(n, \ NXP_PIT_INSERT_IRQ_CONFIG_INTO_ARRAY) \ }; #endif #define COUNTER_NXP_PIT_DEVICE_INIT(n) \ \ /* Setup the IRQ either for parent irq or per channel irq */ \ NXP_PIT_SETUP_IRQ_CONFIG(n) \ \ /* Create channel declarations */ \ DT_INST_FOREACH_CHILD_STATUS_OKAY(n, \ NXP_PIT_CHANNEL_DECLARATIONS) \ \ /* Array of channel devices */ \ static struct nxp_pit_channel_data *const \ nxp_pit_##n##_channel_datas \ [DT_INST_FOREACH_CHILD_SEP_VARGS( \ n, DT_NODE_HAS_COMPAT, (+), nxp_pit_channel)] = { \ DT_INST_FOREACH_CHILD_STATUS_OKAY(n, \ NXP_PIT_INSERT_CHANNEL_INTO_ARRAY) \ }; \ \ /* forward declaration */ \ static const struct nxp_pit_config nxp_pit_##n##_config; \ \ /* Create all the channel/counter devices */ \ DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(n, \ NXP_PIT_CHANNEL_DEV_INIT, n) \ \ /* This channel device array is needed by the module device ISR */ \ const struct device *const nxp_pit_##n##_channels \ [DT_INST_FOREACH_CHILD_SEP_VARGS( \ n, DT_NODE_HAS_COMPAT, (+), nxp_pit_channel)] = { \ DT_INST_FOREACH_CHILD_STATUS_OKAY(n, \ NXP_PIT_INSERT_CHANNEL_DEVICE_INTO_ARRAY) \ }; \ \ \ NXP_PIT_SETUP_IRQ_ARRAY(n) \ \ /* This config struct is shared by all the channels and parent device */ \ static const struct nxp_pit_config nxp_pit_##n##_config = { \ .info = { \ .max_top_value = \ DT_INST_PROP(n, max_load_value), \ .channels = 0, \ }, \ .base = (PIT_Type *)DT_INST_REG_ADDR(n), \ .irq_config_func = COND_CODE_1(DT_NODE_HAS_PROP( \ DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_pit), interrupts), \ (nxp_pit_irq_config_func_##n), \ (&nxp_pit_irq_config_array[0])), \ .num_channels = DT_INST_FOREACH_CHILD_SEP_VARGS( \ n, DT_NODE_HAS_COMPAT, (+), nxp_pit_channel), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ .clock_subsys = (clock_control_subsys_t) \ DT_INST_CLOCKS_CELL(n, name), \ .data = nxp_pit_##n##_channel_datas, \ .channels = nxp_pit_##n##_channels, \ }; \ \ /* Init parent device in order to handle ISR and init. */ \ DEVICE_DT_INST_DEFINE(n, &nxp_pit_init, NULL, \ NULL, &nxp_pit_##n##_config, POST_KERNEL, \ CONFIG_COUNTER_INIT_PRIORITY, NULL); DT_INST_FOREACH_STATUS_OKAY(COUNTER_NXP_PIT_DEVICE_INIT)