blob: f62173e7274d5c922cc8997adde88044a7212829 [file] [log] [blame]
/*
* 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)