blob: 6640b1aa2f2ad133356f694ddeda74e79cc60c73 [file] [log] [blame]
/*
* 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 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)