Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

990 lines
32 KiB

/*
* Copyright 2023-2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/irq.h>
#include <Emios_Mcl_Ip.h>
#include <Emios_Pwm_Ip.h>
#ifdef CONFIG_PWM_CAPTURE
#include <Emios_Icu_Ip.h>
#endif
#define LOG_MODULE_NAME nxp_s32_emios_pwm
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_PWM_LOG_LEVEL);
#define DT_DRV_COMPAT nxp_s32_emios_pwm
#if !defined(EMIOS_PWM_IP_NUM_OF_CHANNELS_USED)
#define EMIOS_PWM_IP_NUM_OF_CHANNELS_USED EMIOS_PWM_IP_NUM_OF_CHANNELS_USED_U8
#endif
/*
* Need to fill to this array at runtime, cannot do at build time like
* the HAL over configuration tool due to limitation of the integration
*/
#if EMIOS_PWM_IP_USED
#if defined(CONFIG_SOC_SERIES_S32ZE)
#define eMios_Icu_Ip_IndexInChState eMios_Icu_Ip_u8IndexInChState
#endif
extern uint8 eMios_Pwm_Ip_IndexInChState[EMIOS_PWM_IP_INSTANCE_COUNT][EMIOS_PWM_IP_CHANNEL_COUNT];
#define EMIOS_PWM_MASTER_CHANNEL(channel, bus) \
((bus == EMIOS_PWM_IP_BUS_A) ? 23 : \
((bus == EMIOS_PWM_IP_BUS_F) ? 22 : \
((bus == EMIOS_PWM_IP_BUS_BCDE) ? ((channel >> 3) * 8) : channel)))
#endif
#ifdef CONFIG_PWM_CAPTURE
extern uint8 eMios_Icu_Ip_IndexInChState[EMIOS_ICU_IP_INSTANCE_COUNT][EMIOS_ICU_IP_NUM_OF_CHANNELS];
#define EMIOS_ICU_MASTER_CHANNEL(channel, bus) \
((bus == EMIOS_ICU_BUS_A) ? 23 : \
(IF_DISABLED(CONFIG_SOC_SERIES_S32ZE, (bus == EMIOS_ICU_BUS_F) ? 22 :) \
((bus == EMIOS_ICU_BUS_DIVERSE) ? ((channel >> 3) * 8) : channel)))
/* We need maximum three edges for measure both period and cycle */
#define MAX_NUM_EDGE 3
#endif
struct pwm_nxp_s32_channel_data {
bool inverted;
uint8_t master_channel;
#if EMIOS_PWM_IP_USED
uint32_t curr_period;
#endif
#ifdef CONFIG_PWM_CAPTURE
bool continuous;
bool pulse_capture;
bool period_capture;
void *user_data;
pwm_capture_callback_handler_t callback;
eMios_Icu_ValueType edge_buff[MAX_NUM_EDGE];
#endif
};
struct pwm_nxp_s32_data {
uint32_t emios_clk;
#if EMIOS_PWM_IP_USED
uint8_t start_pwm_ch;
#endif
struct pwm_nxp_s32_channel_data ch_data[eMIOS_CH_UC_UC_COUNT];
};
#if EMIOS_PWM_IP_USED
struct pwm_nxp_s32_pulse_info {
uint8_t pwm_pulse_channels;
Emios_Pwm_Ip_ChannelConfigType *pwm_info;
};
#endif
struct pwm_nxp_s32_config {
eMIOS_Type *base;
uint8_t instance;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
const struct pinctrl_dev_config *pincfg;
#if EMIOS_PWM_IP_USED
struct pwm_nxp_s32_pulse_info *pulse_info;
#endif
#ifdef CONFIG_PWM_CAPTURE
eMios_Icu_Ip_ConfigType * icu_cfg;
#endif
};
#if EMIOS_PWM_IP_USED
#ifdef EMIOS_PWM_IP_MODE_OPWFMB_USED
static int pwm_nxp_s32_set_cycles_opwfmb(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
struct pwm_nxp_s32_channel_data *ch_data = &data->ch_data[channel];
Emios_Pwm_Ip_PolarityType polarity;
unsigned int key;
if ((ch_data->inverted != flags) || (!ch_data->curr_period)) {
/* If PWM flag is changed or this is the first time PWM channel is configured */
polarity = (flags & PWM_POLARITY_MASK) ? EMIOS_PWM_IP_ACTIVE_LOW :
EMIOS_PWM_IP_ACTIVE_HIGH;
config->base->CH.UC[channel].C &= ~(eMIOS_C_MODE_MASK |
eMIOS_C_EDPOL_MASK);
config->base->CH.UC[channel].A = pulse_cycles;
config->base->CH.UC[channel].B = period_cycles;
/*
* When entering OPWFMB mode, Output = Cn[EDPOL]. Unless 100% pulse cycle is
* expected, Cn[EDPOL] is set to complement value (i.e 0 if active high and
* 1 if active low).
*/
if (pulse_cycles) {
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(polarity);
} else {
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(!polarity);
}
key = irq_lock();
config->base->CH.UC[channel].C |= eMIOS_C_MODE(EMIOS_PWM_IP_MODE_OPWFMB_FLAG);
if (pulse_cycles) {
/* Restore expected value for Cn[EDPOL] */
config->base->CH.UC[channel].C = (config->base->CH.UC[channel].C &
~(eMIOS_C_EDPOL_MASK)) |
eMIOS_C_EDPOL(!polarity);
}
irq_unlock(key);
ch_data->curr_period = period_cycles;
ch_data->inverted = flags;
} else {
key = irq_lock();
config->base->CH.UC[channel].A = pulse_cycles;
config->base->CH.UC[channel].B = period_cycles;
irq_unlock(key);
}
return 0;
}
#endif
#if defined(EMIOS_PWM_IP_MODE_OPWMCB_USED)
static int pwm_nxp_s32_set_cycles_opwmcb(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
Emios_Pwm_Ip_ChannelConfigType *pwm_info,
pwm_flags_t flags)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
struct pwm_nxp_s32_channel_data *ch_data = &data->ch_data[channel];
struct pwm_nxp_s32_channel_data *master_ch_data = &data->ch_data[ch_data->master_channel];
unsigned int key;
Emios_Pwm_Ip_PolarityType polarity;
/* Convert to written value into eMIOS register */
if (pulse_cycles == 0) {
pulse_cycles = EMIOS_PWM_IP_MAX_CNT_VAL;
} else if (pulse_cycles == period_cycles) {
pulse_cycles = 1;
} else {
pulse_cycles = period_cycles - (pulse_cycles >> 1);
}
if ((ch_data->inverted != flags) || (!ch_data->curr_period)) {
/* If PWM flag is changed or this is the first time PWM channel is configured */
polarity = (flags & PWM_POLARITY_MASK) ? EMIOS_PWM_IP_ACTIVE_LOW :
EMIOS_PWM_IP_ACTIVE_HIGH;
if (master_ch_data->curr_period != period_cycles) {
/*
* Move timebase channel to GPIO mode --> configure period --> MCB mode
* Period can be shared between multiple PWM channels, only configure
* when needed.
*/
config->base->CH.UC[ch_data->master_channel].C &= ~eMIOS_C_MODE_MASK;
config->base->CH.UC[ch_data->master_channel].A = period_cycles;
}
config->base->CH.UC[channel].C &= ~(eMIOS_C_MODE_MASK |
eMIOS_C_EDPOL_MASK |
eMIOS_C_BSL_MASK);
/*
* When entering OPWMCB mode, Output = !Cn[EDPOL]. If 100% pulse cycle is expected
* Cn[EDPOL] is set to complement value (i.e 0 if active high and 1 if active low).
*/
if (pulse_cycles == 1) {
/* 100% pulse cycle */
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(!polarity);
} else {
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(polarity);
}
config->base->CH.UC[channel].A = pulse_cycles;
config->base->CH.UC[channel].B = pwm_info->DeadTime;
key = irq_lock();
config->base->CH.UC[channel].C |= (eMIOS_C_MODE(pwm_info->Mode) |
eMIOS_C_BSL(pwm_info->Timebase));
if (pulse_cycles == 1) {
config->base->CH.UC[channel].C = (config->base->CH.UC[channel].C &
~eMIOS_C_EDPOL_MASK) |
eMIOS_C_EDPOL(polarity);
}
irq_unlock(key);
if (master_ch_data->curr_period != period_cycles) {
config->base->CH.UC[ch_data->master_channel].C |=
eMIOS_C_MODE(EMIOS_IP_MCB_UP_DOWN_COUNTER);
master_ch_data->curr_period = period_cycles;
}
ch_data->inverted = flags;
ch_data->curr_period = period_cycles;
} else if (master_ch_data->curr_period != period_cycles) {
key = irq_lock();
config->base->CH.UC[ch_data->master_channel].A = period_cycles;
config->base->CH.UC[channel].A = pulse_cycles;
irq_unlock(key);
master_ch_data->curr_period = period_cycles;
} else {
config->base->CH.UC[channel].A = pulse_cycles;
}
return 0;
}
#endif
#if defined(EMIOS_PWM_IP_MODE_OPWMB_USED)
static int pwm_nxp_s32_set_cycles_opwmb(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
Emios_Pwm_Ip_ChannelConfigType *pwm_info,
pwm_flags_t flags)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
struct pwm_nxp_s32_channel_data *ch_data = &data->ch_data[channel];
struct pwm_nxp_s32_channel_data *master_ch_data = &data->ch_data[ch_data->master_channel];
Emios_Pwm_Ip_PolarityType polarity;
unsigned int key;
if ((ch_data->inverted != flags) || (!ch_data->curr_period)) {
/* If PWM flag is changed or this is the first time PWM channel is configured */
polarity = (flags & PWM_POLARITY_MASK) ? EMIOS_PWM_IP_ACTIVE_LOW :
EMIOS_PWM_IP_ACTIVE_HIGH;
if (master_ch_data->curr_period != period_cycles) {
/*
* Move timebase channel to GPIO mode --> configure period --> MCB mode
* Period can be shared between multiple PWM channels, only configure
* when needed.
*/
config->base->CH.UC[ch_data->master_channel].C &= ~eMIOS_C_MODE_MASK;
config->base->CH.UC[ch_data->master_channel].A = period_cycles;
}
config->base->CH.UC[channel].C &= ~(eMIOS_C_MODE_MASK |
eMIOS_C_EDPOL_MASK | eMIOS_C_BSL_MASK);
config->base->CH.UC[channel].A = pwm_info->PhaseShift;
config->base->CH.UC[channel].B = pulse_cycles;
/*
* When entering OPWMB mode, Output = Cn[EDPOL]. Unless 100% pulse cycle is
* expected, Cn[EDPOL] is set to complement value (i.e 0 if active high and
* 1 if active low).
*/
if (pulse_cycles == period_cycles) {
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(polarity);
} else {
config->base->CH.UC[channel].C |= eMIOS_C_EDPOL(!polarity);
}
key = irq_lock();
config->base->CH.UC[channel].C |= eMIOS_C_MODE(pwm_info->Mode) |
eMIOS_C_BSL(pwm_info->Timebase);
if (pulse_cycles != period_cycles) {
config->base->CH.UC[channel].C = (config->base->CH.UC[channel].C &
~(eMIOS_C_EDPOL_MASK)) |
eMIOS_C_EDPOL(polarity);
}
if (!pwm_info->PhaseShift) {
/*
* If Phase Shift == 0, Force Match A to ensure PWM pulse can be
* generated immediately. Otherwise, it will need to wait until
* next period boundary.
*/
config->base->CH.UC[channel].C |= eMIOS_C_FORCMA(1);
}
irq_unlock(key);
if (master_ch_data->curr_period != period_cycles) {
config->base->CH.UC[ch_data->master_channel].C |=
eMIOS_C_MODE(EMIOS_IP_MCB_UP_COUNTER);
master_ch_data->curr_period = period_cycles;
}
ch_data->inverted = flags;
ch_data->curr_period = period_cycles;
} else if (master_ch_data->curr_period != period_cycles) {
key = irq_lock();
config->base->CH.UC[ch_data->master_channel].A = period_cycles;
config->base->CH.UC[channel].B = pulse_cycles;
irq_unlock(key);
master_ch_data->curr_period = period_cycles;
} else {
config->base->CH.UC[channel].B = pulse_cycles;
}
return 0;
}
#endif
static int pwm_nxp_s32_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_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
Emios_Pwm_Ip_ChannelConfigType *pwm_info;
uint8_t logic_ch;
if (channel >= EMIOS_PWM_IP_CHANNEL_COUNT) {
LOG_ERR("invalid channel %d", channel);
return -EINVAL;
}
if (eMios_Pwm_Ip_IndexInChState[config->instance][channel] >=
EMIOS_PWM_IP_NUM_OF_CHANNELS_USED) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
logic_ch = eMios_Pwm_Ip_IndexInChState[config->instance][channel] - data->start_pwm_ch;
pwm_info = &config->pulse_info->pwm_info[logic_ch];
switch (pwm_info->Mode) {
#ifdef EMIOS_PWM_IP_MODE_OPWFMB_USED
case EMIOS_PWM_IP_MODE_OPWFMB_FLAG:
if ((period_cycles > EMIOS_PWM_IP_MAX_CNT_VAL) ||
(period_cycles <= EMIOS_PWM_IP_MIN_CNT_VAL)) {
LOG_ERR("Period cycles is out of range");
return -EINVAL;
}
return pwm_nxp_s32_set_cycles_opwfmb(dev, channel, period_cycles,
pulse_cycles, flags);
#endif
#ifdef EMIOS_PWM_IP_MODE_OPWMCB_USED
case EMIOS_PWM_IP_MODE_OPWMCB_TRAIL_EDGE_FLAG:
case EMIOS_PWM_IP_MODE_OPWMCB_LEAD_EDGE_FLAG:
period_cycles = (period_cycles + 2) / 2;
if ((period_cycles > EMIOS_PWM_IP_MAX_CNT_VAL) ||
(period_cycles <= EMIOS_PWM_IP_MIN_CNT_VAL)) {
LOG_ERR("Period cycles is out of range");
return -EINVAL;
}
return pwm_nxp_s32_set_cycles_opwmcb(dev, channel, period_cycles,
pulse_cycles, pwm_info, flags);
#endif
#if defined(EMIOS_PWM_IP_MODE_OPWMB_USED)
case EMIOS_PWM_IP_MODE_OPWMB_FLAG:
if ((period_cycles > EMIOS_PWM_IP_MAX_CNT_VAL) ||
(period_cycles <= EMIOS_PWM_IP_MIN_CNT_VAL)) {
LOG_ERR("Period cycles is out of range");
return -EINVAL;
}
pulse_cycles += pwm_info->PhaseShift;
if (pulse_cycles > period_cycles) {
LOG_ERR("Pulse cycles is out of range");
return -EINVAL;
}
return pwm_nxp_s32_set_cycles_opwmb(dev, channel, period_cycles,
pulse_cycles, pwm_info, flags);
#endif
default:
/* Never reach here */
return 0;
}
}
#endif
#ifdef CONFIG_PWM_CAPTURE
static ALWAYS_INLINE eMios_Icu_ValueType pwm_nxp_s32_capture_calc(eMios_Icu_ValueType first_cnt,
eMios_Icu_ValueType second_cnt)
{
if (first_cnt < second_cnt) {
return second_cnt - first_cnt;
}
/* Counter top value is always 0xFFFF */
return EMIOS_ICU_IP_COUNTER_MASK - first_cnt + second_cnt;
}
static ALWAYS_INLINE eMios_Icu_ValueType pwm_nxp_s32_pulse_calc(bool inverted,
eMios_Icu_ValueType *edge_buff,
eMios_Icu_Ip_LevelType input_state)
{
eMios_Icu_ValueType first_cnt, second_cnt;
if (input_state ^ inverted) {
/* 3 edges captured is raise, fall, raise */
first_cnt = edge_buff[0];
second_cnt = edge_buff[1];
} else {
/* 3 edges captured is fall, raise, fall */
first_cnt = edge_buff[1];
second_cnt = edge_buff[2];
}
return pwm_nxp_s32_capture_calc(first_cnt, second_cnt);
}
static int pwm_nxp_s32_capture_configure(const struct device *dev,
uint32_t channel,
pwm_flags_t flags,
pwm_capture_callback_handler_t cb,
void *user_data)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
if (channel >= EMIOS_ICU_IP_NUM_OF_CHANNELS) {
LOG_ERR("Invalid channel %d", channel);
return -EINVAL;
}
if (!flags) {
LOG_ERR("Invalid PWM capture flag");
return -EINVAL;
}
if (eMios_Icu_Ip_IndexInChState[config->instance][channel] >=
EMIOS_ICU_IP_NUM_OF_CHANNELS_USED) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
/* If interrupt is enabled --> channel is on-going */
if (config->base->CH.UC[channel].C & eMIOS_C_FEN_MASK) {
LOG_ERR("Channel %d is busy", channel);
return -EBUSY;
}
data->ch_data[channel].continuous = (flags & PWM_CAPTURE_MODE_MASK);
data->ch_data[channel].inverted = (flags & PWM_POLARITY_MASK);
data->ch_data[channel].pulse_capture = (flags & PWM_CAPTURE_TYPE_PULSE);
data->ch_data[channel].period_capture = (flags & PWM_CAPTURE_TYPE_PERIOD);
data->ch_data[channel].callback = cb;
data->ch_data[channel].user_data = user_data;
return 0;
}
static int pwm_nxp_s32_capture_enable(const struct device *dev, uint32_t channel)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
eMios_Icu_Ip_EdgeType edge;
uint8_t num_edge;
if (channel >= EMIOS_ICU_IP_NUM_OF_CHANNELS) {
LOG_ERR("Invalid channel %d", channel);
return -EINVAL;
}
if (eMios_Icu_Ip_IndexInChState[config->instance][channel] >=
EMIOS_ICU_IP_NUM_OF_CHANNELS_USED) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
if (!data->ch_data[channel].callback) {
LOG_ERR("Callback is not configured");
return -EINVAL;
}
/* If interrupt is enabled --> channel is on-going */
if (config->base->CH.UC[channel].C & eMIOS_C_FEN_MASK) {
LOG_ERR("Channel %d is busy", channel);
return -EBUSY;
}
/* If just measure period, we just need 2 edges */
if (data->ch_data[channel].period_capture && !data->ch_data[channel].pulse_capture) {
num_edge = 2U;
edge = EMIOS_ICU_RISING_EDGE;
} else {
num_edge = 3U;
edge = EMIOS_ICU_BOTH_EDGES;
}
Emios_Icu_Ip_SetActivation(config->instance, channel, edge);
Emios_Icu_Ip_EnableNotification(config->instance, channel);
Emios_Icu_Ip_StartTimestamp(config->instance, channel,
data->ch_data[channel].edge_buff,
MAX_NUM_EDGE, num_edge);
return 0;
}
static int pwm_nxp_s32_capture_disable(const struct device *dev, uint32_t channel)
{
const struct pwm_nxp_s32_config *config = dev->config;
if (channel >= EMIOS_ICU_IP_NUM_OF_CHANNELS) {
LOG_ERR("Invalid channel %d", channel);
return -EINVAL;
}
if (eMios_Icu_Ip_IndexInChState[config->instance][channel] >=
EMIOS_ICU_IP_NUM_OF_CHANNELS_USED) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
Emios_Icu_Ip_StopTimestamp(config->instance, channel);
return 0;
}
#endif
static int pwm_nxp_s32_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
uint8_t internal_prescaler, global_prescaler, master_channel;
master_channel = data->ch_data[channel].master_channel;
internal_prescaler = (config->base->CH.UC[master_channel].C2 & eMIOS_C2_UCEXTPRE_MASK) >>
eMIOS_C2_UCEXTPRE_SHIFT;
/* Clock source for internal prescaler is from either eMIOS or eMIOS / global prescaler */
if (config->base->CH.UC[master_channel].C2 & eMIOS_C2_UCPRECLK_MASK) {
*cycles = data->emios_clk / (internal_prescaler + 1);
} else {
global_prescaler = (config->base->MCR & eMIOS_MCR_GPRE_MASK) >>
eMIOS_MCR_GPRE_SHIFT;
*cycles = data->emios_clk / ((internal_prescaler + 1) * (global_prescaler + 1));
}
return 0;
}
#if EMIOS_PWM_IP_USED
static int pwm_nxp_s32_pulse_gen_init(const struct device *dev)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
struct pwm_nxp_s32_channel_data *ch_data;
Emios_Pwm_Ip_ChannelConfigType pwm_info;
uint8_t ch_id;
static uint8_t logic_ch;
data->start_pwm_ch = logic_ch;
for (ch_id = 0; ch_id < config->pulse_info->pwm_pulse_channels; ch_id++) {
memcpy(&pwm_info, &config->pulse_info->pwm_info[ch_id],
sizeof(Emios_Pwm_Ip_ChannelConfigType));
/*
* Let eMIOS channel is in GPIO mode, the actual PWM mode will be
* configured at the first time pwm_set* is called.
*/
pwm_info.Mode = EMIOS_PWM_IP_MODE_GPO;
eMios_Pwm_Ip_IndexInChState[config->instance][pwm_info.ChannelId] = logic_ch++;
Emios_Pwm_Ip_InitChannel(config->instance, &pwm_info);
ch_data = &data->ch_data[pwm_info.ChannelId];
ch_data->master_channel = EMIOS_PWM_MASTER_CHANNEL(pwm_info.ChannelId,
pwm_info.Timebase);
}
return 0;
}
#endif
#ifdef CONFIG_PWM_CAPTURE
static int pwm_nxp_s32_pulse_capture_init(const struct device *dev)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
struct pwm_nxp_s32_channel_data *ch_data;
const eMios_Icu_Ip_ChannelConfigType *icu_info;
uint8_t ch_id;
static uint8_t logic_ch;
for (ch_id = 0; ch_id < config->icu_cfg->nNumChannels; ch_id++) {
icu_info = &(*config->icu_cfg->pChannelsConfig)[ch_id];
ch_data = &data->ch_data[icu_info->hwChannel];
eMios_Icu_Ip_IndexInChState[config->instance][icu_info->hwChannel] = logic_ch++;
ch_data->master_channel = EMIOS_ICU_MASTER_CHANNEL(icu_info->hwChannel,
icu_info->CntBus);
}
if (Emios_Icu_Ip_Init(config->instance, config->icu_cfg)) {
return -EINVAL;
}
return 0;
}
static void pwm_nxp_s32_capture_callback(const struct device *dev, uint32_t channel)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
uint32_t period = 0, pulse = 0;
if (data->ch_data[channel].period_capture && !data->ch_data[channel].pulse_capture) {
period = pwm_nxp_s32_capture_calc(data->ch_data[channel].edge_buff[0],
data->ch_data[channel].edge_buff[1]);
} else {
if (data->ch_data[channel].pulse_capture) {
pulse = pwm_nxp_s32_pulse_calc(data->ch_data[channel].inverted,
data->ch_data[channel].edge_buff,
Emios_Icu_Ip_GetInputLevel(config->instance,
channel));
}
if (data->ch_data[channel].period_capture) {
period = pwm_nxp_s32_capture_calc(data->ch_data[channel].edge_buff[0],
data->ch_data[channel].edge_buff[2]);
}
}
if (!data->ch_data[channel].continuous) {
Emios_Icu_Ip_StopTimestamp(config->instance, channel);
}
data->ch_data[channel].callback(dev, channel, period, pulse, 0,
data->ch_data[channel].user_data);
}
#endif
static int pwm_nxp_s32_init(const struct device *dev)
{
const struct pwm_nxp_s32_config *config = dev->config;
struct pwm_nxp_s32_data *data = dev->data;
int err = 0;
if (!device_is_ready(config->clock_dev)) {
return -ENODEV;
}
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
&data->emios_clk)) {
return -EINVAL;
}
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (err < 0) {
return err;
}
#if EMIOS_PWM_IP_USED
err = pwm_nxp_s32_pulse_gen_init(dev);
if (err < 0) {
return err;
}
#endif
#ifdef CONFIG_PWM_CAPTURE
err = pwm_nxp_s32_pulse_capture_init(dev);
if (err < 0) {
return err;
}
#endif
return err;
}
static DEVICE_API(pwm, pwm_nxp_s32_driver_api) = {
.set_cycles = pwm_nxp_s32_set_cycles,
.get_cycles_per_sec = pwm_nxp_s32_get_cycles_per_sec,
#ifdef CONFIG_PWM_CAPTURE
.configure_capture = pwm_nxp_s32_capture_configure,
.enable_capture = pwm_nxp_s32_capture_enable,
.disable_capture = pwm_nxp_s32_capture_disable,
#endif
};
#define EMIOS_PWM_IS_MODE_OPWFMB(node_id) \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, opwfmb)
#define EMIOS_PWM_IS_MODE_OPWMCB(node_id) \
UTIL_OR(DT_ENUM_HAS_VALUE(node_id, pwm_mode, opwmcb_trail_edge), \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, opwmcb_lead_edge)) \
#define EMIOS_PWM_IS_MODE_OPWMB(node_id) \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, opwmb)
#define EMIOS_PWM_IS_MODE_SAIC(node_id) \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, saic)
#define EMIOS_PWM_IS_CAPTURE_MODE(node_id) \
EMIOS_PWM_IS_MODE_SAIC(node_id)
#define EMIOS_PWM_LOG(node_id, msg) \
DT_NODE_PATH(node_id) ": " DT_PROP(node_id, pwm_mode) ": " msg \
#define EMIOS_PWM_VERIFY_MASTER_BUS(node_id) \
BUILD_ASSERT(BIT(DT_PROP(node_id, channel)) & \
DT_PROP_BY_PHANDLE(node_id, master_bus, channel_mask), \
EMIOS_PWM_LOG(node_id, "invalid master bus"));
#define EMIOS_PWM_PULSE_GEN_COMMON_VERIFY(node_id) \
BUILD_ASSERT(DT_NODE_HAS_PROP(node_id, input_filter), \
EMIOS_PWM_LOG(node_id, "input-filter is not used"));
#define EMIOS_PWM_VERIFY_MODE_OPWFMB(node_id) \
EMIOS_PWM_PULSE_GEN_COMMON_VERIFY(node_id) \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, master_bus), \
EMIOS_PWM_LOG(node_id, "master-bus must not be configured")); \
BUILD_ASSERT(DT_PROP(node_id, dead_time) == 0, \
EMIOS_PWM_LOG(node_id, "dead-time is not used")); \
BUILD_ASSERT(DT_PROP(node_id, phase_shift) == 0, \
EMIOS_PWM_LOG(node_id, "phase-shift is not used"));
#define EMIOS_PWM_VERIFY_MODE_OPWMCB(node_id) \
EMIOS_PWM_PULSE_GEN_COMMON_VERIFY(node_id) \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(DT_PHANDLE(node_id, master_bus), mode, \
MCB_UP_DOWN_COUNTER), \
EMIOS_PWM_LOG(node_id, "master-bus must be configured in MCB up-down")); \
BUILD_ASSERT(DT_PROP(node_id, phase_shift) == 0, \
EMIOS_PWM_LOG(node_id, "phase-shift is not used")); \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, prescaler), \
EMIOS_PWM_LOG(node_id, "prescaler is not used," \
" driver takes the value from master bus")); \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(node_id, prescaler_src, prescaled_clock), \
EMIOS_PWM_LOG(node_id, "prescaler-src is not used," \
" always use prescalered source")); \
#define EMIOS_PWM_VERIFY_MODE_OPWMB(node_id) \
EMIOS_PWM_PULSE_GEN_COMMON_VERIFY(node_id) \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(DT_PHANDLE(node_id, master_bus), mode, mcb_up_counter), \
EMIOS_PWM_LOG(node_id, "master-bus must be configured in MCB up")); \
BUILD_ASSERT(DT_PROP(node_id, dead_time) == 0, \
EMIOS_PWM_LOG(node_id, "dead-time is not used")); \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, prescaler), \
EMIOS_PWM_LOG(node_id, "prescaler is not used")); \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(node_id, prescaler_src, prescaled_clock), \
EMIOS_PWM_LOG(node_id, "prescaler-src is not used," \
" always use prescalered source")); \
#define EMIOS_PWM_VERIFY_MODE_SAIC(node_id) \
IF_ENABLED(DT_NODE_HAS_PROP(node_id, master_bus), \
(BUILD_ASSERT( \
DT_ENUM_HAS_VALUE(DT_PHANDLE(node_id, master_bus), mode, mcb_up_counter), \
EMIOS_PWM_LOG(node_id, "master-bus must be configured in MCB up"));)) \
IF_ENABLED(UTIL_NOT(DT_NODE_HAS_PROP(node_id, master_bus)), \
(BUILD_ASSERT( \
BIT(DT_PROP(node_id, channel)) & DT_PROP(DT_GPARENT(node_id), internal_cnt),\
EMIOS_PWM_LOG(node_id, "master-bus must be chosen," \
" channel do not have has counter")))); \
IF_ENABLED(UTIL_NOT(DT_NODE_HAS_PROP(node_id, master_bus)), \
(BUILD_ASSERT(DT_NODE_HAS_PROP(node_id, prescaler), \
EMIOS_PWM_LOG(node_id, "if use internal counter, prescaler must" \
" be configured")))); \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(node_id, prescaler_src, prescaled_clock), \
EMIOS_PWM_LOG(node_id, "prescaler-src is not used," \
" always use prescalered source"));
#define _EMIOS_PWM_VERIFY_CONFIG(node_id) \
IF_ENABLED(DT_NODE_HAS_PROP(node_id, master_bus), \
(EMIOS_PWM_VERIFY_MASTER_BUS(node_id))) \
IF_ENABLED(EMIOS_PWM_IS_MODE_OPWFMB(node_id), \
(EMIOS_PWM_VERIFY_MODE_OPWFMB(node_id))) \
IF_ENABLED(EMIOS_PWM_IS_MODE_OPWMCB(node_id), \
(EMIOS_PWM_VERIFY_MODE_OPWMCB(node_id))) \
IF_ENABLED(EMIOS_PWM_IS_MODE_OPWMB(node_id), \
(EMIOS_PWM_VERIFY_MODE_OPWMB(node_id))) \
IF_ENABLED(EMIOS_PWM_IS_MODE_SAIC(node_id), \
(EMIOS_PWM_VERIFY_MODE_SAIC(node_id)))
#if EMIOS_PWM_IP_USED
/* Macros used to glue devicetree with RTD's definition */
#define EMIOS_PWM_BUS_A EMIOS_PWM_IP_BUS_A
#define EMIOS_PWM_BUS_B EMIOS_PWM_IP_BUS_BCDE
#define EMIOS_PWM_BUS_C EMIOS_PWM_IP_BUS_BCDE
#define EMIOS_PWM_BUS_D EMIOS_PWM_IP_BUS_BCDE
#define EMIOS_PWM_BUS_E EMIOS_PWM_IP_BUS_BCDE
#define EMIOS_PWM_BUS_F EMIOS_PWM_IP_BUS_F
#define EMIOS_PWM_BUS(mode) DT_CAT(EMIOS_PWM_, mode)
#define EMIOS_PWM_MODE(mode) DT_CAT3(EMIOS_PWM_IP_MODE_, mode, _FLAG)
#define EMIOS_PWM_PS_SRC(mode) DT_CAT(EMIOS_PWM_IP_PS_SRC_, mode)
/* Keep minimal configuration used at driver initialization, no PWM signal is produced */
#define _EMIOS_PWM_PULSE_GEN_CONFIG(node_id) \
IF_ENABLED(UTIL_NOT(EMIOS_PWM_IS_CAPTURE_MODE(node_id)), \
({ \
.ChannelId = DT_PROP(node_id, channel), \
.Mode = EMIOS_PWM_MODE(DT_STRING_TOKEN(node_id, pwm_mode)), \
.InternalPsSrc = EMIOS_PWM_PS_SRC(DT_STRING_TOKEN(node_id, prescaler_src)), \
.InternalPs = DT_PROP_OR(node_id, prescaler, \
DT_PROP_BY_PHANDLE(node_id, master_bus, prescaler)) - 1,\
.Timebase = COND_CODE_1(DT_NODE_HAS_PROP(node_id, master_bus), \
(EMIOS_PWM_BUS(DT_STRING_TOKEN( \
DT_PHANDLE(node_id, master_bus), bus_type))), \
(EMIOS_PWM_IP_BUS_INTERNAL)), \
.PhaseShift = DT_PROP(node_id, phase_shift), \
.DeadTime = DT_PROP(node_id, dead_time), \
.OutputDisableSource = EMIOS_PWM_IP_OUTPUT_DISABLE_NONE, \
.OutputPolarity = EMIOS_PWM_IP_ACTIVE_LOW, \
.DebugMode = DT_PROP(node_id, freeze), \
.PeriodCount = 0, \
.DutyCycle = 0, \
},))
#define EMIOS_PWM_PULSE_GEN_CONFIG(n) \
const Emios_Pwm_Ip_ChannelConfigType emios_pwm_##n##_init[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY(n, _EMIOS_PWM_PULSE_GEN_CONFIG) \
}; \
const struct pwm_nxp_s32_pulse_info emios_pwm_##n##_info = { \
.pwm_pulse_channels = ARRAY_SIZE(emios_pwm_##n##_init), \
.pwm_info = (Emios_Pwm_Ip_ChannelConfigType *)emios_pwm_##n##_init, \
};
#define EMIOS_PWM_PULSE_GEN_GET_CONFIG(n) \
.pulse_info = (struct pwm_nxp_s32_pulse_info *)&emios_pwm_##n##_info,
#else
#define EMIOS_PWM_PULSE_GEN_CONFIG(n)
#define EMIOS_PWM_PULSE_GEN_GET_CONFIG(n)
#endif
#ifdef CONFIG_PWM_CAPTURE
/* Macros used to glue devicetree with RTD's definition */
#define EMIOS_ICU_BUS_A EMIOS_ICU_BUS_A
#define EMIOS_ICU_BUS_B EMIOS_ICU_BUS_DIVERSE
#define EMIOS_ICU_BUS_C EMIOS_ICU_BUS_DIVERSE
#define EMIOS_ICU_BUS_D EMIOS_ICU_BUS_DIVERSE
#define EMIOS_ICU_BUS_E EMIOS_ICU_BUS_DIVERSE
#if !defined(CONFIG_SOC_SERIES_S32ZE)
#define EMIOS_ICU_BUS_F EMIOS_ICU_BUS_F
#endif
#define DIGITAL_FILTER_0 EMIOS_DIGITAL_FILTER_BYPASSED
#define DIGITAL_FILTER_2 EMIOS_DIGITAL_FILTER_02
#define DIGITAL_FILTER_4 EMIOS_DIGITAL_FILTER_04
#define DIGITAL_FILTER_8 EMIOS_DIGITAL_FILTER_08
#define DIGITAL_FILTER_16 EMIOS_DIGITAL_FILTER_16
#define EMIOS_PWM_CAPTURE_FILTER(filter) DT_CAT(DIGITAL_FILTER_, filter)
#define EMIOS_PWM_CAPTURE_MODE(mode) DT_CAT(EMIOS_ICU_, mode)
#define EMIOS_PWM_CAPTURE_BUS(mode) DT_CAT(EMIOS_ICU_, mode)
#define EMIOS_PWM_CAPTURE_CB(n, ch) \
DT_CAT5(pwm_nxp_s32_, n, _channel_, ch, _capture_callback)
#define EMIOS_PWM_CALLBACK_DECLARE(node_id, n) \
void EMIOS_PWM_CAPTURE_CB(n, DT_PROP(node_id, channel))(void) \
{ \
pwm_nxp_s32_capture_callback(DEVICE_DT_INST_GET(n), DT_PROP(node_id, channel)); \
} \
#define _EMIOS_PWM_PULSE_CAPTURE_CONFIG(node_id, n) \
IF_ENABLED(EMIOS_PWM_IS_CAPTURE_MODE(node_id), \
({ \
.hwChannel = DT_PROP(node_id, channel), \
.ucMode = EMIOS_PWM_CAPTURE_MODE(DT_STRING_TOKEN(node_id, pwm_mode)), \
.FreezeEn = DT_PROP(node_id, freeze), \
.Prescaler = COND_CODE_1(DT_NODE_HAS_PROP(node_id, master_bus), \
(DT_PROP_BY_PHANDLE(node_id, master_bus, prescaler)), \
(DT_PROP(node_id, prescaler))) - 1, \
.CntBus = COND_CODE_1(DT_NODE_HAS_PROP(node_id, master_bus), \
(EMIOS_PWM_CAPTURE_BUS(DT_STRING_TOKEN( \
DT_PHANDLE(node_id, master_bus), bus_type))),\
(EMIOS_ICU_BUS_INTERNAL_COUNTER)), \
.chMode = EMIOS_ICU_MODE_TIMESTAMP, \
.chSubMode = EMIOS_ICU_MODE_WITHOUT_DMA, \
.measurementMode = EMIOS_ICU_NO_MEASUREMENT, \
.edgeAlignement = EMIOS_ICU_BOTH_EDGES, \
.Filter = EMIOS_PWM_CAPTURE_FILTER(DT_PROP(node_id, input_filter)), \
.callback = NULL_PTR, \
.logicChStateCallback = NULL_PTR, \
.callbackParams = 255U, \
.bWithoutInterrupt = FALSE, \
.timestampBufferType = EMIOS_ICU_CIRCULAR_BUFFER, \
.eMiosChannelNotification = EMIOS_PWM_CAPTURE_CB(n, DT_PROP(node_id, channel)), \
.eMiosOverflowNotification = NULL_PTR, \
},))
#define EMIOS_PWM_PULSE_CAPTURE_CONFIG(n) \
DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(n, EMIOS_PWM_CALLBACK_DECLARE, n) \
const eMios_Icu_Ip_ChannelConfigType emios_pwm_##n##_capture_init[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_VARGS(n, _EMIOS_PWM_PULSE_CAPTURE_CONFIG, n) \
}; \
const eMios_Icu_Ip_ConfigType emios_pwm_##n##_capture_info = { \
.nNumChannels = ARRAY_SIZE(emios_pwm_##n##_capture_init), \
.pChannelsConfig = &emios_pwm_##n##_capture_init, \
};
#define EMIOS_PWM_PULSE_CAPTURE_GET_CONFIG(n) \
.icu_cfg = (eMios_Icu_Ip_ConfigType *)&emios_pwm_##n##_capture_info,
#else
#define EMIOS_PWM_PULSE_CAPTURE_CONFIG(n)
#define EMIOS_PWM_PULSE_CAPTURE_GET_CONFIG(n)
#endif
#define EMIOS_PWM_VERIFY_CONFIG(n) \
DT_INST_FOREACH_CHILD_STATUS_OKAY(n, _EMIOS_PWM_VERIFY_CONFIG)
#define EMIOS_NXP_S32_INSTANCE_CHECK(idx, node_id) \
((DT_REG_ADDR(node_id) == IP_EMIOS_##idx##_BASE) ? idx : 0)
#define EMIOS_NXP_S32_GET_INSTANCE(node_id) \
LISTIFY(__DEBRACKET eMIOS_INSTANCE_COUNT, EMIOS_NXP_S32_INSTANCE_CHECK, (|), node_id)
#define PWM_NXP_S32_INIT_DEVICE(n) \
PINCTRL_DT_INST_DEFINE(n); \
EMIOS_PWM_VERIFY_CONFIG(n) \
EMIOS_PWM_PULSE_GEN_CONFIG(n) \
EMIOS_PWM_PULSE_CAPTURE_CONFIG(n) \
static const struct pwm_nxp_s32_config pwm_nxp_s32_config_##n = { \
.base = (eMIOS_Type *)DT_REG_ADDR(DT_INST_PARENT(n)), \
.instance = EMIOS_NXP_S32_GET_INSTANCE(DT_INST_PARENT(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),\
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
EMIOS_PWM_PULSE_GEN_GET_CONFIG(n) \
EMIOS_PWM_PULSE_CAPTURE_GET_CONFIG(n) \
}; \
static struct pwm_nxp_s32_data pwm_nxp_s32_data_##n; \
DEVICE_DT_INST_DEFINE(n, \
&pwm_nxp_s32_init, \
NULL, \
&pwm_nxp_s32_data_##n, \
&pwm_nxp_s32_config_##n, \
POST_KERNEL, \
CONFIG_PWM_INIT_PRIORITY, \
&pwm_nxp_s32_driver_api);
DT_INST_FOREACH_STATUS_OKAY(PWM_NXP_S32_INIT_DEVICE)