1 /*
2  * Copyright (c) 2006-2024, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2024-02-26     Yilin Sun    Initial version.
9  */
10 
11 #include <rtthread.h>
12 #include <rtdevice.h>
13 
14 #include "fsl_ctimer.h"
15 
16 #define MCX_PWM_MATCH_COUNT 4
17 
18 #ifdef RT_USING_PWM
19 
20 typedef struct
21 {
22     struct rt_device_pwm pwm_device;
23     CTIMER_Type *ct_instance;
24     uint32_t counter_period_ps;
25 } mcx_pwm_obj_t;
26 
27 static CTIMER_Type *mcx_pwm_instances[] = CTIMER_BASE_PTRS;
28 
29 static mcx_pwm_obj_t mcx_pwm_list[ARRAY_SIZE(mcx_pwm_instances)];
30 
mcx_pwm_current_period_channel(mcx_pwm_obj_t * pwm)31 static int mcx_pwm_current_period_channel(mcx_pwm_obj_t *pwm)
32 {
33     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
34 
35     uint32_t mcr = ct->MCR;
36 
37     for (uint8_t i = 0; i < 4; i++)
38     {
39         if (mcr & (1U << (3 * i + CTIMER_MCR_MR0R_SHIFT)))
40         {
41             return i;
42         }
43     }
44 
45     return -1;
46 }
47 
mcx_pwm_first_free_channel(mcx_pwm_obj_t * pwm)48 static int mcx_pwm_first_free_channel(mcx_pwm_obj_t *pwm)
49 {
50     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
51 
52     int pc = mcx_pwm_current_period_channel(pwm);
53 
54     uint32_t pwmc = ct->PWMC;
55 
56     for (uint8_t i = 0; i < 4; i++)
57     {
58         if (pwmc & (1U << i))
59         {
60             /* Skip this channel if there's an active PWM output */
61             continue;
62         }
63 
64         if (i == pc)
65         {
66             /* Skip this channel if this channel is the current period channel */
67             continue;
68         }
69 
70         return i;
71     }
72 
73     /* There are no free channels left. */
74     return -1;
75 }
76 
mcx_pwm_period_set(mcx_pwm_obj_t * pwm,uint32_t period_ns)77 static int mcx_pwm_period_set(mcx_pwm_obj_t *pwm, uint32_t period_ns)
78 {
79     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
80 
81     int p_channel = mcx_pwm_current_period_channel(pwm);
82     if (p_channel < 0)
83     {
84         return -EINVAL;
85     }
86 
87     /* Store new values in shadow registers */
88     ct->MSR[p_channel] = period_ns * 1000 / pwm->counter_period_ps;
89 
90     /* Enable period channel interrupt to check a reload event occurs.
91      * Since interrupts are not configured from NVIC, so no ISR will occur.
92      * Check IR[MRnINT] for reload point.
93      */
94     uint32_t mcr_mask = (CTIMER_MCR_MR0RL_MASK << p_channel) | (CTIMER_MCR_MR0I_MASK << (3 * p_channel));
95 
96     for (uint8_t i = 0; i < 4; i++)
97     {
98         if (ct->PWMC & (1U << i))
99         {
100             /* Channel PWM output is enabled, calculate new values and store into shadow registers */
101             uint32_t new_mr = ct->MR[i] * ct->MSR[p_channel] / ct->MR[p_channel];
102             ct->MSR[i] = new_mr;
103 
104             /* Update MRnRL map */
105             mcr_mask |= CTIMER_MCR_MR0RL_MASK << i;
106         }
107     }
108 
109     /* Reload MRs on next counter reset, enable reload MR interrupt */
110     ct->MCR |= mcr_mask;
111 
112     while ((ct->IR & (CTIMER_IR_MR0INT_MASK << p_channel)) == 0U)
113     {
114         /* -- */
115     }
116 
117     /* Disable reload channel interrupt and MSR synchronization */
118     ct->MCR &= ~mcr_mask;
119 
120     /* Clear interrupt flags. */
121     ct->IR |= (CTIMER_IR_MR0INT_MASK << p_channel);
122 
123     return 0;
124 }
125 
mcx_pwm_period_get(mcx_pwm_obj_t * pwm,uint32_t * period_ns)126 static int mcx_pwm_period_get(mcx_pwm_obj_t *pwm, uint32_t *period_ns)
127 {
128     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
129 
130     int p_channel = mcx_pwm_current_period_channel(pwm);
131     if (p_channel < 0)
132     {
133         return -1;
134     }
135 
136     *period_ns = ct->MR[p_channel] * pwm->counter_period_ps / 1000;
137 
138     return 0;
139 }
140 
mcx_pwm_pulse_set(mcx_pwm_obj_t * pwm,uint8_t channel,uint32_t pulse_ns)141 static int mcx_pwm_pulse_set(mcx_pwm_obj_t *pwm, uint8_t channel, uint32_t pulse_ns)
142 {
143     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
144 
145     int p_channel = mcx_pwm_current_period_channel(pwm);
146     if (p_channel < 0)
147     {
148         return -1;
149     }
150 
151     /* Up-counting counters, the polarity is inversed */
152     ct->MSR[channel] = ct->MR[p_channel] - pulse_ns * 1000 / pwm->counter_period_ps;
153 
154     /* Reload MRn on the next cycle */
155     ct->MCR |= (CTIMER_MCR_MR0RL_MASK << channel);
156 
157     /* Wait for new duty cycle loaded into the MRn */
158     while (ct->MR[channel] != ct->MSR[channel])
159     {
160         /* -- */
161     }
162 
163     /* Disable shadow register updates */
164     ct->MCR &= ~(CTIMER_MCR_MR0RL_MASK << channel);
165 
166     return 0;
167 }
168 
mcx_pwm_pulse_get(mcx_pwm_obj_t * pwm,uint8_t channel,uint32_t * pulse_ns)169 static int mcx_pwm_pulse_get(mcx_pwm_obj_t *pwm, uint8_t channel, uint32_t *pulse_ns)
170 {
171     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
172 
173     int p_channel = mcx_pwm_current_period_channel(pwm);
174     if (p_channel < 0)
175     {
176         return -1;
177     }
178 
179     /* Up-counting counters, the polarity is inversed */
180     *pulse_ns = (ct->MR[p_channel] - ct->MR[channel]) * pwm->counter_period_ps / 1000;
181 
182     return 0;
183 }
184 
mcx_drv_pwm_get(mcx_pwm_obj_t * pwm,struct rt_pwm_configuration * configuration)185 static rt_err_t mcx_drv_pwm_get(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
186 {
187     if (mcx_pwm_period_get(pwm, &configuration->period) < 0)
188     {
189         return -RT_EFAULT;
190     }
191 
192     if (mcx_pwm_pulse_get(pwm, configuration->channel, &configuration->pulse) < 0)
193     {
194         return -RT_EFAULT;
195     }
196 
197     return RT_EOK;
198 }
199 
mcx_drv_pwm_set(mcx_pwm_obj_t * pwm,struct rt_pwm_configuration * configuration)200 static rt_err_t mcx_drv_pwm_set(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
201 {
202     CTIMER_Type *ct = pwm->ct_instance;
203 
204     uint32_t period = configuration->period * 1000 / pwm->counter_period_ps;
205     uint8_t channel = configuration->channel;
206 
207     if ((ct->TCR & CTIMER_TCR_CEN_MASK) == 0U)
208     {
209         /* There's two conditions for a all-zero TCR: either a reset condition or timer is stopped. */
210         /* In either case, we need to initialize the timer instance (AHB RST CTRL). */
211 
212         /* TODO: Do not use SDK functions */
213 
214         ctimer_config_t ct_cfg =
215         {
216             .mode = kCTIMER_TimerMode,
217             .prescale = 1U,
218         };
219 
220         /* Frequency: 150MHz max., we got 32bit counters, we can take that. */
221         /* Approx. maximum period: 28.6 seconds. */
222 
223         CTIMER_Init(ct, &ct_cfg);
224 
225         /* Current timer is not running, we are the first channel being configured. */
226 
227         ct->TC = 0U;   /* Reset counter */
228         ct->PC = 0U;   /* Reset prescaler counter */
229         ct->PR = 0U;   /* Prescaler, divide by 1 to get best resolution */
230         ct->MCR = 0U;  /* Reset interrupt and reset condition */
231         ct->EMR = 0U;  /* Do nothing on match event and output 0 as default state */
232         ct->PWMC = 0U; /* Disable all PWM channels, outputs will be controlled by EMn */
233 
234         /* Here, we have a favoritism of using channel 3 as period channel, unless channel 3 is used for output */
235         if (channel != 3)
236         {
237             ct->MR[3] = period;
238             ct->MCR |= CTIMER_MCR_MR3R_MASK;
239         }
240         else
241         {
242             /* Use channel 2 as period channel. */
243             ct->MR[2] = period;
244             ct->MCR |= CTIMER_MCR_MR2R_MASK;
245         }
246 
247         /* Start counter */
248         ct->TCR |= CTIMER_TCR_CEN_MASK;
249     }
250     else
251     {
252         /*
253          * Due to the nature of the CTimer, one of the 4 match channels is needed for period control (frequency)
254          * To find out which one is the current period channel, check the MRxR bit for each match output.
255          * If we are configuring the same match being used as periodic channel, configure the next free match as period
256          * then current channel can be re-used. If all 4 channels are in use then the function will fail with an errno.
257          */
258 
259         /* The timer is running, check whether we need to re-locate the period channel */
260         int p_channel = mcx_pwm_current_period_channel(pwm);
261         if (p_channel < 0)
262         {
263             return -RT_EINVAL;
264         }
265 
266         if (p_channel == channel)
267         {
268             /* We need to re-locate the period channel */
269 
270             int f_channel = mcx_pwm_first_free_channel(pwm);
271             if (f_channel < 0)
272             {
273                 /* There's no free channel, bail out. */
274                 return -RT_EBUSY;
275             }
276 
277             /* Transfer the period channel to first free channel */
278 
279             /* Step 1: Copy current period to first free channel */
280             ct->MR[f_channel] = ct->MR[p_channel];
281 
282             /* Step 2: Enable reset for new period channel */
283             /* Note: it's safe doing it here since both old and new channel MRs contains same value */
284             ct->MCR |= (CTIMER_MCR_MR0R_MASK << (3 * f_channel));
285 
286             /* Step 3: Disable reset for old period channel */
287             ct->MCR &= ~(CTIMER_MCR_MR0R_MASK << (3 * p_channel));
288 
289             /* The old period channel is now available for PWM output */
290             p_channel = f_channel;
291         }
292 
293         if (mcx_pwm_period_set(pwm, configuration->period) < 0)
294         {
295             return -RT_EINVAL;
296         }
297     }
298 
299     if (mcx_pwm_pulse_set(pwm, channel, configuration->pulse) < 0)
300     {
301         return -RT_EINVAL;
302     }
303 
304     return 0;
305 }
306 
mcx_drv_pwm_enable(mcx_pwm_obj_t * pwm,struct rt_pwm_configuration * configuration)307 static rt_err_t mcx_drv_pwm_enable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
308 {
309     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
310 
311     ct->PWMC |= (1U << configuration->channel);
312 
313     return 0;
314 }
315 
mcx_drv_pwm_disable(mcx_pwm_obj_t * pwm,struct rt_pwm_configuration * configuration)316 static rt_err_t mcx_drv_pwm_disable(mcx_pwm_obj_t *pwm, struct rt_pwm_configuration *configuration)
317 {
318     CTIMER_Type *ct = (CTIMER_Type *)pwm->ct_instance;
319 
320     ct->PWMC &= ~(1U << configuration->channel);
321 
322     return 0;
323 }
324 
mcx_drv_pwm_control(struct rt_device_pwm * device,int cmd,void * args)325 static rt_err_t mcx_drv_pwm_control(struct rt_device_pwm *device, int cmd, void *args)
326 {
327     mcx_pwm_obj_t *pwm = device->parent.user_data;
328     struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)args;
329 
330     switch (cmd)
331     {
332     case PWM_CMD_ENABLE:
333         return mcx_drv_pwm_enable(pwm, configuration);
334 
335     case PWM_CMD_DISABLE:
336         return mcx_drv_pwm_disable(pwm, configuration);
337 
338     case PWM_CMD_SET:
339         return mcx_drv_pwm_set(pwm, configuration);
340 
341     case PWM_CMD_GET:
342         return mcx_drv_pwm_get(pwm, configuration);
343 
344     default:
345         return -RT_EINVAL;
346     }
347 
348     return RT_EOK;
349 }
350 
351 static struct rt_pwm_ops mcx_pwm_ops =
352 {
353     .control = mcx_drv_pwm_control,
354 };
355 
mcx_pwm_init(void)356 int mcx_pwm_init(void)
357 {
358     rt_err_t ret;
359     char name_buf[8];
360 
361     for (uint8_t i = 0; i < ARRAY_SIZE(mcx_pwm_instances); i++)
362     {
363         mcx_pwm_list[i].ct_instance = mcx_pwm_instances[i];
364         mcx_pwm_list[i].counter_period_ps = 1000000000000ULL / CLOCK_GetCTimerClkFreq(i);
365 
366         rt_snprintf(name_buf, sizeof(name_buf), "pwm%d", i);
367 
368         ret = rt_device_pwm_register(&mcx_pwm_list[i].pwm_device, name_buf, &mcx_pwm_ops, &mcx_pwm_list[i]);
369         if (ret != RT_EOK)
370         {
371             return ret;
372         }
373     }
374 
375     return RT_EOK;
376 }
377 
378 INIT_DEVICE_EXPORT(mcx_pwm_init);
379 
380 #endif /* RT_USING_PWM */
381