1 /*
2  * Copyright (c) 2018 Intel Corporation
3  * Copyright (c) 2021 Dennis Ruffer <daruffer@gmail.com>
4  * Copyright (c) 2023 Nick Ward <nix.ward@gmail.com>
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #include <zephyr/drivers/gpio.h>
10 #include <zephyr/shell/shell.h>
11 
12 #include <stdio.h>
13 
14 #define ARGV_DEV 1
15 #define ARGV_PIN 2
16 #define ARGV_CONF 3
17 #define ARGV_VALUE 3
18 #define ARGV_VENDOR_SPECIFIC 4
19 
20 #define NGPIOS_UNKNOWN -1
21 #define PIN_NOT_FOUND UINT8_MAX
22 
23 /* Pin syntax maximum length */
24 #define PIN_SYNTAX_MAX 32
25 #define PIN_NUM_MAX 4
26 
27 struct gpio_ctrl {
28 	const struct device *dev;
29 	int8_t ngpios;
30 	gpio_port_pins_t reserved_mask;
31 	const char **line_names;
32 	uint8_t line_names_len;
33 	const union shell_cmd_entry *subcmd;
34 };
35 
36 struct sh_gpio {
37 	const struct device *dev;
38 	gpio_pin_t pin;
39 };
40 /*
41  * Find idx-th pin reference from the set of non reserved
42  * pin numbers and provided line names.
43  */
port_pin_get(gpio_port_pins_t reserved_mask,const char ** line_names,uint8_t line_names_len,size_t idx,struct shell_static_entry * entry)44 static void port_pin_get(gpio_port_pins_t reserved_mask, const char **line_names,
45 			 uint8_t line_names_len, size_t idx, struct shell_static_entry *entry)
46 {
47 	static char pin_syntax[PIN_SYNTAX_MAX];
48 	static char pin_num[PIN_NUM_MAX];
49 	const char *name;
50 	gpio_pin_t pin;
51 	bool reserved;
52 
53 	entry->handler = NULL;
54 
55 	/* Find allowed numeric pin reference */
56 	for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) {
57 		reserved = ((BIT64(pin) & reserved_mask) != 0);
58 		if (!reserved) {
59 			if (idx == 0) {
60 				break;
61 			}
62 			idx--;
63 		}
64 	}
65 
66 	if (pin < GPIO_MAX_PINS_PER_PORT) {
67 		sprintf(pin_num, "%u", pin);
68 		if ((pin < line_names_len) && (strlen(line_names[pin]) > 0)) {
69 			/* pin can be specified by line name */
70 			name = line_names[pin];
71 			for (int i = 0; i < (sizeof(pin_syntax) - 1); i++) {
72 				/*
73 				 * For line-name tab completion to work replace any
74 				 * space characters with '_'.
75 				 */
76 				pin_syntax[i] = (name[i] != ' ') ? name[i] : '_';
77 				if (name[i] == '\0') {
78 					break;
79 				}
80 			}
81 			pin_syntax[sizeof(pin_syntax) - 1] = '\0';
82 			entry->syntax = pin_syntax;
83 			entry->help = pin_num;
84 		} else {
85 			/* fallback to pin specified by pin number */
86 			entry->syntax = pin_num;
87 			entry->help = NULL;
88 		}
89 	} else {
90 		/* No more pins */
91 		entry->syntax = NULL;
92 		entry->help = NULL;
93 	}
94 }
95 
96 #define GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id)                                              \
97 	COND_CODE_1(DT_NODE_HAS_PROP(node_id, ngpios),                                             \
98 		    (GPIO_DT_RESERVED_RANGES_NGPIOS(node_id, DT_PROP(node_id, ngpios))),           \
99 		    (GPIO_MAX_PINS_PER_PORT))
100 
101 #define GPIO_CTRL_PIN_GET_FN(node_id)                                                              \
102 	static const char *node_id##line_names[] = DT_PROP_OR(node_id, gpio_line_names, {NULL});   \
103                                                                                                    \
104 	static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry);       \
105                                                                                                    \
106 	SHELL_DYNAMIC_CMD_CREATE(node_id##sub_gpio_pin, node_id##cmd_gpio_pin_get);                \
107                                                                                                    \
108 	static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry)        \
109 	{                                                                                          \
110 		gpio_port_pins_t reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id);    \
111 		uint8_t line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0);              \
112                                                                                                    \
113 		port_pin_get(reserved_mask, node_id##line_names, line_names_len, idx, entry);      \
114 		entry->subcmd = NULL;                                                              \
115 	}
116 
117 #define IS_GPIO_CTRL_PIN_GET(node_id)                                                              \
118 	COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_PIN_GET_FN(node_id)), ())
119 
120 DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_PIN_GET)
121 
122 #define GPIO_CTRL_LIST_ENTRY(node_id)                                                              \
123 	{                                                                                          \
124 		.dev = DEVICE_DT_GET(node_id),                                                     \
125 		.ngpios = DT_PROP_OR(node_id, ngpios, NGPIOS_UNKNOWN),                             \
126 		.reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id),                    \
127 		.line_names = node_id##line_names,                                                 \
128 		.line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0),                     \
129 		.subcmd = &node_id##sub_gpio_pin,                                                  \
130 	},
131 
132 #define IS_GPIO_CTRL_LIST(node_id)                                                                 \
133 	COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_LIST_ENTRY(node_id)), ())
134 
135 static const struct gpio_ctrl gpio_list[] = {DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_LIST)};
136 
get_gpio_ctrl(char * name)137 static const struct gpio_ctrl *get_gpio_ctrl(char *name)
138 {
139 	const struct device *dev = device_get_binding(name);
140 	size_t i;
141 
142 	for (i = 0; i < ARRAY_SIZE(gpio_list); i++) {
143 		if (gpio_list[i].dev == dev) {
144 			return &gpio_list[i];
145 		}
146 	}
147 	return NULL;
148 }
149 
line_cmp(const char * input,const char * line_name)150 int line_cmp(const char *input, const char *line_name)
151 {
152 	int i = 0;
153 
154 	while (true) {
155 		if ((input[i] == '_') && (line_name[i] == ' ')) {
156 			/* Allow input underscore to match line_name space */
157 		} else if (input[i] != line_name[i]) {
158 			return (input[i] > line_name[i]) ? 1 : -1;
159 		} else if (line_name[i] == '\0') {
160 			return 0;
161 		}
162 		i++;
163 	}
164 }
165 
get_gpio_pin(const struct shell * sh,const struct gpio_ctrl * ctrl,char * line_name)166 static int get_gpio_pin(const struct shell *sh, const struct gpio_ctrl *ctrl, char *line_name)
167 {
168 	gpio_pin_t pin = PIN_NOT_FOUND;
169 	gpio_pin_t i;
170 	int result;
171 
172 	for (i = 0; i < ctrl->ngpios; i++) {
173 		result = line_cmp(line_name, ctrl->line_names[i]);
174 		if (result == 0) {
175 			if ((BIT64(i) & ctrl->reserved_mask) != 0) {
176 				shell_error(sh, "Reserved pin");
177 				return -EACCES;
178 			} else if (pin == PIN_NOT_FOUND) {
179 				pin = i;
180 			} else {
181 				shell_error(sh, "Line name ambiguous");
182 				return -EFAULT;
183 			}
184 		}
185 	}
186 
187 	if (pin == PIN_NOT_FOUND) {
188 		shell_error(sh, "Line name not found: '%s'", line_name);
189 		return -ENOENT;
190 	}
191 
192 	return pin;
193 }
194 
get_sh_gpio(const struct shell * sh,char ** argv,struct sh_gpio * gpio)195 static int get_sh_gpio(const struct shell *sh, char **argv, struct sh_gpio *gpio)
196 {
197 	const struct gpio_ctrl *ctrl;
198 	int ret = 0;
199 	int pin;
200 
201 	ctrl = get_gpio_ctrl(argv[ARGV_DEV]);
202 	if (ctrl == NULL) {
203 		shell_error(sh, "unknown gpio controller: %s", argv[ARGV_DEV]);
204 		return -EINVAL;
205 	}
206 	gpio->dev = ctrl->dev;
207 	pin = shell_strtoul(argv[ARGV_PIN], 0, &ret);
208 	if (ret != 0) {
209 		pin = get_gpio_pin(sh, ctrl, argv[ARGV_PIN]);
210 		if (pin < 0) {
211 			return pin;
212 		}
213 	} else if ((BIT64(pin) & ctrl->reserved_mask) != 0) {
214 		shell_error(sh, "Reserved pin");
215 		return -EACCES;
216 	}
217 	gpio->pin = pin;
218 
219 	return 0;
220 }
221 
cmd_gpio_conf(const struct shell * sh,size_t argc,char ** argv,void * data)222 static int cmd_gpio_conf(const struct shell *sh, size_t argc, char **argv, void *data)
223 {
224 	gpio_flags_t flags = 0;
225 	gpio_flags_t vendor_specific;
226 	struct sh_gpio gpio;
227 	int ret = 0;
228 
229 	ret = get_sh_gpio(sh, argv, &gpio);
230 	if (ret != 0) {
231 		shell_help(sh);
232 		return SHELL_CMD_HELP_PRINTED;
233 	}
234 
235 	for (int i = 0; i < strlen(argv[ARGV_CONF]); i++) {
236 		switch (argv[ARGV_CONF][i]) {
237 		case 'i':
238 			flags |= GPIO_INPUT;
239 			break;
240 		case 'o':
241 			flags |= GPIO_OUTPUT;
242 			break;
243 		case 'u':
244 			flags |= GPIO_PULL_UP;
245 			break;
246 		case 'd':
247 			flags |= GPIO_PULL_DOWN;
248 			break;
249 		case 'h':
250 			flags |= GPIO_ACTIVE_HIGH;
251 			break;
252 		case 'l':
253 			flags |= GPIO_ACTIVE_LOW;
254 			break;
255 		case '0':
256 			flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW;
257 			break;
258 		case '1':
259 			flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_HIGH;
260 			break;
261 		default:
262 			shell_error(sh, "Unknown: '%c'", argv[ARGV_CONF][i]);
263 			shell_help(sh);
264 			return SHELL_CMD_HELP_PRINTED;
265 		}
266 	}
267 
268 	if (((flags & GPIO_INPUT) != 0) == ((flags & GPIO_OUTPUT) != 0)) {
269 		shell_error(sh, "must be either input or output");
270 		shell_help(sh);
271 		return SHELL_CMD_HELP_PRINTED;
272 	}
273 
274 	if (((flags & GPIO_PULL_UP) != 0) && ((flags & GPIO_PULL_DOWN) != 0)) {
275 		shell_error(sh, "cannot be pull up and pull down");
276 		shell_help(sh);
277 		return SHELL_CMD_HELP_PRINTED;
278 	}
279 
280 	if (((flags & GPIO_ACTIVE_LOW) != 0) && ((flags & GPIO_ACTIVE_HIGH) != 0)) {
281 		shell_error(sh, "cannot be active low and active high");
282 		shell_help(sh);
283 		return SHELL_CMD_HELP_PRINTED;
284 	}
285 
286 	if ((flags & GPIO_OUTPUT) != 0) {
287 		/* Default to active high if not specified */
288 		if ((flags & (GPIO_ACTIVE_LOW | GPIO_ACTIVE_HIGH)) == 0) {
289 			flags |= GPIO_ACTIVE_HIGH;
290 		}
291 		/* Default to initialisation to logic 0 if not specified */
292 		if ((flags & GPIO_OUTPUT_INIT_LOGICAL) == 0) {
293 			flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW;
294 		}
295 	}
296 
297 	if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT_INIT_LOGICAL) != 0)) {
298 		shell_error(sh, "an input cannot be initialised to a logic level");
299 		shell_help(sh);
300 		return SHELL_CMD_HELP_PRINTED;
301 	}
302 
303 	if (((flags & GPIO_OUTPUT_INIT_LOW) != 0) && ((flags & GPIO_OUTPUT_INIT_HIGH) != 0)) {
304 		shell_error(sh, "cannot initialise to logic 0 and logic 1");
305 		shell_help(sh);
306 		return SHELL_CMD_HELP_PRINTED;
307 	}
308 
309 	if (argc == 5) {
310 		vendor_specific = shell_strtoul(argv[ARGV_VENDOR_SPECIFIC], 0, &ret);
311 		if ((ret == 0) && ((vendor_specific & ~(0xFF00U)) == 0)) {
312 			flags |= vendor_specific;
313 		} else {
314 			/*
315 			 * See include/zephyr/dt-bindings/gpio/ for the
316 			 * available flags for your vendor.
317 			 */
318 			shell_error(sh, "vendor specific flags must be within "
319 					"the mask 0xFF00");
320 			shell_help(sh);
321 			return SHELL_CMD_HELP_PRINTED;
322 		}
323 	}
324 
325 	ret = gpio_pin_configure(gpio.dev, gpio.pin, flags);
326 	if (ret != 0) {
327 		shell_error(sh, "error: %d", ret);
328 		return ret;
329 	}
330 
331 	return 0;
332 }
333 
cmd_gpio_get(const struct shell * sh,size_t argc,char ** argv)334 static int cmd_gpio_get(const struct shell *sh, size_t argc, char **argv)
335 {
336 	struct sh_gpio gpio;
337 	int value;
338 	int ret;
339 
340 	ret = get_sh_gpio(sh, argv, &gpio);
341 	if (ret != 0) {
342 		shell_help(sh);
343 		return SHELL_CMD_HELP_PRINTED;
344 	}
345 
346 	value = gpio_pin_get(gpio.dev, gpio.pin);
347 	if (value >= 0) {
348 		shell_print(sh, "%u", value);
349 	} else {
350 		shell_error(sh, "error: %d", value);
351 		return value;
352 	}
353 
354 	return 0;
355 }
356 
cmd_gpio_set(const struct shell * sh,size_t argc,char ** argv)357 static int cmd_gpio_set(const struct shell *sh, size_t argc, char **argv)
358 {
359 	struct sh_gpio gpio;
360 	unsigned long value;
361 	int ret = 0;
362 
363 	ret = get_sh_gpio(sh, argv, &gpio);
364 	if (ret != 0) {
365 		shell_help(sh);
366 		return SHELL_CMD_HELP_PRINTED;
367 	}
368 
369 	value = shell_strtoul(argv[ARGV_VALUE], 0, &ret);
370 	if (ret != 0) {
371 		shell_help(sh);
372 		return SHELL_CMD_HELP_PRINTED;
373 	}
374 
375 	ret = gpio_pin_set(gpio.dev, gpio.pin, value != 0);
376 	if (ret != 0) {
377 		shell_error(sh, "error: %d", ret);
378 		return ret;
379 	}
380 
381 	return 0;
382 }
383 
384 /* 500 msec = 1/2 sec */
385 #define SLEEP_TIME_MS   500
386 
cmd_gpio_blink(const struct shell * sh,size_t argc,char ** argv)387 static int cmd_gpio_blink(const struct shell *sh, size_t argc, char **argv)
388 {
389 	bool msg_one_shot = true;
390 	struct sh_gpio gpio;
391 	size_t count;
392 	char data;
393 	int ret;
394 
395 	ret = get_sh_gpio(sh, argv, &gpio);
396 	if (ret != 0) {
397 		shell_help(sh);
398 		return SHELL_CMD_HELP_PRINTED;
399 	}
400 
401 	/* dummy read to clear any pending input */
402 	(void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count);
403 
404 	while (true) {
405 		(void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count);
406 		if (count != 0) {
407 			break;
408 		}
409 		ret = gpio_pin_toggle(gpio.dev, gpio.pin);
410 		if (ret != 0) {
411 			shell_error(sh, "%d", ret);
412 			break;
413 		} else if (msg_one_shot) {
414 			msg_one_shot = false;
415 			shell_print(sh, "Hit any key to exit");
416 		}
417 		k_msleep(SLEEP_TIME_MS);
418 	}
419 
420 	return 0;
421 }
422 
device_name_get(size_t idx,struct shell_static_entry * entry)423 static void device_name_get(size_t idx, struct shell_static_entry *entry)
424 {
425 	if (idx >= ARRAY_SIZE(gpio_list)) {
426 		entry->syntax = NULL;
427 		return;
428 	}
429 
430 	entry->syntax = gpio_list[idx].dev->name;
431 	entry->handler = NULL;
432 	entry->help = "Device";
433 	entry->subcmd = gpio_list[idx].subcmd;
434 }
435 
436 SHELL_DYNAMIC_CMD_CREATE(sub_gpio_dev, device_name_get);
437 
438 struct pin_info {
439 	const struct device *dev;
440 	bool reserved;
441 	gpio_pin_t pin;
442 	const char *line_name;
443 };
444 
445 struct pin_order_user_data {
446 	const struct shell *sh;
447 	struct pin_info prev;
448 	struct pin_info next;
449 };
450 
451 typedef void (*pin_foreach_func_t)(const struct pin_info *info, void *user_data);
452 
print_gpio_ctrl_info(const struct shell * sh,const struct gpio_ctrl * ctrl)453 static void print_gpio_ctrl_info(const struct shell *sh, const struct gpio_ctrl *ctrl)
454 {
455 	gpio_pin_t pin;
456 	bool reserved;
457 
458 	shell_print(sh, " ngpios: %u", ctrl->ngpios);
459 	shell_print(sh, " Reserved pin mask: 0x%08X", ctrl->reserved_mask);
460 
461 	shell_print(sh, "");
462 
463 	shell_print(sh, " Reserved  Pin  Line Name");
464 	for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) {
465 		if ((pin >= ctrl->ngpios) && (pin >= ctrl->line_names_len)) {
466 			/* Out of info */
467 			break;
468 		}
469 		reserved = (BIT64(pin) & ctrl->reserved_mask) != 0;
470 		shell_print(sh, "     %c     %2u    %s", reserved ? '*' : ' ',
471 			    pin, ctrl->line_names[pin]);
472 	}
473 }
474 
foreach_pin(pin_foreach_func_t func,void * user_data)475 static void foreach_pin(pin_foreach_func_t func, void *user_data)
476 {
477 	gpio_port_pins_t reserved_mask;
478 	struct pin_info info;
479 	gpio_pin_t pin;
480 	size_t i;
481 
482 	for (i = 0; i < ARRAY_SIZE(gpio_list); i++) {
483 		for (pin = 0; pin < gpio_list[i].ngpios; pin++) {
484 			info.dev = gpio_list[i].dev;
485 			reserved_mask = gpio_list[i].reserved_mask;
486 			info.reserved = (BIT64(pin) & reserved_mask) != 0;
487 			info.pin = pin;
488 			if (pin < gpio_list[i].line_names_len) {
489 				info.line_name = gpio_list[i].line_names[pin];
490 			} else {
491 				info.line_name = "";
492 			}
493 			func(&info, user_data);
494 		}
495 	}
496 }
497 
pin_cmp(const struct pin_info * a,const struct pin_info * b)498 static int pin_cmp(const struct pin_info *a, const struct pin_info *b)
499 {
500 	int result = strcmp(a->line_name, b->line_name);
501 
502 	if (result != 0) {
503 		return result;
504 	}
505 	result = strcmp(a->dev->name, b->dev->name);
506 	if (result != 0) {
507 		return result;
508 	}
509 	result = (int)a->pin - (int)b->pin;
510 
511 	return result;
512 }
513 
pin_get_next(const struct pin_info * info,void * user_data)514 static void pin_get_next(const struct pin_info *info, void *user_data)
515 {
516 	struct pin_order_user_data *data = user_data;
517 	int result;
518 
519 	if (data->prev.line_name != NULL) {
520 		result = pin_cmp(info, &data->prev);
521 	} else {
522 		result = 1;
523 	}
524 	if (result > 0) {
525 		if (data->next.line_name == NULL) {
526 			data->next = *info;
527 			return;
528 		}
529 		result = pin_cmp(info, &data->next);
530 		if (result < 0) {
531 			data->next = *info;
532 		}
533 	}
534 }
535 
pin_ordered(const struct pin_info * info,void * user_data)536 static void pin_ordered(const struct pin_info *info, void *user_data)
537 {
538 	struct pin_order_user_data *data = user_data;
539 
540 	ARG_UNUSED(info);
541 
542 	foreach_pin(pin_get_next, data);
543 
544 	shell_print(data->sh, "   %-12s %-8c %-16s %2u",
545 		    data->next.line_name,
546 		    data->next.reserved ? '*' : ' ',
547 		    data->next.dev->name,
548 		    data->next.pin);
549 
550 	data->prev = data->next;
551 	data->next.line_name = NULL;
552 }
553 
print_ordered_info(const struct shell * sh)554 static void print_ordered_info(const struct shell *sh)
555 {
556 	struct pin_order_user_data data = {0};
557 
558 	data.sh = sh;
559 
560 	shell_print(sh, "  %-12s %-8s %-16s %-3s",
561 		"Line", "Reserved", "Device", "Pin");
562 
563 	foreach_pin(pin_ordered, &data);
564 }
565 
cmd_gpio_info(const struct shell * sh,size_t argc,char ** argv)566 static int cmd_gpio_info(const struct shell *sh, size_t argc, char **argv)
567 {
568 	const struct gpio_ctrl *ctrl = get_gpio_ctrl(argv[ARGV_DEV]);
569 
570 	if (ctrl == NULL) {
571 		/* No device specified */
572 		print_ordered_info(sh);
573 		return 0;
574 	}
575 
576 	print_gpio_ctrl_info(sh, ctrl);
577 
578 	return 0;
579 }
580 
581 SHELL_STATIC_SUBCMD_SET_CREATE(sub_gpio,
582 	SHELL_CMD_ARG(conf, &sub_gpio_dev,
583 		"Configure GPIO pin\n"
584 		"Usage: gpio conf <device> <pin> <configuration <i|o>[u|d][h|l][0|1]> [vendor specific]\n"
585 		"<i|o> - input|output\n"
586 		"[u|d] - pull up|pull down, otherwise open\n"
587 		"[h|l] - active high|active low, otherwise defaults to active high\n"
588 		"[0|1] - initialise to logic 0|logic 1, otherwise defaults to logic 0\n"
589 		"[vendor specific] - configuration flags within the mask 0xFF00\n"
590 		"                    see include/zephyr/dt-bindings/gpio/",
591 		cmd_gpio_conf, 4, 1),
592 	SHELL_CMD_ARG(get, &sub_gpio_dev,
593 		"Get GPIO pin value\n"
594 		"Usage: gpio get <device> <pin>", cmd_gpio_get, 3, 0),
595 	SHELL_CMD_ARG(set, &sub_gpio_dev,
596 		"Set GPIO pin value\n"
597 		"Usage: gpio set <device> <pin> <level 0|1>", cmd_gpio_set, 4, 0),
598 	SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_BLINK_CMD, blink, &sub_gpio_dev,
599 		"Blink GPIO pin\n"
600 		"Usage: gpio blink <device> <pin>", cmd_gpio_blink, 3, 0),
601 	SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_INFO_CMD, info, &sub_gpio_dev,
602 		"GPIO Information\n"
603 		"Usage: gpio info [device]", cmd_gpio_info, 1, 1),
604 	SHELL_SUBCMD_SET_END /* Array terminated. */
605 );
606 
607 SHELL_CMD_REGISTER(gpio, &sub_gpio, "GPIO commands", NULL);
608