340 lines
11 KiB
C
340 lines
11 KiB
C
/*
|
|
* Copyright 2024 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
#define DT_DRV_COMPAT nxp_flexio_pwm
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/drivers/pwm.h>
|
|
#include <fsl_flexio.h>
|
|
#include <fsl_clock.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/drivers/misc/nxp_flexio/nxp_flexio.h>
|
|
|
|
|
|
LOG_MODULE_REGISTER(pwm_nxp_flexio, CONFIG_PWM_LOG_LEVEL);
|
|
|
|
#define FLEXIO_PWM_TIMER_CMP_MAX_VALUE (0xFFFFU)
|
|
#define FLEXIO_PWM_TIMCMP_CMP_UPPER_SHIFT (0x8U)
|
|
#define FLEXIO_MAX_PWM_CHANNELS 8
|
|
|
|
enum pwm_nxp_flexio_polarity {
|
|
FLEXIO_PWM_ACTIVE_HIGH = 0x0U,
|
|
FLEXIO_PWM_ACTIVE_LOW = 0x1U
|
|
};
|
|
|
|
enum pwm_nxp_flexio_timerinit {
|
|
/** Timer Initial output is logic one */
|
|
FLEXIO_PWM_TIMER_INIT_HIGH = 0x00U,
|
|
/** Timer Initial output is logic zero */
|
|
FLEXIO_PWM_TIMER_INIT_LOW = 0x1U
|
|
};
|
|
|
|
enum pwm_nxp_flexio_prescaler {
|
|
/* Decrement counter on Flexio clock */
|
|
FLEXIO_PWM_CLK_DIV_1 = 0U,
|
|
/* Decrement counter on Flexio clock divided by 16 */
|
|
FLEXIO_PWM_CLK_DIV_16 = 4U,
|
|
/* Decrement counter on Flexio clock divided by 256 */
|
|
FLEXIO_PWM_CLK_DIV_256 = 5U
|
|
};
|
|
|
|
enum pwm_nxp_flexio_timer_mode {
|
|
/** Timer disabled */
|
|
FLEXIO_PWM_TIMER_DISABLED = 0x00U,
|
|
/** Timer in 8 bit Pwm High mode */
|
|
FLEXIO_PWM_TIMER_PWM_HIGH = 0x02U,
|
|
/** Timer in 8 bit Pwm Low mode */
|
|
FLEXIO_PWM_TIMER_PWM_LOW = 0x06U
|
|
};
|
|
|
|
enum pwm_nxp_flexio_timer_pin {
|
|
/** Timer Pin output disabled */
|
|
FLEXIO_PWM_TIMER_PIN_OUTPUT_DISABLE = 0x00U,
|
|
/** Timer Pin Output mode */
|
|
FLEXIO_PWM_TIMER_PIN_OUTPUT_ENABLE = 0x03U
|
|
};
|
|
|
|
struct pwm_nxp_flexio_channel_config {
|
|
/** Flexio used pin index */
|
|
uint8_t pin_id;
|
|
/** Counter decrement clock prescaler */
|
|
enum pwm_nxp_flexio_prescaler prescaler;
|
|
/** Actual Prescaler divisor */
|
|
uint8_t prescaler_div;
|
|
};
|
|
|
|
struct pwm_nxp_flexio_pulse_info {
|
|
uint8_t pwm_pulse_channels;
|
|
struct pwm_nxp_flexio_channel_config *pwm_info;
|
|
};
|
|
|
|
struct pwm_nxp_flexio_config {
|
|
const struct device *flexio_dev;
|
|
FLEXIO_Type *flexio_base;
|
|
const struct pinctrl_dev_config *pincfg;
|
|
const struct device *clock_dev;
|
|
clock_control_subsys_t clock_subsys;
|
|
const struct pwm_nxp_flexio_pulse_info *pulse_info;
|
|
const struct nxp_flexio_child *child;
|
|
};
|
|
|
|
struct pwm_nxp_flexio_data {
|
|
uint32_t period_cycles[FLEXIO_MAX_PWM_CHANNELS];
|
|
uint32_t flexio_clk;
|
|
};
|
|
|
|
static int pwm_nxp_flexio_set_cycles(const struct device *dev,
|
|
uint32_t channel, uint32_t period_cycles,
|
|
uint32_t pulse_cycles, pwm_flags_t flags)
|
|
{
|
|
const struct pwm_nxp_flexio_config *config = dev->config;
|
|
struct pwm_nxp_flexio_data *data = dev->data;
|
|
flexio_timer_config_t timerConfig;
|
|
struct pwm_nxp_flexio_channel_config *pwm_info;
|
|
FLEXIO_Type *flexio_base = (FLEXIO_Type *)(config->flexio_base);
|
|
struct nxp_flexio_child *child = (struct nxp_flexio_child *)(config->child);
|
|
enum pwm_nxp_flexio_polarity polarity;
|
|
|
|
/* Check received parameters for sanity */
|
|
if (channel >= config->pulse_info->pwm_pulse_channels) {
|
|
LOG_ERR("Invalid channel");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (period_cycles == 0) {
|
|
LOG_ERR("Channel can not be set to inactive level");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (FLEXIO_PWM_TIMER_CMP_MAX_VALUE <= (uint16_t)pulse_cycles) {
|
|
LOG_ERR("Duty cycle is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (FLEXIO_PWM_TIMER_CMP_MAX_VALUE <= (uint16_t)(period_cycles - pulse_cycles)) {
|
|
LOG_ERR("low period of the cycle is out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (pulse_cycles > period_cycles) {
|
|
LOG_ERR("Duty cycle cannot be greater than 100 percent");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pwm_info = &config->pulse_info->pwm_info[channel];
|
|
|
|
if ((flags & PWM_POLARITY_INVERTED) == 0) {
|
|
polarity = FLEXIO_PWM_ACTIVE_HIGH;
|
|
} else {
|
|
polarity = FLEXIO_PWM_ACTIVE_LOW;
|
|
}
|
|
|
|
if (polarity == FLEXIO_PWM_ACTIVE_HIGH) {
|
|
timerConfig.timerOutput = kFLEXIO_TimerOutputOneNotAffectedByReset;
|
|
timerConfig.timerMode = kFLEXIO_TimerModeDual8BitPWM;
|
|
|
|
} else {
|
|
timerConfig.timerOutput = kFLEXIO_TimerOutputZeroNotAffectedByReset;
|
|
timerConfig.timerMode = kFLEXIO_TimerModeDual8BitPWMLow;
|
|
}
|
|
|
|
data->period_cycles[channel] = period_cycles;
|
|
|
|
timerConfig.timerCompare = ((uint8_t)(pulse_cycles - 1U)) |
|
|
((uint8_t)(data->period_cycles[channel] - pulse_cycles - 1U)
|
|
<< FLEXIO_PWM_TIMCMP_CMP_UPPER_SHIFT);
|
|
|
|
timerConfig.timerDecrement = pwm_info->prescaler;
|
|
timerConfig.timerStop = kFLEXIO_TimerStopBitDisabled;
|
|
timerConfig.timerEnable = kFLEXIO_TimerEnabledAlways;
|
|
timerConfig.timerDisable = kFLEXIO_TimerDisableNever;
|
|
timerConfig.timerStart = kFLEXIO_TimerStartBitDisabled;
|
|
timerConfig.timerReset = kFLEXIO_TimerResetNever;
|
|
timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal;
|
|
|
|
/* Enable the pin out for the selected timer */
|
|
timerConfig.pinConfig = FLEXIO_PWM_TIMER_PIN_OUTPUT_ENABLE;
|
|
timerConfig.pinPolarity = polarity;
|
|
|
|
/* Select the pin that the selected timer will output the signal on */
|
|
timerConfig.pinSelect = pwm_info->pin_id;
|
|
|
|
FLEXIO_SetTimerConfig(flexio_base, child->res.timer_index[channel], &timerConfig);
|
|
|
|
#if (defined(FSL_FEATURE_FLEXIO_HAS_PIN_REGISTER) && FSL_FEATURE_FLEXIO_HAS_PIN_REGISTER)
|
|
/* Disable pin override if active to support channels working in cases not 0% 100% */
|
|
if (FLEXIO_GetPinOverride(flexio_base, pwm_info->pin_id)) {
|
|
FLEXIO_ConfigPinOverride(flexio_base, pwm_info->pin_id, false);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int pwm_nxp_flexio_get_cycles_per_sec(const struct device *dev,
|
|
uint32_t channel,
|
|
uint64_t *cycles)
|
|
{
|
|
const struct pwm_nxp_flexio_config *config = dev->config;
|
|
struct pwm_nxp_flexio_data *data = dev->data;
|
|
struct pwm_nxp_flexio_channel_config *pwm_info;
|
|
|
|
/* If get_cycles is called directly after init */
|
|
if (data->period_cycles[channel] == 0) {
|
|
LOG_ERR("First set the period of this channel to a non zero value");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
pwm_info = &config->pulse_info->pwm_info[channel];
|
|
*cycles = (uint64_t)(((data->flexio_clk) * 2) /
|
|
((data->period_cycles[channel]) * (pwm_info->prescaler_div)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcux_flexio_pwm_init(const struct device *dev)
|
|
{
|
|
const struct pwm_nxp_flexio_config *config = dev->config;
|
|
struct pwm_nxp_flexio_data *data = dev->data;
|
|
flexio_timer_config_t timerConfig;
|
|
uint8_t ch_id = 0;
|
|
int err;
|
|
struct pwm_nxp_flexio_channel_config *pwm_info;
|
|
FLEXIO_Type *flexio_base = (FLEXIO_Type *)(config->flexio_base);
|
|
struct nxp_flexio_child *child = (struct nxp_flexio_child *)(config->child);
|
|
|
|
if (!device_is_ready(config->clock_dev)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
|
|
&data->flexio_clk)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = nxp_flexio_child_attach(config->flexio_dev, child);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
for (ch_id = 0; ch_id < config->pulse_info->pwm_pulse_channels; ch_id++) {
|
|
pwm_info = &config->pulse_info->pwm_info[ch_id];
|
|
|
|
/* Reset timer settings */
|
|
(void)memset(&timerConfig, 0, sizeof(timerConfig));
|
|
FLEXIO_SetTimerConfig(flexio_base, child->res.timer_index[ch_id], &timerConfig);
|
|
|
|
#if (defined(FSL_FEATURE_FLEXIO_HAS_PIN_REGISTER) && FSL_FEATURE_FLEXIO_HAS_PIN_REGISTER)
|
|
/* Reset the value driven on the corresponding pin */
|
|
FLEXIO_SetPinLevel(flexio_base, pwm_info->pin_id, false);
|
|
FLEXIO_ConfigPinOverride(flexio_base, pwm_info->pin_id, false);
|
|
#endif
|
|
/* Timer output is logic one and is not affected by timer reset */
|
|
timerConfig.timerOutput = kFLEXIO_TimerOutputOneNotAffectedByReset;
|
|
/* Set the timer mode to dual 8-bit counter PWM high */
|
|
timerConfig.timerMode = kFLEXIO_TimerModeDual8BitPWM;
|
|
|
|
/* Timer scaling factor w.r.t Flexio Clock */
|
|
timerConfig.timerDecrement = pwm_info->prescaler;
|
|
|
|
/* Program the PWM pulse */
|
|
timerConfig.timerCompare = 0;
|
|
|
|
/* Configure Timer CFG and CTL bits to support PWM mode */
|
|
timerConfig.timerStop = kFLEXIO_TimerStopBitDisabled;
|
|
timerConfig.timerEnable = kFLEXIO_TimerEnabledAlways;
|
|
timerConfig.timerDisable = kFLEXIO_TimerDisableNever;
|
|
timerConfig.timerStart = kFLEXIO_TimerStartBitDisabled;
|
|
timerConfig.timerReset = kFLEXIO_TimerResetNever;
|
|
timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal;
|
|
|
|
/* Enable the pin out and set a default polarity for the selected timer */
|
|
timerConfig.pinConfig = FLEXIO_PWM_TIMER_PIN_OUTPUT_ENABLE;
|
|
timerConfig.pinPolarity = kFLEXIO_PinActiveHigh;
|
|
|
|
/* Select the pin that the selected timer will output the signal on */
|
|
timerConfig.pinSelect = pwm_info->pin_id;
|
|
|
|
FLEXIO_SetTimerConfig(flexio_base, child->res.timer_index[ch_id], &timerConfig);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pwm_driver_api pwm_nxp_flexio_driver_api = {
|
|
.set_cycles = pwm_nxp_flexio_set_cycles,
|
|
.get_cycles_per_sec = pwm_nxp_flexio_get_cycles_per_sec,
|
|
};
|
|
|
|
#define _FLEXIO_PWM_PULSE_GEN_CONFIG(n) \
|
|
{ \
|
|
.pin_id = DT_PROP(n, pin_id), \
|
|
.prescaler = _CONCAT(FLEXIO_PWM_CLK_DIV_, DT_PROP(n, prescaler)), \
|
|
.prescaler_div = DT_PROP(n, prescaler), \
|
|
},
|
|
|
|
#define FLEXIO_PWM_PULSE_GEN_CONFIG(n) \
|
|
static struct pwm_nxp_flexio_channel_config flexio_pwm_##n##_init[] = { \
|
|
DT_INST_FOREACH_CHILD_STATUS_OKAY(n, _FLEXIO_PWM_PULSE_GEN_CONFIG) \
|
|
}; \
|
|
static const struct pwm_nxp_flexio_pulse_info flexio_pwm_##n##_info = { \
|
|
.pwm_pulse_channels = ARRAY_SIZE(flexio_pwm_##n##_init), \
|
|
.pwm_info = flexio_pwm_##n##_init, \
|
|
};
|
|
|
|
#define FLEXIO_PWM_TIMER_INDEX_INIT(n) \
|
|
static uint8_t flexio_pwm_##n##_timer_index[ARRAY_SIZE(flexio_pwm_##n##_init)];
|
|
|
|
#define FLEXIO_PWM_CHILD_CONFIG(n) \
|
|
static const struct nxp_flexio_child mcux_flexio_pwm_child_##n = { \
|
|
.isr = NULL, \
|
|
.user_data = NULL, \
|
|
.res = { \
|
|
.shifter_index = NULL, \
|
|
.shifter_count = 0, \
|
|
.timer_index = (uint8_t *)flexio_pwm_##n##_timer_index, \
|
|
.timer_count = ARRAY_SIZE(flexio_pwm_##n##_init) \
|
|
} \
|
|
};
|
|
|
|
#define FLEXIO_PWM_PULSE_GEN_GET_CONFIG(n) \
|
|
.pulse_info = &flexio_pwm_##n##_info,
|
|
|
|
|
|
#define PWM_NXP_FLEXIO_PWM_INIT(n) \
|
|
PINCTRL_DT_INST_DEFINE(n); \
|
|
FLEXIO_PWM_PULSE_GEN_CONFIG(n) \
|
|
FLEXIO_PWM_TIMER_INDEX_INIT(n) \
|
|
FLEXIO_PWM_CHILD_CONFIG(n) \
|
|
static const struct pwm_nxp_flexio_config pwm_nxp_flexio_config_##n = { \
|
|
.flexio_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \
|
|
.flexio_base = (FLEXIO_Type *)DT_REG_ADDR(DT_INST_PARENT(n)), \
|
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
|
|
.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(n))), \
|
|
.clock_subsys = (clock_control_subsys_t)DT_CLOCKS_CELL(DT_INST_PARENT(n), name),\
|
|
.child = &mcux_flexio_pwm_child_##n, \
|
|
FLEXIO_PWM_PULSE_GEN_GET_CONFIG(n) \
|
|
}; \
|
|
\
|
|
static struct pwm_nxp_flexio_data pwm_nxp_flexio_data_##n; \
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
&mcux_flexio_pwm_init, \
|
|
NULL, \
|
|
&pwm_nxp_flexio_data_##n, \
|
|
&pwm_nxp_flexio_config_##n, \
|
|
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \
|
|
&pwm_nxp_flexio_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(PWM_NXP_FLEXIO_PWM_INIT)
|