| /* |
| * Copyright (c) 2018 Intel Corporation |
| * Copyright (c) 2021 Dennis Ruffer <daruffer@gmail.com> |
| * Copyright (c) 2023 Nick Ward <nix.ward@gmail.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/shell/shell.h> |
| |
| #include <stdio.h> |
| |
| #define ARGV_DEV 1 |
| #define ARGV_PIN 2 |
| #define ARGV_CONF 3 |
| #define ARGV_VALUE 3 |
| #define ARGV_VENDOR_SPECIFIC 4 |
| |
| #define NGPIOS_UNKNOWN -1 |
| #define PIN_NOT_FOUND UINT8_MAX |
| |
| /* Pin syntax maximum length */ |
| #define PIN_SYNTAX_MAX 32 |
| #define PIN_NUM_MAX 4 |
| |
| struct gpio_ctrl { |
| const struct device *dev; |
| int8_t ngpios; |
| gpio_port_pins_t reserved_mask; |
| const char **line_names; |
| uint8_t line_names_len; |
| const union shell_cmd_entry *subcmd; |
| }; |
| |
| struct sh_gpio { |
| const struct device *dev; |
| gpio_pin_t pin; |
| }; |
| /* |
| * Find idx-th pin reference from the set of non reserved |
| * pin numbers and provided line names. |
| */ |
| static void 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) |
| { |
| static char pin_syntax[PIN_SYNTAX_MAX]; |
| static char pin_num[PIN_NUM_MAX]; |
| const char *name; |
| gpio_pin_t pin; |
| bool reserved; |
| |
| entry->handler = NULL; |
| |
| /* Find allowed numeric pin reference */ |
| for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) { |
| reserved = ((BIT64(pin) & reserved_mask) != 0); |
| if (!reserved) { |
| if (idx == 0) { |
| break; |
| } |
| idx--; |
| } |
| } |
| |
| if (pin < GPIO_MAX_PINS_PER_PORT) { |
| sprintf(pin_num, "%u", pin); |
| if ((pin < line_names_len) && (strlen(line_names[pin]) > 0)) { |
| /* pin can be specified by line name */ |
| name = line_names[pin]; |
| for (int i = 0; i < (sizeof(pin_syntax) - 1); i++) { |
| /* |
| * For line-name tab completion to work replace any |
| * space characters with '_'. |
| */ |
| pin_syntax[i] = (name[i] != ' ') ? name[i] : '_'; |
| if (name[i] == '\0') { |
| break; |
| } |
| } |
| pin_syntax[sizeof(pin_syntax) - 1] = '\0'; |
| entry->syntax = pin_syntax; |
| entry->help = pin_num; |
| } else { |
| /* fallback to pin specified by pin number */ |
| entry->syntax = pin_num; |
| entry->help = NULL; |
| } |
| } else { |
| /* No more pins */ |
| entry->syntax = NULL; |
| entry->help = NULL; |
| } |
| } |
| |
| #define GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id) \ |
| COND_CODE_1(DT_NODE_HAS_PROP(node_id, ngpios), \ |
| (GPIO_DT_RESERVED_RANGES_NGPIOS(node_id, DT_PROP(node_id, ngpios))), \ |
| (GPIO_MAX_PINS_PER_PORT)) |
| |
| #define GPIO_CTRL_PIN_GET_FN(node_id) \ |
| static const char *node_id##line_names[] = DT_PROP_OR(node_id, gpio_line_names, {NULL}); \ |
| \ |
| static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry); \ |
| \ |
| SHELL_DYNAMIC_CMD_CREATE(node_id##sub_gpio_pin, node_id##cmd_gpio_pin_get); \ |
| \ |
| static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry) \ |
| { \ |
| gpio_port_pins_t reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id); \ |
| uint8_t line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0); \ |
| \ |
| port_pin_get(reserved_mask, node_id##line_names, line_names_len, idx, entry); \ |
| entry->subcmd = NULL; \ |
| } |
| |
| #define IS_GPIO_CTRL_PIN_GET(node_id) \ |
| COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_PIN_GET_FN(node_id)), ()) |
| |
| DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_PIN_GET) |
| |
| #define GPIO_CTRL_LIST_ENTRY(node_id) \ |
| { \ |
| .dev = DEVICE_DT_GET(node_id), \ |
| .ngpios = DT_PROP_OR(node_id, ngpios, NGPIOS_UNKNOWN), \ |
| .reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id), \ |
| .line_names = node_id##line_names, \ |
| .line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0), \ |
| .subcmd = &node_id##sub_gpio_pin, \ |
| }, |
| |
| #define IS_GPIO_CTRL_LIST(node_id) \ |
| COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_LIST_ENTRY(node_id)), ()) |
| |
| static const struct gpio_ctrl gpio_list[] = {DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_LIST)}; |
| |
| static const struct gpio_ctrl *get_gpio_ctrl(char *name) |
| { |
| const struct device *dev = device_get_binding(name); |
| size_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(gpio_list); i++) { |
| if (gpio_list[i].dev == dev) { |
| return &gpio_list[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| int line_cmp(const char *input, const char *line_name) |
| { |
| int i = 0; |
| |
| while (true) { |
| if ((input[i] == '_') && (line_name[i] == ' ')) { |
| /* Allow input underscore to match line_name space */ |
| } else if (input[i] != line_name[i]) { |
| return (input[i] > line_name[i]) ? 1 : -1; |
| } else if (line_name[i] == '\0') { |
| return 0; |
| } |
| i++; |
| } |
| } |
| |
| static int get_gpio_pin(const struct shell *sh, const struct gpio_ctrl *ctrl, char *line_name) |
| { |
| gpio_pin_t pin = PIN_NOT_FOUND; |
| gpio_pin_t i; |
| int result; |
| |
| for (i = 0; i < ctrl->ngpios; i++) { |
| result = line_cmp(line_name, ctrl->line_names[i]); |
| if (result == 0) { |
| if ((BIT64(i) & ctrl->reserved_mask) != 0) { |
| shell_error(sh, "Reserved pin"); |
| return -EACCES; |
| } else if (pin == PIN_NOT_FOUND) { |
| pin = i; |
| } else { |
| shell_error(sh, "Line name ambiguous"); |
| return -EFAULT; |
| } |
| } |
| } |
| |
| if (pin == PIN_NOT_FOUND) { |
| shell_error(sh, "Line name not found: '%s'", line_name); |
| return -ENOENT; |
| } |
| |
| return pin; |
| } |
| |
| static int get_sh_gpio(const struct shell *sh, char **argv, struct sh_gpio *gpio) |
| { |
| const struct gpio_ctrl *ctrl; |
| int ret = 0; |
| int pin; |
| |
| ctrl = get_gpio_ctrl(argv[ARGV_DEV]); |
| if (ctrl == NULL) { |
| shell_error(sh, "unknown gpio controller: %s", argv[ARGV_DEV]); |
| return -EINVAL; |
| } |
| gpio->dev = ctrl->dev; |
| pin = shell_strtoul(argv[ARGV_PIN], 0, &ret); |
| if (ret != 0) { |
| pin = get_gpio_pin(sh, ctrl, argv[ARGV_PIN]); |
| if (pin < 0) { |
| return pin; |
| } |
| } else if ((BIT64(pin) & ctrl->reserved_mask) != 0) { |
| shell_error(sh, "Reserved pin"); |
| return -EACCES; |
| } |
| gpio->pin = pin; |
| |
| return 0; |
| } |
| |
| static int cmd_gpio_conf(const struct shell *sh, size_t argc, char **argv, void *data) |
| { |
| gpio_flags_t flags = 0; |
| gpio_flags_t vendor_specific; |
| struct sh_gpio gpio; |
| int ret = 0; |
| |
| ret = get_sh_gpio(sh, argv, &gpio); |
| if (ret != 0) { |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| for (int i = 0; i < strlen(argv[ARGV_CONF]); i++) { |
| switch (argv[ARGV_CONF][i]) { |
| case 'i': |
| flags |= GPIO_INPUT; |
| break; |
| case 'o': |
| flags |= GPIO_OUTPUT; |
| break; |
| case 'u': |
| flags |= GPIO_PULL_UP; |
| break; |
| case 'd': |
| flags |= GPIO_PULL_DOWN; |
| break; |
| case 'h': |
| flags |= GPIO_ACTIVE_HIGH; |
| break; |
| case 'l': |
| flags |= GPIO_ACTIVE_LOW; |
| break; |
| case '0': |
| flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW; |
| break; |
| case '1': |
| flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_HIGH; |
| break; |
| default: |
| shell_error(sh, "Unknown: '%c'", argv[ARGV_CONF][i]); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| } |
| |
| if (((flags & GPIO_INPUT) != 0) == ((flags & GPIO_OUTPUT) != 0)) { |
| shell_error(sh, "must be either input or output"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| if (((flags & GPIO_PULL_UP) != 0) && ((flags & GPIO_PULL_DOWN) != 0)) { |
| shell_error(sh, "cannot be pull up and pull down"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| if (((flags & GPIO_ACTIVE_LOW) != 0) && ((flags & GPIO_ACTIVE_HIGH) != 0)) { |
| shell_error(sh, "cannot be active low and active high"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| if ((flags & GPIO_OUTPUT) != 0) { |
| /* Default to active high if not specified */ |
| if ((flags & (GPIO_ACTIVE_LOW | GPIO_ACTIVE_HIGH)) == 0) { |
| flags |= GPIO_ACTIVE_HIGH; |
| } |
| /* Default to initialisation to logic 0 if not specified */ |
| if ((flags & GPIO_OUTPUT_INIT_LOGICAL) == 0) { |
| flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW; |
| } |
| } |
| |
| if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT_INIT_LOGICAL) != 0)) { |
| shell_error(sh, "an input cannot be initialised to a logic level"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| if (((flags & GPIO_OUTPUT_INIT_LOW) != 0) && ((flags & GPIO_OUTPUT_INIT_HIGH) != 0)) { |
| shell_error(sh, "cannot initialise to logic 0 and logic 1"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| if (argc == 5) { |
| vendor_specific = shell_strtoul(argv[ARGV_VENDOR_SPECIFIC], 0, &ret); |
| if ((ret == 0) && ((vendor_specific & ~(0xFF00U)) == 0)) { |
| flags |= vendor_specific; |
| } else { |
| /* |
| * See include/zephyr/dt-bindings/gpio/ for the |
| * available flags for your vendor. |
| */ |
| shell_error(sh, "vendor specific flags must be within " |
| "the mask 0xFF00"); |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| } |
| |
| ret = gpio_pin_configure(gpio.dev, gpio.pin, flags); |
| if (ret != 0) { |
| shell_error(sh, "error: %d", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_gpio_get(const struct shell *sh, size_t argc, char **argv) |
| { |
| struct sh_gpio gpio; |
| int value; |
| int ret; |
| |
| ret = get_sh_gpio(sh, argv, &gpio); |
| if (ret != 0) { |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| value = gpio_pin_get(gpio.dev, gpio.pin); |
| if (value >= 0) { |
| shell_print(sh, "%u", value); |
| } else { |
| shell_error(sh, "error: %d", value); |
| return value; |
| } |
| |
| return 0; |
| } |
| |
| static int cmd_gpio_set(const struct shell *sh, size_t argc, char **argv) |
| { |
| struct sh_gpio gpio; |
| unsigned long value; |
| int ret = 0; |
| |
| ret = get_sh_gpio(sh, argv, &gpio); |
| if (ret != 0) { |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| value = shell_strtoul(argv[ARGV_VALUE], 0, &ret); |
| if (ret != 0) { |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| ret = gpio_pin_set(gpio.dev, gpio.pin, value != 0); |
| if (ret != 0) { |
| shell_error(sh, "error: %d", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* 500 msec = 1/2 sec */ |
| #define SLEEP_TIME_MS 500 |
| |
| static int cmd_gpio_blink(const struct shell *sh, size_t argc, char **argv) |
| { |
| bool msg_one_shot = true; |
| struct sh_gpio gpio; |
| size_t count; |
| char data; |
| int ret; |
| |
| ret = get_sh_gpio(sh, argv, &gpio); |
| if (ret != 0) { |
| shell_help(sh); |
| return SHELL_CMD_HELP_PRINTED; |
| } |
| |
| /* dummy read to clear any pending input */ |
| (void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count); |
| |
| while (true) { |
| (void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count); |
| if (count != 0) { |
| break; |
| } |
| ret = gpio_pin_toggle(gpio.dev, gpio.pin); |
| if (ret != 0) { |
| shell_error(sh, "%d", ret); |
| break; |
| } else if (msg_one_shot) { |
| msg_one_shot = false; |
| shell_print(sh, "Hit any key to exit"); |
| } |
| k_msleep(SLEEP_TIME_MS); |
| } |
| |
| return 0; |
| } |
| |
| static void device_name_get(size_t idx, struct shell_static_entry *entry) |
| { |
| if (idx >= ARRAY_SIZE(gpio_list)) { |
| entry->syntax = NULL; |
| return; |
| } |
| |
| entry->syntax = gpio_list[idx].dev->name; |
| entry->handler = NULL; |
| entry->help = "Device"; |
| entry->subcmd = gpio_list[idx].subcmd; |
| } |
| |
| SHELL_DYNAMIC_CMD_CREATE(sub_gpio_dev, device_name_get); |
| |
| struct pin_info { |
| const struct device *dev; |
| bool reserved; |
| gpio_pin_t pin; |
| const char *line_name; |
| }; |
| |
| struct pin_order_user_data { |
| const struct shell *sh; |
| struct pin_info prev; |
| struct pin_info next; |
| }; |
| |
| typedef void (*pin_foreach_func_t)(const struct pin_info *info, void *user_data); |
| |
| static void print_gpio_ctrl_info(const struct shell *sh, const struct gpio_ctrl *ctrl) |
| { |
| gpio_pin_t pin; |
| bool reserved; |
| |
| shell_print(sh, " ngpios: %u", ctrl->ngpios); |
| shell_print(sh, " Reserved pin mask: 0x%08X", ctrl->reserved_mask); |
| |
| shell_print(sh, ""); |
| |
| shell_print(sh, " Reserved Pin Line Name"); |
| for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) { |
| if ((pin >= ctrl->ngpios) && (pin >= ctrl->line_names_len)) { |
| /* Out of info */ |
| break; |
| } |
| reserved = (BIT64(pin) & ctrl->reserved_mask) != 0; |
| shell_print(sh, " %c %2u %s", reserved ? '*' : ' ', |
| pin, ctrl->line_names[pin]); |
| } |
| } |
| |
| static void foreach_pin(pin_foreach_func_t func, void *user_data) |
| { |
| gpio_port_pins_t reserved_mask; |
| struct pin_info info; |
| gpio_pin_t pin; |
| size_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(gpio_list); i++) { |
| for (pin = 0; pin < gpio_list[i].ngpios; pin++) { |
| info.dev = gpio_list[i].dev; |
| reserved_mask = gpio_list[i].reserved_mask; |
| info.reserved = (BIT64(pin) & reserved_mask) != 0; |
| info.pin = pin; |
| if (pin < gpio_list[i].line_names_len) { |
| info.line_name = gpio_list[i].line_names[pin]; |
| } else { |
| info.line_name = ""; |
| } |
| func(&info, user_data); |
| } |
| } |
| } |
| |
| static int pin_cmp(const struct pin_info *a, const struct pin_info *b) |
| { |
| int result = strcmp(a->line_name, b->line_name); |
| |
| if (result != 0) { |
| return result; |
| } |
| result = strcmp(a->dev->name, b->dev->name); |
| if (result != 0) { |
| return result; |
| } |
| result = (int)a->pin - (int)b->pin; |
| |
| return result; |
| } |
| |
| static void pin_get_next(const struct pin_info *info, void *user_data) |
| { |
| struct pin_order_user_data *data = user_data; |
| int result; |
| |
| if (data->prev.line_name != NULL) { |
| result = pin_cmp(info, &data->prev); |
| } else { |
| result = 1; |
| } |
| if (result > 0) { |
| if (data->next.line_name == NULL) { |
| data->next = *info; |
| return; |
| } |
| result = pin_cmp(info, &data->next); |
| if (result < 0) { |
| data->next = *info; |
| } |
| } |
| } |
| |
| static void pin_ordered(const struct pin_info *info, void *user_data) |
| { |
| struct pin_order_user_data *data = user_data; |
| |
| ARG_UNUSED(info); |
| |
| foreach_pin(pin_get_next, data); |
| |
| shell_print(data->sh, " %-12s %-8c %-16s %2u", |
| data->next.line_name, |
| data->next.reserved ? '*' : ' ', |
| data->next.dev->name, |
| data->next.pin); |
| |
| data->prev = data->next; |
| data->next.line_name = NULL; |
| } |
| |
| static void print_ordered_info(const struct shell *sh) |
| { |
| struct pin_order_user_data data = {0}; |
| |
| data.sh = sh; |
| |
| shell_print(sh, " %-12s %-8s %-16s %-3s", |
| "Line", "Reserved", "Device", "Pin"); |
| |
| foreach_pin(pin_ordered, &data); |
| } |
| |
| static int cmd_gpio_info(const struct shell *sh, size_t argc, char **argv) |
| { |
| const struct gpio_ctrl *ctrl = get_gpio_ctrl(argv[ARGV_DEV]); |
| |
| if (ctrl == NULL) { |
| /* No device specified */ |
| print_ordered_info(sh); |
| return 0; |
| } |
| |
| print_gpio_ctrl_info(sh, ctrl); |
| |
| return 0; |
| } |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(sub_gpio, |
| SHELL_CMD_ARG(conf, &sub_gpio_dev, |
| "Configure GPIO pin\n" |
| "Usage: gpio conf <device> <pin> <configuration <i|o>[u|d][h|l][0|1]> [vendor specific]\n" |
| "<i|o> - input|output\n" |
| "[u|d] - pull up|pull down, otherwise open\n" |
| "[h|l] - active high|active low, otherwise defaults to active high\n" |
| "[0|1] - initialise to logic 0|logic 1, otherwise defaults to logic 0\n" |
| "[vendor specific] - configuration flags within the mask 0xFF00\n" |
| " see include/zephyr/dt-bindings/gpio/", |
| cmd_gpio_conf, 4, 1), |
| SHELL_CMD_ARG(get, &sub_gpio_dev, |
| "Get GPIO pin value\n" |
| "Usage: gpio get <device> <pin>", cmd_gpio_get, 3, 0), |
| SHELL_CMD_ARG(set, &sub_gpio_dev, |
| "Set GPIO pin value\n" |
| "Usage: gpio set <device> <pin> <level 0|1>", cmd_gpio_set, 4, 0), |
| SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_BLINK_CMD, blink, &sub_gpio_dev, |
| "Blink GPIO pin\n" |
| "Usage: gpio blink <device> <pin>", cmd_gpio_blink, 3, 0), |
| SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_INFO_CMD, info, &sub_gpio_dev, |
| "GPIO Information\n" |
| "Usage: gpio info [device]", cmd_gpio_info, 1, 1), |
| SHELL_SUBCMD_SET_END /* Array terminated. */ |
| ); |
| |
| SHELL_CMD_REGISTER(gpio, &sub_gpio, "GPIO commands", NULL); |