sysinfo: Add gpio-sysinfo driver
This uses the newly-added dm_gpio_get_values_as_int_base3 function to
implement a sysinfo device. The revision map is stored in the device tree.
Signed-off-by: Sean Anderson <sean.anderson@seco.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/sysinfo/Kconfig b/drivers/sysinfo/Kconfig
index 85c1e81..381dcd8 100644
--- a/drivers/sysinfo/Kconfig
+++ b/drivers/sysinfo/Kconfig
@@ -30,4 +30,12 @@
one which provides a way to specify this SMBIOS information in the
devicetree, without needing any board-specific functionality.
+config SYSINFO_GPIO
+ bool "Enable gpio sysinfo driver"
+ help
+ Support querying gpios to determine board revision. This uses gpios to
+ form a ternary number (when they are pulled-up, -down, or floating).
+ This ternary number is then mapped to a board revision name using
+ device tree properties.
+
endif
diff --git a/drivers/sysinfo/Makefile b/drivers/sysinfo/Makefile
index 6d04fcb..d9f708b 100644
--- a/drivers/sysinfo/Makefile
+++ b/drivers/sysinfo/Makefile
@@ -4,5 +4,6 @@
# Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
obj-y += sysinfo-uclass.o
obj-$(CONFIG_SYSINFO_GAZERBEAM) += gazerbeam.o
+obj-$(CONFIG_SYSINFO_GPIO) += gpio.o
obj-$(CONFIG_SYSINFO_SANDBOX) += sandbox.o
obj-$(CONFIG_SYSINFO_SMBIOS) += smbios.o
diff --git a/drivers/sysinfo/gpio.c b/drivers/sysinfo/gpio.c
new file mode 100644
index 0000000..1d7f050
--- /dev/null
+++ b/drivers/sysinfo/gpio.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <sysinfo.h>
+#include <asm/gpio.h>
+#include <dm/device_compat.h>
+
+/**
+ * struct sysinfo_gpio_priv - GPIO sysinfo private data
+ * @gpios: List of GPIOs used to detect the revision
+ * @gpio_num: The number of GPIOs in @gpios
+ * @revision: The revision as detected from the GPIOs.
+ */
+struct sysinfo_gpio_priv {
+ struct gpio_desc *gpios;
+ int gpio_num, revision;
+};
+
+static int sysinfo_gpio_detect(struct udevice *dev)
+{
+ int ret;
+ struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
+
+ ret = dm_gpio_get_values_as_int_base3(priv->gpios, priv->gpio_num);
+ if (ret < 0)
+ return ret;
+
+ priv->revision = ret;
+ return 0;
+}
+
+static int sysinfo_gpio_get_int(struct udevice *dev, int id, int *val)
+{
+ struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
+
+ switch (id) {
+ case SYSINFO_ID_BOARD_MODEL:
+ *val = priv->revision;
+ return 0;
+ default:
+ return -EINVAL;
+ };
+}
+
+static int sysinfo_gpio_get_str(struct udevice *dev, int id, size_t size, char *val)
+{
+ struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
+
+ switch (id) {
+ case SYSINFO_ID_BOARD_MODEL: {
+ const char *name = NULL;
+ int i, ret;
+ u32 revision;
+
+ for (i = 0; i < priv->gpio_num; i++) {
+ ret = dev_read_u32_index(dev, "revisions", i,
+ &revision);
+ if (ret) {
+ if (ret != -EOVERFLOW)
+ return ret;
+ break;
+ }
+
+ if (revision == priv->revision) {
+ ret = dev_read_string_index(dev, "names", i,
+ &name);
+ if (ret < 0)
+ return ret;
+ break;
+ }
+ }
+ if (!name)
+ name = "unknown";
+
+ strncpy(val, name, size);
+ val[size - 1] = '\0';
+ return 0;
+ } default:
+ return -EINVAL;
+ };
+}
+
+static const struct sysinfo_ops sysinfo_gpio_ops = {
+ .detect = sysinfo_gpio_detect,
+ .get_int = sysinfo_gpio_get_int,
+ .get_str = sysinfo_gpio_get_str,
+};
+
+static int sysinfo_gpio_probe(struct udevice *dev)
+{
+ int ret;
+ struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
+
+ priv->gpio_num = gpio_get_list_count(dev, "gpios");
+ if (priv->gpio_num < 0) {
+ dev_err(dev, "could not get gpios length (err = %d)\n",
+ priv->gpio_num);
+ return priv->gpio_num;
+ }
+
+ priv->gpios = calloc(priv->gpio_num, sizeof(*priv->gpios));
+ if (!priv->gpios) {
+ dev_err(dev, "could not allocate memory for %d gpios\n",
+ priv->gpio_num);
+ return -ENOMEM;
+ }
+
+ ret = gpio_request_list_by_name(dev, "gpios", priv->gpios,
+ priv->gpio_num, GPIOD_IS_IN);
+ if (ret != priv->gpio_num) {
+ dev_err(dev, "could not get gpios (err = %d)\n",
+ priv->gpio_num);
+ return ret;
+ }
+
+ if (!dev_read_bool(dev, "revisions") || !dev_read_bool(dev, "names")) {
+ dev_err(dev, "revisions or names properties missing\n");
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id sysinfo_gpio_ids[] = {
+ { .compatible = "gpio-sysinfo" },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(sysinfo_gpio) = {
+ .name = "sysinfo_gpio",
+ .id = UCLASS_SYSINFO,
+ .of_match = sysinfo_gpio_ids,
+ .ops = &sysinfo_gpio_ops,
+ .priv_auto = sizeof(struct sysinfo_gpio_priv),
+ .probe = sysinfo_gpio_probe,
+};