Add timer module for stepper driver.
[bertos.git] / bertos / cpu / arm / drv / stepper_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 2008 Develer S.r.l. (http://www.develer.com/)
30  * All Rights Reserved.
31  * -->
32  *
33  * \brief Stepper driver interface implementation.
34  *
35  * This module use the three timer on the at91 family, to generate a
36  * six periodic variable pwm waveform. The pulse width is fix, and could
37  * change by setting the STEPPER_DELAY_ON_COMPARE_C define, but you make
38  * an attention to do this, becouse the pulse width is not exactly 
39  * STEPPER_DELAY_ON_COMPARE_C. The pulse width depend also to latency 
40  * time of cpu to serve an interrupt, this generate an pwm waveform affect
41  * to noise. This noise not effect the period but only the pulse width,
42  * becouse the raising edge is generate by hardware comply with the our
43  * period settings.
44  *
45  * Note: is most important to set STEPPER_DELAY_ON_COMPARE_C value minor
46  * than a interrupt time service, becouse the falling edge must be happen
47  * inside to inerrupt service to guarantee a correct functionaly of pwm
48  * generator.
49  * 
50  *
51  * \version $Id$
52  *
53  * \author Daniele Basile <asterix@develer.com>
54  */
55
56 #include "stepper_at91.h"
57
58 #include <cfg/macros.h>
59 #include <cfg/debug.h>
60
61 #include <cpu/types.h>
62 #include <cpu/irq.h>
63
64 #include <io/arm.h>
65
66 #include "appconfig.h"
67
68 /*
69  * Delay to set C compare to clear output
70  * on select TIO output
71  */
72 #define STEPPER_DELAY_ON_COMPARE_C 20
73
74 /*
75  * Forward declaration for interrupt handler
76  */
77 static void stepper_tc0_irq(void);
78 static void stepper_tc1_irq(void);
79 static void stepper_tc2_irq(void);
80
81 ///< Static array of timer counter struct for stepper.
82 static struct TimerCounter stepper_timers[CONFIG_TC_STEPPER_MAX_NUM] =
83 {
84         { //Timer Counter settings for TIOA0 output pin
85                 .timer_id = TC0_ID,
86                 .blk_ctrl_set = TC_NONEXC0,
87                 .chl_mode_reg = &TC0_CMR,
88                 .chl_ctrl_reg = &TC0_CCR,
89                 .comp_effect_mask = TC_ACPA_MASK,
90                 .comp_effect_set = TC_ACPA_SET_OUTPUT,
91                 .comp_effect_clear = TC_ACPA_CLEAR_OUTPUT,
92                 .comp_effect_c_mask = TC_ACPC_MASK,
93                 .comp_effect_c_clear = TC_ACPC_CLEAR_OUTPUT,
94                 .ext_event_set = TC_EEVT_XC0,
95                 .comp_reg = &TC0_RA,
96                 .comp_c_reg = &TC0_RC,
97                 .count_val_reg = &TC0_CV,
98                 .irq_enable_reg = &TC0_IER,
99                 .irq_disable_reg = &TC0_IDR,
100                 .irq_set_mask = BV(TC_CPAS),
101                 .irq_mask_reg = &TC0_IMR,
102                 .isr = stepper_tc0_irq,
103                 .status_reg = &TC0_SR,
104                 .tio_pin = TIOA0,
105                 .callback = NULL,
106                 .motor = NULL,
107         },
108         { //Timer Counter settings for TIOB0 output pin
109                 .timer_id = TC0_ID,
110                 .blk_ctrl_set = TC_NONEXC0,
111                 .chl_mode_reg = &TC0_CMR,
112                 .chl_ctrl_reg = &TC0_CCR,
113                 .comp_reg = &TC0_RB,
114                 .comp_c_reg = &TC0_RC,
115                 .count_val_reg = &TC0_CV,
116                 .comp_effect_mask = TC_BCPB_MASK,
117                 .comp_effect_set = TC_BCPB_SET_OUTPUT,
118                 .comp_effect_clear = TC_BCPB_CLEAR_OUTPUT,
119                 .comp_effect_c_mask = TC_BCPC_MASK,
120                 .comp_effect_c_clear = TC_BCPC_CLEAR_OUTPUT,
121                 .ext_event_set = TC_EEVT_XC0,
122                 .irq_enable_reg = &TC0_IER,
123                 .irq_disable_reg = &TC0_IDR,
124                 .irq_set_mask = BV(TC_CPBS),
125                 .irq_mask_reg = &TC0_IMR,
126                 .isr = stepper_tc0_irq,
127                 .status_reg = &TC0_SR,
128                 .tio_pin = TIOB0,
129                 .callback = NULL,
130                 .motor = NULL,
131         },
132         { //Timer Counter settings for TIOA1 output pin
133                 .timer_id = TC1_ID,
134                 .blk_ctrl_set = TC_NONEXC1,
135                 .chl_mode_reg = &TC1_CMR,
136                 .chl_ctrl_reg = &TC1_CCR,
137                 .comp_reg = &TC1_RA,
138                 .comp_c_reg = &TC1_RC,
139                 .count_val_reg = &TC1_CV,
140                 .comp_effect_mask = TC_ACPA_MASK,
141                 .comp_effect_set = TC_ACPA_SET_OUTPUT,
142                 .comp_effect_clear = TC_ACPA_CLEAR_OUTPUT,
143                 .comp_effect_c_mask = TC_ACPC_MASK,
144                 .comp_effect_c_clear = TC_ACPC_CLEAR_OUTPUT,
145                 .ext_event_set = TC_EEVT_XC1,
146                 .irq_enable_reg = &TC1_IER,
147                 .irq_disable_reg = &TC1_IDR,
148                 .irq_set_mask = BV(TC_CPAS),
149                 .irq_mask_reg = &TC1_IMR,
150                 .isr = stepper_tc1_irq,
151                 .status_reg = &TC1_SR,
152                 .tio_pin = TIOA1,
153                 .callback = NULL,
154                 .motor = NULL,
155         },
156         { //Timer Counter settings for TIOB1 output pin
157                 .timer_id = TC1_ID,
158                 .blk_ctrl_set = TC_NONEXC1,
159                 .chl_mode_reg = &TC1_CMR,
160                 .chl_ctrl_reg = &TC1_CCR,
161                 .comp_reg = &TC1_RB,
162                 .comp_c_reg = &TC1_RC,
163                 .count_val_reg = &TC1_CV,
164                 .comp_effect_mask = TC_BCPB_MASK,
165                 .comp_effect_set = TC_BCPB_SET_OUTPUT,
166                 .comp_effect_clear = TC_BCPB_CLEAR_OUTPUT,
167                 .comp_effect_c_mask = TC_BCPC_MASK,
168                 .comp_effect_c_clear = TC_BCPC_CLEAR_OUTPUT,
169                 .ext_event_set = TC_EEVT_XC1,
170                 .irq_enable_reg = &TC1_IER,
171                 .irq_disable_reg = &TC1_IDR,
172                 .irq_set_mask = BV(TC_CPBS),
173                 .irq_mask_reg = &TC1_IMR,
174                 .isr = stepper_tc1_irq,
175                 .status_reg = &TC1_SR,
176                 .tio_pin = TIOB1,
177                 .callback = NULL,
178                 .motor = NULL,
179         },
180         { //Timer Counter settings for TIOA2 output pin
181                 .timer_id = TC2_ID,
182                 .blk_ctrl_set = TC_NONEXC2,
183                 .chl_mode_reg = &TC2_CMR,
184                 .chl_ctrl_reg = &TC2_CCR,
185                 .comp_reg = &TC2_RA,
186                 .comp_c_reg = &TC2_RC,
187                 .count_val_reg = &TC2_CV,
188                 .comp_effect_mask = TC_ACPA_MASK,
189                 .comp_effect_set = TC_ACPA_SET_OUTPUT,
190                 .comp_effect_clear = TC_ACPA_CLEAR_OUTPUT,
191                 .comp_effect_c_mask = TC_ACPC_MASK,
192                 .comp_effect_c_clear = TC_ACPC_CLEAR_OUTPUT,
193                 .ext_event_set = TC_EEVT_XC2,
194                 .irq_enable_reg = &TC2_IER,
195                 .irq_disable_reg = &TC2_IDR,
196                 .irq_set_mask = BV(TC_CPAS),
197                 .irq_mask_reg = &TC2_IMR,
198                 .isr = stepper_tc2_irq,
199                 .status_reg = &TC2_SR,
200                 .tio_pin = TIOA2,
201                 .callback = NULL,
202                 .motor = NULL,
203         },
204         { //Timer Counter settings for TIOB2 output pin
205                 .timer_id = TC2_ID,
206                 .blk_ctrl_set = TC_NONEXC2,
207                 .chl_mode_reg = &TC2_CMR,
208                 .chl_ctrl_reg = &TC2_CCR,
209                 .comp_reg = &TC2_RB,
210                 .comp_c_reg = &TC2_RC,
211                 .count_val_reg = &TC2_CV,
212                 .comp_effect_mask = TC_BCPB_MASK,
213                 .comp_effect_set = TC_BCPB_SET_OUTPUT,
214                 .comp_effect_clear = TC_BCPB_CLEAR_OUTPUT,
215                 .comp_effect_c_mask = TC_BCPC_MASK,
216                 .comp_effect_c_clear = TC_BCPC_CLEAR_OUTPUT,
217                 .ext_event_set = TC_EEVT_XC2,
218                 .irq_enable_reg = &TC2_IER,
219                 .irq_disable_reg = &TC2_IDR,
220                 .irq_set_mask = BV(TC_CPBS),
221                 .irq_mask_reg = &TC2_IMR,
222                 .isr = stepper_tc2_irq,
223                 .status_reg = &TC2_SR,
224                 .tio_pin = TIOB2,
225                 .callback = NULL,
226                 .motor = NULL,
227         }
228 };
229
230 /**
231  * Generic TIO interrupt handler.
232  */
233 INLINE void stepper_tc_tio_irq(struct TimerCounter * t)
234 {
235         //
236         *t->chl_mode_reg &= ~t->comp_effect_c_mask;
237         *t->chl_mode_reg |= t->comp_effect_c_clear;
238
239         /*
240          * Cleat TIO output on c register compare.
241          * This generate an pulse with variable lenght, this
242          * depend to delay that interrupt is realy service.
243          */
244         *t->comp_c_reg = *t->count_val_reg + STEPPER_DELAY_ON_COMPARE_C;
245
246         //Call the associate callback
247         t->callback(t->motor);
248
249         *t->chl_mode_reg &= ~t->comp_effect_c_mask;
250 }
251
252
253 /*
254  * Interrupt handler for timer counter TCKL0
255  */
256 static void ISR_FUNC stepper_tc0_irq(void)
257 {
258         /*
259          * Warning: when we read the status_reg register, we reset it.
260          * That mean if is occur an interrupt event we can read only
261          * the last that has been occur. To not miss an interrupt event
262          * we save the status_reg register and then we read it.
263          */
264         uint32_t  status_reg = TC0_SR & TC0_IMR;
265
266         if ((status_reg & BV(TC_CPBS)) && (status_reg & BV(TC_CPAS)))
267                 STEPPER_STROBE_ON;
268
269         if (status_reg & BV(TC_CPAS))
270                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA0]);
271
272         if (status_reg & BV(TC_CPBS))
273                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB0]);
274
275         STEPPER_STROBE_OFF;
276         /* Inform hw that we have served the IRQ */
277         AIC_EOICR = 0;
278
279 }
280
281 /*
282  * Interrupt handler for timer counter TCKL1
283  */
284 static void ISR_FUNC stepper_tc1_irq(void)
285 {
286         STEPPER_STROBE_ON_1;
287         /*
288          * Warning: when we read the status_reg register, we reset it.
289          * That mean if is occur an interrupt event we can read only
290          * the last that has been occur. To not miss an interrupt event
291          * we save the status_reg register and then we read it.
292          */
293         uint32_t  status_reg = TC1_SR & TC1_IMR;
294
295         if (status_reg & BV(TC_CPAS))
296                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA1]);
297
298         if (status_reg & BV(TC_CPBS))
299                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB1]);
300
301
302         /* Inform hw that we have served the IRQ */
303         AIC_EOICR = 0;
304         STEPPER_STROBE_OFF_1;
305 }
306
307
308 /*
309  * Interrupt handler for timer counter TCKL2
310  */
311 static void ISR_FUNC stepper_tc2_irq(void)
312 {
313
314         /*
315          * Warning: when we read the status_reg register, we reset it.
316          * That mean if is occur an interrupt event we can read only
317          * the last that has been occur. To not miss an interrupt event
318          * we save the status_reg register and then we read it.
319          */
320         uint32_t  status_reg = TC2_SR & TC2_IMR;
321
322         STEPPER_STROBE_ON_2;
323         if (status_reg & BV(TC_CPAS))
324                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA2]);
325
326         if (status_reg & BV(TC_CPBS))
327                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB2]);
328
329         STEPPER_STROBE_OFF_2;
330         /* Inform hw that we have served the IRQ */
331         AIC_EOICR = 0;
332
333 }
334
335 /**
336  * Timer couter setup.
337  *
338  * This function apply to select timer couter all needed settings.
339  * Every settings are stored in stepper_timers[].
340  */
341 void stepper_tc_setup(int index, stepper_isr_t callback, struct Stepper *motor)
342 {
343         ASSERT(index < CONFIG_TC_STEPPER_MAX_NUM);
344
345         motor->timer = &stepper_timers[index];
346
347         //Disable PIO controller and enable TIO function
348         TIO_PIO_PDR = BV(motor->timer->tio_pin);
349         TIO_PIO_ABSR = BV(motor->timer->tio_pin);
350
351         /*
352          * Sets timer counter in waveform mode.
353          * We set as default:
354          * - Waveform mode 00 (see datasheet for more detail.)
355          * - Master clock prescaler to STEPPER_MCK_PRESCALER
356          * - Set none external event
357          * - Clear pin output on comp_reg
358          * - None effect on reg C compare
359          */
360         *motor->timer->chl_mode_reg = BV(TC_WAVE);
361         *motor->timer->chl_mode_reg |= motor->timer->ext_event_set;
362         *motor->timer->chl_mode_reg &= ~TC_WAVSEL_MASK;
363         *motor->timer->chl_mode_reg |= TC_WAVSEL_UP;
364         *motor->timer->chl_mode_reg |= STEPPER_MCK_PRESCALER;
365         *motor->timer->chl_mode_reg |= motor->timer->comp_effect_clear;
366         *motor->timer->chl_mode_reg &= ~motor->timer->comp_effect_c_mask;
367
368         //Reset comp_reg and C compare register
369         *motor->timer->comp_reg = 0;
370         *motor->timer->comp_c_reg = 0;
371
372         //Register interrupt vector
373         cpuflags_t flags;
374         IRQ_SAVE_DISABLE(flags);
375
376         /*
377          * Warning: To guarantee a correct management of interrupt event, we must
378          * trig the interrupt on level sensitive. This becouse, we have only a common
379          * line for interrupt request, and if we have at the same time two interrupt
380          * request could be that the is service normaly but the second will never
381          *  been detected and interrupt will stay active but never serviced.
382          */
383         AIC_SVR(motor->timer->timer_id) = motor->timer->isr;
384         AIC_SMR(motor->timer->timer_id) = AIC_SRCTYPE_INT_LEVEL_SENSITIVE;
385         AIC_IECR = BV(motor->timer->timer_id);
386
387         // Disable interrupt on select timer counter
388         stepper_tc_irq_disable(motor->timer);
389
390         IRQ_RESTORE(flags);
391
392         //Register callback
393         motor->timer->callback = callback;
394         motor->timer->motor = motor;
395 }
396
397 /**
398  * Timer counter init.
399  */
400 void stepper_tc_init(void)
401 {
402         STEPPER_STROBE_INIT;
403
404         ASSERT(CONFIG_NUM_STEPPER_MOTORS <= CONFIG_TC_STEPPER_MAX_NUM);
405
406         /*
407          * Enable timer counter:
408          * - power on all timer counter
409          * - disable all interrupt
410          * - disable all external event/timer source
411          */
412         for (int i = 0; i < CONFIG_TC_STEPPER_MAX_NUM; i++)
413         {
414                 PMC_PCER = BV(stepper_timers[i].timer_id);
415                 *stepper_timers[i].irq_disable_reg = 0xFFFFFFFF;
416                 TC_BMR = stepper_timers[i].blk_ctrl_set;
417         }
418
419         /*
420          * Enable timer counter and start it.
421          */
422         for (int i = 0; i < CONFIG_TC_STEPPER_MAX_NUM; i++)
423                 *stepper_timers[i].chl_ctrl_reg = (BV(TC_CLKEN) | BV(TC_SWTRG));
424
425 }
426