Commit 7973535e authored by Bill Waters's avatar Bill Waters Committed by Benjamin Cabé
Browse files

drivers: pwm: Infineon: PWM driver improvements and bug fixes



This change makes improvements and bug fixes for the Infineon PWM
driver.  These include:

* Removes hard coded register addresss from driver.
* Addresses issues causing pwm_api and pwm_gpio_loopback tests to
fail, as well as functional failures.
* Restructures device tree file to better represent the hardware
architecture of the tcpwm module.
* Allows configuration of hardware behavior when PWM is disabled.

Signed-off-by: default avatarBill Waters <bill.waters@infineon.com>
parent 8e1f2c70
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
# Infineon TCPWM PWM configuration options

# Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or
# an affiliate of Cypress Semiconductor Corporation
# Copyright (c) 2025 Infineon Technologies AG,
# or an affiliate of Infineon Technologies AG.
#
# SPDX-License-Identifier: Apache-2.0
#
# Infineon TCPWM PWM configuration options

config PWM_INFINEON_TCPWM
	bool "Infineon TCPWM driver"
	default y
	depends on DT_HAS_INFINEON_TCPWM_PWM_ENABLED
	depends on SOC_FAMILY_INFINEON_CAT1B
	select USE_INFINEON_PWM
	select PINCTRL
	help
	  This option enables the PWM driver for Infineon TCPWM peripheral.
	  This option enables the PWM driver in the Infineon TCPWM peripheral.
+69 −67
Original line number Diff line number Diff line
/*
 * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or
 * an affiliate of Cypress Semiconductor Corporation
 * Copyright (c) 2025 Infineon Technologies AG,
 * or an affiliate of Infineon Technologies AG.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
@@ -13,38 +13,31 @@

#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 <cyhal_hw_resources.h>
#include <cyhal_hw_types.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pwm_ifx_tcpwm, CONFIG_PWM_LOG_LEVEL);

#define PWM_REG_BASE TCPWM0

struct ifx_tcpwm_pwm_data {
	uint32_t pwm_num;
};

struct ifx_tcpwm_pwm_config {
	TCPWM_GRP_CNT_Type *reg_addr;
	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)
{
	struct ifx_tcpwm_pwm_data *data = dev->data;
	const struct ifx_tcpwm_pwm_config *config = dev->config;
	cy_en_tcpwm_status_t status;
	int ret;
	uint32_t addr_offset = (uint32_t)config->reg_addr - TCPWM0_BASE;
	uint32_t clk_connection;

	const cy_stc_tcpwm_pwm_config_t pwm_config = {
@@ -63,17 +56,13 @@ static int ifx_tcpwm_pwm_init(const struct device *dev)
	Cy_SysClk_PeriphSetDivider(config->divider_type, config->divider_sel, config->divider_val);
	Cy_SysClk_PeriphEnableDivider(config->divider_type, config->divider_sel);

	/* This is very specific to the cyw920829m2evk_02 and may need to be modified
	 * for other boards.
	 */
	if (addr_offset < sizeof(TCPWM_GRP_Type)) {
		clk_connection =
			PCLK_TCPWM0_CLOCK_COUNTER_EN0 + (addr_offset / sizeof(TCPWM_GRP_CNT_Type));
	/* Calculate clock connection based on TCPWM index */
	if (config->resolution_32_bits) {
		clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN0 + config->tcpwm_index;
	} else {
		addr_offset -= sizeof(TCPWM_GRP_Type);
		clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN256 +
				 (addr_offset / sizeof(TCPWM_GRP_CNT_Type));
		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);
@@ -82,8 +71,7 @@ static int ifx_tcpwm_pwm_init(const struct device *dev)
	}

	/* Configure the TCPWM to be a PWM */
	data->pwm_num += addr_offset / sizeof(TCPWM_GRP_CNT_Type);
	status = Cy_TCPWM_PWM_Init(PWM_REG_BASE, data->pwm_num, &pwm_config);
	status = IFX_TCPWM_PWM_Init(config->reg_base, &pwm_config);
	if (status != CY_TCPWM_SUCCESS) {
		return -ENOTSUP;
	}
@@ -92,10 +80,12 @@ static int ifx_tcpwm_pwm_init(const struct device *dev)
}

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)
				    uint32_t period_cycles, uint32_t pulse_cycles,
				    pwm_flags_t flags)
{
	struct ifx_tcpwm_pwm_data *data = dev->data;
	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))) {
@@ -108,43 +98,54 @@ static int ifx_tcpwm_pwm_set_cycles(const struct device *dev, uint32_t channel,
		}
		return -EINVAL;
	}

	if ((period_cycles == 0) || (pulse_cycles == 0)) {
		Cy_TCPWM_PWM_Disable(PWM_REG_BASE, data->pwm_num);
	if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED) {
		config->reg_base->CTRL |= TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk;
	} else {
		/* Update period and compare values using buffer registers so the new values
		 * take effect on the next TC event
		 */
		Cy_TCPWM_PWM_SetPeriod1(PWM_REG_BASE, data->pwm_num, period_cycles);
		Cy_TCPWM_PWM_SetCompare0BufVal(PWM_REG_BASE, data->pwm_num, pulse_cycles);
		config->reg_base->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk;
	}

		/* Trigger the swap by writing to the SW trigger command register. */
		Cy_TCPWM_TriggerCaptureOrSwap_Single(PWM_REG_BASE, data->pwm_num);
	ctrl_temp = config->reg_base->CTRL & ~TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE_Msk;

		if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED) {
			config->reg_addr->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk;
			config->reg_addr->CTRL |= _VAL2FLD(TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE,
							   CY_TCPWM_PWM_INVERT_ENABLE);
	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);
		}
	}

		/* TODO: Add 2-bit field to top 8 bits of pwm_flags_t to set this.
		 * #define    CY_TCPWM_PWM_OUTPUT_HIGHZ    (0U)
		 * #define    CY_TCPWM_PWM_OUTPUT_RETAIN   (1U)
		 * #define    CY_TCPWM_PWM_OUTPUT_LOW      (2U)
		 * #define    CY_TCPWM_PWM_OUTPUT_HIGH     (3U)
		 * if ((flags & __) == __) {
		 *	config->reg_addr->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE_Msk;
		 *	config->reg_addr->CTRL |= _VAL2FLD(TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE,
		 *					   __);
		 * }
	/* 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 */
		Cy_TCPWM_PWM_Enable(PWM_REG_BASE, data->pwm_num);
	IFX_TCPWM_PWM_Enable(config->reg_base);

	/* Start the TCPWM block */
		Cy_TCPWM_TriggerStart_Single(PWM_REG_BASE, data->pwm_num);
	}
	IFX_TCPWM_TriggerStart_Single(config->reg_base);

	return 0;
}
@@ -167,19 +168,20 @@ static DEVICE_API(pwm, ifx_tcpwm_pwm_api) = {
#define INFINEON_TCPWM_PWM_INIT(n)                                                                 \
	PINCTRL_DT_INST_DEFINE(n);                                                                 \
                                                                                                   \
	static struct ifx_tcpwm_pwm_data pwm_tcpwm_data_##n;                                       \
                                                                                                   \
	static struct ifx_tcpwm_pwm_config pwm_tcpwm_config_##n = {                                \
		.reg_addr = (TCPWM_GRP_CNT_Type *)DT_INST_REG_ADDR(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_INST_PROP(n, resolution) == 32) ? true : false,          \
		.divider_type = DT_INST_PROP(n, divider_type),                                     \
		.divider_sel = DT_INST_PROP(n, divider_sel),                                       \
		.divider_val = DT_INST_PROP(n, divider_val),                                       \
		.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, &pwm_tcpwm_data_##n,                    \
			      &pwm_tcpwm_config_##n, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY,        \
			      &ifx_tcpwm_pwm_api);
	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)
+188 −133
Original line number Diff line number Diff line
@@ -215,141 +215,196 @@
			status = "disabled";
		};

		counter0_0: counter@404a0000 {
			compatible = "infineon,cat1-counter";
		tcpwm0: tcpwm0@404a0000 {
			reg = <0x404a0000 0x10000>;
			#address-cells = <1>;
			#size-cells = <1>;

			tcpwm0_0: tcpwm0_0@404a0000 {
				compatible = "infineon,tcpwm";
				reg = <0x404a0000 0x80>;
				interrupts = <42 4>;
				resolution = <32>;
				status = "disabled";
		};
		counter0_1: counter@404a0080 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a0080 0x80>;
			interrupts = <43 4>;
			resolution = <32>;

				pwm0_0: pwm0_0 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		counter1_0: counter@404a8000 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8000 0x80>;
			interrupts = <44 4>;
			resolution = <16>;

				counter0_0: counter0_0 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};
		counter1_1: counter@404a8080 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8080 0x80>;
			interrupts = <45 4>;
			resolution = <16>;
			status = "disabled";

			};
		counter1_2: counter@404a8100 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8100 0x80>;
			interrupts = <46 4>;
			resolution = <16>;

			tcpwm0_1: tcpwm0_1@404a0080 {
				compatible = "infineon,tcpwm";
				reg = <0x404a0080 0x80>;
				interrupts = <43 4>;
				resolution = <32>;
				status = "disabled";
		};
		counter1_3: counter@404a8180 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8180 0x80>;
			interrupts = <47 4>;
			resolution = <16>;

				pwm0_1: pwm0_1 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		counter1_4: counter@404a8200 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8200 0x80>;
			interrupts = <48 4>;
			resolution = <16>;

				counter0_1: counter0_1 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};
		counter1_5: counter@404a8280 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8280 0x80>;
			interrupts = <49 4>;
			resolution = <16>;
			status = "disabled";

			};
		counter1_6: counter@404a8300 {
			compatible = "infineon,cat1-counter";
			reg = <0x404a8300 0x80>;
			interrupts = <50 4>;
		};

		tcpwm1: tcpwm1@404a8000 {
			reg = <0x404a8000 0x10000>;
			#address-cells = <1>;
			#size-cells = <1>;

			tcpwm1_0: tcpwm1_0@404a8000 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8000 0x80>;
				interrupts = <44 4>;
				resolution = <16>;
				status = "disabled";
		};

		pwm0_0: pwm@404a0000 {
				pwm1_0: pwm1_0 {
					compatible = "infineon,tcpwm-pwm";
			reg = <0x404a0000 0x80>;
			interrupts = <42 4>;
			resolution = <32>;
			status = "disabled";
					#pwm-cells = <3>;
		};
		pwm0_1: pwm@404a0080 {
			compatible = "infineon,tcpwm-pwm";
			reg = <0x404a0080 0x80>;
			interrupts = <43 4>;
			resolution = <32>;
					status = "disabled";
			#pwm-cells = <3>;
				};
		pwm1_0: pwm@404a8000 {
			compatible = "infineon,tcpwm-pwm";
			reg = <0x404a8000 0x80>;
			interrupts = <44 4>;
			resolution = <16>;

				counter1_0: counter1_0 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
			#pwm-cells = <3>;
				};
		pwm1_1: pwm@404a8080 {
			compatible = "infineon,tcpwm-pwm";

			};

			tcpwm1_1: tcpwm1_1@404a8080 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8080 0x80>;
				interrupts = <45 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_1: pwm1_1 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		pwm1_2: pwm@404a8100 {
			compatible = "infineon,tcpwm-pwm";

				counter1_1: counter1_1 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};

			tcpwm1_2: tcpwm1_2@404a8100 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8100 0x80>;
				interrupts = <46 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_2: pwm1_2 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		pwm1_3: pwm@404a8180 {
			compatible = "infineon,tcpwm-pwm";

				counter1_2: counter1_2 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};

			tcpwm1_3: tcpwm1_3@404a8180 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8180 0x80>;
				interrupts = <47 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_3: pwm1_3 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		pwm1_4: pwm@404a8200 {
			compatible = "infineon,tcpwm-pwm";

				counter1_3: counter1_3 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};

			tcpwm1_4: tcpwm1_4@404a8200 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8200 0x80>;
				interrupts = <48 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_4: pwm1_4 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		pwm1_5: pwm@404a8280 {
			compatible = "infineon,tcpwm-pwm";

				counter1_4: counter1_4 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};

			tcpwm1_5: tcpwm1_5@404a8280 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8280 0x80>;
				interrupts = <49 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_5: pwm1_5 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};
		pwm1_6: pwm@404a8300 {
			compatible = "infineon,tcpwm-pwm";

				counter1_5: counter1_5 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};

			tcpwm1_6: tcpwm1_6@404a8300 {
				compatible = "infineon,tcpwm";
				reg = <0x404a8300 0x80>;
				interrupts = <50 4>;
				resolution = <16>;
				status = "disabled";

				pwm1_6: pwm1_6 {
					compatible = "infineon,tcpwm-pwm";
					#pwm-cells = <3>;
					status = "disabled";
				};

				counter1_6: counter1_6 {
					compatible = "infineon,tcpwm-counter";
					status = "disabled";
				};

			};
		};

		dma0: dw@40180000 {
+9 −43
Original line number Diff line number Diff line
# Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or
# an affiliate of Cypress Semiconductor Corporation
# Copyright (c) 2025 Infineon Technologies AG,
# or an affiliate of Infineon Technologies AG.
#
# SPDX-License-Identifier: Apache-2.0

@@ -10,16 +10,13 @@ compatible: "infineon,tcpwm-pwm"
include: [pwm-controller.yaml, pinctrl-device.yaml, "infineon,system-interrupts.yaml"]

properties:
  reg:
    type: array
    required: true

  interrupts:
    type: array
    description: Required for non-cat1c devices

  system-interrupts:
    description: Required for cat1c devices
  "#pwm-cells":
    const: 3
    description: |
      Number of items to expect in a PWM
      - channel of the timer used for PWM (not used)
      - period to set in ns
      - flags: standard flags like PWM_POLARITY_NORMAL

  pinctrl-0:
    description: |
@@ -36,37 +33,6 @@ properties:
  pinctrl-names:
    required: true

  resolution:
    type: int

  divider-type:
    type: int
    description: |
      Specifies which type of divider to use.
      Defined by cy_en_divider_types_t in cy_sysclk.h.
    required: true

  divider-sel:
    type: int
    description: |
      Specifies which divider of the selected type to configure.
    required: true

  divider-val:
    type: int
    description: |
      Causes integer division of (divider value + 1), or division by 1 to 256
      (8-bit divider) or 1 to 65536 (16-bit divider).
    required: true

  "#pwm-cells":
    const: 3
    description: |
      Number of items to expect in a PWM
      - channel of the timer used for PWM (not used)
      - period to set in ns
      - flags: standard flags like PWM_POLARITY_NORMAL

pwm-cells:
  - channel
  - period
+48 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 Infineon Technologies AG,
# or an affiliate of Infineon Technologies AG.
#
# SPDX-License-Identifier: Apache-2.0

description: Infineon CAT1 TCPWM (Timer/Counter/PWM) node

compatible: "infineon,tcpwm"

include: base.yaml

properties:
  reg:
    type: array
    required: true
    description: Register base address and size information

  interrupts:
    type: array
    description: Interrupt mapping for TCPWM instance

  divider-type:
    type: int
    description: |
      Specifies which type of divider to use.
      Defined by cy_en_divider_types_t in cy_sysclk.h.
    required: true

  divider-sel:
    type: int
    description: |
      Specifies which divider of the selected type to configure.
    required: true

  divider-val:
    type: int
    description: |
      Causes integer division of (divider value + 1), or division by 1 to 256
      (8-bit divider) or 1 to 65536 (16-bit divider).
    required: true

  resolution:
    type: int
    required: true
    description: Counter/timer resolution (16 or 32 bits)
    enum:
      - 16
      - 32
Loading