1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
4 */
5
6#include <dm.h>
7#include <log.h>
8#include <sysinfo.h>
9#include <asm/gpio.h>
10#include <dm/device_compat.h>
11
12/**
13 * struct sysinfo_gpio_priv - GPIO sysinfo private data
14 * @gpios: List of GPIOs used to detect the revision
15 * @gpio_num: The number of GPIOs in @gpios
16 * @revision: The revision as detected from the GPIOs.
17 */
18struct sysinfo_gpio_priv {
19	struct gpio_desc *gpios;
20	int gpio_num, revision;
21};
22
23static int sysinfo_gpio_detect(struct udevice *dev)
24{
25	int ret;
26	struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
27
28	ret = dm_gpio_get_values_as_int_base3(priv->gpios, priv->gpio_num);
29	if (ret < 0)
30		return ret;
31
32	priv->revision = ret;
33	return 0;
34}
35
36static int sysinfo_gpio_get_int(struct udevice *dev, int id, int *val)
37{
38	struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
39
40	switch (id) {
41	case SYSINFO_ID_BOARD_MODEL:
42		*val = priv->revision;
43		return 0;
44	default:
45		return -EINVAL;
46	};
47}
48
49static int sysinfo_gpio_get_str(struct udevice *dev, int id, size_t size, char *val)
50{
51	struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
52
53	switch (id) {
54	case SYSINFO_ID_BOARD_MODEL: {
55		const char *name = NULL;
56		int i, ret;
57		u32 revision;
58
59		for (i = 0; ; i++) {
60			ret = dev_read_u32_index(dev, "revisions", i,
61						 &revision);
62			if (ret) {
63				if (ret != -EOVERFLOW)
64					return ret;
65				break;
66			}
67
68			if (revision == priv->revision) {
69				ret = dev_read_string_index(dev, "names", i,
70							    &name);
71				if (ret < 0)
72					return ret;
73				break;
74			}
75		}
76		if (!name)
77			name = "unknown";
78
79		strncpy(val, name, size);
80		val[size - 1] = '\0';
81		return 0;
82	}
83	default:
84		return -EINVAL;
85	};
86}
87
88static const struct sysinfo_ops sysinfo_gpio_ops = {
89	.detect = sysinfo_gpio_detect,
90	.get_int = sysinfo_gpio_get_int,
91	.get_str = sysinfo_gpio_get_str,
92};
93
94static int sysinfo_gpio_probe(struct udevice *dev)
95{
96	int ret;
97	struct sysinfo_gpio_priv *priv = dev_get_priv(dev);
98
99	priv->gpio_num = gpio_get_list_count(dev, "gpios");
100	if (priv->gpio_num < 0) {
101		dev_err(dev, "could not get gpios length (err = %d)\n",
102			priv->gpio_num);
103		return priv->gpio_num;
104	}
105
106	priv->gpios = calloc(priv->gpio_num, sizeof(*priv->gpios));
107	if (!priv->gpios) {
108		dev_err(dev, "could not allocate memory for %d gpios\n",
109			priv->gpio_num);
110		return -ENOMEM;
111	}
112
113	ret = gpio_request_list_by_name(dev, "gpios", priv->gpios,
114					priv->gpio_num, GPIOD_IS_IN);
115	if (ret != priv->gpio_num) {
116		dev_err(dev, "could not get gpios (err = %d)\n",
117			priv->gpio_num);
118		return ret;
119	}
120
121	if (!dev_read_bool(dev, "revisions") || !dev_read_bool(dev, "names")) {
122		dev_err(dev, "revisions or names properties missing\n");
123		return -ENOENT;
124	}
125
126	return 0;
127}
128
129static const struct udevice_id sysinfo_gpio_ids[] = {
130	{ .compatible = "gpio-sysinfo" },
131	{ /* sentinel */ }
132};
133
134U_BOOT_DRIVER(sysinfo_gpio) = {
135	.name           = "sysinfo_gpio",
136	.id             = UCLASS_SYSINFO,
137	.of_match       = sysinfo_gpio_ids,
138	.ops		= &sysinfo_gpio_ops,
139	.priv_auto	= sizeof(struct sysinfo_gpio_priv),
140	.probe          = sysinfo_gpio_probe,
141};
142