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