diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 308a2f5b142..2bf100aceb2 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 184cbcc7cc0..ff9856d9d57 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -99,4 +99,6 @@ source "drivers/pwm/Kconfig.mcux_ctimer" source "drivers/pwm/Kconfig.numaker" +source "drivers/pwm/Kconfig.nxp_s32_emios" + endif # PWM diff --git a/drivers/pwm/Kconfig.nxp_s32_emios b/drivers/pwm/Kconfig.nxp_s32_emios new file mode 100644 index 00000000000..363e676822e --- /dev/null +++ b/drivers/pwm/Kconfig.nxp_s32_emios @@ -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. diff --git a/drivers/pwm/pwm_nxp_s32_emios.c b/drivers/pwm/pwm_nxp_s32_emios.c new file mode 100644 index 00000000000..f090ba70e88 --- /dev/null +++ b/drivers/pwm/pwm_nxp_s32_emios.c @@ -0,0 +1,403 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE_NAME nxp_s32_emios_pwm +#include +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) diff --git a/dts/arm/nxp/nxp_s32k344_m7.dtsi b/dts/arm/nxp/nxp_s32k344_m7.dtsi index 31a4c1453d5..b5f7549bea8 100644 --- a/dts/arm/nxp/nxp_s32k344_m7.dtsi +++ b/dts/arm/nxp/nxp_s32k344_m7.dtsi @@ -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"; + }; }; }; }; diff --git a/dts/bindings/pwm/nxp,s32-emios-pwm.yaml b/dts/bindings/pwm/nxp,s32-emios-pwm.yaml new file mode 100644 index 00000000000..17ed23f9e46 --- /dev/null +++ b/dts/bindings/pwm/nxp,s32-emios-pwm.yaml @@ -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.