/* * Copyright (c) 2019, Linaro Limited. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_imx_gpt #include #include #include LOG_MODULE_REGISTER(mcux_gpt, CONFIG_COUNTER_LOG_LEVEL); struct mcux_gpt_config { /* info must be first element */ struct counter_config_info info; GPT_Type *base; clock_name_t clock_source; }; struct mcux_gpt_data { counter_alarm_callback_t alarm_callback; counter_top_callback_t top_callback; void *alarm_user_data; void *top_user_data; }; static int mcux_gpt_start(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; GPT_StartTimer(config->base); return 0; } static int mcux_gpt_stop(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; GPT_StopTimer(config->base); return 0; } static int mcux_gpt_get_value(struct device *dev, uint32_t *ticks) { const struct mcux_gpt_config *config = dev->config_info; *ticks = GPT_GetCurrentTimerCount(config->base); return 0; } static int mcux_gpt_set_alarm(struct device *dev, uint8_t chan_id, const struct counter_alarm_cfg *alarm_cfg) { const struct mcux_gpt_config *config = dev->config_info; struct mcux_gpt_data *data = dev->driver_data; uint32_t current = GPT_GetCurrentTimerCount(config->base); uint32_t ticks = alarm_cfg->ticks; if (chan_id != 0) { LOG_ERR("Invalid channel id"); return -EINVAL; } if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) { ticks += current; } if (data->alarm_callback) { return -EBUSY; } data->alarm_callback = alarm_cfg->callback; data->alarm_user_data = alarm_cfg->user_data; GPT_SetOutputCompareValue(config->base, kGPT_OutputCompare_Channel1, ticks); GPT_EnableInterrupts(config->base, kGPT_OutputCompare1InterruptEnable); return 0; } static int mcux_gpt_cancel_alarm(struct device *dev, uint8_t chan_id) { const struct mcux_gpt_config *config = dev->config_info; struct mcux_gpt_data *data = dev->driver_data; if (chan_id != 0) { LOG_ERR("Invalid channel id"); return -EINVAL; } GPT_DisableInterrupts(config->base, kGPT_OutputCompare1InterruptEnable); data->alarm_callback = NULL; return 0; } void mcux_gpt_isr(void *p) { struct device *dev = p; const struct mcux_gpt_config *config = dev->config_info; struct mcux_gpt_data *data = dev->driver_data; uint32_t current = GPT_GetCurrentTimerCount(config->base); uint32_t status; status = GPT_GetStatusFlags(config->base, kGPT_OutputCompare1Flag | kGPT_RollOverFlag); GPT_ClearStatusFlags(config->base, status); __DSB(); if ((status & kGPT_OutputCompare1Flag) && data->alarm_callback) { GPT_DisableInterrupts(config->base, kGPT_OutputCompare1InterruptEnable); counter_alarm_callback_t alarm_cb = data->alarm_callback; data->alarm_callback = NULL; alarm_cb(dev, 0, current, data->alarm_user_data); } if ((status & kGPT_RollOverFlag) && data->top_callback) { data->top_callback(dev, data->top_user_data); } } static uint32_t mcux_gpt_get_pending_int(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; return GPT_GetStatusFlags(config->base, kGPT_OutputCompare1Flag); } static int mcux_gpt_set_top_value(struct device *dev, const struct counter_top_cfg *cfg) { const struct mcux_gpt_config *config = dev->config_info; struct mcux_gpt_data *data = dev->driver_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; } data->top_callback = cfg->callback; data->top_user_data = cfg->user_data; GPT_EnableInterrupts(config->base, kGPT_RollOverFlagInterruptEnable); return 0; } static uint32_t mcux_gpt_get_top_value(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; return config->info.max_top_value; } static uint32_t mcux_gpt_get_max_relative_alarm(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; return config->info.max_top_value; } static int mcux_gpt_init(struct device *dev) { const struct mcux_gpt_config *config = dev->config_info; gpt_config_t gptConfig; uint32_t clock_freq; /* Adjust divider to match expected freq */ clock_freq = CLOCK_GetFreq(config->clock_source); if (clock_freq % config->info.freq) { LOG_ERR("Cannot Adjust GPT freq to %u\n", config->info.freq); return -EINVAL; } GPT_GetDefaultConfig(&gptConfig); gptConfig.enableFreeRun = true; /* Do not reset on compare */ gptConfig.clockSource = kGPT_ClockSource_Periph; gptConfig.divider = clock_freq / config->info.freq; GPT_Init(config->base, &gptConfig); return 0; } static const struct counter_driver_api mcux_gpt_driver_api = { .start = mcux_gpt_start, .stop = mcux_gpt_stop, .get_value = mcux_gpt_get_value, .set_alarm = mcux_gpt_set_alarm, .cancel_alarm = mcux_gpt_cancel_alarm, .set_top_value = mcux_gpt_set_top_value, .get_pending_int = mcux_gpt_get_pending_int, .get_top_value = mcux_gpt_get_top_value, .get_max_relative_alarm = mcux_gpt_get_max_relative_alarm, }; #define GPT_DEVICE_INIT_MCUX(n) \ static struct mcux_gpt_data mcux_gpt_data_ ## n; \ \ static const struct mcux_gpt_config mcux_gpt_config_ ## n = { \ .base = (void *)DT_INST_REG_ADDR(n), \ .clock_source = kCLOCK_PerClk, \ .info = { \ .max_top_value = UINT32_MAX, \ .freq = 25000000, \ .channels = 1, \ .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ }, \ }; \ \ static int mcux_gpt_## n ##_init(struct device *dev); \ DEVICE_AND_API_INIT(mcux_gpt ## n, \ DT_INST_LABEL(n), \ mcux_gpt_## n ##_init, \ &mcux_gpt_data_ ## n, \ &mcux_gpt_config_ ## n, \ POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &mcux_gpt_driver_api); \ \ static int mcux_gpt_## n ##_init(struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), \ DT_INST_IRQ(n, priority), \ mcux_gpt_isr, DEVICE_GET(mcux_gpt ## n), 0);\ irq_enable(DT_INST_IRQN(n)); \ return mcux_gpt_init(dev); \ } \ DT_INST_FOREACH_STATUS_OKAY(GPT_DEVICE_INIT_MCUX)