1 /*
2  * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or
3  * an affiliate of Cypress Semiconductor Corporation
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @brief ADC driver for Infineon CAT1 MCU family.
10  */
11 
12 #define DT_DRV_COMPAT infineon_cat1_pwm
13 
14 #include <zephyr/drivers/pwm.h>
15 #include <zephyr/drivers/pinctrl.h>
16 
17 #include <cy_tcpwm_pwm.h>
18 #include <cy_gpio.h>
19 #include <cy_sysclk.h>
20 #include <cyhal_hw_resources.h>
21 #include <cyhal_hw_types.h>
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(pwm_ifx_cat1, CONFIG_PWM_LOG_LEVEL);
25 
26 #define PWM_REG_BASE TCPWM0
27 
28 struct ifx_cat1_pwm_data {
29 	uint32_t pwm_num;
30 };
31 
32 struct ifx_cat1_pwm_config {
33 	TCPWM_GRP_CNT_Type *reg_addr;
34 	const struct pinctrl_dev_config *pcfg;
35 	bool resolution_32_bits;
36 	cy_en_divider_types_t divider_type;
37 	uint32_t divider_sel;
38 	uint32_t divider_val;
39 };
40 
ifx_cat1_pwm_init(const struct device * dev)41 static int ifx_cat1_pwm_init(const struct device *dev)
42 {
43 	struct ifx_cat1_pwm_data *data = dev->data;
44 	const struct ifx_cat1_pwm_config *config = dev->config;
45 	cy_en_tcpwm_status_t status;
46 	int ret;
47 	uint32_t addr_offset = (uint32_t)config->reg_addr - TCPWM0_BASE;
48 	uint32_t clk_connection;
49 
50 	const cy_stc_tcpwm_pwm_config_t pwm_config = {
51 		.pwmMode = CY_TCPWM_PWM_MODE_PWM,
52 		.clockPrescaler = CY_TCPWM_PWM_PRESCALER_DIVBY_1,
53 		.pwmAlignment = CY_TCPWM_PWM_LEFT_ALIGN,
54 		.runMode = CY_TCPWM_PWM_CONTINUOUS,
55 		.countInputMode = CY_TCPWM_INPUT_LEVEL,
56 		.countInput = CY_TCPWM_INPUT_1,
57 	};
58 
59 	/* Configure PWM clock */
60 	Cy_SysClk_PeriphDisableDivider(config->divider_type, config->divider_sel);
61 	Cy_SysClk_PeriphSetDivider(config->divider_type, config->divider_sel, config->divider_val);
62 	Cy_SysClk_PeriphEnableDivider(config->divider_type, config->divider_sel);
63 
64 	/* This is very specific to the cyw920829m2evk_02 and may need to be modified
65 	 * for other boards.
66 	 */
67 	if (addr_offset < sizeof(TCPWM_GRP_Type)) {
68 		clk_connection =
69 			PCLK_TCPWM0_CLOCK_COUNTER_EN0 + (addr_offset / sizeof(TCPWM_GRP_CNT_Type));
70 	} else {
71 		addr_offset -= sizeof(TCPWM_GRP_Type);
72 		clk_connection = PCLK_TCPWM0_CLOCK_COUNTER_EN256 +
73 				 (addr_offset / sizeof(TCPWM_GRP_CNT_Type));
74 	}
75 	Cy_SysClk_PeriphAssignDivider(clk_connection, config->divider_type, config->divider_sel);
76 
77 	ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
78 	if (ret < 0) {
79 		return ret;
80 	}
81 
82 	/* Configure the TCPWM to be a PWM */
83 	data->pwm_num += addr_offset / sizeof(TCPWM_GRP_CNT_Type);
84 	status = Cy_TCPWM_PWM_Init(PWM_REG_BASE, data->pwm_num, &pwm_config);
85 	if (status != CY_TCPWM_SUCCESS) {
86 		return -ENOTSUP;
87 	}
88 
89 	return 0;
90 }
91 
ifx_cat1_pwm_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)92 static int ifx_cat1_pwm_set_cycles(const struct device *dev, uint32_t channel,
93 				   uint32_t period_cycles, uint32_t pulse_cycles, pwm_flags_t flags)
94 {
95 	struct ifx_cat1_pwm_data *data = dev->data;
96 	const struct ifx_cat1_pwm_config *config = dev->config;
97 
98 	if (!config->resolution_32_bits &&
99 	    ((period_cycles > UINT16_MAX) || (pulse_cycles > UINT16_MAX))) {
100 		/* 16-bit resolution */
101 		if (period_cycles > UINT16_MAX) {
102 			LOG_ERR("Period cycles more than 16-bits (%u)", period_cycles);
103 		}
104 		if (pulse_cycles > UINT16_MAX) {
105 			LOG_ERR("Pulse cycles more than 16-bits (%u)", pulse_cycles);
106 		}
107 		return -EINVAL;
108 	}
109 
110 	if ((period_cycles == 0) || (pulse_cycles == 0)) {
111 		Cy_TCPWM_PWM_Disable(PWM_REG_BASE, data->pwm_num);
112 	} else {
113 		Cy_TCPWM_PWM_SetPeriod0(PWM_REG_BASE, data->pwm_num, period_cycles);
114 		Cy_TCPWM_PWM_SetCompare0Val(PWM_REG_BASE, data->pwm_num, pulse_cycles);
115 
116 		if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED) {
117 			config->reg_addr->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE_Msk;
118 			config->reg_addr->CTRL |= _VAL2FLD(TCPWM_GRP_CNT_V2_CTRL_QUAD_ENCODING_MODE,
119 							   CY_TCPWM_PWM_INVERT_ENABLE);
120 		}
121 
122 		/* TODO: Add 2-bit field to top 8 bits of pwm_flags_t to set this.
123 		 * #define    CY_TCPWM_PWM_OUTPUT_HIGHZ    (0U)
124 		 * #define    CY_TCPWM_PWM_OUTPUT_RETAIN   (1U)
125 		 * #define    CY_TCPWM_PWM_OUTPUT_LOW      (2U)
126 		 * #define    CY_TCPWM_PWM_OUTPUT_HIGH     (3U)
127 		 * if ((flags & __) == __) {
128 		 *	config->reg_addr->CTRL &= ~TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE_Msk;
129 		 *	config->reg_addr->CTRL |= _VAL2FLD(TCPWM_GRP_CNT_V2_CTRL_PWM_DISABLE_MODE,
130 		 *					   __);
131 		 * }
132 		 */
133 
134 		/* Enable the TCPWM for PWM mode of operation */
135 		Cy_TCPWM_PWM_Enable(PWM_REG_BASE, data->pwm_num);
136 
137 		/* Start the TCPWM block */
138 		Cy_TCPWM_TriggerStart_Single(PWM_REG_BASE, data->pwm_num);
139 	}
140 
141 	return 0;
142 }
143 
ifx_cat1_pwm_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)144 static int ifx_cat1_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel,
145 					   uint64_t *cycles)
146 {
147 	const struct ifx_cat1_pwm_config *config = dev->config;
148 
149 	*cycles = Cy_SysClk_PeriphGetFrequency(config->divider_type, config->divider_sel);
150 
151 	return 0;
152 }
153 
154 static DEVICE_API(pwm, ifx_cat1_pwm_api) = {
155 	.set_cycles = ifx_cat1_pwm_set_cycles,
156 	.get_cycles_per_sec = ifx_cat1_pwm_get_cycles_per_sec,
157 };
158 
159 #define INFINEON_CAT1_PWM_INIT(n)                                                                  \
160 	PINCTRL_DT_INST_DEFINE(n);                                                                 \
161                                                                                                    \
162 	static struct ifx_cat1_pwm_data pwm_cat1_data_##n;                                         \
163                                                                                                    \
164 	static struct ifx_cat1_pwm_config pwm_cat1_config_##n = {                                  \
165 		.reg_addr = (TCPWM_GRP_CNT_Type *)DT_INST_REG_ADDR(n),                             \
166 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                         \
167 		.resolution_32_bits = (DT_INST_PROP(n, resolution) == 32) ? true : false,          \
168 		.divider_type = DT_INST_PROP(n, divider_type),                                     \
169 		.divider_sel = DT_INST_PROP(n, divider_sel),                                       \
170 		.divider_val = DT_INST_PROP(n, divider_val),                                       \
171 	};                                                                                         \
172                                                                                                    \
173 	DEVICE_DT_INST_DEFINE(n, ifx_cat1_pwm_init, NULL, &pwm_cat1_data_##n,                      \
174 			      &pwm_cat1_config_##n, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY,         \
175 			      &ifx_cat1_pwm_api);
176 
177 DT_INST_FOREACH_STATUS_OKAY(INFINEON_CAT1_PWM_INIT)
178