Merge branch "preempt" in "trunk".
[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/cfg_stepper.h"
59 #include <cfg/macros.h>
60 #include <cfg/debug.h>
61
62 #include <cpu/types.h>
63 #include <cpu/irq.h>
64
65 #include <io/arm.h>
66
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 ISR_PROTO(stepper_tc0_irq);
78 static ISR_PROTO(stepper_tc1_irq);
79 static ISR_PROTO(stepper_tc2_irq);
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 DECLARE_ISR(stepper_tc0_irq)
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_CPAS))
267                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA0]);
268
269         if (status_reg & BV(TC_CPBS))
270                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB0]);
271
272         /* Inform hw that we have served the IRQ */
273         AIC_EOICR = 0;
274
275 }
276
277 /*
278  * Interrupt handler for timer counter TCKL1
279  */
280 DECLARE_ISR(stepper_tc1_irq)
281 {
282         /*
283          * Warning: when we read the status_reg register, we reset it.
284          * That mean if is occur an interrupt event we can read only
285          * the last that has been occur. To not miss an interrupt event
286          * we save the status_reg register and then we read it.
287          */
288         uint32_t  status_reg = TC1_SR & TC1_IMR;
289
290         if (status_reg & BV(TC_CPAS))
291                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA1]);
292
293         if (status_reg & BV(TC_CPBS))
294                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB1]);
295
296
297         /* Inform hw that we have served the IRQ */
298         AIC_EOICR = 0;
299 }
300
301
302 /*
303  * Interrupt handler for timer counter TCKL2
304  */
305 DECLARE_ISR(stepper_tc2_irq)
306 {
307
308         /*
309          * Warning: when we read the status_reg register, we reset it.
310          * That mean if is occur an interrupt event we can read only
311          * the last that has been occur. To not miss an interrupt event
312          * we save the status_reg register and then we read it.
313          */
314         uint32_t  status_reg = TC2_SR & TC2_IMR;
315
316         if (status_reg & BV(TC_CPAS))
317                 stepper_tc_tio_irq(&stepper_timers[TC_TIOA2]);
318
319         if (status_reg & BV(TC_CPBS))
320                 stepper_tc_tio_irq(&stepper_timers[TC_TIOB2]);
321
322         /* Inform hw that we have served the IRQ */
323         AIC_EOICR = 0;
324
325 }
326
327 /**
328  * Timer couter setup.
329  *
330  * This function apply to select timer couter all needed settings.
331  * Every settings are stored in stepper_timers[].
332  */
333 void stepper_tc_setup(int index, stepper_isr_t callback, struct Stepper *motor)
334 {
335         ASSERT(index < CONFIG_TC_STEPPER_MAX_NUM);
336
337         motor->timer = &stepper_timers[index];
338
339         //Disable PIO controller and enable TIO function
340         TIO_PIO_PDR = BV(motor->timer->tio_pin);
341         TIO_PIO_ABSR = BV(motor->timer->tio_pin);
342
343         /*
344          * Sets timer counter in waveform mode.
345          * We set as default:
346          * - Waveform mode 00 (see datasheet for more detail.)
347          * - Master clock prescaler to STEPPER_MCK_PRESCALER
348          * - Set none external event
349          * - Clear pin output on comp_reg
350          * - None effect on reg C compare
351          */
352         *motor->timer->chl_mode_reg = BV(TC_WAVE);
353         *motor->timer->chl_mode_reg |= motor->timer->ext_event_set;
354         *motor->timer->chl_mode_reg &= ~TC_WAVSEL_MASK;
355         *motor->timer->chl_mode_reg |= TC_WAVSEL_UP;
356         *motor->timer->chl_mode_reg |= STEPPER_MCK_PRESCALER;
357         *motor->timer->chl_mode_reg |= motor->timer->comp_effect_clear;
358         *motor->timer->chl_mode_reg &= ~motor->timer->comp_effect_c_mask;
359
360         //Reset comp_reg and C compare register
361         *motor->timer->comp_reg = 0;
362         *motor->timer->comp_c_reg = 0;
363
364         //Register interrupt vector
365         cpu_flags_t flags;
366         IRQ_SAVE_DISABLE(flags);
367
368         /*
369          * Warning: To guarantee a correct management of interrupt event, we must
370          * trig the interrupt on level sensitive. This becouse, we have only a common
371          * line for interrupt request, and if we have at the same time two interrupt
372          * request could be that the is service normaly but the second will never
373          *  been detected and interrupt will stay active but never serviced.
374          */
375         AIC_SVR(motor->timer->timer_id) = motor->timer->isr;
376         AIC_SMR(motor->timer->timer_id) = AIC_SRCTYPE_INT_LEVEL_SENSITIVE;
377         AIC_IECR = BV(motor->timer->timer_id);
378
379         // Disable interrupt on select timer counter
380         stepper_tc_irq_disable(motor->timer);
381
382         IRQ_RESTORE(flags);
383
384         //Register callback
385         motor->timer->callback = callback;
386         motor->timer->motor = motor;
387 }
388
389 /**
390  * Timer counter init.
391  */
392 void stepper_tc_init(void)
393 {
394         STEPPER_STROBE_INIT;
395
396         ASSERT(CONFIG_NUM_STEPPER_MOTORS <= CONFIG_TC_STEPPER_MAX_NUM);
397
398         /*
399          * Enable timer counter:
400          * - power on all timer counter
401          * - disable all interrupt
402          * - disable all external event/timer source
403          */
404         for (int i = 0; i < CONFIG_TC_STEPPER_MAX_NUM; i++)
405         {
406                 PMC_PCER = BV(stepper_timers[i].timer_id);
407                 *stepper_timers[i].irq_disable_reg = 0xFFFFFFFF;
408                 TC_BMR = stepper_timers[i].blk_ctrl_set;
409         }
410
411         /*
412          * Enable timer counter and start it.
413          */
414         for (int i = 0; i < CONFIG_TC_STEPPER_MAX_NUM; i++)
415                 *stepper_timers[i].chl_ctrl_reg = (BV(TC_CLKEN) | BV(TC_SWTRG));
416
417 }
418