altos: Replace C code attiny async output with inline asm
[fw/altos] / src / attiny / ao_clock.c
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19
20 volatile AO_TICK_TYPE   ao_tick_count;
21 static volatile AO_TICK_TYPE    ao_wakeup_count;
22
23 ISR(TIMER1_COMPA_vect)
24 {
25         ++ao_tick_count;
26         if ((int16_t) (ao_tick_count - ao_wakeup_count) >= 0)
27                 ao_wakeup((void *) &ao_tick_count);
28 }
29
30 uint16_t
31 ao_time(void)
32 {
33         uint16_t        r;
34
35         cli();
36         r = ao_tick_count;
37         sei();
38         return r;
39 }
40
41 #if AVR_CLOCK == 8000000UL
42 #define AO_CLKPS        0       /* divide by 1 */
43 #define AO_CS           10      /* prescale by 512 */
44 #endif
45 #if AVR_CLOCK == 4000000UL
46 #define AO_CLKPS        1       /* divide by 2 */
47 #define AO_CS           9       /* prescale by 256 */
48 #endif
49 #if AVR_CLOCK == 2000000UL
50 #define AO_CLKPS        2       /* divide by 4 */
51 #define AO_CS           8       /* prescale by 128 */
52 #endif
53 #if AVR_CLOCK == 1000000UL
54 #define AO_CLKPS        3       /* divide by 8 */
55 #define AO_CS           7       /* prescale by 64 */
56 #endif
57 #if AVR_CLOCK == 500000UL
58 #define AO_CLKPS        4       /* divide by 16 */
59 #define AO_CS           6       /* prescale by 32 */
60 #endif
61 #if AVR_CLOCK == 250000UL
62 #define AO_CLKPS        5       /* divide by 32 */
63 #define AO_CS           5       /* prescale by 16 */
64 #endif
65 #if AVR_CLOCK == 125000UL
66 #define AO_CLKPS        6       /* divide by 64 */
67 #define AO_CS           4       /* prescale by 32 */
68 #endif
69 #if AVR_CLOCK == 62500UL
70 #define AO_CLKPS        7       /* divide by 128 */
71 #define AO_CS           4       /* prescale by 32 */
72 #endif
73
74 void
75 ao_timer_init(void)
76 {
77         cli();
78         CLKPR = (1 << CLKPCE);
79         CLKPR = (AO_CLKPS << CLKPS0);
80         sei();
81
82         /* Overall division ratio is 512 * 125,
83          * so our 8MHz base clock ends up as a 125Hz
84          * clock
85          */
86         TCCR1 = ((1 << CTC1) |          /* Clear timer on match */
87                  (0 << PWM1A) |         /* Not PWM mode */
88                  (0 << COM1A0) |        /* Don't change output pins */
89                  (0 << COM1A1) |        /*  ... */
90                  (AO_CS << CS10));      /* Prescale */
91         GTCCR = ((0 << PWM1B) |         /* Not PWM mode */
92                  (0 << COM1B1) |        /* Don't change output pins */
93                  (0 << COM1B0) |        /*  ... */
94                  (0 << FOC1B) |         /* Don't force output compare */
95                  (0 << FOC1A) |         /*  ... */
96                  (0 << PSR1));          /* Don't bother to reset scaler */
97
98         OCR1A = 0;
99         OCR1B = 0;
100         OCR1C = 124;                    /* Divide by as many 5s as we can (5^3 = 125) */
101
102         TIMSK = ((1 << OCIE1A) |        /* Enable TIMER1_COMPA interrupt */
103                  (0 << OCIE1B) |        /* Disable TIMER1_COMPB interrupt */
104                  (0 << TOIE1));         /* Disable TIMER1_OVF interrupt */
105         DDRB |= 2;
106 }
107
108 #define PER_LOOP        8
109 #define US_LOOPS        ((AVR_CLOCK / 1000000) / PER_LOOP)
110
111 void ao_delay_us(uint16_t us)
112 {
113 #if US_LOOPS > 1
114         us *= US_LOOPS;
115 #endif
116         for (;;) {
117                 ao_arch_nop();
118                 ao_arch_nop();
119                 ao_arch_nop();
120                 --us;
121                 /* A bit funky to keep the optimizer
122                  * from short-circuiting the test */
123                 if (!((uint8_t) (us | (us >> 8))))
124                         break;
125         }
126 }
127
128 void
129 ao_delay_until(uint16_t target)
130 {
131         cli();
132         ao_wakeup_count = target;
133         while ((int16_t) (target - ao_tick_count) > 0)
134                 ao_sleep((void *) &ao_tick_count);
135         sei();
136 }
137
138 void
139 ao_delay(uint16_t ticks)
140 {
141         ao_delay_until(ao_time() + ticks);
142 }
143