|  | /* | 
|  | * Copyright 2020,2023-2024 NXP | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nxp_pit | 
|  |  | 
|  | #include <zephyr/drivers/counter.h> | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include <fsl_pit.h> | 
|  |  | 
|  | #define LOG_MODULE_NAME counter_pit | 
|  | #include <zephyr/logging/log.h> | 
|  | 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 DEVICE_API(counter, 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) |