altos: Add debounce helper. Use in button and quadrature drivers for TeleLCO
[fw/altos] / src / stm / ao_debounce.c
1 /*
2  * Copyright © 2013 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 #include <ao_debounce.h>
20
21 static uint8_t                  ao_debounce_initialized;
22 static uint8_t                  ao_debounce_running;
23 static struct ao_debounce       *ao_debounce;
24
25 static void
26 ao_debounce_on(void)
27 {
28         stm_tim6.cr1 = ((0 << STM_TIM67_CR1_ARPE) |
29                         (0 << STM_TIM67_CR1_OPM) |
30                         (1 << STM_TIM67_CR1_URS) |
31                         (0 << STM_TIM67_CR1_UDIS) |
32                         (1 << STM_TIM67_CR1_CEN));
33 }
34
35 static void
36 ao_debounce_off(void)
37 {
38         stm_tim6.cr1 = ((0 << STM_TIM67_CR1_ARPE) |
39                         (0 << STM_TIM67_CR1_OPM) |
40                         (1 << STM_TIM67_CR1_URS) |
41                         (0 << STM_TIM67_CR1_UDIS) |
42                         (0 << STM_TIM67_CR1_CEN));
43 }
44
45 static void
46 _ao_debounce_set(struct ao_debounce *debounce, uint8_t value)
47 {
48         if (value != debounce->value) {
49                 debounce->value = value;
50                 debounce->_set(debounce, value);
51         }
52         _ao_debounce_stop(debounce);
53 }
54
55 /*
56  * Get the current value, set the result when we've
57  * reached the debounce count limit
58  */
59 static void
60 _ao_debounce_check(struct ao_debounce *debounce)
61 {
62         if (debounce->_get(debounce)) {
63                 if (debounce->count < 0)
64                         debounce->count = 0;
65                 if (debounce->count < debounce->hold) {
66                         if (++debounce->count == debounce->hold)
67                                 _ao_debounce_set(debounce, 1);
68                 }
69         } else {
70                 if (debounce->count > 0)
71                         debounce->count = 0;
72                 if (debounce->count > -debounce->hold) {
73                         if (--debounce->count == -debounce->hold)
74                                 _ao_debounce_set(debounce, 0);
75                 }
76         }
77 }
78
79 /*
80  * Start monitoring one pin
81  */
82 void
83 _ao_debounce_start(struct ao_debounce *debounce)
84 {
85         if (debounce->running)
86                 return;
87         debounce->running = 1;
88
89         /* Reset the counter */
90         debounce->count = 0;
91
92         /* Link into list */
93         debounce->next = ao_debounce;
94         ao_debounce = debounce;
95
96         /* Make sure the timer is running */
97         if (!ao_debounce_running++)
98                 ao_debounce_on();
99
100         /* And go check the current value */
101         _ao_debounce_check(debounce);
102 }
103
104 /*
105  * Stop monitoring one pin
106  */
107 void
108 _ao_debounce_stop(struct ao_debounce *debounce)
109 {
110         struct ao_debounce **prev;
111         if (!debounce->running)
112                 return;
113
114         debounce->running = 0;
115
116         /* Unlink */
117         for (prev = &ao_debounce; (*prev); prev = &((*prev)->next)) {
118                 if (*prev == debounce) {
119                         *prev = debounce->next;
120                         break;
121                 }
122         }
123         debounce->next = NULL;
124
125         /* Turn off the timer if possible */
126         if (!--ao_debounce_running)
127                 ao_debounce_off();
128 }
129
130 void stm_tim6_isr(void)
131 {
132         struct ao_debounce      *debounce, *next;
133         if (stm_tim6.sr & (1 << STM_TIM67_SR_UIF)) {
134                 stm_tim6.sr = 0;
135
136                 /* Walk the current list, allowing the current
137                  * object to be removed from the list
138                  */
139                 for (debounce = ao_debounce; debounce; debounce = next) {
140                         next = debounce->next;
141                         _ao_debounce_check(debounce);
142                 }
143         }
144 }
145
146 /*
147  * According to the STM clock-configuration, timers run
148  * twice as fast as the APB1 clock *if* the APB1 prescaler
149  * is greater than 1.
150  */
151
152 #if AO_APB1_PRESCALER > 1
153 #define TIMER_23467_SCALER 2
154 #else
155 #define TIMER_23467_SCALER 1
156 #endif
157
158 #define TIMER_100kHz    ((AO_PCLK1 * TIMER_23467_SCALER) / 100000)
159
160 void
161 ao_debounce_init(void)
162 {
163         if (ao_debounce_initialized)
164                 return;
165         ao_debounce_initialized = 1;
166
167         stm_nvic_set_enable(STM_ISR_TIM6_POS);
168         stm_nvic_set_priority(STM_ISR_TIM6_POS, AO_STM_NVIC_CLOCK_PRIORITY);
169
170         /* Turn on timer 6 */
171         stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_TIM6EN);
172
173         stm_tim6.psc = TIMER_100kHz;
174         stm_tim6.arr = 9;
175         stm_tim6.cnt = 0;
176
177         /* Enable update interrupt */
178         stm_tim6.dier = (1 << STM_TIM67_DIER_UIE);
179
180         /* Poke timer to reload values */
181         stm_tim6.egr |= (1 << STM_TIM67_EGR_UG);
182
183         stm_tim6.cr2 = (STM_TIM67_CR2_MMS_RESET << STM_TIM67_CR2_MMS);
184
185         /* And turn it off (for now) */
186         ao_debounce_off();
187 }