flash: New Spansion FM4 flash driver
[openocd.git] / src / flash / nor / fm4.c
1 /*
2 * Spansion FM4 flash
3 *
4 * Copyright (c) 2015 Andreas Färber
5 *
6 * Based on S6E2CC_MN709-00007 for S6E2CC/C5/C4/C3/C2/C1 series
7 * Based on MB9B560R_MN709-00005 for MB9BFx66/x67/x68 series
8 */
9
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif
13
14 #include "imp.h"
15 #include <helper/binarybuffer.h>
16 #include <target/algorithm.h>
17 #include <target/armv7m.h>
18
19 #define FLASH_BASE 0x40000000
20 #define FASZR (FLASH_BASE + 0x000)
21 #define DFCTRLR (FLASH_BASE + 0x030)
22 #define DFCTRLR_DFE (1UL << 0)
23
24 #define WDG_BASE 0x40011000
25 #define WDG_CTL (WDG_BASE + 0x008)
26 #define WDG_LCK (WDG_BASE + 0xC00)
27
28 enum fm4_variant {
29 mb9bfx66,
30 mb9bfx67,
31 mb9bfx68,
32
33 s6e2cx8,
34 s6e2cx9,
35 s6e2cxa,
36 };
37
38 struct fm4_flash_bank {
39 enum fm4_variant variant;
40 int macro_nr;
41 bool probed;
42 };
43
44 static int fm4_disable_hw_watchdog(struct target *target)
45 {
46 int retval;
47
48 retval = target_write_u32(target, WDG_LCK, 0x1ACCE551);
49 if (retval != ERROR_OK)
50 return retval;
51
52 retval = target_write_u32(target, WDG_LCK, 0xE5331AAE);
53 if (retval != ERROR_OK)
54 return retval;
55
56 retval = target_write_u32(target, WDG_CTL, 0);
57 if (retval != ERROR_OK)
58 return retval;
59
60 return ERROR_OK;
61 }
62
63 static int fm4_enter_flash_cpu_programming_mode(struct target *target)
64 {
65 uint32_t u32_value;
66 int retval;
67
68 /* FASZR ASZ = CPU programming mode */
69 retval = target_write_u32(target, FASZR, 0x00000001);
70 if (retval != ERROR_OK)
71 return retval;
72 retval = target_read_u32(target, FASZR, &u32_value);
73 if (retval != ERROR_OK)
74 return retval;
75
76 return ERROR_OK;
77 }
78
79 static int fm4_enter_flash_cpu_rom_mode(struct target *target)
80 {
81 uint32_t u32_value;
82 int retval;
83
84 /* FASZR ASZ = CPU ROM mode */
85 retval = target_write_u32(target, FASZR, 0x00000002);
86 if (retval != ERROR_OK)
87 return retval;
88 retval = target_read_u32(target, FASZR, &u32_value);
89 if (retval != ERROR_OK)
90 return retval;
91
92 return ERROR_OK;
93 }
94
95 static int fm4_flash_erase(struct flash_bank *bank, int first, int last)
96 {
97 struct target *target = bank->target;
98 struct working_area *workarea;
99 struct reg_param reg_params[4];
100 struct armv7m_algorithm armv7m_algo;
101 unsigned i;
102 int retval, sector;
103 const uint8_t erase_sector_code[] = {
104 #include "../../../contrib/loaders/flash/fm4/erase.inc"
105 };
106
107 if (target->state != TARGET_HALTED) {
108 LOG_WARNING("Cannot communicate... target not halted.");
109 return ERROR_TARGET_NOT_HALTED;
110 }
111
112 LOG_DEBUG("Spansion FM4 erase sectors %d to %d", first, last);
113
114 retval = fm4_disable_hw_watchdog(target);
115 if (retval != ERROR_OK)
116 return retval;
117
118 retval = fm4_enter_flash_cpu_programming_mode(target);
119 if (retval != ERROR_OK)
120 return retval;
121
122 retval = target_alloc_working_area(target, sizeof(erase_sector_code),
123 &workarea);
124 if (retval != ERROR_OK) {
125 LOG_ERROR("No working area available.");
126 retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
127 goto err_alloc_code;
128 }
129 retval = target_write_buffer(target, workarea->address,
130 sizeof(erase_sector_code), erase_sector_code);
131 if (retval != ERROR_OK)
132 goto err_write_code;
133
134 armv7m_algo.common_magic = ARMV7M_COMMON_MAGIC;
135 armv7m_algo.core_mode = ARM_MODE_THREAD;
136
137 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT);
138 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);
139 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);
140 init_reg_param(&reg_params[3], "r3", 32, PARAM_IN);
141
142 for (sector = first; sector <= last; sector++) {
143 uint32_t addr = bank->base + bank->sectors[sector].offset;
144 uint32_t result;
145
146 buf_set_u32(reg_params[0].value, 0, 32, (addr & ~0xffff) | 0xAA8);
147 buf_set_u32(reg_params[1].value, 0, 32, (addr & ~0xffff) | 0x554);
148 buf_set_u32(reg_params[2].value, 0, 32, addr);
149
150 retval = target_run_algorithm(target,
151 0, NULL,
152 ARRAY_SIZE(reg_params), reg_params,
153 workarea->address, 0,
154 1000, &armv7m_algo);
155 if (retval != ERROR_OK) {
156 LOG_ERROR("Error executing flash sector erase "
157 "programming algorithm");
158 retval = ERROR_FLASH_OPERATION_FAILED;
159 goto err_run;
160 }
161
162 result = buf_get_u32(reg_params[3].value, 0, 32);
163 if (result == 2) {
164 LOG_ERROR("Timeout error from flash sector erase programming algorithm");
165 retval = ERROR_FLASH_OPERATION_FAILED;
166 goto err_run_ret;
167 } else if (result != 0) {
168 LOG_ERROR("Unexpected error %d from flash sector erase programming algorithm", result);
169 retval = ERROR_FLASH_OPERATION_FAILED;
170 goto err_run_ret;
171 } else
172 retval = ERROR_OK;
173
174 bank->sectors[sector].is_erased = 1;
175 }
176
177 err_run_ret:
178 err_run:
179 for (i = 0; i < ARRAY_SIZE(reg_params); i++)
180 destroy_reg_param(&reg_params[i]);
181
182 err_write_code:
183 target_free_working_area(target, workarea);
184
185 err_alloc_code:
186 if (retval != ERROR_OK)
187 fm4_enter_flash_cpu_rom_mode(target);
188 else
189 retval = fm4_enter_flash_cpu_rom_mode(target);
190
191 return retval;
192 }
193
194 static int fm4_flash_write(struct flash_bank *bank, const uint8_t *buffer,
195 uint32_t offset, uint32_t byte_count)
196 {
197 struct target *target = bank->target;
198 struct working_area *code_workarea, *data_workarea;
199 struct reg_param reg_params[6];
200 struct armv7m_algorithm armv7m_algo;
201 uint32_t halfword_count = DIV_ROUND_UP(byte_count, 2);
202 uint32_t result;
203 unsigned i;
204 int retval;
205 const uint8_t write_block_code[] = {
206 #include "../../../contrib/loaders/flash/fm4/write.inc"
207 };
208
209 LOG_DEBUG("Spansion FM4 write at 0x%08" PRIx32 " (%" PRId32 " bytes)",
210 offset, byte_count);
211
212 if (offset & 0x1) {
213 LOG_ERROR("offset 0x%" PRIx32 " breaks required 2-byte alignment",
214 offset);
215 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
216 }
217 if (byte_count & 0x1) {
218 LOG_WARNING("length %" PRId32 " is not 2-byte aligned, rounding up",
219 byte_count);
220 }
221
222 if (target->state != TARGET_HALTED) {
223 LOG_WARNING("Cannot communicate... target not halted.");
224 return ERROR_TARGET_NOT_HALTED;
225 }
226
227 retval = fm4_disable_hw_watchdog(target);
228 if (retval != ERROR_OK)
229 return retval;
230
231 retval = target_alloc_working_area(target, sizeof(write_block_code),
232 &code_workarea);
233 if (retval != ERROR_OK) {
234 LOG_ERROR("No working area available for write code.");
235 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
236 }
237 retval = target_write_buffer(target, code_workarea->address,
238 sizeof(write_block_code), write_block_code);
239 if (retval != ERROR_OK)
240 goto err_write_code;
241
242 retval = target_alloc_working_area(target,
243 MIN(halfword_count * 2, target_get_working_area_avail(target)),
244 &data_workarea);
245 if (retval != ERROR_OK) {
246 LOG_ERROR("No working area available for write data.");
247 retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
248 goto err_alloc_data;
249 }
250
251 armv7m_algo.common_magic = ARMV7M_COMMON_MAGIC;
252 armv7m_algo.core_mode = ARM_MODE_THREAD;
253
254 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT);
255 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);
256 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);
257 init_reg_param(&reg_params[3], "r3", 32, PARAM_OUT);
258 init_reg_param(&reg_params[4], "r4", 32, PARAM_OUT);
259 init_reg_param(&reg_params[5], "r5", 32, PARAM_IN);
260
261 retval = fm4_enter_flash_cpu_programming_mode(target);
262 if (retval != ERROR_OK)
263 goto err_flash_mode;
264
265 while (byte_count > 0) {
266 uint32_t halfwords = MIN(halfword_count, data_workarea->size / 2);
267 uint32_t addr = bank->base + offset;
268
269 LOG_DEBUG("copying %" PRId32 " bytes to SRAM 0x%08" PRIx32,
270 MIN(halfwords * 2, byte_count), data_workarea->address);
271
272 retval = target_write_buffer(target, data_workarea->address,
273 MIN(halfwords * 2, byte_count), buffer);
274 if (retval != ERROR_OK) {
275 LOG_ERROR("Error writing data buffer");
276 retval = ERROR_FLASH_OPERATION_FAILED;
277 goto err_write_data;
278 }
279
280 LOG_DEBUG("writing 0x%08" PRIx32 "-0x%08" PRIx32 " (%" PRId32 "x)",
281 addr, addr + halfwords * 2 - 1, halfwords);
282
283 buf_set_u32(reg_params[0].value, 0, 32, (addr & ~0xffff) | 0xAA8);
284 buf_set_u32(reg_params[1].value, 0, 32, (addr & ~0xffff) | 0x554);
285 buf_set_u32(reg_params[2].value, 0, 32, addr);
286 buf_set_u32(reg_params[3].value, 0, 32, data_workarea->address);
287 buf_set_u32(reg_params[4].value, 0, 32, halfwords);
288
289 retval = target_run_algorithm(target,
290 0, NULL,
291 ARRAY_SIZE(reg_params), reg_params,
292 code_workarea->address, 0,
293 5 * 60 * 1000, &armv7m_algo);
294 if (retval != ERROR_OK) {
295 LOG_ERROR("Error executing flash sector erase "
296 "programming algorithm");
297 retval = ERROR_FLASH_OPERATION_FAILED;
298 goto err_run;
299 }
300
301 result = buf_get_u32(reg_params[5].value, 0, 32);
302 if (result == 2) {
303 LOG_ERROR("Timeout error from flash write "
304 "programming algorithm");
305 retval = ERROR_FLASH_OPERATION_FAILED;
306 goto err_run_ret;
307 } else if (result != 0) {
308 LOG_ERROR("Unexpected error %d from flash write "
309 "programming algorithm", result);
310 retval = ERROR_FLASH_OPERATION_FAILED;
311 goto err_run_ret;
312 } else
313 retval = ERROR_OK;
314
315 halfword_count -= halfwords;
316 offset += halfwords * 2;
317 buffer += halfwords * 2;
318 byte_count -= MIN(halfwords * 2, byte_count);
319 }
320
321 err_run_ret:
322 err_run:
323 err_write_data:
324 retval = fm4_enter_flash_cpu_rom_mode(target);
325
326 err_flash_mode:
327 for (i = 0; i < ARRAY_SIZE(reg_params); i++)
328 destroy_reg_param(&reg_params[i]);
329
330 target_free_working_area(target, data_workarea);
331 err_alloc_data:
332 err_write_code:
333 target_free_working_area(target, code_workarea);
334
335 return retval;
336 }
337
338 static int mb9bf_probe(struct flash_bank *bank)
339 {
340 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
341 uint32_t flash_addr = bank->base;
342 int i;
343
344 switch (fm4_bank->variant) {
345 case mb9bfx66:
346 bank->num_sectors = 12;
347 break;
348 case mb9bfx67:
349 bank->num_sectors = 16;
350 break;
351 case mb9bfx68:
352 bank->num_sectors = 20;
353 break;
354 default:
355 return ERROR_FLASH_OPER_UNSUPPORTED;
356 }
357
358 LOG_DEBUG("%d sectors", bank->num_sectors);
359 bank->sectors = calloc(bank->num_sectors,
360 sizeof(struct flash_sector));
361 for (i = 0; i < bank->num_sectors; i++) {
362 if (i < 4)
363 bank->sectors[i].size = 8 * 1024;
364 else if (i == 4)
365 bank->sectors[i].size = 32 * 1024;
366 else
367 bank->sectors[i].size = 64 * 1024;
368 bank->sectors[i].offset = flash_addr - bank->base;
369 bank->sectors[i].is_erased = -1;
370 bank->sectors[i].is_protected = -1;
371
372 bank->size += bank->sectors[i].size;
373 flash_addr += bank->sectors[i].size;
374 }
375
376 return ERROR_OK;
377 }
378
379 static void s6e2cc_init_sector(struct flash_sector *sector, int sa)
380 {
381 if (sa < 8)
382 sector->size = 8 * 1024;
383 else if (sa == 8)
384 sector->size = 32 * 1024;
385 else
386 sector->size = 64 * 1024;
387
388 sector->is_erased = -1;
389 sector->is_protected = -1;
390 }
391
392 static int s6e2cc_probe(struct flash_bank *bank)
393 {
394 struct target *target = bank->target;
395 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
396 uint32_t u32_value;
397 uint32_t flash_addr = bank->base;
398 int i, retval, num_sectors, num_extra_sectors;
399
400 retval = target_read_u32(target, DFCTRLR, &u32_value);
401 if (retval != ERROR_OK)
402 return retval;
403 if (u32_value & DFCTRLR_DFE) {
404 LOG_WARNING("Dual Flash mode is not implemented.");
405 return ERROR_FLASH_OPER_UNSUPPORTED;
406 }
407
408 switch (fm4_bank->variant) {
409 case s6e2cx8:
410 num_sectors = (fm4_bank->macro_nr == 0) ? 20 : 0;
411 break;
412 case s6e2cx9:
413 num_sectors = (fm4_bank->macro_nr == 0) ? 20 : 12;
414 break;
415 case s6e2cxa:
416 num_sectors = 20;
417 break;
418 default:
419 return ERROR_FLASH_OPER_UNSUPPORTED;
420 }
421 num_extra_sectors = (fm4_bank->macro_nr == 0) ? 1 : 4;
422 bank->num_sectors = num_sectors + num_extra_sectors;
423
424 LOG_DEBUG("%d sectors", bank->num_sectors);
425 bank->sectors = calloc(bank->num_sectors,
426 sizeof(struct flash_sector));
427 for (i = 0; i < num_sectors; i++) {
428 int sa = 4 + i;
429 bank->sectors[i].offset = flash_addr - bank->base;
430 s6e2cc_init_sector(&bank->sectors[i], sa);
431
432 bank->size += bank->sectors[i].size;
433 flash_addr += bank->sectors[i].size;
434 }
435
436 flash_addr = (fm4_bank->macro_nr == 0) ? 0x00406000 : 0x00408000;
437 for (; i < bank->num_sectors; i++) {
438 int sa = 4 - num_extra_sectors + (i - num_sectors);
439 bank->sectors[i].offset = flash_addr - bank->base;
440 s6e2cc_init_sector(&bank->sectors[i], sa);
441
442 /*
443 * Don't increase bank->size for these sectors
444 * to avoid an overlap between Flash Macros #0 and #1.
445 */
446 flash_addr += bank->sectors[i].size;
447 }
448
449 return ERROR_OK;
450 }
451
452 static int fm4_probe(struct flash_bank *bank)
453 {
454 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
455 int retval;
456
457 if (fm4_bank->probed)
458 return ERROR_OK;
459
460 if (bank->target->state != TARGET_HALTED) {
461 LOG_WARNING("Cannot communicate... target not halted.");
462 return ERROR_TARGET_NOT_HALTED;
463 }
464
465 switch (fm4_bank->variant) {
466 case mb9bfx66:
467 case mb9bfx67:
468 case mb9bfx68:
469 retval = mb9bf_probe(bank);
470 break;
471 case s6e2cx8:
472 case s6e2cx9:
473 case s6e2cxa:
474 retval = s6e2cc_probe(bank);
475 break;
476 default:
477 return ERROR_FLASH_OPER_UNSUPPORTED;
478 }
479 if (retval != ERROR_OK)
480 return retval;
481
482 fm4_bank->probed = true;
483
484 return ERROR_OK;
485 }
486
487 static int fm4_auto_probe(struct flash_bank *bank)
488 {
489 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
490
491 if (fm4_bank->probed)
492 return ERROR_OK;
493
494 return fm4_probe(bank);
495 }
496
497 static int fm4_protect_check(struct flash_bank *bank)
498 {
499 return ERROR_OK;
500 }
501
502 static int fm4_get_info_command(struct flash_bank *bank, char *buf, int buf_size)
503 {
504 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
505 const char *name;
506
507 if (bank->target->state != TARGET_HALTED) {
508 LOG_WARNING("Cannot communicate... target not halted.");
509 return ERROR_TARGET_NOT_HALTED;
510 }
511
512 switch (fm4_bank->variant) {
513 case mb9bfx66:
514 name = "MB9BFx66";
515 break;
516 case mb9bfx67:
517 name = "MB9BFx67";
518 break;
519 case mb9bfx68:
520 name = "MB9BFx68";
521 break;
522 case s6e2cx8:
523 name = "S6E2Cx8";
524 break;
525 case s6e2cx9:
526 name = "S6E2Cx9";
527 break;
528 case s6e2cxa:
529 name = "S6E2CxA";
530 break;
531 default:
532 name = "unknown";
533 break;
534 }
535
536 switch (fm4_bank->variant) {
537 case s6e2cx8:
538 case s6e2cx9:
539 case s6e2cxa:
540 snprintf(buf, buf_size, "%s MainFlash Macro #%i",
541 name, fm4_bank->macro_nr);
542 break;
543 default:
544 snprintf(buf, buf_size, "%s MainFlash", name);
545 break;
546 }
547
548 return ERROR_OK;
549 }
550
551 static bool fm4_name_match(const char *s, const char *pattern)
552 {
553 int i = 0;
554
555 while (s[i]) {
556 /* If the match string is shorter, ignore excess */
557 if (!pattern[i])
558 return true;
559 /* Use x as wildcard */
560 if (pattern[i] != 'x' && tolower(s[i]) != tolower(pattern[i]))
561 return false;
562 i++;
563 }
564 return true;
565 }
566
567 static int mb9bf_bank_setup(struct flash_bank *bank, const char *variant)
568 {
569 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
570
571 if (fm4_name_match(variant, "MB9BFx66")) {
572 fm4_bank->variant = mb9bfx66;
573 } else if (fm4_name_match(variant, "MB9BFx67")) {
574 fm4_bank->variant = mb9bfx67;
575 } else if (fm4_name_match(variant, "MB9BFx68")) {
576 fm4_bank->variant = mb9bfx68;
577 } else {
578 LOG_WARNING("MB9BF variant %s not recognized.", variant);
579 return ERROR_FLASH_OPER_UNSUPPORTED;
580 }
581
582 return ERROR_OK;
583 }
584
585 static int s6e2cc_bank_setup(struct flash_bank *bank, const char *variant)
586 {
587 struct fm4_flash_bank *fm4_bank = bank->driver_priv;
588
589 if (fm4_name_match(variant, "S6E2Cx8")) {
590 fm4_bank->variant = s6e2cx8;
591 } else if (fm4_name_match(variant, "S6E2Cx9")) {
592 fm4_bank->variant = s6e2cx9;
593 } else if (fm4_name_match(variant, "S6E2CxA")) {
594 fm4_bank->variant = s6e2cxa;
595 } else {
596 LOG_WARNING("S6E2CC variant %s not recognized.", variant);
597 return ERROR_FLASH_OPER_UNSUPPORTED;
598 }
599
600 return ERROR_OK;
601 }
602
603 FLASH_BANK_COMMAND_HANDLER(fm4_flash_bank_command)
604 {
605 struct fm4_flash_bank *fm4_bank;
606 const char *variant;
607 int ret;
608
609 if (CMD_ARGC < 7)
610 return ERROR_COMMAND_SYNTAX_ERROR;
611
612 variant = CMD_ARGV[6];
613
614 fm4_bank = malloc(sizeof(struct fm4_flash_bank));
615 if (!fm4_bank)
616 return ERROR_FLASH_OPERATION_FAILED;
617
618 fm4_bank->probed = false;
619 fm4_bank->macro_nr = (bank->base == 0x00000000) ? 0 : 1;
620
621 bank->driver_priv = fm4_bank;
622
623 if (fm4_name_match(variant, "MB9BF"))
624 ret = mb9bf_bank_setup(bank, variant);
625 else if (fm4_name_match(variant, "S6E2Cx"))
626 ret = s6e2cc_bank_setup(bank, variant);
627 else {
628 LOG_WARNING("Family %s not recognized.", variant);
629 ret = ERROR_FLASH_OPER_UNSUPPORTED;
630 }
631 if (ret != ERROR_OK)
632 free(fm4_bank);
633 return ret;
634 }
635
636 static const struct command_registration fm4_exec_command_handlers[] = {
637 COMMAND_REGISTRATION_DONE
638 };
639
640 static const struct command_registration fm4_command_handlers[] = {
641 {
642 .name = "fm4",
643 .mode = COMMAND_ANY,
644 .help = "fm4 flash command group",
645 .usage = "",
646 .chain = fm4_exec_command_handlers,
647 },
648 COMMAND_REGISTRATION_DONE
649 };
650
651 struct flash_driver fm4_flash = {
652 .name = "fm4",
653 .commands = fm4_command_handlers,
654 .flash_bank_command = fm4_flash_bank_command,
655 .info = fm4_get_info_command,
656 .probe = fm4_probe,
657 .auto_probe = fm4_auto_probe,
658 .protect_check = fm4_protect_check,
659 .erase = fm4_flash_erase,
660 .erase_check = default_flash_blank_check,
661 .write = fm4_flash_write,
662 };

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)