summaryrefslogtreecommitdiff
path: root/lib/utils/gpio/fdt_gpio.c
blob: 7258128debd027cadf43fd2a2f4c05acc87d05f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2021 Western Digital Corporation or its affiliates.
 *
 * Authors:
 *   Anup Patel <anup.patel@wdc.com>
 */

#include <libfdt.h>
#include <sbi/sbi_error.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/gpio/fdt_gpio.h>

/* List of FDT gpio drivers generated at compile time */
extern struct fdt_gpio *fdt_gpio_drivers[];
extern unsigned long fdt_gpio_drivers_size;

static struct fdt_gpio *fdt_gpio_driver(struct gpio_chip *chip)
{
	int pos;

	if (!chip)
		return NULL;

	for (pos = 0; pos < fdt_gpio_drivers_size; pos++) {
		if (chip->driver == fdt_gpio_drivers[pos])
			return fdt_gpio_drivers[pos];
	}

	return NULL;
}

static int fdt_gpio_init(void *fdt, u32 phandle)
{
	int pos, nodeoff, rc;
	struct fdt_gpio *drv;
	const struct fdt_match *match;

	/* Find node offset */
	nodeoff = fdt_node_offset_by_phandle(fdt, phandle);
	if (nodeoff < 0)
		return nodeoff;

	/* Check "gpio-controller" property */
	if (!fdt_getprop(fdt, nodeoff, "gpio-controller", &rc))
		return SBI_EINVAL;

	/* Try all GPIO drivers one-by-one */
	for (pos = 0; pos < fdt_gpio_drivers_size; pos++) {
		drv = fdt_gpio_drivers[pos];

		match = fdt_match_node(fdt, nodeoff, drv->match_table);
		if (match && drv->init) {
			rc = drv->init(fdt, nodeoff, phandle, match);
			if (rc == SBI_ENODEV)
				continue;
			if (rc)
				return rc;
			return 0;
		}
	}

	return SBI_ENOSYS;
}

static int fdt_gpio_chip_find(void *fdt, u32 phandle,
			      struct gpio_chip **out_chip)
{
	int rc;
	struct gpio_chip *chip = gpio_chip_find(phandle);

	if (!chip) {
		/* GPIO chip not found so initialize matching driver */
		rc = fdt_gpio_init(fdt, phandle);
		if (rc)
			return rc;

		/* Try to find GPIO chip again */
		chip = gpio_chip_find(phandle);
		if (!chip)
			return SBI_ENOSYS;
	}

	if (out_chip)
		*out_chip = chip;

	return 0;
}

int fdt_gpio_pin_get(void *fdt, int nodeoff, int index,
		     struct gpio_pin *out_pin)
{
	int rc;
	u32 phandle;
	struct fdt_gpio *drv;
	struct gpio_chip *chip = NULL;
	struct fdt_phandle_args pargs;

	if (!fdt || (nodeoff < 0) || (index < 0) || !out_pin)
		return SBI_EINVAL;

	pargs.node_offset = pargs.args_count = 0;
	rc = fdt_parse_phandle_with_args(fdt, nodeoff,
					 "gpios", "#gpio-cells",
					 index, &pargs);
	if (rc)
		return rc;

	phandle = fdt_get_phandle(fdt, pargs.node_offset);
	rc = fdt_gpio_chip_find(fdt, phandle, &chip);
	if (rc)
		return rc;

	drv = fdt_gpio_driver(chip);
	if (!drv || !drv->xlate)
		return SBI_ENOSYS;

	return drv->xlate(chip, &pargs, out_pin);
}

int fdt_gpio_simple_xlate(struct gpio_chip *chip,
			  const struct fdt_phandle_args *pargs,
			  struct gpio_pin *out_pin)
{
	if ((pargs->args_count < 2) || (chip->ngpio <= pargs->args[0]))
		return SBI_EINVAL;

	out_pin->chip = chip;
	out_pin->offset = pargs->args[0];
	out_pin->flags = pargs->args[1];
	return 0;
}