]> git.gag.com Git - fw/altos/blob - src/drivers/ao_ps2.c
handle repeated invocations without failing as often
[fw/altos] / src / drivers / ao_ps2.c
1 /*
2  * Copyright © 2016 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, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  */
14
15 #include "ao.h"
16 #include "ao_ps2.h"
17 #include "ao_exti.h"
18
19 static struct ao_fifo   ao_ps2_rx_fifo;
20
21 static uint16_t         ao_ps2_tx;
22 static uint8_t          ao_ps2_tx_count;
23
24 static AO_TICK_TYPE     ao_ps2_tick;
25 static uint16_t         ao_ps2_value;
26 static uint8_t          ao_ps2_count;
27
28 uint8_t                 ao_ps2_stdin;
29
30 uint8_t                 ao_ps2_scancode_set;
31
32 #define AO_PS2_CLOCK_MODE(pull) ((pull) | AO_EXTI_MODE_FALLING | AO_EXTI_PRIORITY_MED)
33
34 static void
35 ao_ps2_isr(void);
36
37 static uint8_t
38 _ao_ps2_parity(uint8_t value)
39 {
40         uint8_t parity = 1;
41         uint8_t b;
42
43         for (b = 0; b < 8; b++) {
44                 parity ^= (value & 1);
45                 value >>= 1;
46         }
47         return parity;
48 }
49
50 static int
51 _ao_ps2_poll(void)
52 {
53         uint8_t u;
54         if (ao_fifo_empty(ao_ps2_rx_fifo)) {
55                 return AO_READ_AGAIN;
56         }
57         ao_fifo_remove(ao_ps2_rx_fifo, u);
58
59         return (int) u;
60 }
61
62 uint8_t
63 ao_ps2_get(void)
64 {
65         int c;
66         ao_arch_block_interrupts();
67         while ((c = _ao_ps2_poll()) == AO_READ_AGAIN)
68                 ao_sleep(&ao_ps2_rx_fifo);
69         ao_arch_release_interrupts();
70         return (uint8_t) c;
71 }
72
73
74 int
75 ao_ps2_poll(void)
76 {
77         int     c;
78         ao_arch_block_interrupts();
79         c = _ao_ps2_poll();
80         ao_arch_release_interrupts();
81         return (uint8_t) c;
82 }
83
84 void
85 ao_ps2_put(uint8_t c)
86 {
87         ao_arch_block_interrupts();
88         ao_ps2_tx = ((uint16_t) c) | (_ao_ps2_parity(c) << 8) | (3 << 9);
89         ao_ps2_tx_count = 11;
90         ao_exti_disable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
91         ao_arch_release_interrupts();
92
93         /* pull the clock pin down */
94         ao_enable_output(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT, AO_PS2_CLOCK_PIN, 0);
95         ao_delay(0);
96
97         /* pull the data pin down for the start bit */
98         ao_enable_output(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN, 0);
99         ao_delay(0);
100
101         /* switch back to input mode for the interrupt to work */
102         ao_exti_setup(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT,
103                       AO_PS2_CLOCK_MODE(AO_EXTI_MODE_PULL_UP),
104                       ao_ps2_isr);
105         ao_exti_enable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
106
107         /* wait for the bits to drain */
108         while (ao_ps2_tx_count)
109                 ao_sleep(&ao_ps2_tx_count);
110
111 }
112
113 static uint8_t  ao_ps2_down[128 / 8];
114
115 static void
116 ao_ps2_set_down(uint8_t code, uint8_t value)
117 {
118         uint8_t shift = (code & 0x07);
119         uint8_t byte = code >> 3;
120
121         ao_ps2_down[byte] = (ao_ps2_down[byte] & ~(1 << shift)) | (value << shift);
122 }
123
124 uint8_t
125 ao_ps2_is_down(uint8_t code)
126 {
127         uint8_t shift = (code & 0x07);
128         uint8_t byte = code >> 3;
129
130         return (ao_ps2_down[byte] >> shift) & 1;
131 }
132
133 static void
134 _ao_ps2_set_leds(void)
135 {
136         uint8_t led = 0;
137         if (ao_ps2_is_down(AO_PS2_CAPS_LOCK))
138                 led |= AO_PS2_SET_LEDS_CAPS;
139         if (ao_ps2_is_down(AO_PS2_NUM_LOCK))
140                 led |= AO_PS2_SET_LEDS_NUM;
141         if (ao_ps2_is_down(AO_PS2_SCROLL_LOCK))
142                 led |= AO_PS2_SET_LEDS_SCROLL;
143         ao_arch_release_interrupts();
144         ao_ps2_put(AO_PS2_SET_LEDS);
145         while (ao_ps2_get() != 0xfa);
146         ao_ps2_put(led);
147         ao_arch_block_interrupts();
148 }
149
150 static uint8_t
151 ao_ps2_is_lock(uint8_t code) {
152         switch (code) {
153         case AO_PS2_CAPS_LOCK:
154         case AO_PS2_NUM_LOCK:
155         case AO_PS2_SCROLL_LOCK:
156                 return 1;
157         }
158         return 0;
159 }
160
161 static void
162 _ao_ps2_set_scancode_set(uint8_t set)
163 {
164         ao_ps2_scancode_set = set;
165         ao_arch_release_interrupts();
166         ao_ps2_put(AO_PS2_SET_SCAN_CODE_SET);
167         while (ao_ps2_get() != 0xfa);
168         ao_ps2_put(set);
169         ao_ps2_put(AO_PS2_SET_KEY_TYPEMATIC_MAKE_BREAK);
170         while (ao_ps2_get() != 0xfa);
171         ao_arch_block_interrupts();
172 }
173
174 static int
175 _ao_ps2_poll_key(void)
176 {
177         int     c;
178         uint8_t set_led = 0;
179         static uint8_t  saw_break;
180
181         c = _ao_ps2_poll();
182         if (c < 0) {
183                 if (ao_ps2_scancode_set != 3) {
184                         _ao_ps2_set_scancode_set(3);
185                 }
186                 return c;
187         }
188
189         if (c == AO_PS2_BREAK) {
190                 saw_break = 1;
191                 return AO_READ_AGAIN;
192         }
193         if (c & 0x80)
194                 return AO_READ_AGAIN;
195
196         if (ao_ps2_is_lock(c)) {
197                 if (saw_break) {
198                         saw_break = 0;
199                         return AO_READ_AGAIN;
200                 }
201                 if (ao_ps2_is_down(c))
202                         saw_break = 1;
203                 set_led = 1;
204         }
205         if (saw_break) {
206                 saw_break = 0;
207                 ao_ps2_set_down(c, 0);
208                 c |= 0x80;
209         } else
210                 ao_ps2_set_down(c, 1);
211         if (set_led)
212                 _ao_ps2_set_leds();
213
214         if (ao_ps2_scancode_set != 3)
215                 _ao_ps2_set_scancode_set(3);
216
217         return c;
218 }
219
220 int
221 ao_ps2_poll_key(void)
222 {
223         int     c;
224         ao_arch_block_interrupts();
225         c = _ao_ps2_poll_key();
226         ao_arch_release_interrupts();
227         return c;
228 }
229
230 uint8_t
231 ao_ps2_get_key(void)
232 {
233         int     c;
234         ao_arch_block_interrupts();
235         while ((c = _ao_ps2_poll_key()) == AO_READ_AGAIN)
236                 ao_sleep(&ao_ps2_rx_fifo);
237         ao_arch_release_interrupts();
238         return (uint8_t) c;
239 }
240
241 static const uint8_t    ao_ps2_asciimap[128][2] = {
242         [AO_PS2_A] = { 'a', 'A' },
243         [AO_PS2_B] = { 'b', 'B' },
244         [AO_PS2_C] = { 'c', 'C' },
245         [AO_PS2_D] = { 'd', 'D' },
246         [AO_PS2_E] = { 'e', 'E' },
247         [AO_PS2_F] = { 'f', 'F' },
248         [AO_PS2_G] = { 'g', 'G' },
249         [AO_PS2_H] = { 'h', 'H' },
250         [AO_PS2_I] = { 'i', 'I' },
251         [AO_PS2_J] = { 'j', 'J' },
252         [AO_PS2_K] = { 'k', 'K' },
253         [AO_PS2_L] = { 'l', 'L' },
254         [AO_PS2_M] = { 'm', 'M' },
255         [AO_PS2_N] = { 'n', 'N' },
256         [AO_PS2_O] = { 'o', 'O' },
257         [AO_PS2_P] = { 'p', 'P' },
258         [AO_PS2_Q] = { 'q', 'Q' },
259         [AO_PS2_R] = { 'r', 'R' },
260         [AO_PS2_S] = { 's', 'S' },
261         [AO_PS2_T] = { 't', 'T' },
262         [AO_PS2_U] = { 'u', 'U' },
263         [AO_PS2_V] = { 'v', 'V' },
264         [AO_PS2_W] = { 'w', 'W' },
265         [AO_PS2_X] = { 'x', 'X' },
266         [AO_PS2_Y] = { 'y', 'Y' },
267         [AO_PS2_Z] = { 'z', 'Z' },
268
269         [AO_PS2_0] = { '0', ')' },
270         [AO_PS2_1] = { '1', '!' },
271         [AO_PS2_2] = { '2', '@' },
272         [AO_PS2_3] = { '3', '#' },
273         [AO_PS2_4] = { '4', '$' },
274         [AO_PS2_5] = { '5', '%' },
275         [AO_PS2_6] = { '6', '^' },
276         [AO_PS2_7] = { '7', '&' },
277         [AO_PS2_8] = { '8', '*' },
278         [AO_PS2_9] = { '9', '(' },
279
280         [AO_PS2_GRAVE] = { '`', '~' },
281         [AO_PS2_HYPHEN] = { '-', '_' },
282         [AO_PS2_EQUAL] = { '=', '+' },
283         [AO_PS2_BACKSLASH] = { '\\', '|' },
284         [AO_PS2_BACKSPACE] = { '\010', '\010' },
285         [AO_PS2_SPACE] = { ' ', ' ' },
286         [AO_PS2_TAB] = { '\t', '\t' },
287
288         [AO_PS2_ENTER] = { '\r', '\r' },
289         [AO_PS2_ESC] = { '\033', '\033' },
290
291         [AO_PS2_OPEN_SQ] = { '[', '{' },
292         [AO_PS2_DELETE] = { '\177', '\177' },
293
294         [AO_PS2_KP_TIMES] = { '*', '*' },
295         [AO_PS2_KP_PLUS] = { '+', '+' },
296         [AO_PS2_KP_ENTER] = { '\r', '\r' },
297         [AO_PS2_KP_DECIMAL] = { '.', '.' },
298         [AO_PS2_KP_0] = { '0', '0' },
299         [AO_PS2_KP_1] = { '1', '1' },
300         [AO_PS2_KP_2] = { '2', '2' },
301         [AO_PS2_KP_3] = { '3', '3' },
302         [AO_PS2_KP_4] = { '4', '4' },
303         [AO_PS2_KP_5] = { '5', '5' },
304         [AO_PS2_KP_6] = { '6', '6' },
305         [AO_PS2_KP_7] = { '7', '7' },
306         [AO_PS2_KP_8] = { '8', '8' },
307         [AO_PS2_KP_9] = { '9', '9' },
308         [AO_PS2_CLOSE_SQ] = { ']', '}' },
309         [AO_PS2_SEMICOLON] = { ';', ':' },
310         [AO_PS2_ACUTE] = { '\'', '"' },
311         [AO_PS2_COMMA] = { ',', '<' },
312         [AO_PS2_PERIOD] = { '.', '>' },
313         [AO_PS2_SLASH] = { '/', '?' },
314 };
315
316 int
317 ao_ps2_ascii(uint8_t key)
318 {
319         uint8_t col;
320         char a;
321
322         /* Skip key releases */
323         if (key & 0x80)
324                 return AO_READ_AGAIN;
325
326         col = 0;
327         if (ao_ps2_is_down(AO_PS2_L_SHIFT) || ao_ps2_is_down(AO_PS2_R_SHIFT))
328                 col = 1;
329
330         /* caps lock */
331         a = ao_ps2_asciimap[key][0];
332         if (!a)
333                 return AO_READ_AGAIN;
334
335         if ('a' <= a && a <= 'z')
336                 if (ao_ps2_is_down(AO_PS2_CAPS_LOCK))
337                         col ^= 1;
338         a = ao_ps2_asciimap[key][col];
339         if ('@' <= a && a <= 0x7f && (ao_ps2_is_down(AO_PS2_L_CTRL) || ao_ps2_is_down(AO_PS2_R_CTRL)))
340                 a &= 0x1f;
341         return a;
342 }
343
344 int
345 _ao_ps2_pollchar(void)
346 {
347         int     key;
348
349         key = _ao_ps2_poll_key();
350         if (key < 0)
351                 return key;
352         return ao_ps2_ascii(key);
353 }
354
355 char
356 ao_ps2_getchar(void)
357 {
358         int     c;
359         ao_arch_block_interrupts();
360         while ((c = _ao_ps2_pollchar()) == AO_READ_AGAIN)
361                 ao_sleep(&ao_ps2_rx_fifo);
362         ao_arch_release_interrupts();
363         return (char) c;
364 }
365
366 static void
367 ao_ps2_isr(void)
368 {
369         uint8_t bit;
370
371         if (ao_ps2_tx_count) {
372                 ao_gpio_set(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN, ao_ps2_tx&1);
373                 ao_ps2_tx >>= 1;
374                 ao_ps2_tx_count--;
375                 if (!ao_ps2_tx_count) {
376                         ao_enable_input(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_EXTI_MODE_PULL_UP);
377                         ao_wakeup(&ao_ps2_tx_count);
378                 }
379                 return;
380         }
381         /* reset if its been a while */
382         if ((ao_tick_count - ao_ps2_tick) > AO_MS_TO_TICKS(100))
383                 ao_ps2_count = 0;
384         ao_ps2_tick = ao_tick_count;
385
386         bit = ao_gpio_get(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN);
387         if (ao_ps2_count == 0) {
388                 /* check for start bit, ignore if not zero */
389                 if (bit)
390                         return;
391                 ao_ps2_value = 0;
392         } else if (ao_ps2_count < 9) {
393                 ao_ps2_value |= (bit << (ao_ps2_count - 1));
394         } else if (ao_ps2_count == 10) {
395                 ao_fifo_insert(ao_ps2_rx_fifo, ao_ps2_value);
396                 ao_wakeup(&ao_ps2_rx_fifo);
397                 if (ao_ps2_stdin)
398                         ao_wakeup(&ao_stdin_ready);
399                 ao_ps2_count = 0;
400                 return;
401         }
402         ao_ps2_count++;
403 }
404
405 void
406 ao_ps2_init(void)
407 {
408         ao_enable_input(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT,
409                         AO_EXTI_MODE_PULL_UP);
410
411         ao_enable_port(AO_PS2_CLOCK_PORT);
412
413         ao_exti_setup(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT,
414                       AO_PS2_CLOCK_MODE(AO_EXTI_MODE_PULL_UP),
415                       ao_ps2_isr);
416         ao_exti_enable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
417
418         ao_ps2_scancode_set = 2;
419 }