Refactor pwm driver; add documentation.
[bertos.git] / bertos / cpu / arm / drv / pwm_at91.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2011 Develer S.r.l. (http://www.develer.com/)
30  *
31  * -->
32  *
33  *
34  * \brief PWM hardware-specific implementation
35  *
36  * \author Daniele Basile <asterix@develer.com>
37  * \author Francesco Sacchi <batt@develer.com>
38  */
39
40 #include <drv/pwm.h>
41 #include "pwm_at91.h"
42 #include <hw/hw_cpufreq.h>
43 #include "cfg/cfg_pwm.h"
44
45 // Define logging setting (for cfg/log.h module).
46 #define LOG_LEVEL         PWM_LOG_LEVEL
47 #define LOG_FORMAT        PWM_LOG_FORMAT
48 #include <cfg/log.h>
49
50 #include <cfg/macros.h>
51 #include <cfg/debug.h>
52
53 #include <io/arm.h>
54 #include <cpu/irq.h>
55
56 #define PWM_HW_MAX_PRESCALER_STEP         10
57 #define PWM_HW_MAX_PERIOD             0xFFFF
58
59 #if CFG_PWM_ENABLE_OLD_API
60         #include "hw/pwm_map.h"
61
62         /**
63          * Register structure for pwm driver.
64          * This array content all data and register pointer
65          * to manage pwm peripheral device.
66          */
67         static PwmChannel pwm_map[PWM_CNT] =
68         {
69                 {//PWM Channel 0
70                         .duty_zero = false,
71                         .pol = false,
72                         .pwm_pin = BV(PWM0),
73                         .mode_reg = &PWM_CMR0,
74                         .duty_reg = &PWM_CDTY0,
75                         .period_reg = &PWM_CPRD0,
76                         .update_reg = &PWM_CUPD0,
77                 },
78                 {//PWM Channel 1
79                         .duty_zero = false,
80                         .pol = false,
81                         .pwm_pin = BV(PWM1),
82                         .mode_reg = &PWM_CMR1,
83                         .duty_reg = &PWM_CDTY1,
84                         .period_reg = &PWM_CPRD1,
85                         .update_reg = &PWM_CUPD1,
86                 },
87                 {//PWM Channel 2
88                         .duty_zero = false,
89                         .pol = false,
90                         .pwm_pin = BV(PWM2),
91                         .mode_reg = &PWM_CMR2,
92                         .duty_reg = &PWM_CDTY2,
93                         .period_reg = &PWM_CPRD2,
94                         .update_reg = &PWM_CUPD2,
95                 },
96                 {//PWM Channel 3
97                         .duty_zero = false,
98                         .pol = false,
99                         .pwm_pin = BV(PWM3),
100                         .mode_reg = &PWM_CMR3,
101                         .duty_reg = &PWM_CDTY3,
102                         .period_reg = &PWM_CPRD3,
103                         .update_reg = &PWM_CUPD3,
104                 }
105         };
106
107
108         /**
109          * Get preiod from select channel
110          *
111          * \a dev channel
112          */
113         pwm_period_t pwm_hw_getPeriod(PwmDev dev)
114         {
115                 return *pwm_map[dev].period_reg;
116         }
117
118         /**
119          * Set pwm waveform frequecy.
120          *
121          * \a freq in Hz
122          */
123         void pwm_hw_setFrequency(PwmDev dev, uint32_t freq)
124         {
125                 uint32_t period = 0;
126
127                 for(int i = 0; i <= PWM_HW_MAX_PRESCALER_STEP; i++)
128                 {
129                         period = CPU_FREQ / (BV(i) * freq);
130                         LOG_INFO("period[%ld], prescale[%d]\n", period, i);
131                         if ((period < PWM_HW_MAX_PERIOD) && (period != 0))
132                         {
133                                 //Clean previous channel prescaler, and set new
134                                 *pwm_map[dev].mode_reg &= ~PWM_CPRE_MCK_MASK;
135                                 *pwm_map[dev].mode_reg |= i;
136                                 //Set pwm period
137                                 *pwm_map[dev].period_reg = period;
138                                 break;
139                         }
140                 }
141
142                 LOG_INFO("PWM ch[%d] period[%ld]\n", dev, period);
143         }
144
145         /**
146          * Set pwm duty cycle.
147          *
148          * \a duty value 0 - 2^16
149          */
150         void pwm_hw_setDutyUnlock(PwmDev dev, uint16_t duty)
151         {
152                 ASSERT(duty <= (uint16_t)*pwm_map[dev].period_reg);
153
154
155                 /*
156                  * If polarity flag is true we must invert
157                  * PWM polarity.
158                  */
159                 if (pwm_map[dev].pol)
160                 {
161                         duty = (uint16_t)*pwm_map[dev].period_reg - duty;
162                         LOG_INFO("Inverted duty[%d], pol[%d]\n", duty, pwm_map[dev].pol);
163                 }
164
165                 /*
166                  * WARNING: is forbidden to write 0 to duty cycle value,
167                  * and so for duty = 0 we must enable PIO and clear output!
168                  */
169                 if (!duty)
170                 {
171                         PWM_PIO_CODR = pwm_map[dev].pwm_pin;
172                         PWM_PIO_PER  = pwm_map[dev].pwm_pin;
173                         pwm_map[dev].duty_zero = true;
174                 }
175                 else
176                 {
177                         PWM_PIO_PDR = pwm_map[dev].pwm_pin;
178                         PWM_PIO_ABSR = pwm_map[dev].pwm_pin;
179
180                         *pwm_map[dev].update_reg = duty;
181                         pwm_map[dev].duty_zero = false;
182                 }
183
184                 PWM_ENA = BV(dev);
185                 LOG_INFO("PWM ch[%d] duty[%d], period[%ld]\n", dev, duty, *pwm_map[dev].period_reg);
186         }
187
188
189         /**
190          * Enable select pwm channel
191          */
192         void pwm_hw_enable(PwmDev dev)
193         {
194                 if (!pwm_map[dev].duty_zero)
195                 {
196                         PWM_PIO_PDR  = pwm_map[dev].pwm_pin;
197                         PWM_PIO_ABSR = pwm_map[dev].pwm_pin;
198                 }
199         }
200
201         /**
202          * Disable select pwm channel
203          */
204         void pwm_hw_disable(PwmDev dev)
205         {
206                 PWM_PIO_PER = pwm_map[dev].pwm_pin;
207         }
208
209         /**
210          * Set PWM polarity to select pwm channel
211          */
212         void pwm_hw_setPolarity(PwmDev dev, bool pol)
213         {
214                         pwm_map[dev].pol = pol;
215                         LOG_INFO("Set pol[%d]\n", pwm_map[dev].pol);
216         }
217
218         /**
219          * Init pwm.
220          */
221         void pwm_hw_init(void)
222         {
223
224                 /*
225                  * Init pwm:
226                  * WARNING: is forbidden to write 0 to duty cycle value,
227                  * and so for duty = 0 we must enable PIO and clear output!
228                  * - clear PIO outputs
229                  * - enable PIO outputs
230                  * - Disable PIO and enable PWM functions
231                  * - Power on PWM
232                  */
233                 PWM_PIO_CODR = BV(PWM0) | BV(PWM1) | BV(PWM2) | BV(PWM3);
234                 PWM_PIO_OER  = BV(PWM0) | BV(PWM1) | BV(PWM2) | BV(PWM3);
235                 PWM_PIO_PDR  = BV(PWM0) | BV(PWM1) | BV(PWM2) | BV(PWM3);
236                 PWM_PIO_ABSR = BV(PWM0) | BV(PWM1) | BV(PWM2) | BV(PWM3);
237                 PMC_PCER |= BV(PWMC_ID);
238
239                 /* Disable all channels. */
240                 PWM_DIS = 0xFFFFFFFF;
241                 /* Disable prescalers A and B */
242                 PWM_MR = 0;
243
244                 /*
245                  * Set pwm mode:
246                  * - set period alidned to left
247                  * - set output waveform to start at high level
248                  * - allow duty cycle modify at next period event
249                  */
250                 for (int ch = 0; ch < PWM_CNT; ch++)
251                 {
252                         *pwm_map[ch].mode_reg = 0;
253                         *pwm_map[ch].mode_reg = BV(PWM_CPOL);
254                 }
255
256         }
257
258 #else
259
260         typedef struct PwmChannelRegs
261         {
262                 reg32_t CMR;
263                 reg32_t CDTY;
264                 reg32_t CPRD;
265                 reg32_t CCNT;
266                 reg32_t CUPD;
267         } PwmChannelRegs;
268
269
270         /*
271          * Set pwm waveform frequecy.
272          */
273         void pwm_hw_setFrequency(Pwm *ctx, pwm_freq_t freq)
274         {
275                 uint32_t period = 0;
276
277                 for(int i = 0; i <= PWM_HW_MAX_PRESCALER_STEP; i++)
278                 {
279                         period = CPU_FREQ / (BV(i) * freq);
280                         LOG_INFO("period[%ld], prescale[%d]\n", period, i);
281                         if ((period < PWM_HW_MAX_PERIOD) && (period != 0))
282                         {
283                                 //Clear previous channel prescaler, and set new
284                                 ctx->hw->base->CMR &= ~PWM_CPRE_MCK_MASK;
285                                 ctx->hw->base->CMR |= i;
286                                 //Set pwm period
287                                 ATOMIC(
288                                         ctx->hw->base->CPRD = period;
289                                         ctx->hw->base->CDTY = period;
290                                 );
291                                 break;
292                         }
293                 }
294
295                 LOG_INFO("PWM ch[%d] period[%ld]\n", ctx->ch, period);
296         }
297
298         pwm_hwreg_t pwm_hw_getPeriod(Pwm *ctx)
299         {
300                 return ctx->hw->base->CPRD;
301         }
302
303         /*
304          * Set pwm duty cycle.
305          *
306          * duty value 0 - (2^16 - 1)
307          */
308         void pwm_hw_setDuty(Pwm *ctx, pwm_hwreg_t hw_duty)
309         {
310                 ASSERT(hw_duty <= ctx->hw->base->CPRD);
311
312                 /*
313                  * WARNING: is forbidden to write 0 or 1 to duty cycle value,
314                  * and so for duty < 2 we must enable PIO and clear output!
315                  */
316                 if (hw_duty < 2)
317                 {
318                         hw_duty = 2;
319                         PWM_PIO_PER = ctx->hw->pwm_pin;
320                 }
321                 else
322                         PWM_PIO_PDR = ctx->hw->pwm_pin;
323
324                 ctx->hw->base->CUPD = hw_duty;
325                 LOG_INFO("PWM ch[%d] duty[%d], period[%ld]\n", ctx->ch, hw_duty, ctx->hw->base->CPRD);
326         }
327
328         static PwmHardware pwm_channels[] =
329         {
330                 {//PWM Channel 0
331                         .pwm_pin = BV(PWM0),
332                         .base = (volatile PwmChannelRegs *)&PWM_CMR0,
333                 },
334                 {//PWM Channel 1
335                         .pwm_pin = BV(PWM1),
336                         .base = (volatile PwmChannelRegs *)&PWM_CMR1,
337                 },
338                 {//PWM Channel 2
339                         .pwm_pin = BV(PWM2),
340                         .base = (volatile PwmChannelRegs *)&PWM_CMR2,
341                 },
342                 {//PWM Channel 3
343                         .pwm_pin = BV(PWM3),
344                         .base = (volatile PwmChannelRegs *)&PWM_CMR3,
345                 },
346         };
347
348         /*
349          * Init pwm.
350          */
351         void pwm_hw_init(Pwm *ctx, unsigned ch)
352         {
353
354                 ctx->hw = &pwm_channels[ch];
355
356                 /*
357                  * Init pwm:
358                  * - clear PIO outputs
359                  * - enable PIO outputs
360                  * - Enable PWM functions
361                  * - Power on PWM
362                  */
363                 PWM_PIO_CODR = ctx->hw->pwm_pin;
364                 PWM_PIO_OER  = ctx->hw->pwm_pin;
365                 PWM_PIO_PER  = ctx->hw->pwm_pin;
366                 PWM_PIO_ABSR = ctx->hw->pwm_pin;
367
368                 PMC_PCER |= BV(PWMC_ID);
369
370                 /* Disable prescalers A and B */
371                 PWM_MR = 0;
372
373                 /*
374                  * Set pwm mode:
375                  * WARNING: is forbidden to write 0 or 1 to duty cycle value,
376                  * and so for start we set duty to 2.
377                  * Also:
378                  * - set period aligned to left
379                  * - set output waveform to start at high level
380                  * - allow duty cycle modify at next period event
381                  */
382                 ctx->hw->base->CDTY = 2;
383                 ctx->hw->base->CMR = BV(PWM_CPOL);
384                 PWM_ENA = BV(ch);
385         }
386
387 #endif