drivers/bcm2835gpio: Add support for activity LED
[openocd.git] / src / jtag / drivers / bcm2835gpio.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2
3 /***************************************************************************
4 * Copyright (C) 2013 by Paul Fertser, fercerpav@gmail.com *
5 * *
6 * Copyright (C) 2012 by Creative Product Design, marc @ cpdesign.com.au *
7 * Based on at91rm9200.c (c) Anders Larsen *
8 * and RPi GPIO examples by Gert van Loo & Dom *
9 ***************************************************************************/
10
11 #ifdef HAVE_CONFIG_H
12 #include "config.h"
13 #endif
14
15 #include <jtag/adapter.h>
16 #include <jtag/interface.h>
17 #include <transport/transport.h>
18 #include "bitbang.h"
19
20 #include <sys/mman.h>
21
22 uint32_t bcm2835_peri_base = 0x20000000;
23 #define BCM2835_GPIO_BASE (bcm2835_peri_base + 0x200000) /* GPIO controller */
24
25 #define BCM2835_PADS_GPIO_0_27 (bcm2835_peri_base + 0x100000)
26 #define BCM2835_PADS_GPIO_0_27_OFFSET (0x2c / 4)
27
28 /* See "GPIO Function Select Registers (GPFSELn)" in "Broadcom BCM2835 ARM Peripherals" datasheet. */
29 #define BCM2835_GPIO_MODE_INPUT 0
30 #define BCM2835_GPIO_MODE_OUTPUT 1
31
32 /* GPIO setup macros */
33 #define MODE_GPIO(g) (*(pio_base+((g)/10))>>(((g)%10)*3) & 7)
34 #define INP_GPIO(g) do { *(pio_base+((g)/10)) &= ~(7<<(((g)%10)*3)); } while (0)
35 #define SET_MODE_GPIO(g, m) do { /* clear the mode bits first, then set as necessary */ \
36 INP_GPIO(g); \
37 *(pio_base+((g)/10)) |= ((m)<<(((g)%10)*3)); } while (0)
38 #define OUT_GPIO(g) SET_MODE_GPIO(g, BCM2835_GPIO_MODE_OUTPUT)
39
40 #define GPIO_SET (*(pio_base+7)) /* sets bits which are 1, ignores bits which are 0 */
41 #define GPIO_CLR (*(pio_base+10)) /* clears bits which are 1, ignores bits which are 0 */
42 #define GPIO_LEV (*(pio_base+13)) /* current level of the pin */
43
44 static int dev_mem_fd;
45 static volatile uint32_t *pio_base = MAP_FAILED;
46 static volatile uint32_t *pads_base = MAP_FAILED;
47
48 /* Transition delay coefficients */
49 static int speed_coeff = 113714;
50 static int speed_offset = 28;
51 static unsigned int jtag_delay;
52
53 static const struct adapter_gpio_config *adapter_gpio_config;
54 static int initial_gpio_mode[ADAPTER_GPIO_IDX_NUM];
55
56 static bool is_gpio_config_valid(enum adapter_gpio_config_index idx)
57 {
58 /* Only chip 0 is supported, accept unset value (-1) too */
59 return adapter_gpio_config[idx].chip_num >= -1
60 && adapter_gpio_config[idx].chip_num <= 0
61 && adapter_gpio_config[idx].gpio_num >= 0
62 && adapter_gpio_config[idx].gpio_num <= 31;
63 }
64
65 static void set_gpio_value(const struct adapter_gpio_config *gpio_config, int value)
66 {
67 value = value ^ (gpio_config->active_low ? 1 : 0);
68 switch (gpio_config->drive) {
69 case ADAPTER_GPIO_DRIVE_MODE_PUSH_PULL:
70 if (value)
71 GPIO_SET = 1 << gpio_config->gpio_num;
72 else
73 GPIO_CLR = 1 << gpio_config->gpio_num;
74 /* For performance reasons assume the GPIO is already set as an output
75 * and therefore the call can be omitted here.
76 */
77 break;
78 case ADAPTER_GPIO_DRIVE_MODE_OPEN_DRAIN:
79 if (value) {
80 INP_GPIO(gpio_config->gpio_num);
81 } else {
82 GPIO_CLR = 1 << gpio_config->gpio_num;
83 OUT_GPIO(gpio_config->gpio_num);
84 }
85 break;
86 case ADAPTER_GPIO_DRIVE_MODE_OPEN_SOURCE:
87 if (value) {
88 GPIO_SET = 1 << gpio_config->gpio_num;
89 OUT_GPIO(gpio_config->gpio_num);
90 } else {
91 INP_GPIO(gpio_config->gpio_num);
92 }
93 break;
94 }
95 }
96
97 static void restore_gpio(enum adapter_gpio_config_index idx)
98 {
99 if (is_gpio_config_valid(idx))
100 SET_MODE_GPIO(adapter_gpio_config[idx].gpio_num, initial_gpio_mode[idx]);
101 }
102
103 static void initialize_gpio(enum adapter_gpio_config_index idx)
104 {
105 if (!is_gpio_config_valid(idx))
106 return;
107
108 initial_gpio_mode[idx] = MODE_GPIO(adapter_gpio_config[idx].gpio_num);
109 LOG_DEBUG("saved GPIO mode for %s (GPIO %d %d): %d",
110 adapter_gpio_get_name(idx), adapter_gpio_config[idx].chip_num, adapter_gpio_config[idx].gpio_num,
111 initial_gpio_mode[idx]);
112
113 if (adapter_gpio_config[idx].pull != ADAPTER_GPIO_PULL_NONE) {
114 LOG_WARNING("BCM2835 GPIO does not support pull-up or pull-down settings (signal %s)",
115 adapter_gpio_get_name(idx));
116 }
117
118 switch (adapter_gpio_config[idx].init_state) {
119 case ADAPTER_GPIO_INIT_STATE_INACTIVE:
120 set_gpio_value(&adapter_gpio_config[idx], 0);
121 break;
122 case ADAPTER_GPIO_INIT_STATE_ACTIVE:
123 set_gpio_value(&adapter_gpio_config[idx], 1);
124 break;
125 case ADAPTER_GPIO_INIT_STATE_INPUT:
126 INP_GPIO(adapter_gpio_config[idx].gpio_num);
127 break;
128 }
129
130 /* Direction for non push-pull is already set by set_gpio_value() */
131 if (adapter_gpio_config[idx].drive == ADAPTER_GPIO_DRIVE_MODE_PUSH_PULL)
132 OUT_GPIO(adapter_gpio_config[idx].gpio_num);
133 }
134
135 static bb_value_t bcm2835gpio_read(void)
136 {
137 unsigned int shift = adapter_gpio_config[ADAPTER_GPIO_IDX_TDO].gpio_num;
138 uint32_t value = (GPIO_LEV >> shift) & 1;
139 return value ^ (adapter_gpio_config[ADAPTER_GPIO_IDX_TDO].active_low ? BB_HIGH : BB_LOW);
140
141 }
142
143 static int bcm2835gpio_write(int tck, int tms, int tdi)
144 {
145 uint32_t set = tck << adapter_gpio_config[ADAPTER_GPIO_IDX_TCK].gpio_num |
146 tms << adapter_gpio_config[ADAPTER_GPIO_IDX_TMS].gpio_num |
147 tdi << adapter_gpio_config[ADAPTER_GPIO_IDX_TDI].gpio_num;
148 uint32_t clear = !tck << adapter_gpio_config[ADAPTER_GPIO_IDX_TCK].gpio_num |
149 !tms << adapter_gpio_config[ADAPTER_GPIO_IDX_TMS].gpio_num |
150 !tdi << adapter_gpio_config[ADAPTER_GPIO_IDX_TDI].gpio_num;
151
152 GPIO_SET = set;
153 GPIO_CLR = clear;
154
155 for (unsigned int i = 0; i < jtag_delay; i++)
156 asm volatile ("");
157
158 return ERROR_OK;
159 }
160
161 /* Requires push-pull drive mode for swclk and swdio */
162 static int bcm2835gpio_swd_write_fast(int swclk, int swdio)
163 {
164 swclk = swclk ^ (adapter_gpio_config[ADAPTER_GPIO_IDX_SWCLK].active_low ? 1 : 0);
165 swdio = swdio ^ (adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].active_low ? 1 : 0);
166
167 uint32_t set = swclk << adapter_gpio_config[ADAPTER_GPIO_IDX_SWCLK].gpio_num |
168 swdio << adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num;
169 uint32_t clear = !swclk << adapter_gpio_config[ADAPTER_GPIO_IDX_SWCLK].gpio_num |
170 !swdio << adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num;
171
172 GPIO_SET = set;
173 GPIO_CLR = clear;
174
175 for (unsigned int i = 0; i < jtag_delay; i++)
176 asm volatile ("");
177
178 return ERROR_OK;
179 }
180
181 /* Generic mode that works for open-drain/open-source drive modes, but slower */
182 static int bcm2835gpio_swd_write_generic(int swclk, int swdio)
183 {
184 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO], swdio);
185 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_SWCLK], swclk); /* Write clock last */
186
187 for (unsigned int i = 0; i < jtag_delay; ++i)
188 asm volatile ("");
189
190 return ERROR_OK;
191 }
192
193 /* (1) assert or (0) deassert reset lines */
194 static int bcm2835gpio_reset(int trst, int srst)
195 {
196 /* As the "adapter reset_config" command keeps the srst and trst gpio drive
197 * mode settings in sync we can use our standard set_gpio_value() function
198 * that honours drive mode and active low.
199 */
200 if (is_gpio_config_valid(ADAPTER_GPIO_IDX_SRST))
201 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_SRST], srst);
202
203 if (is_gpio_config_valid(ADAPTER_GPIO_IDX_TRST))
204 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_TRST], trst);
205
206 LOG_DEBUG("BCM2835 GPIO: bcm2835gpio_reset(%d, %d), trst_gpio: %d %d, srst_gpio: %d %d",
207 trst, srst,
208 adapter_gpio_config[ADAPTER_GPIO_IDX_TRST].chip_num, adapter_gpio_config[ADAPTER_GPIO_IDX_TRST].gpio_num,
209 adapter_gpio_config[ADAPTER_GPIO_IDX_SRST].chip_num, adapter_gpio_config[ADAPTER_GPIO_IDX_SRST].gpio_num);
210 return ERROR_OK;
211 }
212
213 static void bcm2835_swdio_drive(bool is_output)
214 {
215 if (is_output) {
216 if (is_gpio_config_valid(ADAPTER_GPIO_IDX_SWDIO_DIR))
217 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO_DIR], 1);
218 OUT_GPIO(adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num);
219 } else {
220 INP_GPIO(adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num);
221 if (is_gpio_config_valid(ADAPTER_GPIO_IDX_SWDIO_DIR))
222 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO_DIR], 0);
223 }
224 }
225
226 static int bcm2835_swdio_read(void)
227 {
228 unsigned int shift = adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num;
229 uint32_t value = (GPIO_LEV >> shift) & 1;
230 return value ^ (adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].active_low ? 1 : 0);
231 }
232
233 static int bcm2835gpio_khz(int khz, int *jtag_speed)
234 {
235 if (!khz) {
236 LOG_DEBUG("BCM2835 GPIO: RCLK not supported");
237 return ERROR_FAIL;
238 }
239 *jtag_speed = speed_coeff/khz - speed_offset;
240 if (*jtag_speed < 0)
241 *jtag_speed = 0;
242 return ERROR_OK;
243 }
244
245 static int bcm2835gpio_speed_div(int speed, int *khz)
246 {
247 *khz = speed_coeff/(speed + speed_offset);
248 return ERROR_OK;
249 }
250
251 static int bcm2835gpio_speed(int speed)
252 {
253 jtag_delay = speed;
254 return ERROR_OK;
255 }
256
257 COMMAND_HANDLER(bcm2835gpio_handle_speed_coeffs)
258 {
259 if (CMD_ARGC == 2) {
260 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], speed_coeff);
261 COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], speed_offset);
262 }
263
264 command_print(CMD, "BCM2835 GPIO: speed_coeffs = %d, speed_offset = %d",
265 speed_coeff, speed_offset);
266 return ERROR_OK;
267 }
268
269 COMMAND_HANDLER(bcm2835gpio_handle_peripheral_base)
270 {
271 if (CMD_ARGC == 1)
272 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], bcm2835_peri_base);
273
274 command_print(CMD, "BCM2835 GPIO: peripheral_base = 0x%08x",
275 bcm2835_peri_base);
276 return ERROR_OK;
277 }
278
279 static const struct command_registration bcm2835gpio_subcommand_handlers[] = {
280 {
281 .name = "speed_coeffs",
282 .handler = &bcm2835gpio_handle_speed_coeffs,
283 .mode = COMMAND_CONFIG,
284 .help = "SPEED_COEFF and SPEED_OFFSET for delay calculations.",
285 .usage = "[SPEED_COEFF SPEED_OFFSET]",
286 },
287 {
288 .name = "peripheral_base",
289 .handler = &bcm2835gpio_handle_peripheral_base,
290 .mode = COMMAND_CONFIG,
291 .help = "peripheral base to access GPIOs (RPi1 0x20000000, RPi2 0x3F000000).",
292 .usage = "[base]",
293 },
294
295 COMMAND_REGISTRATION_DONE
296 };
297
298 static const struct command_registration bcm2835gpio_command_handlers[] = {
299 {
300 .name = "bcm2835gpio",
301 .mode = COMMAND_ANY,
302 .help = "perform bcm2835gpio management",
303 .chain = bcm2835gpio_subcommand_handlers,
304 .usage = "",
305 },
306 COMMAND_REGISTRATION_DONE
307 };
308
309 static bool bcm2835gpio_jtag_mode_possible(void)
310 {
311 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_TCK))
312 return false;
313 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_TMS))
314 return false;
315 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_TDI))
316 return false;
317 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_TDO))
318 return false;
319 return true;
320 }
321
322 static bool bcm2835gpio_swd_mode_possible(void)
323 {
324 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_SWCLK))
325 return false;
326 if (!is_gpio_config_valid(ADAPTER_GPIO_IDX_SWDIO))
327 return false;
328 return true;
329 }
330
331 static void bcm2835gpio_munmap(void)
332 {
333 if (pio_base != MAP_FAILED) {
334 munmap((void *)pio_base, sysconf(_SC_PAGE_SIZE));
335 pio_base = MAP_FAILED;
336 }
337
338 if (pads_base != MAP_FAILED) {
339 munmap((void *)pads_base, sysconf(_SC_PAGE_SIZE));
340 pads_base = MAP_FAILED;
341 }
342 }
343
344 static int bcm2835gpio_blink(int on)
345 {
346 if (is_gpio_config_valid(ADAPTER_GPIO_IDX_LED))
347 set_gpio_value(&adapter_gpio_config[ADAPTER_GPIO_IDX_LED], on);
348
349 return ERROR_OK;
350 }
351
352 static struct bitbang_interface bcm2835gpio_bitbang = {
353 .read = bcm2835gpio_read,
354 .write = bcm2835gpio_write,
355 .swdio_read = bcm2835_swdio_read,
356 .swdio_drive = bcm2835_swdio_drive,
357 .swd_write = bcm2835gpio_swd_write_generic,
358 .blink = bcm2835gpio_blink,
359 };
360
361 static int bcm2835gpio_init(void)
362 {
363 LOG_INFO("BCM2835 GPIO JTAG/SWD bitbang driver");
364
365 bitbang_interface = &bcm2835gpio_bitbang;
366 adapter_gpio_config = adapter_gpio_get_config();
367
368 if (transport_is_jtag() && !bcm2835gpio_jtag_mode_possible()) {
369 LOG_ERROR("Require tck, tms, tdi and tdo gpios for JTAG mode");
370 return ERROR_JTAG_INIT_FAILED;
371 }
372
373 if (transport_is_swd() && !bcm2835gpio_swd_mode_possible()) {
374 LOG_ERROR("Require swclk and swdio gpio for SWD mode");
375 return ERROR_JTAG_INIT_FAILED;
376 }
377
378 dev_mem_fd = open("/dev/gpiomem", O_RDWR | O_SYNC);
379 if (dev_mem_fd < 0) {
380 LOG_DEBUG("Cannot open /dev/gpiomem, fallback to /dev/mem");
381 dev_mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
382 }
383 if (dev_mem_fd < 0) {
384 LOG_ERROR("open: %s", strerror(errno));
385 return ERROR_JTAG_INIT_FAILED;
386 }
387
388 pio_base = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE,
389 MAP_SHARED, dev_mem_fd, BCM2835_GPIO_BASE);
390
391 if (pio_base == MAP_FAILED) {
392 LOG_ERROR("mmap: %s", strerror(errno));
393 close(dev_mem_fd);
394 return ERROR_JTAG_INIT_FAILED;
395 }
396
397 pads_base = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE,
398 MAP_SHARED, dev_mem_fd, BCM2835_PADS_GPIO_0_27);
399
400 if (pads_base == MAP_FAILED) {
401 LOG_ERROR("mmap: %s", strerror(errno));
402 bcm2835gpio_munmap();
403 close(dev_mem_fd);
404 return ERROR_JTAG_INIT_FAILED;
405 }
406
407 close(dev_mem_fd);
408
409 /* set 4mA drive strength, slew rate limited, hysteresis on */
410 pads_base[BCM2835_PADS_GPIO_0_27_OFFSET] = 0x5a000008 + 1;
411
412 /* Configure JTAG/SWD signals. Default directions and initial states are handled
413 * by adapter.c and "adapter gpio" command.
414 */
415 if (transport_is_jtag()) {
416 initialize_gpio(ADAPTER_GPIO_IDX_TDO);
417 initialize_gpio(ADAPTER_GPIO_IDX_TDI);
418 initialize_gpio(ADAPTER_GPIO_IDX_TMS);
419 initialize_gpio(ADAPTER_GPIO_IDX_TCK);
420 initialize_gpio(ADAPTER_GPIO_IDX_TRST);
421 }
422
423 if (transport_is_swd()) {
424 /* swdio and its buffer should be initialized in the order that prevents
425 * two outputs from being connected together. This will occur if the
426 * swdio GPIO of the AM335x is configured as an output while its
427 * external buffer is configured to send the swdio signal from the
428 * target to the AM335x.
429 */
430 if (adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].init_state == ADAPTER_GPIO_INIT_STATE_INPUT) {
431 initialize_gpio(ADAPTER_GPIO_IDX_SWDIO);
432 initialize_gpio(ADAPTER_GPIO_IDX_SWDIO_DIR);
433 } else {
434 initialize_gpio(ADAPTER_GPIO_IDX_SWDIO_DIR);
435 initialize_gpio(ADAPTER_GPIO_IDX_SWDIO);
436 }
437
438 initialize_gpio(ADAPTER_GPIO_IDX_SWCLK);
439
440 if (adapter_gpio_config[ADAPTER_GPIO_IDX_SWCLK].drive == ADAPTER_GPIO_DRIVE_MODE_PUSH_PULL &&
441 adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].drive == ADAPTER_GPIO_DRIVE_MODE_PUSH_PULL) {
442 LOG_DEBUG("BCM2835 GPIO using fast mode for SWD write");
443 bcm2835gpio_bitbang.swd_write = bcm2835gpio_swd_write_fast;
444 } else {
445 LOG_DEBUG("BCM2835 GPIO using generic mode for SWD write");
446 bcm2835gpio_bitbang.swd_write = bcm2835gpio_swd_write_generic;
447 }
448 }
449
450 initialize_gpio(ADAPTER_GPIO_IDX_SRST);
451 initialize_gpio(ADAPTER_GPIO_IDX_LED);
452
453 return ERROR_OK;
454 }
455
456 static int bcm2835gpio_quit(void)
457 {
458 if (transport_is_jtag()) {
459 restore_gpio(ADAPTER_GPIO_IDX_TDO);
460 restore_gpio(ADAPTER_GPIO_IDX_TDI);
461 restore_gpio(ADAPTER_GPIO_IDX_TCK);
462 restore_gpio(ADAPTER_GPIO_IDX_TMS);
463 restore_gpio(ADAPTER_GPIO_IDX_TRST);
464 }
465
466 if (transport_is_swd()) {
467 /* Restore swdio/swdio_dir to their initial modes, even if that means
468 * connecting two outputs. Begin by making swdio an input so that the
469 * current and final states of swdio and swdio_dir do not have to be
470 * considered to calculate the safe restoration order.
471 */
472 INP_GPIO(adapter_gpio_config[ADAPTER_GPIO_IDX_SWDIO].gpio_num);
473 restore_gpio(ADAPTER_GPIO_IDX_SWDIO_DIR);
474 restore_gpio(ADAPTER_GPIO_IDX_SWDIO);
475 restore_gpio(ADAPTER_GPIO_IDX_SWCLK);
476 }
477
478 restore_gpio(ADAPTER_GPIO_IDX_SRST);
479 restore_gpio(ADAPTER_GPIO_IDX_LED);
480
481 bcm2835gpio_munmap();
482
483 return ERROR_OK;
484 }
485
486
487 static const char * const bcm2835_transports[] = { "jtag", "swd", NULL };
488
489 static struct jtag_interface bcm2835gpio_interface = {
490 .supported = DEBUG_CAP_TMS_SEQ,
491 .execute_queue = bitbang_execute_queue,
492 };
493 struct adapter_driver bcm2835gpio_adapter_driver = {
494 .name = "bcm2835gpio",
495 .transports = bcm2835_transports,
496 .commands = bcm2835gpio_command_handlers,
497
498 .init = bcm2835gpio_init,
499 .quit = bcm2835gpio_quit,
500 .reset = bcm2835gpio_reset,
501 .speed = bcm2835gpio_speed,
502 .khz = bcm2835gpio_khz,
503 .speed_div = bcm2835gpio_speed_div,
504
505 .jtag_ops = &bcm2835gpio_interface,
506 .swd_ops = &bitbang_swd,
507 };

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)