| /* |
| * Copyright (c) 2025 Infineon Technologies AG, |
| * or an affiliate of Infineon Technologies AG. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * PWM driver for Infineon MCUs using the TCPWM block. |
| */ |
| |
| #define DT_DRV_COMPAT infineon_tcpwm_pwm |
| |
| #include <zephyr/drivers/pwm.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/timer/ifx_tcpwm.h> |
| #include <zephyr/dt-bindings/pwm/pwm_ifx_tcpwm.h> |
| |
| #include <cy_tcpwm_pwm.h> |
| #include <cy_gpio.h> |
| #include <cy_sysclk.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(pwm_ifx_tcpwm, CONFIG_PWM_LOG_LEVEL); |
| |
| struct ifx_tcpwm_pwm_config { |
| TCPWM_GRP_CNT_Type *reg_base; |
| const struct pinctrl_dev_config *pcfg; |
| bool resolution_32_bits; |
| cy_en_divider_types_t divider_type; |
| uint32_t divider_sel; |
| uint32_t divider_val; |
| uint32_t tcpwm_index; |
| }; |
| |
| static int ifx_tcpwm_pwm_init(const struct device *dev) |
| { |
| const struct ifx_tcpwm_pwm_config *config = dev->config; |
| cy_en_tcpwm_status_t status; |
| int ret; |
| uint32_t clk_connection; |
| |
| const cy_stc_tcpwm_pwm_config_t pwm_config = { |
| .pwmMode = CY_TCPWM_PWM_MODE_PWM, |
| .clockPrescaler = CY_TCPWM_PWM_PRESCALER_DIVBY_1, |
| .pwmAlignment = CY_TCPWM_PWM_LEFT_ALIGN, |
| .runMode = CY_TCPWM_PWM_CONTINUOUS, |
| .countInputMode = CY_TCPWM_INPUT_LEVEL, |
| .countInput = CY_TCPWM_INPUT_1, |
| .enableCompareSwap = true, |
| .enablePeriodSwap = true, |
| }; |
| |
| /* Configure PWM clock */ |
| Cy_SysClk_PeriphDisableDivider(config->divider_type, config->divider_sel); |
| Cy_SysClk_PeriphSetDivider(config->divider_type, config->divider_sel, config->divider_val); |
| Cy_SysClk_PeriphEnableDivider(config->divider_type, config->divider_sel); |
| |
| /* Calculate clock connection based on TCPWM index */ |
| if (config->resolution_32_bits) { |
| clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN0 + config->tcpwm_index; |
| } else { |
| clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN256 + config->tcpwm_index; |
| } |
| |
| Cy_SysClk_PeriphAssignDivider(clk_connection, config->divider_type, config->divider_sel); |
| |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Configure the TCPWM to be a PWM */ |
| status = IFX_TCPWM_PWM_Init(config->reg_base, &pwm_config); |
| if (status != CY_TCPWM_SUCCESS) { |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int ifx_tcpwm_pwm_set_cycles(const struct device *dev, uint32_t channel, |
| uint32_t period_cycles, uint32_t pulse_cycles, |
| pwm_flags_t flags) |
| { |
| ARG_UNUSED(channel); |
| |
| const struct ifx_tcpwm_pwm_config *config = dev->config; |
| uint32_t pwm_status; |
| uint32_t ctrl_temp; |
| |
| if (!config->resolution_32_bits && |
| ((period_cycles > UINT16_MAX) || (pulse_cycles > UINT16_MAX))) { |
| /* 16-bit resolution */ |
| if (period_cycles > UINT16_MAX) { |
| LOG_ERR("Period cycles more than 16-bits (%u)", period_cycles); |
| } |
| if (pulse_cycles > UINT16_MAX) { |
| LOG_ERR("Pulse cycles more than 16-bits (%u)", pulse_cycles); |
| } |
| return -EINVAL; |
| } |
| if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED) { |
| config->reg_base->CTRL |= TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk; |
| } else { |
| config->reg_base->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk; |
| } |
| |
| ctrl_temp = config->reg_base->CTRL & ~TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE_Msk; |
| |
| config->reg_base->CTRL = ctrl_temp | _VAL2FLD(TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE, |
| (flags & PWM_IFX_TCPWM_OUTPUT_MASK) >> |
| PWM_IFX_TCPWM_OUTPUT_POS); |
| |
| /* If the PWM is not yet running, write the period and compare directly pwm won't start |
| * correctly. |
| */ |
| pwm_status = IFX_TCPWM_PWM_GetStatus(config->reg_base); |
| if ((pwm_status & TCPWM_GRP_CNT_V2_STATUS_RUNNING_Msk) == 0) { |
| if ((period_cycles != 0) && (pulse_cycles != 0)) { |
| IFX_TCPWM_PWM_SetPeriod0(config->reg_base, period_cycles - 1); |
| IFX_TCPWM_PWM_SetCompare0Val(config->reg_base, pulse_cycles); |
| } |
| } |
| |
| /* Special case, if period_cycles is 0, set the period and compare to zero. If we were to |
| * disable the PWM, the output would be set to High-Z, wheras this will set the output to |
| * the zero duty cycle state instead. |
| */ |
| if (period_cycles == 0) { |
| IFX_TCPWM_PWM_SetPeriod1(config->reg_base, 0); |
| IFX_TCPWM_PWM_SetCompare0BufVal(config->reg_base, 0); |
| IFX_TCPWM_TriggerCaptureOrSwap_Single(config->reg_base); |
| } else { |
| /* Update period and compare values using buffer registers so the new values take |
| * effect on the next TC event. This prevents glitches in PWM output depending on |
| * where in the PWM cycle the update occurs. |
| */ |
| IFX_TCPWM_PWM_SetPeriod1(config->reg_base, period_cycles - 1); |
| IFX_TCPWM_PWM_SetCompare0BufVal(config->reg_base, pulse_cycles); |
| |
| /* Trigger the swap by writing to the SW trigger command register. |
| */ |
| IFX_TCPWM_TriggerCaptureOrSwap_Single(config->reg_base); |
| } |
| /* Enable the TCPWM for PWM mode of operation */ |
| IFX_TCPWM_PWM_Enable(config->reg_base); |
| |
| /* Start the TCPWM block */ |
| IFX_TCPWM_TriggerStart_Single(config->reg_base); |
| |
| return 0; |
| } |
| |
| static int ifx_tcpwm_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel, |
| uint64_t *cycles) |
| { |
| ARG_UNUSED(channel); |
| |
| const struct ifx_tcpwm_pwm_config *config = dev->config; |
| |
| *cycles = Cy_SysClk_PeriphGetFrequency(config->divider_type, config->divider_sel); |
| |
| return 0; |
| } |
| |
| static DEVICE_API(pwm, ifx_tcpwm_pwm_api) = { |
| .set_cycles = ifx_tcpwm_pwm_set_cycles, |
| .get_cycles_per_sec = ifx_tcpwm_pwm_get_cycles_per_sec, |
| }; |
| |
| #define INFINEON_TCPWM_PWM_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| \ |
| static const struct ifx_tcpwm_pwm_config pwm_tcpwm_config_##n = { \ |
| .reg_base = (TCPWM_GRP_CNT_Type *)DT_REG_ADDR(DT_INST_PARENT(n)), \ |
| .tcpwm_index = (DT_REG_ADDR(DT_INST_PARENT(n)) - \ |
| DT_REG_ADDR(DT_PARENT(DT_INST_PARENT(n)))) / \ |
| DT_REG_SIZE(DT_INST_PARENT(n)), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .resolution_32_bits = \ |
| (DT_PROP(DT_INST_PARENT(n), resolution) == 32) ? true : false, \ |
| .divider_type = DT_PROP(DT_INST_PARENT(n), divider_type), \ |
| .divider_sel = DT_PROP(DT_INST_PARENT(n), divider_sel), \ |
| .divider_val = DT_PROP(DT_INST_PARENT(n), divider_val), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, ifx_tcpwm_pwm_init, NULL, NULL, &pwm_tcpwm_config_##n, \ |
| POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &ifx_tcpwm_pwm_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INFINEON_TCPWM_PWM_INIT) |