1 /***************************************************************************//**
2 * \file cyhal_pwm.c
3 *
4 * \brief
5 * Provides a high level interface for interacting with the Infineon PWM. This is
6 * a wrapper around the lower level PDL API.
7 *
8 ********************************************************************************
9 * \copyright
10 * Copyright 2018-2022 Cypress Semiconductor Corporation (an Infineon company) or
11 * an affiliate of Cypress Semiconductor Corporation
12 *
13 * SPDX-License-Identifier: Apache-2.0
14 *
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 *     http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 *******************************************************************************/
27 
28 /**
29  * \addtogroup group_hal_impl_pwm PWM (Pulse Width Modulator)
30  * \ingroup group_hal_impl
31  * \{
32  * \section section_hal_impl_pwm_compl_pins Complementary PWM output
33  * The PWM HAL driver allows generation of a normal and an inverted output. PSoC™ devices support
34  * complementary pin pairs to which the normal and inverted signals can be routed. To identify
35  * the complementary pin for a given pin, open the PSoC™ device datasheet and navigate to the
36  * 'Multiple Alternate Functions' table. Each column represents an alternate function of the pin
37  * in the corresponding row. Find your pin and make a note of the tcpwm[X].line[Y]:Z. The
38  * complementary pin is found by looking up the pin against tcpwm[X].line_<b>compl</b>[Y]:Z from
39  * the same column. For example, the image below shows a pair of complementary pins (P0.0 and P0.1)
40  * identified by the tcpwm[0].line[0]:0 and tcpwm[0].line_compl[0]:0 mapping. These complementary
41  * pins can be supplied to \ref cyhal_pwm_init_adv using <b>pin</b> and <b>compl_pin</b> parameters
42  * in any order. \image html pwm_compl_pins.png "Complementary PWM pins"
43  *
44  * \section section_psoc6_pwm_resolution PWM Resolution
45  * On CAT1 (PSoC™ 6) devices, not all PWMs hardware blocks are of the same resolution. The
46  * resolution of the PWM associated with a given pin is specified by the `TCPWM<idx>_CNT_CNT_WIDTH`
47  * macro (provided by cy_device_headers.h in mtb-pdl-cat1), where `<idx>` is the index associated
48  * with the `tcpwm` portion of the entry in the pin function table. For example, if the pin
49  * function is `tcpwm[1].line[3]:Z`, `<idx>` would be 1.
50  *
51  * By default, the PWM HAL driver will configure the input clock frequency such that all PWM
52  * instances are able to provide the same maximum period regardless of the underlying resolution,
53  * but period and duty cycle can be specified with reduced granularity on lower-resolution PWM
54  * instances. If an application is more sensitive to PWM precision than maximum period, or if a
55  * longer maximum period is required (with correspondingly reduced precision), it is possible to
56  * override the default clock by passing a \ref cyhal_clock_t instance to the
57  * \ref cyhal_pwm_init function with a custom frequency specified. See the \ref group_hal_clock
58  * HAL documentation for more details.
59  *
60  * \} group_hal_impl_pwm
61  */
62 
63 #include <string.h>
64 
65 #include "cyhal_clock.h"
66 #include "cyhal_gpio.h"
67 #include "cyhal_pwm.h"
68 #include "cyhal_interconnect.h"
69 #include "cyhal_syspm.h"
70 #include "cyhal_utils.h"
71 
72 #if (CYHAL_DRIVER_AVAILABLE_PWM)
73 
74 #if defined(__cplusplus)
75 extern "C" {
76 #endif
77 
78 #if defined(CY_IP_MXTCPWM_INSTANCES)
79 #define _CYHAL_PWM_MAX_WIDTH         32
80 #else
81 #define _CYHAL_PWM_MAX_WIDTH         16
82 #endif
83 #define _CYHAL_PWM_MAX_DEAD_TIME_CYCLES    255
84 static const uint32_t _CYHAL_PWM_US_PER_SEC = 1000000u;
85 
86 #if defined(CY_IP_MXTCPWM) && (CY_IP_MXTCPWM_VERSION >= 2)
87 /** The configuration of PWM output signal for Center and Asymmetric alignment with overflow and underflow swapped */
88 #define _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED (_VAL2FLD(TCPWM_GRP_CNT_V2_TR_PWM_CTRL_CC0_MATCH_MODE, CY_TCPWM_PWM_TR_CTRL2_INVERT) | \
89                                          _VAL2FLD(TCPWM_GRP_CNT_V2_TR_PWM_CTRL_OVERFLOW_MODE, CY_TCPWM_PWM_TR_CTRL2_CLEAR) | \
90                                          _VAL2FLD(TCPWM_GRP_CNT_V2_TR_PWM_CTRL_UNDERFLOW_MODE, CY_TCPWM_PWM_TR_CTRL2_SET))
91 #else
92 /** The configuration of PWM output signal for Center and Asymmetric alignment with overflow and underflow swapped */
93 #define _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED (_VAL2FLD(TCPWM_CNT_TR_CTRL2_CC_MATCH_MODE, CY_TCPWM_PWM_TR_CTRL2_INVERT) | \
94                                          _VAL2FLD(TCPWM_CNT_TR_CTRL2_OVERFLOW_MODE, CY_TCPWM_PWM_TR_CTRL2_CLEAR) | \
95                                          _VAL2FLD(TCPWM_CNT_TR_CTRL2_UNDERFLOW_MODE, CY_TCPWM_PWM_TR_CTRL2_SET))
96 #endif
97 
98 
_cyhal_pwm_convert_alignment(cyhal_pwm_alignment_t hal_alignment,uint32_t * pdl_alignment,bool swapped)99 static cy_rslt_t _cyhal_pwm_convert_alignment(cyhal_pwm_alignment_t hal_alignment, uint32_t *pdl_alignment, bool swapped)
100 {
101     switch (hal_alignment)
102     {
103         case CYHAL_PWM_LEFT_ALIGN:
104             *pdl_alignment = (swapped) ? CY_TCPWM_PWM_RIGHT_ALIGN : CY_TCPWM_PWM_LEFT_ALIGN;
105             return CY_RSLT_SUCCESS;
106         case CYHAL_PWM_RIGHT_ALIGN:
107             *pdl_alignment = (swapped) ? CY_TCPWM_PWM_LEFT_ALIGN : CY_TCPWM_PWM_RIGHT_ALIGN;
108             return CY_RSLT_SUCCESS;
109         case CYHAL_PWM_CENTER_ALIGN:
110             *pdl_alignment = CY_TCPWM_PWM_CENTER_ALIGN;
111             return CY_RSLT_SUCCESS;
112         default:
113             return CYHAL_PWM_RSLT_BAD_ARGUMENT;
114     }
115 }
116 
cyhal_pwm_set_period_and_compare(cyhal_pwm_t * obj,uint32_t period,uint32_t compare)117 static cy_rslt_t cyhal_pwm_set_period_and_compare(cyhal_pwm_t *obj, uint32_t period, uint32_t compare)
118 {
119     if (period < 1 || period > (uint32_t)((1 << _CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].max_count)) - 1)
120         return CYHAL_PWM_RSLT_BAD_ARGUMENT;
121 
122     cyhal_gpio_t pin = obj->pin;
123     cyhal_gpio_t pin_compl = obj->pin_compl;
124 
125     Cy_TCPWM_PWM_SetCompare0(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource), 0u);
126     Cy_TCPWM_PWM_SetPeriod0(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource), period - 1u);
127 
128     #if defined(CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL)
129     bool swapped_pins =
130         (NC == pin || (_CYHAL_UTILS_GET_RESOURCE_INST(pin, cyhal_pin_map_tcpwm_line_compl, &obj->tcpwm.resource) != NULL)) &&
131         (NC == pin_compl || (_CYHAL_UTILS_GET_RESOURCE_INST(pin_compl, cyhal_pin_map_tcpwm_line, &obj->tcpwm.resource) != NULL));
132     #else
133     CY_UNUSED_PARAMETER(pin);
134     CY_UNUSED_PARAMETER(pin_compl);
135     bool swapped_pins = false;
136     #endif /* defined(CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL) */
137 
138     #if defined(CY_IP_MXTCPWM) && (CY_IP_MXTCPWM_VERSION >= 2)
139     uint32_t pwm_ctrl_reg = TCPWM_GRP_CNT_TR_PWM_CTRL(obj->tcpwm.base, _CYHAL_TCPWM_GET_GRP(obj->tcpwm.resource.block_num),
140                                 _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
141     uint32_t cc1_ignore_mask = (0 == _CYHAL_TCPWM_GET_GRP(obj->tcpwm.resource.block_num)) ?
142                                 0 : CY_TCPWM_PWM_MODE_CC1_IGNORE;
143     bool is_center_aligned = (pwm_ctrl_reg == (CY_TCPWM_PWM_MODE_CNTR_OR_ASYMM | cc1_ignore_mask)) ||
144                                 (pwm_ctrl_reg == (_CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED | cc1_ignore_mask));
145     #else
146     bool is_center_aligned = (TCPWM_CNT_TR_CTRL2(obj->tcpwm.base, obj->tcpwm.resource.channel_num) == CY_TCPWM_PWM_MODE_CNTR_OR_ASYMM) ||
147                                 (TCPWM_CNT_TR_CTRL2(obj->tcpwm.base, obj->tcpwm.resource.channel_num) == _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED);
148     #endif /* CY_IP_MXTCPWM_VERSION >= 2 or other */
149 
150     uint32_t new_compare_value = (swapped_pins && !is_center_aligned)
151         ? period - compare
152         : compare;
153 
154     if (new_compare_value >= period)
155         new_compare_value = period - 1;
156 
157     Cy_TCPWM_PWM_SetCompare0(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource), new_compare_value);
158     if (Cy_TCPWM_PWM_GetCounter(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource)) >= new_compare_value)
159     {
160         Cy_TCPWM_PWM_SetCounter(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource), 0);
161     }
162 
163     return CY_RSLT_SUCCESS;
164 }
165 
_cyhal_pwm_update_clock_freq(cyhal_pwm_t * obj,uint32_t period_us)166 static cy_rslt_t _cyhal_pwm_update_clock_freq(cyhal_pwm_t *obj, uint32_t period_us)
167 {
168     cy_rslt_t result = CY_RSLT_SUCCESS;
169     /* If we don't own the clock, it is up to the application to pick a suitable divider.
170      * If a non-zero dead time was set, the required clock is calculated to facilitate the requested
171      * dead time, which does not change when we change the requested period */
172     if(obj->tcpwm.dedicated_clock && false == obj->dead_time_set)
173     {
174         uint32_t source_hz = _cyhal_utils_get_peripheral_clock_frequency(&(obj->tcpwm.resource));
175         // Pick the highest frequency that will let us achieve the requested period, for maximum granularity
176         uint64_t max_period_counts = (uint64_t)((uint64_t)1u << _CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].max_count);
177         /* Round down to make sure we err on the side of handling longer than the requested period */
178         /* Conceptually this is:
179          * counts_per_us = max_period_counts / period_us
180          * desired_freq_hz = counts_per_us * 1e6
181          * But if we break those out separately, we lose too much precision to rounding in counts_per_us
182          */
183         uint64_t desired_freq_hz = (uint64_t)((max_period_counts * 1000000) / period_us);
184 
185         if(desired_freq_hz > source_hz)
186         {
187             /* The source clock is well below that max frequency that would permit the requested period, so just
188              * use the source clock as-is */
189             desired_freq_hz = source_hz;
190         }
191         /* Always round the divider up because it's better to err on the side of a slower clock, ensuring that we can always
192          * meet the requested period at the possible expense of granularity */
193         uint32_t div = (uint32_t)((source_hz + (desired_freq_hz - 1))/ desired_freq_hz);
194         /* When we allocate a dedicated clock divider we ask for 16-bit (or greater). For consistent behavior, always limit the frequency
195          * to what a 16-bit divider can handle */
196         if(div > (((uint32_t)1u) << 16)) /* Don't use UINT16_MAX. A 16-bit divider can truly support up to a 2^16 divide ratio */
197         {
198             div = ((uint32_t)1u) << 16;
199         }
200         en_clk_dst_t pclk = (en_clk_dst_t)(_CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].clock_dst + obj->tcpwm.resource.channel_num);
201         result = _cyhal_utils_peri_pclk_set_divider(pclk, &(obj->tcpwm.clock), div - 1);
202         if(CY_RSLT_SUCCESS == result)
203         {
204             obj->tcpwm.clock_hz = cyhal_clock_get_frequency(&obj->tcpwm.clock);
205         }
206     }
207     return result;
208 }
209 
_cyhal_pwm_init_clock(cyhal_pwm_t * obj,uint32_t dead_time_us,const cyhal_clock_t * clk)210 static cy_rslt_t _cyhal_pwm_init_clock(cyhal_pwm_t *obj, uint32_t dead_time_us, const cyhal_clock_t* clk)
211 {
212     cy_rslt_t result = CY_RSLT_SUCCESS;
213     en_clk_dst_t pclk = (en_clk_dst_t)(_CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].clock_dst + obj->tcpwm.resource.channel_num);
214     if (NULL != clk)
215     {
216         obj->tcpwm.clock = *clk;
217         if (CY_SYSCLK_SUCCESS != _cyhal_utils_peri_pclk_assign_divider(pclk, &(obj->tcpwm.clock)))
218         {
219             result = CYHAL_PWM_RSLT_FAILED_CLOCK_INIT;
220         }
221     }
222     else if (CY_RSLT_SUCCESS == (result = _cyhal_utils_allocate_clock(&obj->tcpwm.clock, &obj->tcpwm.resource, CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT, true)))
223     {
224         obj->tcpwm.dedicated_clock = true;
225         uint32_t source_hz = _cyhal_utils_get_peripheral_clock_frequency(&(obj->tcpwm.resource));
226         uint32_t div = 0;
227         if (dead_time_us > 0)
228         {
229             div = (((uint64_t)source_hz * dead_time_us) / (_CYHAL_PWM_US_PER_SEC * _CYHAL_PWM_MAX_DEAD_TIME_CYCLES)) + 1;
230             #if defined (COMPONENT_CAT5)
231                 /* TODO: dividers are limited on this device. Need to investigate a better solution.
232                  * Refer to TCPWM_TPORT_CLK_DIV_SEL_t. Below is an approximation that will hopefully
233                    give good enough scale when configuring the PWM in the configure() function.
234                  */
235                 if(( div % 2 != 0) && ( div != 1 ) && ( div < 12 ))
236                 {
237                     div++;
238                 }
239                 else if ((div > 12) && (div < 16))
240                 {
241                     div = 16;
242                 }
243                 else if (div > 16)
244                 {
245                     div = 32; // Max allowed
246                 }
247             #endif
248         }
249         else
250         {
251             div = 1; /* Will be adjusted as necessary when the period is set */
252         }
253 
254         if (0 == div ||
255             CY_SYSCLK_SUCCESS != _cyhal_utils_peri_pclk_set_divider(pclk, &(obj->tcpwm.clock), div - 1) ||
256             CY_SYSCLK_SUCCESS != _cyhal_utils_peri_pclk_enable_divider(pclk, &(obj->tcpwm.clock)) ||
257             CY_SYSCLK_SUCCESS != _cyhal_utils_peri_pclk_assign_divider(pclk, &(obj->tcpwm.clock)))
258         {
259             result = CYHAL_PWM_RSLT_FAILED_CLOCK_INIT;
260         }
261     }
262     if (CY_RSLT_SUCCESS == result)
263     {
264         obj->tcpwm.clock_hz = cyhal_clock_get_frequency(&obj->tcpwm.clock);
265     }
266 
267     return result;
268 }
269 
_cyhal_pwm_init_hw(cyhal_pwm_t * obj,const cy_stc_tcpwm_pwm_config_t * config,bool swapped)270 static cy_rslt_t _cyhal_pwm_init_hw(cyhal_pwm_t *obj, const cy_stc_tcpwm_pwm_config_t* config, bool swapped)
271 {
272     cy_rslt_t result = Cy_TCPWM_PWM_Init(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource), config);
273 
274     if ((swapped) && (config->pwmAlignment == CY_TCPWM_PWM_CENTER_ALIGN))
275     {
276         #if defined(CY_IP_MXTCPWM) && (CY_IP_MXTCPWM_VERSION >= 2)
277         uint32_t cntr_asym_swapped_reg_val = (0 == TCPWM_GRP_CNT_GET_GRP(_CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource))) ?
278             /* Group 0 counters does not have CC1 */
279             _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED :
280             _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED | CY_TCPWM_PWM_MODE_CC1_IGNORE;
281         TCPWM_GRP_CNT_TR_PWM_CTRL(obj->tcpwm.base, TCPWM_GRP_CNT_GET_GRP(_CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource)),
282                 _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource)) = cntr_asym_swapped_reg_val;
283         #else
284         TCPWM_CNT_TR_CTRL2(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource)) = _CYHAL_PWM_MODE_CNTR_OR_ASYMM_UO_SWAPPED;
285         #endif /* CY_IP_MXTCPWM_VERSION >= 2 or other */
286     }
287 
288     if (CY_RSLT_SUCCESS == result)
289     {
290         _cyhal_tcpwm_init_data(&obj->tcpwm);
291         obj->dead_time_set = (0u != config->deadTimeClocks);
292         Cy_TCPWM_PWM_Enable(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
293     }
294 
295     return result;
296 }
297 
cyhal_pwm_init_cfg(cyhal_pwm_t * obj,const cyhal_pwm_configurator_t * cfg)298 cy_rslt_t cyhal_pwm_init_cfg(cyhal_pwm_t *obj, const cyhal_pwm_configurator_t *cfg)
299 {
300     obj->tcpwm.resource = *cfg->resource;
301     obj->tcpwm.base     = _CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].base;
302     /* Pass 0 for dead time us because we can't easily calculate that, but that's okay because the
303      * configurator will already have calculated assigned a clock and calculated dead time cycles for us */
304     cy_rslt_t result = _cyhal_pwm_init_clock(obj, 0u, cfg->clock);
305 
306     if(CY_RSLT_SUCCESS == result)
307     {
308         /* Hard-code swapped to false becuase the configurator doesn't support swapping today, and if it adds support
309          * for it in the future it will handle the relevant register manipulations itself */
310         result = _cyhal_pwm_init_hw(obj, cfg->config, false);
311     }
312 
313     if(CY_RSLT_SUCCESS != result)
314     {
315         cyhal_pwm_free(obj);
316     }
317 
318     return result;
319 }
320 
321 
cyhal_pwm_init_adv(cyhal_pwm_t * obj,cyhal_gpio_t pin,cyhal_gpio_t compl_pin,cyhal_pwm_alignment_t pwm_alignment,bool continuous,uint32_t dead_time_us,bool invert,const cyhal_clock_t * clk)322 cy_rslt_t cyhal_pwm_init_adv(cyhal_pwm_t *obj, cyhal_gpio_t pin, cyhal_gpio_t compl_pin, cyhal_pwm_alignment_t pwm_alignment, bool continuous, uint32_t dead_time_us, bool invert, const cyhal_clock_t *clk)
323 {
324     CY_ASSERT(NULL != obj);
325 
326     cy_rslt_t result = CY_RSLT_SUCCESS;
327     memset(obj, 0, sizeof(cyhal_pwm_t));
328     /* Explicitly marked not allocated resources as invalid to prevent freeing them. */
329     obj->tcpwm.resource.type = CYHAL_RSC_INVALID;
330     obj->pin                 = CYHAL_NC_PIN_VALUE;
331     obj->pin_compl           = CYHAL_NC_PIN_VALUE;
332 
333     const cyhal_resource_pin_mapping_t* map = _CYHAL_UTILS_TRY_ALLOC(pin, CYHAL_RSC_TCPWM, cyhal_pin_map_tcpwm_line);
334 
335     bool swapped = false;
336     #if defined(CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL)
337     if (map == NULL)
338     {
339         swapped = true;
340         map = _CYHAL_UTILS_TRY_ALLOC(pin, CYHAL_RSC_TCPWM, cyhal_pin_map_tcpwm_line_compl);
341     }
342     #endif
343 
344     if (map == NULL)
345     {
346         result = CYHAL_PWM_RSLT_BAD_ARGUMENT;
347     }
348     else
349     {
350         _CYHAL_UTILS_ASSIGN_RESOURCE(obj->tcpwm.resource, CYHAL_RSC_TCPWM, map);
351         obj->tcpwm.base     = _CYHAL_TCPWM_DATA[_CYHAL_TCPWM_ADJUST_BLOCK_INDEX(obj->tcpwm.resource.block_num)].base;
352     }
353 
354     if(CY_RSLT_SUCCESS == result)
355     {
356         result = _cyhal_utils_reserve_and_connect(map, (uint8_t)CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE);
357     }
358     if (CY_RSLT_SUCCESS == result)
359     {
360         obj->pin = pin;
361         #if defined (COMPONENT_CAT5)
362             Cy_TCPWM_SelectTrigmuxOutput(TCPWM_TRIGMUX_OUTPUT_LINE_OUT);
363             Cy_TCPWM_SelectInputSignalForWGPOMux(map->channel_num, (TCPWM_LOGIC_TRIGMUX_INPUT_t)(TCPWM_LOGIC_TRIGMUX_WGPO_INPUT_LINE_OUT_0 + map->channel_num));
364         #endif
365     }
366 
367     if (CY_RSLT_SUCCESS == result && NC != compl_pin)
368     {
369     #if defined(CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL)
370         const cyhal_resource_pin_mapping_t *map_compl = swapped
371             ? _cyhal_utils_get_resource(compl_pin, cyhal_pin_map_tcpwm_line,
372                 sizeof(cyhal_pin_map_tcpwm_line) / sizeof(cyhal_resource_pin_mapping_t), &(obj->tcpwm.resource), false)
373             :
374               _cyhal_utils_get_resource(compl_pin, cyhal_pin_map_tcpwm_line_compl,
375                 sizeof(cyhal_pin_map_tcpwm_line_compl) / sizeof(cyhal_resource_pin_mapping_t), &(obj->tcpwm.resource), false);
376 
377         if (NULL == map_compl)
378         {
379             result = CYHAL_PWM_RSLT_BAD_ARGUMENT;
380         }
381         else
382         {
383             result = _cyhal_utils_reserve_and_connect(map_compl, (uint8_t)CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL);
384             if (CY_RSLT_SUCCESS == result)
385             {
386                 obj->pin_compl = compl_pin;
387                 #if defined (COMPONENT_CAT5)
388                     Cy_TCPWM_SelectTrigmuxOutput(TCPWM_TRIGMUX_OUTPUT_LINE_OUT);
389                     Cy_TCPWM_SelectInputSignalForWGPOMux(map_compl->channel_num, (TCPWM_LOGIC_TRIGMUX_INPUT_t)(TCPWM_LOGIC_TRIGMUX_WGPO_INPUT_LINE_OUT_0 + map_compl->channel_num));
390                 #endif
391             }
392         }
393     #else
394         result = CYHAL_PWM_RSLT_BAD_ARGUMENT;
395     #endif /* defined(CYHAL_PIN_MAP_DRIVE_MODE_TCPWM_LINE_COMPL) */
396     }
397 
398     if (CY_RSLT_SUCCESS == result)
399     {
400         result = _cyhal_pwm_init_clock(obj, dead_time_us, clk);
401     }
402 
403     uint32_t pdl_alignment = CY_TCPWM_PWM_LEFT_ALIGN;
404     if (CY_RSLT_SUCCESS == result)
405     {
406         result = _cyhal_pwm_convert_alignment(pwm_alignment, &pdl_alignment, swapped);
407     }
408 
409     if (CY_RSLT_SUCCESS == result)
410     {
411         uint8_t dead_time = (uint8_t)(dead_time_us * obj->tcpwm.clock_hz / _CYHAL_PWM_US_PER_SEC);
412 
413         cy_stc_tcpwm_pwm_config_t pdl_config =
414         {
415             .pwmMode           = (dead_time == 0) ? CY_TCPWM_PWM_MODE_PWM : CY_TCPWM_PWM_MODE_DEADTIME,
416             .clockPrescaler    = CY_TCPWM_PWM_PRESCALER_DIVBY_1,
417             .pwmAlignment      = pdl_alignment,
418             .deadTimeClocks    = dead_time,
419             .runMode           = (continuous) ? CY_TCPWM_PWM_CONTINUOUS : CY_TCPWM_PWM_ONESHOT,
420             .period0           = 0UL,
421             .period1           = 0UL,
422             .enablePeriodSwap  = false,
423             .compare0          = 0UL,
424             .compare1          = 0UL,
425             .enableCompareSwap = false,
426             .interruptSources  = CY_TCPWM_INT_NONE,
427             .invertPWMOut      = (invert) ? CY_TCPWM_PWM_INVERT_ENABLE : CY_TCPWM_PWM_INVERT_DISABLE,
428             .invertPWMOutN     = (invert) ? CY_TCPWM_PWM_INVERT_ENABLE : CY_TCPWM_PWM_INVERT_DISABLE,
429             .killMode          = CY_TCPWM_PWM_STOP_ON_KILL,
430             .swapInputMode     = CY_TCPWM_INPUT_RISINGEDGE,
431             .swapInput         = CY_TCPWM_INPUT_0,
432             .reloadInputMode   = CY_TCPWM_INPUT_RISINGEDGE,
433             .reloadInput       = CY_TCPWM_INPUT_0,
434             .startInputMode    = CY_TCPWM_INPUT_RISINGEDGE,
435             .startInput        = CY_TCPWM_INPUT_0,
436             .killInputMode     = CY_TCPWM_INPUT_RISINGEDGE,
437             .killInput         = CY_TCPWM_INPUT_0,
438             .countInputMode    = CY_TCPWM_INPUT_LEVEL,
439             .countInput        = CY_TCPWM_INPUT_1
440         };
441 
442         result = _cyhal_pwm_init_hw(obj, &pdl_config, swapped);
443     }
444 
445     if (CY_RSLT_SUCCESS != result)
446     {
447         cyhal_pwm_free(obj);
448     }
449 
450     return result;
451 }
452 
cyhal_pwm_free(cyhal_pwm_t * obj)453 void cyhal_pwm_free(cyhal_pwm_t *obj)
454 {
455     CY_ASSERT(NULL != obj);
456     if(false == obj->tcpwm.owned_by_configurator)
457     {
458         _cyhal_utils_release_if_used(&obj->pin);
459         _cyhal_utils_release_if_used(&obj->pin_compl);
460     }
461 
462     _cyhal_tcpwm_free(&obj->tcpwm);
463 }
464 
cyhal_pwm_set_period(cyhal_pwm_t * obj,uint32_t period_us,uint32_t pulse_width_us)465 cy_rslt_t cyhal_pwm_set_period(cyhal_pwm_t *obj, uint32_t period_us, uint32_t pulse_width_us)
466 {
467     CY_ASSERT(NULL != obj);
468     cy_rslt_t result = _cyhal_pwm_update_clock_freq(obj, period_us);
469     if(CY_RSLT_SUCCESS == result)
470     {
471         uint32_t period = (uint32_t)((uint64_t)period_us * obj->tcpwm.clock_hz / _CYHAL_PWM_US_PER_SEC);
472         uint32_t width = (uint32_t)((uint64_t)pulse_width_us * obj->tcpwm.clock_hz / _CYHAL_PWM_US_PER_SEC);
473         result = cyhal_pwm_set_period_and_compare(obj, period, width);
474     }
475     return result;
476 }
477 
cyhal_pwm_set_duty_cycle(cyhal_pwm_t * obj,float duty_cycle,uint32_t frequencyhal_hz)478 cy_rslt_t cyhal_pwm_set_duty_cycle(cyhal_pwm_t *obj, float duty_cycle, uint32_t frequencyhal_hz)
479 {
480     CY_ASSERT(NULL != obj);
481     if (duty_cycle < 0.0f || duty_cycle > 100.0f || frequencyhal_hz < 1)
482         return CYHAL_PWM_RSLT_BAD_ARGUMENT;
483     /* Always round up to make sure we wind up able to meet the requested period */
484     uint32_t period_us = (_CYHAL_PWM_US_PER_SEC + (frequencyhal_hz - 1))/ frequencyhal_hz;
485     cy_rslt_t result = _cyhal_pwm_update_clock_freq(obj, period_us);
486     if(CY_RSLT_SUCCESS == result)
487     {
488         uint32_t period = (obj->tcpwm.clock_hz + (frequencyhal_hz >> 1)) / frequencyhal_hz;
489         uint32_t width = (uint32_t)(duty_cycle * 0.01f * period);
490         result = cyhal_pwm_set_period_and_compare(obj, period, width);
491     }
492     return result;
493 }
494 
cyhal_pwm_start(cyhal_pwm_t * obj)495 cy_rslt_t cyhal_pwm_start(cyhal_pwm_t *obj)
496 {
497     CY_ASSERT(NULL != obj);
498     if (_cyhal_tcpwm_pm_transition_pending())
499     {
500         return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
501     }
502     Cy_TCPWM_PWM_Enable(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
503     #if defined(CY_IP_MXTCPWM) && (CY_IP_MXTCPWM_VERSION >= 2)
504     Cy_TCPWM_TriggerReloadOrIndex_Single(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
505     #else
506     Cy_TCPWM_TriggerReloadOrIndex(obj->tcpwm.base, 1 << _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
507     #endif
508     return CY_RSLT_SUCCESS;
509 }
510 
cyhal_pwm_stop(cyhal_pwm_t * obj)511 cy_rslt_t cyhal_pwm_stop(cyhal_pwm_t *obj)
512 {
513     CY_ASSERT(NULL != obj);
514     Cy_TCPWM_PWM_Disable(obj->tcpwm.base, _CYHAL_TCPWM_CNT_NUMBER(obj->tcpwm.resource));
515     return CY_RSLT_SUCCESS;
516 }
517 
_cyhal_pwm_translate_input_signal(cyhal_pwm_input_t signal)518 static cyhal_tcpwm_input_t _cyhal_pwm_translate_input_signal(cyhal_pwm_input_t signal)
519 {
520     switch(signal)
521     {
522         case CYHAL_PWM_INPUT_START:
523             return CYHAL_TCPWM_INPUT_START;
524         case CYHAL_PWM_INPUT_STOP:
525             return CYHAL_TCPWM_INPUT_STOP;
526         case CYHAL_PWM_INPUT_RELOAD:
527             return CYHAL_TCPWM_INPUT_RELOAD;
528         case CYHAL_PWM_INPUT_COUNT:
529             return CYHAL_TCPWM_INPUT_COUNT;
530         case CYHAL_PWM_INPUT_CAPTURE:
531             return CYHAL_TCPWM_INPUT_CAPTURE;
532         default:
533             CY_ASSERT(false);
534             return (cyhal_tcpwm_input_t)0;
535     }
536 }
537 
_cyhal_pwm_translate_output_signal(cyhal_pwm_output_t signal)538 static cyhal_tcpwm_output_t _cyhal_pwm_translate_output_signal(cyhal_pwm_output_t signal)
539 {
540     switch(signal)
541     {
542         case CYHAL_PWM_OUTPUT_OVERFLOW:
543             return CYHAL_TCPWM_OUTPUT_OVERFLOW;
544         case CYHAL_PWM_OUTPUT_UNDERFLOW:
545             return CYHAL_TCPWM_OUTPUT_UNDERFLOW;
546         case CYHAL_PWM_OUTPUT_COMPARE_MATCH:
547             return CYHAL_TCPWM_OUTPUT_COMPARE_MATCH;
548         case CYHAL_PWM_OUTPUT_LINE_OUT:
549             return CYHAL_TCPWM_OUTPUT_LINE_OUT;
550         default:
551             CY_ASSERT(false);
552             return (cyhal_tcpwm_output_t)0;
553     }
554 }
555 
cyhal_pwm_connect_digital2(cyhal_pwm_t * obj,cyhal_source_t source,cyhal_pwm_input_t signal,cyhal_edge_type_t edge_type)556 cy_rslt_t cyhal_pwm_connect_digital2(cyhal_pwm_t *obj, cyhal_source_t source, cyhal_pwm_input_t signal, cyhal_edge_type_t edge_type)
557 {
558     cyhal_tcpwm_input_t tcpwm_signal = _cyhal_pwm_translate_input_signal(signal);
559     return _cyhal_tcpwm_connect_digital(&(obj->tcpwm), source, tcpwm_signal, edge_type);
560 }
561 
cyhal_pwm_connect_digital(cyhal_pwm_t * obj,cyhal_source_t source,cyhal_pwm_input_t signal)562 cy_rslt_t cyhal_pwm_connect_digital(cyhal_pwm_t *obj, cyhal_source_t source, cyhal_pwm_input_t signal)
563 {
564     /* Signal type just tells us edge vs. level, but TCPWM lets you customize which edge you want. So default
565      * to rising edge. If the application cares about the edge type, it can use connect_digital2 */
566 #if defined(CY_IP_M0S8PERI_TR) || defined(CY_IP_MXPERI_TR) || defined(CY_IP_MXSPERI)
567     cyhal_signal_type_t signal_type = _CYHAL_TRIGGER_GET_SOURCE_TYPE(source);
568     cyhal_edge_type_t edge_type = (signal_type == CYHAL_SIGNAL_TYPE_LEVEL) ? CYHAL_EDGE_TYPE_LEVEL : CYHAL_EDGE_TYPE_RISING_EDGE;
569     return cyhal_pwm_connect_digital2(obj, source, signal, edge_type);
570 #else
571     CY_UNUSED_PARAMETER(obj);
572     CY_UNUSED_PARAMETER(source);
573     CY_UNUSED_PARAMETER(signal);
574     return CYHAL_PWM_RSLT_BAD_ARGUMENT;
575 #endif
576 }
577 
cyhal_pwm_enable_output(cyhal_pwm_t * obj,cyhal_pwm_output_t signal,cyhal_source_t * source)578 cy_rslt_t cyhal_pwm_enable_output(cyhal_pwm_t *obj, cyhal_pwm_output_t signal, cyhal_source_t *source)
579 {
580     cyhal_tcpwm_output_t tcpwm_signal = _cyhal_pwm_translate_output_signal(signal);
581     return _cyhal_tcpwm_enable_output(&(obj->tcpwm), tcpwm_signal, source);
582 }
583 
cyhal_pwm_disconnect_digital(cyhal_pwm_t * obj,cyhal_source_t source,cyhal_pwm_input_t signal)584 cy_rslt_t cyhal_pwm_disconnect_digital(cyhal_pwm_t *obj, cyhal_source_t source, cyhal_pwm_input_t signal)
585 {
586     return _cyhal_tcpwm_disconnect_digital(&(obj->tcpwm), source, _cyhal_pwm_translate_input_signal(signal));
587 }
588 
cyhal_pwm_disable_output(cyhal_pwm_t * obj,cyhal_pwm_output_t signal)589 cy_rslt_t cyhal_pwm_disable_output(cyhal_pwm_t *obj, cyhal_pwm_output_t signal)
590 {
591     return _cyhal_tcpwm_disable_output(&(obj->tcpwm), _cyhal_pwm_translate_output_signal(signal));
592 }
593 
594 #if defined(__cplusplus)
595 }
596 #endif
597 
598 #endif /* CYHAL_DRIVER_AVAILABLE_PWM */
599