| /* |
| * 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) |