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

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)