| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright 2025 Gateworks Corporation |
| */ |
| |
| #include <command.h> |
| #include <hexdump.h> |
| #include <i2c.h> |
| #include <dm.h> |
| #include <dm/device.h> |
| #include <dm/device_compat.h> |
| #include <dm/device-internal.h> // device_remove/device_unbind |
| #include <asm-generic/gpio.h> |
| #include <fdt_support.h> |
| #include <linux/delay.h> |
| |
| #include "fsa.h" |
| |
| static int fsa; |
| static struct udevice *fsa_gpiodevs[FSA_MAX] = { NULL }; |
| |
| /* find the ofnode of the FSA i2c bus */ |
| static ofnode fsa_get_ofnode(int fsa) |
| { |
| char str[32]; |
| |
| /* by alias */ |
| snprintf(str, sizeof(str), "fsa%d", fsa); |
| return ofnode_get_aliases_node(str); |
| } |
| |
| static int fsa_get_dtnode(void *fdt, int fsa) |
| { |
| char str[32]; |
| |
| /* by alias */ |
| snprintf(str, sizeof(str), "fsa%d", fsa); |
| return fdt_path_offset(fdt, fdt_get_alias(fdt, str)); |
| } |
| |
| static const char * const fsa_gpio_config_names[] = { "NC", "", "input", "output-low", |
| "output-high" }; |
| |
| static const char *fsa_gpio_config_name(struct fsa_gpio_desc *desc) |
| { |
| if (desc->config < ARRAY_SIZE(fsa_gpio_config_names)) |
| return fsa_gpio_config_names[desc->config]; |
| return NULL; |
| }; |
| |
| static char *fsa_get_gpio_desc(struct fsa_gpio_desc *desc, char *str, int sz) |
| { |
| str[0] = 0; |
| if (desc->source == 0xff) { |
| snprintf(str, sz, "fsa_gpio%d : %s %s", |
| desc->offset + 1, |
| desc->name, |
| fsa_gpio_config_name(desc)); |
| } else if (desc->config) { |
| snprintf(str, sz, "gpio@%02x_%02d: %s %s", |
| desc->source, |
| desc->offset, |
| desc->name, |
| fsa_gpio_config_name(desc)); |
| } |
| return str; |
| } |
| |
| static void fsa_show_gpio_descs(const char *prefix, int fsa, struct fsa_board_info *board_info, |
| struct fsa_user_info *user_info) |
| { |
| char str[128]; |
| int i; |
| |
| /* display slot specific gpios */ |
| for (i = 0; i < board_info->sockgpios; i++) { |
| fsa_get_gpio_desc(&user_info->gpios[i], str, sizeof(str)); |
| printf("%s%-2d: %s\n", prefix, i, str); |
| } |
| /* display io-expander specific gpios */ |
| if (fsa_gpiodevs[fsa]) { |
| for (i = board_info->sockgpios; |
| i < (board_info->sockgpios + board_info->ioexpgpios); |
| i++) { |
| fsa_get_gpio_desc(&user_info->gpios[i], str, sizeof(str)); |
| printf("%s%-2d: %s\n", prefix, i, str); |
| } |
| } |
| } |
| |
| /* detect gpio expander by address and deal with enabling/disabling/adding gpio expander to dt */ |
| static int fsa_get_gpiodev(int fsa, int addr, struct udevice **devp) |
| { |
| struct udevice *bus, *dev; |
| char gpio_name[32]; |
| int ret; |
| |
| ret = device_get_global_by_ofnode(fsa_get_ofnode(fsa), &bus); |
| if (ret) |
| return ret; |
| |
| sprintf(gpio_name, "gpio@%02x", addr); |
| |
| /* probe device on i2c bus */ |
| ret = dm_i2c_probe(bus, addr, 0, &dev); |
| switch (ret) { |
| case -EREMOTEIO: /* chip is not present on i2c bus */ |
| /* if device is in dt remove/unbind/disable it */ |
| ret = device_find_child_by_name(bus, gpio_name, &dev); |
| if (ret) |
| return ret; |
| ret = ofnode_set_enabled(dev_ofnode(dev), false); |
| if (ret) |
| return ret; |
| ret = device_unbind(dev); |
| if (ret) |
| return ret; |
| ret = device_remove(dev, DM_REMOVE_NORMAL); |
| if (ret) |
| return ret; |
| return ret; |
| case -ENOSYS: /* chip found but driver invalid */ |
| /* if device is in not in dt add/bind it */ |
| return ret; |
| case 0: /* chip responded and driver bound */ |
| break; |
| } |
| |
| if (devp) |
| *devp = dev; |
| return 0; |
| } |
| |
| /* add gpio's to gpio device: GPIO device must be probed before you can manipulate it */ |
| static int fsa_config_gpios(int fsa, struct fsa_user_info *info, int gpios, struct udevice *dev) |
| { |
| struct fsa_gpio_desc *desc; |
| struct gpio_desc gdesc; |
| struct udevice *gdev; |
| int i, ret, flags; |
| char name[32]; |
| |
| /* configure GPIO's */ |
| for (i = 0; i < gpios; i++) { |
| desc = &info->gpios[i]; |
| if (desc->config < FSA_GPIO_INPUT) |
| continue; |
| memset(&gdesc, 0, sizeof(gdesc)); |
| |
| if (desc->source == 0xff) { |
| /* Board specific IMX8M GPIO's: find dev of controller by line-name */ |
| sprintf(name, "fsa%d_gpio%d", fsa, desc->offset + 1); |
| uclass_foreach_dev_probe(UCLASS_GPIO, gdev) { |
| ret = dev_read_stringlist_search(gdev, "gpio-line-names", name); |
| if (ret >= 0) { |
| gdesc.dev = gdev; |
| gdesc.offset = ret; |
| break; |
| } |
| } |
| } else { |
| /* port expander GPIOs */ |
| gdesc.dev = dev; |
| gdesc.offset = desc->offset; |
| } |
| |
| if (!gdesc.dev) |
| continue; |
| |
| sprintf(name, "fsa%d_%s", fsa, desc->name); |
| switch (desc->config) { |
| case FSA_GPIO_INPUT: |
| flags = GPIOD_IS_IN; |
| break; |
| case FSA_GPIO_OUTPUT_LOW: |
| flags = GPIOD_IS_OUT; |
| break; |
| case FSA_GPIO_OUTPUT_HIGH: |
| flags = GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE; |
| break; |
| } |
| if (!dm_gpio_request(&gdesc, name)) |
| dm_gpio_clrset_flags(&gdesc, GPIOD_MASK_DIR, flags); |
| } |
| |
| return 0; |
| } |
| |
| static int fsa_read_board_config(int fsa, struct fsa_board_info *info) |
| { |
| struct udevice *dev; |
| int chksum; |
| int i, ret; |
| ofnode node; |
| |
| /* find eeprom dev */ |
| node = ofnode_find_subnode(fsa_get_ofnode(fsa), "eeprom@54"); |
| if (!ofnode_valid(node)) |
| return -EINVAL; |
| ret = device_get_global_by_ofnode(node, &dev); |
| if (ret) |
| return ret; |
| |
| /* read eeprom */ |
| ret = dm_i2c_read(dev, 0, (uint8_t *)info, sizeof(*info)); |
| if (ret) { |
| dev_err(dev, "read failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* validate checksum */ |
| for (chksum = 0, i = 0; i < (int)sizeof(*info) - 2; i++) |
| chksum += ((unsigned char *)info)[i]; |
| if ((info->chksum[0] != ((chksum >> 8) & 0xff)) || |
| (info->chksum[1] != (chksum & 0xff))) { |
| dev_err(dev, "FSA%d EEPROM: Invalid User Config Checksum\n", fsa); |
| print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, info, sizeof(*info)); |
| memset(info, 0, sizeof(*info)); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int fsa_read_user_config(int fsa, struct fsa_user_info *info) |
| { |
| struct udevice *dev; |
| int chksum; |
| int i, ret; |
| ofnode node; |
| |
| /* find eeprom dev */ |
| node = ofnode_find_subnode(fsa_get_ofnode(fsa), "eeprom@55"); |
| if (!ofnode_valid(node)) |
| return -EINVAL; |
| ret = device_get_global_by_ofnode(node, &dev); |
| if (ret) |
| return ret; |
| |
| /* read eeprom */ |
| ret = dm_i2c_read(dev, 0, (uint8_t *)info, sizeof(*info)); |
| if (ret) { |
| dev_err(dev, "read failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* validate checksum */ |
| for (chksum = 0, i = 0; i < (int)sizeof(*info) - 2; i++) |
| chksum += ((unsigned char *)info)[i]; |
| if ((info->chksum[0] != ((chksum >> 8) & 0xff)) || |
| (info->chksum[1] != (chksum & 0xff))) { |
| dev_err(dev, "FSA%d EEPROM: Invalid User Config Checksum\n", fsa); |
| print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, info, sizeof(*info)); |
| memset(info, 0, sizeof(*info)); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int fsa_write_user_config(int fsa, struct fsa_user_info *info) |
| { |
| struct udevice *bus, *dev; |
| int i, n, chunk, slave, base, ret; |
| ofnode node; |
| int chksum; |
| |
| /* create checksum */ |
| for (chksum = 0, i = 0; i < (int)sizeof(*info) - 2; i++) |
| chksum += ((unsigned char *)info)[i]; |
| info->chksum[0] = chksum >> 8; |
| info->chksum[1] = chksum & 0xff; |
| |
| /* find eeprom dev */ |
| node = ofnode_find_subnode(fsa_get_ofnode(fsa), "eeprom@55"); |
| ret = device_get_global_by_ofnode(node, &dev); |
| if (ret) |
| return ret; |
| bus = dev->parent; |
| base = dev_read_addr(dev); |
| |
| /* write in 16byte chunks (multi-byte writes fail larger than that) */ |
| chunk = 16; |
| slave = -1; |
| for (i = 0; i < sizeof(*info); i += chunk) { |
| /* select device based on offset */ |
| if ((base + (i / 256)) != slave) { |
| slave = base + (i / 256); |
| ret = i2c_get_chip(bus, slave, 1, &dev); |
| if (ret) { |
| dev_err(bus, "failed to get eeprom@0x%02x: %d\n", slave, ret); |
| return ret; |
| } |
| } |
| /* select byte count */ |
| n = sizeof(*info) - i; |
| if (n > chunk) |
| n = chunk; |
| ret = dm_i2c_write(dev, i % 256, (uint8_t *)info + i, n); |
| if (ret) { |
| dev_err(dev, "write failed: %d\n", ret); |
| return ret; |
| } |
| mdelay(11); |
| } |
| |
| return ret; |
| } |
| |
| static int fsa_detect(int fsa, struct fsa_board_info *board_info, struct fsa_user_info *user_info, |
| bool gpio) |
| { |
| int ret; |
| |
| ret = fsa_read_board_config(fsa, board_info); |
| if (ret) |
| return ret; |
| if (user_info) { |
| ret = fsa_read_user_config(fsa, user_info); |
| if (ret) |
| return ret; |
| /* detect port expander */ |
| if (gpio && !fsa_get_gpiodev(fsa, 0x20, &fsa_gpiodevs[fsa])) |
| fsa_config_gpios(fsa, user_info, |
| board_info->sockgpios + board_info->ioexpgpios, |
| fsa_gpiodevs[fsa]); |
| } |
| |
| return ret; |
| } |
| |
| static int ft_fixup_stringlist_elem(void *fdt, int offset, const char *prop, int elem, |
| const char *val) |
| { |
| const char *list, *end; |
| char *new, *buf; |
| int length; |
| int sz = 0; |
| int i = 0; |
| int ret; |
| |
| if (offset < 0 || elem < 0 || !val) { |
| printf("%s -EINVAL\n", __func__); |
| return -EINVAL; |
| } |
| |
| list = fdt_getprop(fdt, offset, prop, &length); |
| |
| /* no property or invalid params */ |
| if (!list || length < 0) { |
| printf("%s failed - no property\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* create new buffer with enough space */ |
| buf = calloc(1, length + strlen(val)); |
| new = buf; |
| |
| /* iterate over current stringlist and build new list into buf */ |
| end = list + length; |
| while (list < end) { |
| length = strnlen(list, end - list) + 1; |
| sz += length; |
| /* insert new value into buf */ |
| if (elem == i) { |
| strcpy(new, val); |
| new += strlen(val) + 1; |
| } else { |
| strcpy(new, list); |
| new += length; |
| } |
| list += length; |
| i++; |
| } |
| length = new - buf; |
| ret = fdt_setprop(fdt, offset, prop, buf, length); |
| free(buf); |
| if (ret) |
| printf("%s failed %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int ft_fixup_fsa_gpio_name(void *fdt, int offset, int fsa, int gpio, const char *name) |
| { |
| const char *prop = "gpio-line-names"; |
| char str[32]; |
| |
| sprintf(str, "fsa%d_%s", fsa, name); |
| |
| if (!fdt_getprop(fdt, offset, prop, NULL)) { |
| char buf[16] = { 0 }; |
| |
| fdt_setprop(fdt, offset, prop, &buf, sizeof(buf)); |
| } |
| |
| return ft_fixup_stringlist_elem(fdt, offset, prop, gpio, str); |
| } |
| |
| static void fsa_show_details(int fsa, struct fsa_board_info *board, struct fsa_user_info *user) |
| { |
| printf("FSA%d: %s\n", fsa, board->model); |
| printf("description: %s\n", user->desc); |
| printf("overlay: %s\n", user->overlay); |
| fsa_show_gpio_descs("\t", fsa, board, user); |
| } |
| |
| int fsa_init(void) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| int fsa, ret; |
| |
| for (fsa = 1; fsa < FSA_MAX; fsa++) { |
| ret = fsa_detect(fsa, &board_info, &user_info, true); |
| if (!ret) |
| printf("FSA%d: %s %s\n", fsa, board_info.model, user_info.desc); |
| } |
| |
| return 0; |
| } |
| |
| int fsa_show(void) |
| { |
| struct fsa_board_info board_info; |
| int fsa, ret; |
| |
| for (fsa = 1; fsa < FSA_MAX; fsa++) { |
| ret = fsa_detect(fsa, &board_info, NULL, false); |
| if (!ret) { |
| printf("FSA%d : %s %d %02x-%02x-%02x%02x\n", fsa, |
| board_info.model, board_info.serial, |
| board_info.mfgdate[0], board_info.mfgdate[1], |
| board_info.mfgdate[2], board_info.mfgdate[3]); |
| } |
| } |
| return 0; |
| } |
| |
| /* fixup gpio line names for fsa gpios */ |
| int fsa_ft_fixup(void *fdt) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| int fsa, i, ret; |
| char path[128]; |
| char str[32]; |
| ofnode node; |
| int off; |
| |
| /* iterate over FSA's and rename gpio's */ |
| for (fsa = 1; fsa < FSA_MAX; fsa++) { |
| /* disable FSA ioexp node if disabled in controlling dt */ |
| off = fdt_subnode_offset(fdt, fsa_get_dtnode(fdt, fsa), "gpio@20"); |
| if (off >= 0) { |
| if (!fdt_get_path(fdt, off, path, sizeof(path))) { |
| node = ofnode_path(path); |
| if (ofnode_valid(node) && !ofnode_is_enabled(node)) |
| fdt_setprop_string(fdt, off, "status", "disabled"); |
| } |
| } |
| |
| /* detect FSA eeprom */ |
| if (fsa_detect(fsa, &board_info, &user_info, false)) |
| continue; |
| |
| /* configure GPIO's */ |
| for (i = 0; i < board_info.sockgpios + board_info.ioexpgpios; i++) { |
| if (user_info.gpios[i].config < FSA_GPIO_INPUT) |
| continue; |
| |
| if (user_info.gpios[i].source == 0xff) { |
| /* Board specific IMX8M GPIO's */ |
| for (off = fdt_node_offset_by_prop_value(fdt, 0, |
| "gpio-controller", NULL, |
| 0); |
| off >= 0; |
| off = fdt_node_offset_by_prop_value(fdt, off, |
| "gpio-controller", NULL, |
| 0) |
| ) { |
| sprintf(str, "fsa%d_gpio%d", fsa, |
| user_info.gpios[i].offset + 1); |
| ret = fdt_stringlist_search(fdt, off, "gpio-line-names", |
| str); |
| if (ret >= 0) { |
| ft_fixup_fsa_gpio_name(fdt, off, fsa, ret, |
| user_info.gpios[i].name); |
| break; |
| } |
| } |
| } else { |
| /* port expander GPIOs */ |
| off = fdt_subnode_offset(fdt, fsa_get_dtnode(fdt, fsa), "gpio@20"); |
| ft_fixup_fsa_gpio_name(fdt, off, fsa, user_info.gpios[i].offset, |
| user_info.gpios[i].name); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int do_fsa_dev(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| int i; |
| |
| if (argc < 2) { |
| /* list FSAs */ |
| printf("detecting FSA Adapters:\n"); |
| for (i = 1; i < FSA_MAX; i++) { |
| if (!fsa_read_board_config(i, &board_info) && |
| !fsa_read_user_config(i, &user_info)) |
| printf("FSA%d : %s %s\n", i, board_info.model, user_info.desc); |
| } |
| } else { |
| /* select FSA */ |
| fsa = simple_strtoul(argv[1], NULL, 10); |
| } |
| |
| if (fsa) { |
| /* read FSA */ |
| if (!fsa_read_board_config(fsa, &board_info) && |
| !fsa_read_user_config(fsa, &user_info)) { |
| printf("selected:\n"); |
| fsa_show_details(fsa, &board_info, &user_info); |
| } else { |
| printf("FSA%d not detected\n", fsa); |
| fsa = 0; |
| } |
| } else { |
| printf("no FSA currently selected\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int do_fsa_desc(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| |
| /* strip off leading cmd arg */ |
| argc--; |
| argv++; |
| |
| if (!fsa) { |
| printf("No FSA selected\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| if (fsa_read_board_config(fsa, &board_info) || fsa_read_user_config(fsa, &user_info)) { |
| printf("can't detect FSA%d\n", fsa); |
| return CMD_RET_USAGE; |
| } |
| |
| /* set */ |
| if (argc) { |
| strlcpy(user_info.desc, argv[0], sizeof(user_info.desc)); |
| if (fsa_write_user_config(fsa, &user_info)) |
| return CMD_RET_FAILURE; |
| } |
| |
| /* show */ |
| fsa_show_details(fsa, &board_info, &user_info); |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_fsa_overlay(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| |
| /* strip off leading cmd arg */ |
| argc--; |
| argv++; |
| |
| if (!fsa) { |
| printf("No FSA selected\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| if (fsa_read_board_config(fsa, &board_info) || fsa_read_user_config(fsa, &user_info)) { |
| printf("can't detect FSA%d\n", fsa); |
| return CMD_RET_USAGE; |
| } |
| |
| /* set */ |
| if (argc) { |
| strlcpy(user_info.overlay, argv[0], sizeof(user_info.overlay)); |
| if (fsa_write_user_config(fsa, &user_info)) |
| return CMD_RET_FAILURE; |
| } |
| |
| /* show */ |
| fsa_show_details(fsa, &board_info, &user_info); |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_fsa_gpio(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct fsa_board_info board_info; |
| struct fsa_user_info user_info; |
| struct fsa_gpio_desc desc; |
| char str[64]; |
| int i, j; |
| |
| /* strip off leading cmd arg */ |
| argc--; |
| argv++; |
| |
| if (!fsa) { |
| printf("No FSA selected\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| if (fsa_read_board_config(fsa, &board_info) || fsa_read_user_config(fsa, &user_info)) { |
| printf("can't detect FSA%d\n", fsa); |
| return CMD_RET_USAGE; |
| } |
| |
| if (!argc) { |
| /* show all gpios */ |
| fsa_show_gpio_descs("\t", fsa, &board_info, &user_info); |
| return CMD_RET_SUCCESS; |
| } |
| |
| if (!isdigit(argv[0][0])) { |
| printf("invalid gpio offset: %s\n", argv[0]); |
| return CMD_RET_USAGE; |
| } |
| |
| memset(&desc, 0, sizeof(desc)); |
| i = simple_strtoul(argv[0], NULL, 10); |
| |
| if (i >= 0 && i < board_info.sockgpios) { |
| desc.offset = i; |
| desc.source = 0xff; |
| } else if (i >= board_info.sockgpios && |
| i < (board_info.sockgpios + board_info.ioexpgpios) && |
| fsa_gpiodevs[fsa]) { |
| desc.offset = i - board_info.sockgpios; |
| desc.source = 0x20; |
| } else { |
| printf("invalid index %d", i); |
| return CMD_RET_FAILURE; |
| } |
| |
| if (argc > 1) { |
| if (user_info.gpios[i].config == FSA_GPIO_NC) { |
| printf("can not alter NC gpio\n"); |
| return CMD_RET_FAILURE; |
| } |
| strlcpy(desc.name, argv[1], sizeof(desc.name)); |
| if (!*desc.name) { |
| printf("FSA%d %s erasing gpio %d\n", fsa, board_info.model, i); |
| memset(&user_info.gpios[i], 0, sizeof(desc)); |
| if (fsa_write_user_config(fsa, &user_info)) |
| return CMD_RET_FAILURE; |
| return CMD_RET_SUCCESS; |
| } |
| } |
| if (argc > 2) { |
| if (user_info.gpios[i].config == FSA_GPIO_NC) { |
| printf("can not alter NC gpio\n"); |
| return CMD_RET_FAILURE; |
| } |
| for (j = 1; j < ARRAY_SIZE(fsa_gpio_config_names); j++) { |
| if (!strcasecmp(argv[2], fsa_gpio_config_names[j])) { |
| desc.config = j; |
| break; |
| } |
| }; |
| if (j >= ARRAY_SIZE(fsa_gpio_config_names)) { |
| printf("invalid config type '%s\n", argv[2]); |
| return CMD_RET_FAILURE; |
| } |
| } |
| |
| /* show a specific gpio */ |
| if (argc == 1) { |
| printf("FSA%d %s showing gpio %d\n", fsa, board_info.model, i); |
| printf("%s\n", fsa_get_gpio_desc(&user_info.gpios[i], str, sizeof(str))); |
| return CMD_RET_SUCCESS; |
| } |
| |
| /* set a specific gpio */ |
| else if (argc == 3) { |
| printf("FSA%d %s updating gpio %d\n", fsa, board_info.model, i); |
| memcpy(&user_info.gpios[i], &desc, sizeof(desc)); |
| if (fsa_write_user_config(fsa, &user_info)) |
| return CMD_RET_FAILURE; |
| return CMD_RET_SUCCESS; |
| } |
| |
| return CMD_RET_USAGE; |
| } |
| |
| static struct cmd_tbl cmd_fsa_sub[] = { |
| U_BOOT_CMD_MKENT(dev, 1, 1, do_fsa_dev, "", ""), |
| U_BOOT_CMD_MKENT(gpio, 4, 1, do_fsa_gpio, "", ""), |
| U_BOOT_CMD_MKENT(description, 1, 1, do_fsa_desc, "", ""), |
| U_BOOT_CMD_MKENT(overlay, 1, 1, do_fsa_overlay, "", ""), |
| }; |
| |
| static int do_fsa(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct cmd_tbl *c; |
| |
| /* strip off leading fsa arg */ |
| argc--; |
| argv++; |
| |
| c = find_cmd_tbl(argv[0], cmd_fsa_sub, ARRAY_SIZE(cmd_fsa_sub)); |
| if (c) |
| return c->cmd(cmdtp, flag, argc, argv); |
| return CMD_RET_USAGE; |
| } |
| |
| U_BOOT_LONGHELP(fsa, |
| "dev [dev] - show or set current FSA adapter\n" |
| "fsa gpio - show current gpio descriptors\n" |
| "fsa gpio [<offset>]|[<offset> <source>] - show a specific gpio descriptor\n" |
| "fsa gpio [<offset> <name> <input|output-low|output-high> [source]] - set a gpio descriptor\n" |
| "fsa description [description] - show or set the FSA user description string\n" |
| "fsa overlay [overlay] - show or set the FSA overlay string\n" |
| ); |
| |
| U_BOOT_CMD(fsa, 6, 1, do_fsa, |
| "Flexible Socket Adapter", |
| fsa_help_text |
| ); |