blob: 1af8021057c42bb1ff6f2fc348296e1f3c552258 [file] [edit]
// 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
);