jtag: linuxgpiod: drop extra parenthesis
[openocd.git] / src / flash / nor / aducm360.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Copyright (C) 2015 by Ivan Buliev *
5 * i.buliev@mikrosistemi.com *
6 ***************************************************************************/
7
8 /***************************************************************************
9 * This version for ADuCM360 is largely based on the following flash *
10 * drivers: *
11 * - aduc702x.c *
12 * Copyright (C) 2008 by Kevin McGuire *
13 * Copyright (C) 2008 by Marcel Wijlaars *
14 * Copyright (C) 2009 by Michael Ashton *
15 * and *
16 * - stm32f1x.c *
17 * Copyright (C) 2005 by Dominic Rath *
18 * Dominic.Rath@gmx.de *
19 * *
20 * Copyright (C) 2008 by Spencer Oliver *
21 * spen@spen-soft.co.uk *
22 * *
23 * Copyright (C) 2011 by Andreas Fritiofson *
24 * andreas.fritiofson@gmail.com *
25 ***************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include "imp.h"
32 #include <helper/binarybuffer.h>
33 #include <helper/time_support.h>
34 #include <target/algorithm.h>
35 #include <target/armv7m.h>
36
37 static int aducm360_build_sector_list(struct flash_bank *bank);
38 static int aducm360_check_flash_completion(struct target *target, unsigned int timeout_ms);
39 static int aducm360_set_write_enable(struct target *target, int enable);
40
41 #define ADUCM360_FLASH_BASE 0x40002800
42 #define ADUCM360_FLASH_FEESTA 0x0000
43 #define ADUCM360_FLASH_FEECON0 0x0004
44 #define ADUCM360_FLASH_FEECMD 0x0008
45 #define ADUCM360_FLASH_FEEADR0L 0x0010
46 #define ADUCM360_FLASH_FEEADR0H 0x0014
47 #define ADUCM360_FLASH_FEEADR1L 0x0018
48 #define ADUCM360_FLASH_FEEADR1H 0x001C
49 #define ADUCM360_FLASH_FEEKEY 0x0020
50 #define ADUCM360_FLASH_FEEPROL 0x0028
51 #define ADUCM360_FLASH_FEEPROH 0x002C
52 #define ADUCM360_FLASH_FEESIGL 0x0030
53 #define ADUCM360_FLASH_FEESIGH 0x0034
54 #define ADUCM360_FLASH_FEECON1 0x0038
55 #define ADUCM360_FLASH_FEEADRAL 0x0048
56 #define ADUCM360_FLASH_FEEADRAH 0x004C
57 #define ADUCM360_FLASH_FEEAEN0 0x0078
58 #define ADUCM360_FLASH_FEEAEN1 0x007C
59 #define ADUCM360_FLASH_FEEAEN2 0x0080
60
61 /* flash bank aducm360 0 0 0 0 <target#> */
62 FLASH_BANK_COMMAND_HANDLER(aducm360_flash_bank_command)
63 {
64 bank->base = 0x00000000;
65 bank->size = 0x00020000;
66
67 aducm360_build_sector_list(bank);
68
69 return ERROR_OK;
70 }
71
72 #define FLASH_SECTOR_SIZE 512
73
74 /* ----------------------------------------------------------------------- */
75 static int aducm360_build_sector_list(struct flash_bank *bank)
76 {
77 uint32_t offset = 0;
78
79 /* sector size is 512 */
80 bank->num_sectors = bank->size / FLASH_SECTOR_SIZE;
81 bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
82 for (unsigned i = 0; i < bank->num_sectors; ++i) {
83 bank->sectors[i].offset = offset;
84 bank->sectors[i].size = FLASH_SECTOR_SIZE;
85 offset += bank->sectors[i].size;
86 bank->sectors[i].is_erased = -1;
87 bank->sectors[i].is_protected = 0;
88 }
89
90 return ERROR_OK;
91 }
92
93 /* ----------------------------------------------------------------------- */
94 static int aducm360_mass_erase(struct target *target)
95 {
96 uint32_t value;
97 int res = ERROR_OK;
98
99 /* Clear any old status */
100 target_read_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEESTA, &value);
101
102 /* Enable the writing to the flash*/
103 aducm360_set_write_enable(target, 1);
104
105 /* Unlock for writing */
106 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEKEY, 0x0000F456);
107 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEKEY, 0x0000F123);
108 /* Issue the 'MASSERASE' command */
109 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEECMD, 0x00000003);
110
111 /* Check the result */
112 res = aducm360_check_flash_completion(target, 3500);
113 if (res != ERROR_OK) {
114 LOG_ERROR("mass erase failed.");
115 aducm360_set_write_enable(target, 0);
116 res = ERROR_FLASH_OPERATION_FAILED;
117 }
118
119 return res;
120 }
121
122 /* ----------------------------------------------------------------------- */
123 static int aducm360_page_erase(struct target *target, uint32_t padd)
124 {
125 uint32_t value;
126 int res = ERROR_OK;
127
128 /* Clear any old status */
129 target_read_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEESTA, &value);
130
131 /* Enable the writing to the flash*/
132 aducm360_set_write_enable(target, 1);
133
134 /* Unlock for writing */
135 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEKEY, 0x0000F456);
136 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEKEY, 0x0000F123);
137 /* Write the sector address */
138 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEADR0L, padd & 0xFFFF);
139 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEEADR0H, (padd>>16) & 0xFFFF);
140 /* Issue the 'ERASEPAGE' command */
141 target_write_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEECMD, 0x00000001);
142
143 /* Check the result */
144 res = aducm360_check_flash_completion(target, 50);
145 if (res != ERROR_OK) {
146 LOG_ERROR("page erase failed at 0x%08" PRIx32, padd);
147 aducm360_set_write_enable(target, 0);
148 res = ERROR_FLASH_OPERATION_FAILED;
149 }
150
151 return res;
152 }
153
154 /* ----------------------------------------------------------------------- */
155 static int aducm360_erase(struct flash_bank *bank, unsigned int first,
156 unsigned int last)
157 {
158 int res = ERROR_OK;
159 int i;
160 int count;
161 struct target *target = bank->target;
162 uint32_t padd;
163
164 if (((first | last) == 0) || ((first == 0) && (last >= bank->num_sectors))) {
165 res = aducm360_mass_erase(target);
166 } else {
167 count = last - first + 1;
168 for (i = 0; i < count; ++i) {
169 padd = bank->base + ((first+i)*FLASH_SECTOR_SIZE);
170 res = aducm360_page_erase(target, padd);
171 if (res != ERROR_OK)
172 break;
173 }
174 }
175
176 return res;
177 }
178
179 /* ----------------------------------------------------------------------- */
180 static int aducm360_write_block_sync(
181 struct flash_bank *bank,
182 const uint8_t *buffer,
183 uint32_t offset,
184 uint32_t count)
185 {
186 struct target *target = bank->target;
187 uint32_t target_buffer_size = 8192;
188 struct working_area *helper;
189 struct working_area *target_buffer;
190 uint32_t address = bank->base + offset;
191 struct reg_param reg_params[8];
192 int retval = ERROR_OK;
193 uint32_t entry_point = 0, exit_point = 0;
194 uint32_t res;
195 struct armv7m_algorithm armv7m_algo;
196
197 static const uint32_t aducm360_flash_write_code[] = {
198 /* helper.code */
199 0x88AF4D10, 0x0704F047, 0x682F80AF, 0x600E6806,
200 0xF017882F, 0xF43F0F08, 0xF851AFFB, 0x42B77B04,
201 0x800DF040, 0x0004F100, 0xF47F3A04, 0x686FAFEF,
202 0x0704F027, 0xF04F80AF, 0xF0000400, 0xF04FB802,
203 0xBE000480, 0x40002800, 0x00015000, 0x20000000,
204 0x00013000
205 };
206
207 LOG_DEBUG("'aducm360_write_block_sync' requested, dst:0x%08" PRIx32 ", count:0x%08" PRIx32 "bytes.",
208 address, count);
209
210 /* ----- Check the destination area for a Long Word alignment ----- */
211 if (((count%4) != 0) || ((offset%4) != 0)) {
212 LOG_ERROR("write block must be multiple of four bytes in offset & length");
213 return ERROR_FAIL;
214 }
215
216 /* ----- Allocate space in the target's RAM for the helper code ----- */
217 if (target_alloc_working_area(target, sizeof(aducm360_flash_write_code),
218 &helper) != ERROR_OK) {
219 LOG_WARNING("no working area available, can't do block memory writes");
220 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
221 }
222
223 /* ----- Upload the helper code to the space in the target's RAM ----- */
224 uint8_t code[sizeof(aducm360_flash_write_code)];
225 target_buffer_set_u32_array(target, code, ARRAY_SIZE(aducm360_flash_write_code),
226 aducm360_flash_write_code);
227 retval = target_write_buffer(target, helper->address, sizeof(code), code);
228 if (retval != ERROR_OK)
229 return retval;
230 entry_point = helper->address;
231
232 /* ----- Allocate space in the target's RAM for the user application's object code ----- */
233 while (target_alloc_working_area_try(target, target_buffer_size, &target_buffer) != ERROR_OK) {
234 LOG_WARNING("couldn't allocate a buffer space of 0x%08" PRIx32 "bytes in the target's SRAM.",
235 target_buffer_size);
236 target_buffer_size /= 2;
237 if (target_buffer_size <= 256) { /* No room available */
238 LOG_WARNING("no large enough working area available, can't do block memory writes");
239 target_free_working_area(target, helper);
240 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
241 }
242 }
243
244 /* ----- Prepare the target for the helper ----- */
245 armv7m_algo.common_magic = ARMV7M_COMMON_MAGIC;
246 armv7m_algo.core_mode = ARM_MODE_THREAD;
247
248 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT); /*SRC */
249 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT); /*DST */
250 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT); /*COUNT */
251 init_reg_param(&reg_params[3], "r3", 32, PARAM_OUT); /*not used */
252 init_reg_param(&reg_params[4], "r4", 32, PARAM_IN); /*RESULT */
253
254 /* ===== Execute the Main Programming Loop! ===== */
255 while (count > 0) {
256 uint32_t thisrun_count = (count > target_buffer_size) ? target_buffer_size : count;
257
258 /* ----- Upload the chunk ----- */
259 retval = target_write_buffer(target, target_buffer->address, thisrun_count, buffer);
260 if (retval != ERROR_OK)
261 break;
262 /* Set the arguments for the helper */
263 buf_set_u32(reg_params[0].value, 0, 32, target_buffer->address); /*SRC */
264 buf_set_u32(reg_params[1].value, 0, 32, address); /*DST */
265 buf_set_u32(reg_params[2].value, 0, 32, thisrun_count); /*COUNT */
266 buf_set_u32(reg_params[3].value, 0, 32, 0); /*NOT USED*/
267
268 retval = target_run_algorithm(target, 0, NULL, 5,
269 reg_params, entry_point, exit_point, 10000, &armv7m_algo);
270 if (retval != ERROR_OK) {
271 LOG_ERROR("error executing aducm360 flash write algorithm");
272 break;
273 }
274
275 res = buf_get_u32(reg_params[4].value, 0, 32);
276 if (res) {
277 LOG_ERROR("aducm360 fast sync algorithm reports an error (%02" PRIX32 ")", res);
278 retval = ERROR_FAIL;
279 break;
280 }
281
282 buffer += thisrun_count;
283 address += thisrun_count;
284 count -= thisrun_count;
285 }
286
287 target_free_working_area(target, target_buffer);
288 target_free_working_area(target, helper);
289
290 destroy_reg_param(&reg_params[0]);
291 destroy_reg_param(&reg_params[1]);
292 destroy_reg_param(&reg_params[2]);
293 destroy_reg_param(&reg_params[3]);
294 destroy_reg_param(&reg_params[4]);
295
296 return retval;
297 }
298
299 /* ----------------------------------------------------------------------- */
300 static int aducm360_write_block_async(
301 struct flash_bank *bank,
302 const uint8_t *buffer,
303 uint32_t offset,
304 uint32_t count)
305 {
306 struct target *target = bank->target;
307 uint32_t target_buffer_size = 1024;
308 struct working_area *helper;
309 struct working_area *target_buffer;
310 uint32_t address = bank->base + offset;
311 struct reg_param reg_params[9];
312 int retval = ERROR_OK;
313 uint32_t entry_point = 0, exit_point = 0;
314 uint32_t res;
315 uint32_t wcount;
316 struct armv7m_algorithm armv7m_algo;
317
318 static const uint32_t aducm360_flash_write_code[] = {
319 /* helper.code */
320 0x4050F8DF, 0xF04588A5, 0x80A50504, 0x8000F8D0,
321 0x0F00F1B8, 0x8016F000, 0x45476847, 0xAFF6F43F,
322 0x6B04F857, 0x6B04F842, 0xF0158825, 0xF43F0F08,
323 0x428FAFFB, 0xF100BF28, 0x60470708, 0xB10B3B01,
324 0xBFE4F7FF, 0xF02588A5, 0x80A50504, 0x0900F04F,
325 0xBE00BF00, 0x40002800, 0x20000000, 0x20000100,
326 0x00013000
327 };
328
329 LOG_DEBUG("'aducm360_write_block_async' requested, dst:0x%08" PRIx32 ", count:0x%08" PRIx32 "bytes.",
330 address, count);
331
332 /* ----- Check the destination area for a Long Word alignment ----- */
333 if (((count%4) != 0) || ((offset%4) != 0)) {
334 LOG_ERROR("write block must be multiple of four bytes in offset & length");
335 return ERROR_FAIL;
336 }
337 wcount = count/4;
338
339 /* ----- Allocate space in the target's RAM for the helper code ----- */
340 if (target_alloc_working_area(target, sizeof(aducm360_flash_write_code),
341 &helper) != ERROR_OK) {
342 LOG_WARNING("no working area available, can't do block memory writes");
343 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
344 }
345
346 /* ----- Upload the helper code to the space in the target's RAM ----- */
347 uint8_t code[sizeof(aducm360_flash_write_code)];
348 target_buffer_set_u32_array(target, code, ARRAY_SIZE(aducm360_flash_write_code),
349 aducm360_flash_write_code);
350 retval = target_write_buffer(target, helper->address, sizeof(code), code);
351 if (retval != ERROR_OK)
352 return retval;
353 entry_point = helper->address;
354
355 /* ----- Allocate space in the target's RAM for the user application's object code ----- */
356 while (target_alloc_working_area_try(target, target_buffer_size, &target_buffer) != ERROR_OK) {
357 LOG_WARNING("couldn't allocate a buffer space of 0x%08" PRIx32 "bytes in the target's SRAM.",
358 target_buffer_size);
359 target_buffer_size /= 2;
360 if (target_buffer_size <= 256) { /* No room available */
361 LOG_WARNING("no large enough working area available, can't do block memory writes");
362 target_free_working_area(target, helper);
363 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
364 }
365 }
366
367 /* ----- Prepare the target for the helper ----- */
368 armv7m_algo.common_magic = ARMV7M_COMMON_MAGIC;
369 armv7m_algo.core_mode = ARM_MODE_THREAD;
370
371 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT); /*SRCBEG */
372 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT); /*SRCEND */
373 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT); /*DST */
374 init_reg_param(&reg_params[3], "r3", 32, PARAM_OUT); /*COUNT (LWs)*/
375 init_reg_param(&reg_params[4], "r9", 32, PARAM_IN); /*RESULT */
376
377 buf_set_u32(reg_params[0].value, 0, 32, target_buffer->address);
378 buf_set_u32(reg_params[1].value, 0, 32, target_buffer->address + target_buffer->size);
379 buf_set_u32(reg_params[2].value, 0, 32, address);
380 buf_set_u32(reg_params[3].value, 0, 32, wcount);
381
382 retval = target_run_flash_async_algorithm(target, buffer, wcount, 4,
383 0, NULL,
384 5, reg_params,
385 target_buffer->address, target_buffer->size,
386 entry_point, exit_point,
387 &armv7m_algo);
388 if (retval != ERROR_OK) {
389 LOG_ERROR("error executing aducm360 flash write algorithm");
390 } else {
391 res = buf_get_u32(reg_params[4].value, 0, 32); /*RESULT*/
392 if (res) {
393 LOG_ERROR("aducm360 fast async algorithm reports an error (%02" PRIX32 ")", res);
394 retval = ERROR_FAIL;
395 }
396 }
397
398 target_free_working_area(target, target_buffer);
399 target_free_working_area(target, helper);
400
401 destroy_reg_param(&reg_params[0]);
402 destroy_reg_param(&reg_params[1]);
403 destroy_reg_param(&reg_params[2]);
404 destroy_reg_param(&reg_params[3]);
405 destroy_reg_param(&reg_params[4]);
406
407 return retval;
408 }
409
410 /* ----------------------------------------------------------------------- */
411 /* If this fn returns ERROR_TARGET_RESOURCE_NOT_AVAILABLE, then the caller can fall
412 * back to another mechanism that does not require onboard RAM
413 *
414 * Caller should not check for other return values specifically
415 */
416 static int aducm360_write_block(struct flash_bank *bank,
417 const uint8_t *buffer,
418 uint32_t offset,
419 uint32_t count)
420 {
421 int choice = 0;
422
423 switch (choice) {
424 case 0:
425 return aducm360_write_block_sync(bank, buffer, offset, count);
426 case 1:
427 return aducm360_write_block_async(bank, buffer, offset, count);
428 default:
429 LOG_ERROR("aducm360_write_block was cancelled (no writing method was chosen)!");
430 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
431 }
432 }
433
434 /* ----------------------------------------------------------------------- */
435 #define FEESTA_WRDONE 0x00000008
436
437 static int aducm360_write_modified(struct flash_bank *bank,
438 const uint8_t *buffer,
439 uint32_t offset,
440 uint32_t count)
441 {
442 uint32_t value;
443 int res = ERROR_OK;
444 uint32_t i, j, a, d;
445 struct target *target = bank->target;
446
447 LOG_DEBUG("performing slow write (offset=0x%08" PRIx32 ", count=0x%08" PRIx32 ")...",
448 offset, count);
449
450 /* Enable the writing to the flash */
451 aducm360_set_write_enable(target, 1);
452
453 /* Clear any old status */
454 target_read_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEESTA, &value);
455
456 for (i = 0; i < count; i += 4) {
457 a = offset+i;
458 for (j = 0; i < 4; i += 1)
459 *((uint8_t *)(&d) + j) = buffer[i+j];
460 target_write_u32(target, a, d);
461 do {
462 target_read_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEESTA, &value);
463 } while (!(value & FEESTA_WRDONE));
464 }
465 aducm360_set_write_enable(target, 0);
466
467 return res;
468 }
469
470 /* ----------------------------------------------------------------------- */
471 static int aducm360_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count)
472 {
473 int retval;
474
475 /* try using a block write */
476 retval = aducm360_write_block(bank, buffer, offset, count);
477 if (retval != ERROR_OK) {
478 if (retval == ERROR_TARGET_RESOURCE_NOT_AVAILABLE) {
479 /* if block write failed (no sufficient working area),
480 * use normal (slow) JTAG method */
481 LOG_WARNING("couldn't use block writes, falling back to single memory accesses");
482
483 retval = aducm360_write_modified(bank, buffer, offset, count);
484 if (retval != ERROR_OK) {
485 LOG_ERROR("slow write failed");
486 return ERROR_FLASH_OPERATION_FAILED;
487 }
488 }
489 }
490 return retval;
491 }
492
493 /* ----------------------------------------------------------------------- */
494 static int aducm360_probe(struct flash_bank *bank)
495 {
496 return ERROR_OK;
497 }
498
499 /* ----------------------------------------------------------------------- */
500 /* sets FEECON0 bit 2
501 * enable = 1 enables writes & erases, 0 disables them */
502 static int aducm360_set_write_enable(struct target *target, int enable)
503 {
504 /* don't bother to preserve int enable bit here */
505 uint32_t value;
506
507 target_read_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEECON0, &value);
508 if (enable)
509 value |= 0x00000004;
510 else
511 value &= ~0x00000004;
512 target_write_u32(target, ADUCM360_FLASH_BASE + ADUCM360_FLASH_FEECON0, value);
513
514 return ERROR_OK;
515 }
516
517 /* ----------------------------------------------------------------------- */
518 /* wait up to timeout_ms for controller to not be busy,
519 * then check whether the command passed or failed.
520 *
521 * this function sleeps 1ms between checks (after the first one),
522 * so in some cases may slow things down without a usleep after the first read */
523 static int aducm360_check_flash_completion(struct target *target, unsigned int timeout_ms)
524 {
525 uint32_t v = 1;
526
527 int64_t endtime = timeval_ms() + timeout_ms;
528 while (1) {
529 target_read_u32(target, ADUCM360_FLASH_BASE+ADUCM360_FLASH_FEESTA, &v);
530 if ((v & 0x00000001) == 0)
531 break;
532 alive_sleep(1);
533 if (timeval_ms() >= endtime)
534 break;
535 }
536
537 if (!(v & 0x00000004)) /* b2 */
538 return ERROR_FAIL;
539
540 return ERROR_OK;
541 }
542
543 /* ----------------------------------------------------------------------- */
544 const struct flash_driver aducm360_flash = {
545 .name = "aducm360",
546 .flash_bank_command = aducm360_flash_bank_command,
547 .erase = aducm360_erase,
548 .write = aducm360_write,
549 .read = default_flash_read,
550 .probe = aducm360_probe,
551 .auto_probe = aducm360_probe,
552 .erase_check = default_flash_blank_check,
553 };

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)