98dc35b55eee33c8b7cd8ba069912423eaf23674
[fw/altos] / src / drivers / ao_rn4678.c
1 /*
2  * Copyright © 2017 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_rn4678.h>
17 #include <ao_exti.h>
18 #include <stdarg.h>
19
20 static uint8_t  ao_rn_connected;
21
22 #define AO_RN_DEBUG 0
23
24 #if AO_RN_DEBUG
25 static void ao_rn_dbg(char *format, ...) {
26         va_list a;
27         uint32_t        irq = ao_arch_irqsave();
28         ao_arch_release_interrupts();
29         va_start(a, format);
30         vprintf(format, a);
31         va_end(a);
32         flush();
33         ao_arch_irqrestore(irq);
34 }
35
36 static char     ao_rn_dir;
37
38 static void
39 ao_rn_log_char(char c, char dir)
40 {
41         if (dir != ao_rn_dir) {
42                 putchar(dir); putchar('\n');
43                 ao_rn_dir = dir;
44         }
45         switch (c) {
46         case '\r':
47                 putchar('\\'); putchar('r');
48                 break;
49         case '\n':
50                 putchar('\\'); putchar('n');
51                 break;
52         default:
53                 putchar(c);
54         }
55         flush();
56 }
57
58 static void
59 ao_rn_log_out_char(char c)
60 {
61         ao_rn_log_char(c, '}');
62 }
63
64 static void
65 ao_rn_log_in_char(char c)
66 {
67         ao_rn_log_char(c, '{');
68 }
69
70 static inline void
71 ao_rn_putchar(char c)
72 {
73         ao_rn_log_out_char(c);
74         ao_serial_rn_putchar(c);
75 }
76
77 static inline int
78 _ao_rn_pollchar(void)
79 {
80         int     c = _ao_serial_rn_pollchar();
81
82         if (c != AO_READ_AGAIN) {
83                 ao_arch_release_interrupts();
84                 ao_rn_log_in_char((char) c);
85                 ao_arch_block_interrupts();
86         }
87         return c;
88 }
89 #else
90 #define ao_rn_dbg(fmt, ...)
91 #define ao_rn_putchar(c)        ao_serial_rn_putchar(c)
92 #define _ao_rn_pollchar()       _ao_serial_rn_pollchar()
93 #endif
94
95 /* For stdio, this skips all status messages *sigh* */
96
97 #define STATUS_CHAR     '%'
98
99 static const char *status_strings[] = {
100         "RFCOMM_CLOSE",
101         "RFCOMM_OPEN",
102         "CONNECT",
103         "LCONNECT",
104         "DISCONN",
105         "BONDED",
106 };
107
108 #define NUM_STATUS_STRING       (sizeof status_strings/sizeof status_strings[0])
109
110 static char             ao_rn_buffer[64];
111 static int              ao_rn_buf_cnt, ao_rn_buf_ptr;
112 static int              ao_rn_draining;
113 static AO_TICK_TYPE     ao_rn_buf_time;
114
115 /* Well, this is annoying. The status strings from the RN4678 can't be
116  * disabled due to a firmware bug. So, this code finds those in the
117  * input and strips them out.
118  */
119 int
120 _ao_wrap_rn_pollchar(void)
121 {
122         int             c = AO_READ_AGAIN;
123         unsigned        i;
124         int             done = 0;
125
126         while (!done && !ao_rn_draining) {
127                 c = _ao_serial_rn_pollchar();
128
129                 if (c == AO_READ_AGAIN) {
130                         if (ao_rn_buf_cnt && (ao_time() - ao_rn_buf_time) > AO_MS_TO_TICKS(1000)) {
131                                 ao_rn_draining = 1;
132                                 continue;
133                         }
134                         return AO_READ_AGAIN;
135                 }
136
137                 if (ao_rn_buf_cnt) {
138                         /* buffering chars */
139
140                         if (c == STATUS_CHAR) {
141                                 /* End of status string, drop it and carry on */
142                                 ao_rn_buffer[ao_rn_buf_cnt] = '\0';
143 //                              ao_rn_dbg("discard %s\n", ao_rn_buffer);
144                                 ao_rn_buf_cnt = 0;
145                         } else if (ao_rn_buf_cnt == sizeof(ao_rn_buffer)) {
146                                 /* If we filled the buffer, just give up */
147                                 ao_rn_draining = 1;
148                         } else {
149                                 ao_rn_buffer[ao_rn_buf_cnt++] = c;
150                                 for (i = 0; i < NUM_STATUS_STRING; i++) {
151                                         int cmp = strlen(status_strings[i]);
152                                         if (cmp >= ao_rn_buf_cnt)
153                                                 cmp = ao_rn_buf_cnt-1;
154                                         if (memcmp(ao_rn_buffer+1, status_strings[i], cmp) == 0)
155                                                 break;
156                                 }
157                                 if (i == NUM_STATUS_STRING)
158                                         ao_rn_draining = 1;
159                         }
160                 } else if (c == STATUS_CHAR) {
161                         ao_rn_buffer[0] = c;
162                         ao_rn_buf_cnt = 1;
163                         ao_rn_buf_ptr = 0;
164                         ao_rn_buf_time = ao_time();
165                 } else
166                         done = 1;
167         }
168         if (ao_rn_draining) {
169                 c = ao_rn_buffer[ao_rn_buf_ptr++] & 0xff;
170                 if (ao_rn_buf_ptr == ao_rn_buf_cnt) {
171                         ao_rn_buf_ptr = ao_rn_buf_cnt = 0;
172                         ao_rn_draining = 0;
173                 }
174         }
175         return c;
176 }
177
178 static void
179 ao_rn_puts(char *s)
180 {
181         char c;
182
183         while ((c = *s++))
184                 ao_rn_putchar(c);
185 }
186
187 static void
188 ao_rn_drain(void)
189 {
190         int     timeout = 0;
191
192 //      ao_rn_dbg("drain...\n");
193         ao_serial_rn_drain();
194         while (!timeout) {
195                 ao_arch_block_interrupts();
196                 while (_ao_rn_pollchar() == AO_READ_AGAIN) {
197                         if (_ao_serial_rn_sleep_for(AO_MS_TO_TICKS(10))) {
198                                 timeout = 1;
199                                 break;
200                         }
201                 }
202                 ao_arch_release_interrupts();
203         }
204 //      ao_rn_dbg("drain done\n");
205 }
206
207 static void
208 ao_rn_send_cmd(char *cmd, char *param)
209 {
210 //      ao_rn_dbg("send_cmd %s%s\n", cmd, param ? param : "");
211         ao_rn_drain();
212         ao_rn_puts(cmd);
213         if (param)
214                 ao_rn_puts(param);
215         ao_rn_putchar('\r');
216 }
217
218 static int
219 ao_rn_wait_char(AO_TICK_TYPE giveup_time)
220 {
221         int c;
222
223         ao_arch_block_interrupts();
224         while ((c = _ao_rn_pollchar()) == AO_READ_AGAIN) {
225                 AO_TICK_SIGNED  delay = (AO_TICK_SIGNED) (giveup_time - ao_time());
226                 if (delay < 0) {
227                         ao_arch_release_interrupts();
228                         return AO_READ_AGAIN;
229                 }
230                 _ao_serial_rn_sleep_for(delay);
231         }
232         ao_arch_release_interrupts();
233         return c;
234 }
235
236 static int
237 ao_rn_wait_for(int timeout, char *match)
238 {
239         char            reply[AO_RN_MAX_REPLY_LEN + 1];
240         int             match_len = strlen(match);
241         AO_TICK_TYPE    giveup_time = ao_time() + timeout;
242         int             c;
243
244 //      ao_rn_dbg("wait for %d, \"%s\"\n", timeout, match);
245         memset(reply, ' ', sizeof(reply));
246         while (memcmp(reply, match, match_len) != 0) {
247                 c = ao_rn_wait_char(giveup_time);
248                 if (c == AO_READ_AGAIN) {
249 //                      ao_rn_dbg("\twait for timeout\n");
250                         return AO_RN_TIMEOUT;
251                 }
252                 reply[match_len] = (char) c;
253                 memmove(reply, reply+1, match_len);
254                 reply[match_len] = '\0';
255 //              ao_rn_dbg("\tmatch now \"%s\"\n", reply);
256         }
257 //      ao_rn_dbg("\twait for ok\n");
258         return AO_RN_OK;
259 }
260
261 static int
262 ao_rn_wait_line(AO_TICK_TYPE giveup_time, char *line, int len)
263 {
264         char *l = line;
265
266 //      ao_rn_dbg("wait line\n");
267         for (;;) {
268                 int c = ao_rn_wait_char(giveup_time);
269
270                 /* timeout */
271                 if (c == AO_READ_AGAIN) {
272 //                      ao_rn_dbg("\twait line timeout\n");
273                         return AO_RN_TIMEOUT;
274                 }
275
276                 /* done */
277                 if (c == '\r') {
278                         *l = '\0';
279 //                      ao_rn_dbg("\twait line \"%s\"\n", line);
280                         return AO_RN_OK;
281                 }
282
283                 if (c == '\n')
284                         continue;
285
286                 /* buffer overrun */
287                 if (len <= 1)
288                         return AO_RN_ERROR;
289
290                 *l++ = (char) c;
291                 len--;
292         }
293 }
294
295 static int
296 ao_rn_wait_status(void)
297 {
298         char            message[AO_RN_MAX_REPLY_LEN];
299         AO_TICK_TYPE    giveup_time = ao_time() + AO_RN_CMD_TIMEOUT;
300         int             status;
301
302 //      ao_rn_dbg("wait status\n");
303         status = ao_rn_wait_line(giveup_time, message, sizeof (message));
304         if (status == AO_RN_OK)
305                 if (strncmp(message, "AOK", 3) != 0)
306                         status = AO_RN_ERROR;
307         return status;
308 }
309
310 static int
311 ao_rn_set_name(void)
312 {
313         char    sn[8];
314         char    *s = sn + 8;
315         int     n;
316
317 //      ao_rn_dbg("set name...\n");
318         *--s = '\0';
319         n = ao_serial_number;
320         do {
321                 *--s = '0' + n % 10;
322         } while (n /= 10);
323         ao_rn_send_cmd(AO_RN_SET_NAME_CMD "TeleBT-", s);
324         return ao_rn_wait_status();
325 }
326
327 static int
328 ao_rn_get_name(char *name, int len)
329 {
330 //      ao_rn_dbg("get name...\n");
331         ao_rn_send_cmd(AO_RN_GET_NAME_CMD, NULL);
332         return ao_rn_wait_line(ao_time() + AO_RN_CMD_TIMEOUT, name, len);
333 }
334
335 static void
336 ao_rn_check_link(void)
337 {
338         ao_rn_connected = 1 - ao_gpio_get(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, foo);
339 }
340
341 static void
342 ao_rn_isr(void)
343 {
344         ao_rn_check_link();
345         ao_wakeup(&ao_rn_connected);
346 }
347
348 static void
349 ao_bt_panic(int where)
350 {
351         int i;
352         for (;;) {
353                 for (i = 0; i < 50; i++) {
354                         ao_led_toggle(AO_BT_LED);
355                         ao_delay(AO_MS_TO_TICKS(10));
356                 }
357                 ao_led_off(AO_BT_LED);
358                 ao_delay(AO_MS_TO_TICKS(500));
359                 for (i = 0; i < where; i++) {
360                         ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(200));
361                         ao_delay(AO_MS_TO_TICKS(200));
362                 }
363         }
364 }
365
366 static uint8_t  ao_rn_stdio;
367
368 /*
369  * Set the stdio echo for the bluetooth link
370  */
371 void
372 ao_rn_echo(uint8_t echo)
373 {
374         ao_stdios[ao_rn_stdio].echo = echo;
375 }
376
377 static void
378 ao_rn(void)
379 {
380         int     status = AO_RN_ERROR;
381         char    name[17];
382         int     i;
383
384         ao_rn_dbg("ao_rn top\n");
385
386         /* Select CMD mode after the device gets out of reset */
387         ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD);
388
389         for (i = 0; i < 3; i++) {
390                 ao_rn_dbg("reset device\n");
391
392                 ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
393                 ao_delay(AO_MS_TO_TICKS(100));
394
395                 /* Reboot the RN4678 and wait for it to start talking */
396                 ao_rn_drain();
397                 ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 1);
398                 status = ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, AO_RN_REBOOT_MSG);
399                 if (status != AO_RN_OK) {
400                         ao_rn_dbg("reboot failed\n");
401                         continue;
402                 }
403
404                 /* After it reboots, it can take a moment before it responds
405                  * to commands
406                  */
407                 status = ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, "CMD> ");
408
409                 if (status == AO_RN_TIMEOUT) {
410                         ao_rn_puts("$$$");
411                         (void) ao_rn_wait_for(AO_RN_REBOOT_TIMEOUT, "CMD> ");
412                 }
413
414                 ao_rn_send_cmd(AO_RN_VERSION_CMD, NULL);
415                 (void) ao_rn_wait_status();
416
417                 /* Check to see if the name is already set and assume
418                  * that the device is ready to go
419                  */
420                 status = ao_rn_get_name(name, sizeof (name));
421                 if (status != AO_RN_OK) {
422                         ao_rn_dbg("get name failed\n");
423                         status = ao_rn_get_name(name, sizeof (name));
424                         if (status != AO_RN_OK)
425                                 continue;
426                 }
427
428                 if (strncmp(name, "TeleBT-", 7) == 0) {
429                         ao_rn_dbg("name is set\n");
430                         status = AO_RN_OK;
431                         break;
432                 }
433
434                 /* Make the command pin control command/data mode */
435                 ao_rn_send_cmd(AO_RN_SET_COMMAND_PIN, NULL);
436                 if (ao_rn_wait_status() != AO_RN_OK) {
437                         ao_rn_dbg("set command pin failed\n");
438                         continue;
439                 }
440
441                 ao_rn_send_cmd(AO_RN_SET_STATUS_STRING, AO_RN_STATUS_STRING_ENABLE);
442                 if (ao_rn_wait_status() != AO_RN_OK) {
443                         ao_rn_dbg("set status string\n");
444                         continue;
445                 }
446
447                 /* Select 'fast' mode to ignore command sequence (more or less) */
448                 ao_rn_send_cmd(AO_RN_SET_FAST_MODE, NULL);
449                 if (ao_rn_wait_status() != AO_RN_OK) {
450                         ao_rn_dbg("set fast mode failed\n");
451                         continue;
452                 }
453
454                 /* Finally, set the name. Doing this last makes it possible to check
455                  * if the whole sequence has been done
456                  */
457                 if (ao_rn_set_name() != AO_RN_OK) {
458                         ao_rn_dbg("set name failed\n");
459                         continue;
460                 }
461
462                 /* After we've configured the device, go back around and reboot it
463                  * as that's how we get the new configuration to take effect
464                  */
465         }
466         ao_rn_dbg("ao_rn status %d\n", status);
467
468         if (status != AO_RN_OK)
469                 ao_bt_panic(4);
470
471         ao_gpio_set(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_DATA);
472
473         /* Wait for the hardware to finish sending messages, then clear the queue */
474         ao_delay(AO_MS_TO_TICKS(200));
475         ao_rn_drain();
476
477         ao_exti_enable(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN);
478
479 #if AO_RN_DEBUG
480
481         /*
482          * Separate debug code when things aren't working. Just dump
483          * inbound bluetooth characters to stdout
484          */
485         for (;;) {
486                 int     c;
487
488                 ao_arch_block_interrupts();
489                 while ((c = _ao_rn_pollchar()) == AO_READ_AGAIN)
490                         ao_sleep(&ao_serial_rn_rx_fifo);
491                 ao_arch_release_interrupts();
492         }
493 #else
494         ao_rn_stdio = ao_add_stdio(_ao_wrap_rn_pollchar,
495                                    ao_serial_rn_putchar,
496                                    NULL);
497
498         ao_rn_echo(0);
499         ao_rn_check_link();
500         /*
501          * Now just hang around and flash the blue LED when we've got
502          * a connection
503          */
504         for (;;) {
505                 ao_arch_block_interrupts();
506                 while (!ao_rn_connected)
507                         ao_sleep(&ao_rn_connected);
508                 ao_arch_release_interrupts();
509                 while (ao_rn_connected) {
510                         ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(20));
511                         if (ao_rn_buf_cnt != 0)
512                                 ao_wakeup(&ao_stdin_ready);
513                         ao_delay(AO_SEC_TO_TICKS(3));
514                 }
515         }
516 #endif
517 }
518
519 static struct ao_task ao_rn_task;
520
521 static void
522 ao_rn_factory(void)
523 {
524         int     i;
525         int     v = 0;
526
527         /*
528          * Factory reset. Flip pin P3_1 5 times within the first five
529          * seconds of power-on
530          */
531
532         /* Select our target output pin */
533         ao_enable_output(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v);
534
535         /* Turn off the BT device using the SW_BTN pin */
536         printf("Power down BT\n"); flush();
537         ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 0);
538         ao_delay(AO_MS_TO_TICKS(1000));
539
540         /* And turn it back on */
541         printf("Power up BT\n"); flush();
542         ao_gpio_set(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1);
543
544         /* Right after power on, poke P3_1 five times to force a
545          * factory reset
546          */
547         for (i = 0; i < 20; i++) {
548                 v = 1-v;
549                 ao_delay(AO_MS_TO_TICKS(50));
550                 ao_gpio_set(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, foo, v);
551                 ao_led_toggle(AO_BT_LED);
552         }
553
554         /* And let P3_1 float again */
555         ao_enable_input(AO_RN_P3_1_PORT, AO_RN_P3_1_PIN, AO_EXTI_MODE_PULL_NONE);
556
557         printf("Reboot BT\n"); flush();
558         ao_delay(AO_MS_TO_TICKS(100));
559         ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
560         ao_delay(AO_MS_TO_TICKS(100));
561         ao_gpio_set(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 1);
562 }
563
564 #if AO_RN_DEBUG
565 static void
566 ao_rn_send(void)
567 {
568         int     c;
569
570         while ((c = getchar()) != '~')
571                 ao_rn_putchar(c);
572 }
573 #endif
574
575 static const struct ao_cmds rn_cmds[] = {
576         { ao_rn_factory, "F\0Factory reset rn4678" },
577 #if AO_RN_DEBUG
578         { ao_rn_send, "B\0Send data to rn4678. End with ~" },
579 #endif
580         { 0 },
581 };
582
583 void
584 ao_rn4678_init(void)
585 {
586         (void) ao_rn_set_name;
587
588         ao_serial_rn_set_speed(AO_SERIAL_SPEED_115200);
589
590         /* Reset line */
591         ao_enable_output(AO_RN_RST_N_PORT, AO_RN_RST_N_PIN, foo, 0);
592
593         /* SW_BTN */
594         ao_enable_output(AO_RN_SW_BTN_PORT, AO_RN_SW_BTN_PIN, foo, 1);
595
596         /* P3_7 command/data selector */
597         ao_enable_output(AO_RN_CMD_PORT, AO_RN_CMD_PIN, foo, AO_RN_CMD_CMD);
598
599         ao_enable_input(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN, AO_EXTI_MODE_PULL_NONE);
600         ao_exti_setup(AO_RN_CONNECTED_PORT, AO_RN_CONNECTED_PIN,
601                       AO_EXTI_MODE_FALLING|AO_EXTI_MODE_RISING|AO_EXTI_PRIORITY_LOW,
602                       ao_rn_isr);
603
604         ao_cmd_register(rn_cmds);
605         ao_add_task(&ao_rn_task, ao_rn, "bluetooth");
606 }