drivers: pwm: introduce PWM driver for NXP S32 EMIOS

This introduces PWM driver with supporting PWM output
APIs based on NXP S32 EMIOS peripheral. This supports
three mode: OPWFMB, OPWMCB and OPWMB.

OPWFMB uses internal counter, the new period and duty
cycle takes effect immediately.

OPWMCB and OPWMB use external counter as timebase, changing
PWM period at runtime will impact to all channels share the
same timebase. Also the new period and duty cycle take effect
in next period boundary of the timebase

Signed-off-by: Dat Nguyen Duy <dat.nguyenduy@nxp.com>
This commit is contained in:
Dat Nguyen Duy 2023-06-19 16:33:09 +07:00 committed by Carles Cufí
commit 92f3fb79fe
6 changed files with 590 additions and 0 deletions

View file

@ -39,6 +39,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU4 pwm_xmc4xxx_ccu4.c)
zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU8 pwm_xmc4xxx_ccu8.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_CTIMER pwm_mcux_ctimer.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NUMAKER pwm_numaker.c)
zephyr_library_sources_ifdef(CONFIG_PWM_NXP_S32_EMIOS pwm_nxp_s32_emios.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)

View file

@ -99,4 +99,6 @@ source "drivers/pwm/Kconfig.mcux_ctimer"
source "drivers/pwm/Kconfig.numaker"
source "drivers/pwm/Kconfig.nxp_s32_emios"
endif # PWM

View file

@ -0,0 +1,11 @@
# Copyright 2023 NXP
# SPDX-License-Identifier: Apache-2.0
config PWM_NXP_S32_EMIOS
bool "NXP S32 PWM-eMIOS driver"
default y
depends on DT_HAS_NXP_S32_EMIOS_PWM_ENABLED
select NXP_S32_EMIOS
select NOCACHE_MEMORY
help
Enable support for the NXP S32 PWM-eMIOS.

View file

@ -0,0 +1,403 @@
/*
* Copyright 2023 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 <Emios_Mcl_Ip.h>
#include <Emios_Pwm_Ip.h>
#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
/*
* 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
*/
extern uint8 eMios_Pwm_Ip_IndexInChState[EMIOS_PWM_IP_INSTANCE_COUNT][EMIOS_PWM_IP_CHANNEL_COUNT];
struct pwm_nxp_s32_data {
uint32_t emios_clk;
};
struct pwm_nxp_s32_pulse_info {
uint8_t pwm_pulse_channels;
Emios_Pwm_Ip_ChannelConfigType *pwm_info;
};
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;
struct pwm_nxp_s32_pulse_info *pulse_info;
};
#ifdef EMIOS_PWM_IP_MODE_OPWFMB_USED
static int pwm_nxp_s32_set_cycles_internal_timebase(uint8_t instance, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles)
{
bool need_update = false;
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;
}
if (Emios_Pwm_Ip_GetPeriod(instance, channel) != period_cycles) {
Emios_Pwm_Ip_SetPeriod(instance, channel, period_cycles);
need_update = true;
}
if (Emios_Pwm_Ip_GetDutyCycle(instance, channel) != pulse_cycles) {
need_update = true;
if (Emios_Pwm_Ip_SetDutyCycle(instance, channel, pulse_cycles)) {
LOG_ERR("Cannot set pulse cycles");
return -EIO;
}
}
if (need_update) {
/* Force match so that the new period, duty cycle takes effect immediately */
Emios_Pwm_Ip_ForceMatchTrailingEdge(instance, channel, true);
}
return 0;
}
#endif
#if defined(EMIOS_PWM_IP_MODE_OPWMCB_USED) || defined(EMIOS_PWM_IP_MODE_OPWMB_USED)
static int pwm_nxp_s32_set_cycles_external_timebase(uint8_t instance, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles)
{
uint8_t master_channel;
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;
}
if (Emios_Pwm_Ip_GetPeriod(instance, channel) != period_cycles) {
/*
* This mode uses internal counter, so change period and cycle
* don't effect to the others
*/
master_channel = Emios_Pwm_Ip_GetMasterBusChannel(instance, channel);
if (Emios_Mcl_Ip_SetCounterBusPeriod(instance, master_channel, period_cycles)) {
LOG_ERR("Cannot set counter period");
return -EIO;
}
}
if (Emios_Pwm_Ip_GetDutyCycle(instance, channel) != pulse_cycles) {
if (Emios_Pwm_Ip_SetDutyCycle(instance, channel, pulse_cycles)) {
LOG_ERR("Cannot set pulse cycles");
return -EIO;
}
}
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;
Emios_Pwm_Ip_PwmModeType mode;
if (channel >= EMIOS_PWM_IP_CHANNEL_COUNT) {
LOG_ERR("invalid channel %d", channel);
return -EINVAL;
}
mode = Emios_Pwm_Ip_GetChannelMode(config->instance, channel);
if (mode == EMIOS_PWM_IP_MODE_NODEFINE) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
if (flags) {
LOG_ERR("Only support configuring output polarity at boot time");
return -ENOTSUP;
}
switch (mode) {
#ifdef EMIOS_PWM_IP_MODE_OPWFMB_USED
case EMIOS_PWM_IP_MODE_OPWFMB_FLAG:
return pwm_nxp_s32_set_cycles_internal_timebase(config->instance, channel,
period_cycles, pulse_cycles);
#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:
if ((period_cycles % 2)) {
LOG_ERR("OPWMCB mode: period must be an even number");
return -EINVAL;
}
return pwm_nxp_s32_set_cycles_external_timebase(config->instance, channel,
(period_cycles + 2) / 2,
pulse_cycles);
#endif
#if defined(EMIOS_PWM_IP_MODE_OPWMB_USED)
case EMIOS_PWM_IP_MODE_OPWMB_FLAG:
if ((Emios_Pwm_Ip_GetPhaseShift(config->instance, channel) +
pulse_cycles) > period_cycles) {
LOG_ERR("OPWMB mode: new duty cycle + phase shift must <= new period");
return -EINVAL;
}
return pwm_nxp_s32_set_cycles_external_timebase(config->instance, channel,
period_cycles, pulse_cycles);
#endif
default:
/* Never reach here */
break;
}
return 0;
}
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_bus;
if (Emios_Pwm_Ip_GetChannelMode(config->instance, channel) == EMIOS_PWM_IP_MODE_NODEFINE) {
LOG_ERR("Channel %d is not configured for PWM", channel);
return -EINVAL;
}
master_bus = Emios_Pwm_Ip_GetMasterBusChannel(config->instance, channel);
internal_prescaler = (config->base->CH.UC[master_bus].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_bus].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;
}
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;
const Emios_Pwm_Ip_ChannelConfigType *pwm_info;
int err = 0;
uint8_t ch_id;
static uint8_t logic_ch;
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;
}
for (ch_id = 0; ch_id < config->pulse_info->pwm_pulse_channels; ch_id++) {
pwm_info = &config->pulse_info->pwm_info[ch_id];
eMios_Pwm_Ip_IndexInChState[config->instance][pwm_info->ChannelId] = logic_ch++;
Emios_Pwm_Ip_InitChannel(config->instance, pwm_info);
}
return err;
}
static const struct pwm_driver_api pwm_nxp_s32_driver_api = {
.set_cycles = pwm_nxp_s32_set_cycles,
.get_cycles_per_sec = pwm_nxp_s32_get_cycles_per_sec,
};
/* Macros used to glue devicetree with RTD's definition */
#define BUS_A EMIOS_PWM_IP_BUS_A
#define BUS_B EMIOS_PWM_IP_BUS_BCDE
#define BUS_C EMIOS_PWM_IP_BUS_BCDE
#define BUS_D EMIOS_PWM_IP_BUS_BCDE
#define BUS_E EMIOS_PWM_IP_BUS_BCDE
#define BUS_F EMIOS_PWM_IP_BUS_F
#define EMIOS_PWM_MODE(mode) DT_CAT3(EMIOS_PWM_IP_, mode, _FLAG)
#define EMIOS_PWM_POLARITY(mode) DT_CAT(EMIOS_PWM_IP_, mode)
#define EMIOS_PWM_PS_SRC(mode) DT_CAT(EMIOS_PWM_IP_PS_SRC_, mode)
/*
* If timebase is configured in MCB up/down count mode: pwm period = (2 * master bus's period - 2)
*/
#define EMIOS_PWM_PERIOD_TIME_BASE(node_id) \
COND_CODE_1(DT_ENUM_HAS_VALUE(node_id, mode, MCB_UP_DOWN_COUNTER), \
(2 * DT_PROP_BY_PHANDLE(node_id, master_bus, period) - 2), \
(DT_PROP_BY_PHANDLE(node_id, master_bus, period)))
#define EMIOS_PWM_IS_MODE_OPWFMB(node_id) \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, MODE_OPWFMB)
#define EMIOS_PWM_IS_MODE_OPWMCB(node_id) \
UTIL_OR(DT_ENUM_HAS_VALUE(node_id, pwm_mode, MODE_OPWMCB_TRAIL_EDGE), \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, MODE_OPWMCB_LEAD_EDGE)) \
#define EMIOS_PWM_IS_MODE_OPWMB(node_id) \
DT_ENUM_HAS_VALUE(node_id, pwm_mode, MODE_OPWMB)
#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), \
"Node "DT_NODE_PATH(node_id)": invalid master bus");
#define EMIOS_PWM_LOG(node_id, msg) \
DT_NODE_PATH(node_id) ": " DT_PROP(node_id, pwm_mode) ": " msg \
#define EMIOS_PWM_VERIFY_MODE_OPWFMB(node_id) \
BUILD_ASSERT(DT_NODE_HAS_PROP(node_id, period), \
EMIOS_PWM_LOG(node_id, "period must be configured")); \
BUILD_ASSERT(IN_RANGE(DT_PROP(node_id, period), EMIOS_PWM_IP_MIN_CNT_VAL + 1, \
EMIOS_PWM_IP_MAX_CNT_VAL), \
EMIOS_PWM_LOG(node_id, "period is out of range")); \
BUILD_ASSERT(DT_PROP(node_id, duty_cycle) <= DT_PROP(node_id, period), \
EMIOS_PWM_LOG(node_id, "duty-cycle must <= period")); \
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 must not be configured")); \
BUILD_ASSERT(DT_PROP(node_id, phase_shift) == 0, \
EMIOS_PWM_LOG(node_id, "phase-shift must not be configured"));
#define EMIOS_PWM_VERIFY_MODE_OPWMCB(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, duty_cycle) + DT_PROP(node_id, dead_time)) <= \
EMIOS_PWM_PERIOD_TIME_BASE(node_id), \
EMIOS_PWM_LOG(node_id, "duty-cycle + dead-time must <= period")); \
BUILD_ASSERT(DT_PROP(node_id, dead_time) <= DT_PROP(node_id, duty_cycle), \
EMIOS_PWM_LOG(node_id, "dead-time must <= duty-cycle")); \
BUILD_ASSERT(DT_PROP(node_id, phase_shift) == 0, \
EMIOS_PWM_LOG(node_id, "phase-shift must not be configured")); \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, period), \
EMIOS_PWM_LOG(node_id, "period must not be configured," \
" driver takes the value from master bus")); \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, prescaler), \
EMIOS_PWM_LOG(node_id, "prescaler must not be configured," \
" 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 must not be configured," \
" always use prescalered source")); \
#define EMIOS_PWM_VERIFY_MODE_OPWMB(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_NODE_HAS_PROP(node_id, period), \
EMIOS_PWM_LOG(node_id, "period must not be configured," \
" driver takes the value from master bus")); \
BUILD_ASSERT((DT_PROP(node_id, duty_cycle) + DT_PROP(node_id, phase_shift)) <= \
EMIOS_PWM_PERIOD_TIME_BASE(node_id), \
EMIOS_PWM_LOG(node_id, "duty-cycle + phase-shift must <= period")); \
BUILD_ASSERT(DT_PROP(node_id, dead_time) == 0, \
EMIOS_PWM_LOG(node_id, "dead-time must not be configured")); \
BUILD_ASSERT(!DT_NODE_HAS_PROP(node_id, prescaler), \
EMIOS_PWM_LOG(node_id, "prescaler must not be configured")); \
BUILD_ASSERT(DT_ENUM_HAS_VALUE(node_id, prescaler_src, PRESCALED_CLOCK), \
EMIOS_PWM_LOG(node_id, "prescaler-src must not be configured," \
" 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))) \
#define EMIOS_PWM_CONFIG(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 = DT_STRING_TOKEN_OR(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_POLARITY(DT_STRING_TOKEN(node_id, polarity)), \
.DebugMode = DT_PROP(node_id, freeze), \
.PeriodCount = DT_PROP_OR(node_id, period, EMIOS_PWM_PERIOD_TIME_BASE(node_id)),\
.DutyCycle = DT_PROP(node_id, duty_cycle), \
},
#define EMIOS_PWM_GENERATE_CONFIG(n) \
DT_INST_FOREACH_CHILD_STATUS_OKAY(n, EMIOS_PWM_VERIFY_CONFIG) \
const Emios_Pwm_Ip_ChannelConfigType emios_pwm_##n##_init[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY(n, EMIOS_PWM_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_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_GENERATE_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),\
.pulse_info = (struct pwm_nxp_s32_pulse_info *)&emios_pwm_##n##_info, \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(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)

View file

@ -642,6 +642,12 @@
status = "disabled";
};
};
pwm {
compatible = "nxp,s32-emios-pwm";
#pwm-cells = <2>;
status = "disabled";
};
};
emios1: emios@4008c000 {
@ -688,6 +694,12 @@
status = "disabled";
};
};
pwm {
compatible = "nxp,s32-emios-pwm";
#pwm-cells = <2>;
status = "disabled";
};
};
emios2: emios@40090000 {
@ -734,6 +746,12 @@
status = "disabled";
};
};
pwm {
compatible = "nxp,s32-emios-pwm";
#pwm-cells = <2>;
status = "disabled";
};
};
};
};

View file

@ -0,0 +1,155 @@
description: |
NXP S32 eMIOS PWM node for S32 SoCs. Each channel in eMIOS can be configured
to use for PWM operation. There are several PWM modes supported by this module,
some modes only support on channels that have internal counter, some modes
require to use a reference timebase from a master bus.
For example to configuring eMIOS instance 0 with channel 0 for mode OPWFMB, channel 1
for mode OPWMB and channel 2 for mode OPWMCB with deadtime inserted at leading edge:
emios0_pwm: pwm {
pwm_0 {
channel = <0>;
pwm-mode = "MODE_OPWFMB";
prescaler = <8>;
period = <65534>;
duty-cycle = <32768>;
polarity = "ACTIVE_HIGH";
};
pwm_1 {
channel = <1>;
master-bus = <&emios1_bus_a>;
pwm-mode = "MODE_OPWMB";
duty-cycle = <32768>;
phase-shift = <100>;
polarity = "ACTIVE_LOW";
};
pwm_2 {
channel = <2>;
master-bus = <&emios1_bus_b>;
pwm-mode = "MODE_OPWMCB_LEAD_EDGE";
duty-cycle = <32768>;
dead-time = <100>;
polarity = "ACTIVE_LOW";
};
};
OPWMB and OPWMCB modes use reference timebase, the master bus is chosen over
phandle 'master-bus'. For OPWMB mode, PWM's period is master bus's period and
is 2 * master bus's period - 2 for OPWMCB mode. Please notice that the devicetree
node for master bus should be enabled and configured for using, please see
'nxp,s32-emios' bindings.
compatible: "nxp,s32-emios-pwm"
include: [pwm-controller.yaml, base.yaml, pinctrl-device.yaml]
properties:
pinctrl-0:
required: true
pinctrl-names:
required: true
"#pwm-cells":
const: 2
pwm-cells:
- channel
# Period in terms of nanoseconds
- period
child-binding:
description: |
eMIOS PWM channel configuration.
properties:
channel:
type: int
required: true
description: eMIOS PWM channel
master-bus:
type: phandle
description: |
A phandle to master-bus node that will be used as external timebase
for current channel, this can be bypassed if internal counter is used
for PWM operation.
pwm-mode:
type: string
required: true
description: |
Select PWM mode:
- OPWFMB: provides waveforms with variable duty cycle and frequency,
this mode uses internal counter.
- OPWMB: generate pulses with programmable leading and trailing
edge placement. The period is determined by period of
an external counter driven in MCB Up Mode. Changing PWM period
at runtime will impact to all channels share the same timebase.
The new period and cycle take effect in next period boundary.
- OPWMCB: generates a center aligned PWM with dead time insertion to the
leading or trailing edge. The period is determined by period of
an external counter driven in MCB Up Down Mode. Changing PWM period
at runtime will impact to all channels share the same timebase,
The new period and cycle take effect in next period boundary.
enum:
- "MODE_OPWFMB"
- "MODE_OPWMB"
- "MODE_OPWMCB_TRAIL_EDGE"
- "MODE_OPWMCB_LEAD_EDGE"
polarity:
type: string
required: true
description: |
Output polarity for PWM channel.
enum:
- "ACTIVE_LOW"
- "ACTIVE_HIGH"
duty-cycle:
type: int
required: true
description: |
Duty-cycle (in ticks) for PWM channel at boot time.
period:
type: int
description: |
Period (in ticks) for OPWFMB at boot time. Period for the rest
of PWM mode depends on period's master bus. Must be in range [2 ... 65535].
freeze:
type: boolean
description: Freeze individual internal counter when the chip enters Debug mode.
prescaler-src:
type: string
default: "PRESCALED_CLOCK"
description: |
Select clock source for internal counter prescaler.
enum:
- "PRESCALED_CLOCK" # Clock source = eMIOS clock / (global prescaler)
- "MODULE_CLOCK" # Clock source = eMIOS clock
prescaler:
type: int
description: |
The clock divider for internal counter prescaler.
enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
dead-time:
type: int
default: 0
description: |
Dead time (in ticks) for PWM channel in OPWMCB mode.
phase-shift:
type: int
default: 0
description: |
Phase Shift (in ticks) for PWM channel in OPWMB mode.