1/*-
2 * Copyright (c) 2009 Oleksandr Tymoshenko <gonzo@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28#include "opt_platform.h"
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/bus.h>
33#include <sys/gpio.h>
34#include <sys/kernel.h>
35#include <sys/lock.h>
36#include <sys/malloc.h>
37#include <sys/module.h>
38#include <sys/mutex.h>
39
40#include <dev/fdt/fdt_common.h>
41#include <dev/ofw/ofw_bus.h>
42
43#include <dev/gpio/gpiobusvar.h>
44#include <dev/led/led.h>
45
46#include "gpiobus_if.h"
47
48struct gpioled
49{
50	struct gpioleds_softc	*parent_sc;
51	gpio_pin_t		pin;
52	struct cdev		*leddev;
53};
54
55struct gpioleds_softc
56{
57	device_t	sc_dev;
58	device_t	sc_busdev;
59	struct gpioled	*sc_leds;
60	int		sc_total_leds;
61};
62
63static void gpioled_control(void *, int);
64static int gpioled_probe(device_t);
65static int gpioled_attach(device_t);
66static int gpioled_detach(device_t);
67
68static void
69gpioled_control(void *priv, int onoff)
70{
71	struct gpioled *led;
72
73	led = (struct gpioled *)priv;
74	if (led->pin)
75		gpio_pin_set_active(led->pin, onoff);
76}
77
78static void
79gpioleds_attach_led(struct gpioleds_softc *sc, phandle_t node,
80    struct gpioled *led)
81{
82	char *name;
83	int state, err;
84	char *default_state;
85
86	led->parent_sc = sc;
87
88	state = 0;
89	if (OF_getprop_alloc(node, "default-state",
90	    (void **)&default_state) != -1) {
91		if (strcasecmp(default_state, "on") == 0)
92			state = 1;
93		else if (strcasecmp(default_state, "off") == 0)
94			state = 0;
95		else if (strcasecmp(default_state, "keep") == 0)
96			state = -1;
97		else {
98			state = -1;
99			device_printf(sc->sc_dev,
100			    "unknown value for default-state in FDT\n");
101		}
102		OF_prop_free(default_state);
103	}
104
105	name = NULL;
106	if (OF_getprop_alloc(node, "label", (void **)&name) == -1)
107		OF_getprop_alloc(node, "name", (void **)&name);
108
109	if (name == NULL) {
110		device_printf(sc->sc_dev,
111		    "no name provided for gpio LED, skipping\n");
112		return;
113	}
114
115	err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &led->pin);
116	if (err) {
117		device_printf(sc->sc_dev, "<%s> failed to map pin\n", name);
118		if (name)
119			OF_prop_free(name);
120		return;
121	}
122	gpio_pin_setflags(led->pin, GPIO_PIN_OUTPUT);
123
124	led->leddev = led_create_state(gpioled_control, led, name,
125	    state);
126
127	if (name != NULL)
128		OF_prop_free(name);
129}
130
131static void
132gpioleds_detach_led(struct gpioled *led)
133{
134
135	if (led->leddev != NULL)
136		led_destroy(led->leddev);
137
138	if (led->pin)
139		gpio_pin_release(led->pin);
140}
141
142static int
143gpioled_probe(device_t dev)
144{
145	if (!ofw_bus_status_okay(dev))
146		return (ENXIO);
147	if (!ofw_bus_is_compatible(dev, "gpio-leds"))
148		return (ENXIO);
149
150	device_set_desc(dev, "GPIO LEDs");
151
152	return (BUS_PROBE_DEFAULT);
153}
154
155static int
156gpioled_attach(device_t dev)
157{
158	struct gpioleds_softc *sc;
159	phandle_t child, leds;
160	int total_leds;
161
162	if ((leds = ofw_bus_get_node(dev)) == -1)
163		return (ENXIO);
164
165	sc = device_get_softc(dev);
166	sc->sc_dev = dev;
167	sc->sc_busdev = device_get_parent(dev);
168
169	/* Traverse the 'gpio-leds' node and count leds */
170	total_leds = 0;
171	for (child = OF_child(leds); child != 0; child = OF_peer(child)) {
172		if (!OF_hasprop(child, "gpios"))
173			continue;
174		total_leds++;
175	}
176
177	if (total_leds) {
178		sc->sc_leds =  malloc(sizeof(struct gpioled) * total_leds,
179		    M_DEVBUF, M_WAITOK | M_ZERO);
180
181		sc->sc_total_leds = 0;
182		/* Traverse the 'gpio-leds' node and count leds */
183		for (child = OF_child(leds); child != 0; child = OF_peer(child)) {
184			if (!OF_hasprop(child, "gpios"))
185				continue;
186			gpioleds_attach_led(sc, child, &sc->sc_leds[sc->sc_total_leds]);
187			sc->sc_total_leds++;
188		}
189	}
190
191	return (0);
192}
193
194static int
195gpioled_detach(device_t dev)
196{
197	struct gpioleds_softc *sc;
198	int i;
199
200	sc = device_get_softc(dev);
201
202	for (i = 0; i < sc->sc_total_leds; i++)
203		gpioleds_detach_led(&sc->sc_leds[i]);
204
205	if (sc->sc_leds)
206		free(sc->sc_leds, M_DEVBUF);
207
208	return (0);
209}
210
211static device_method_t gpioled_methods[] = {
212	/* Device interface */
213	DEVMETHOD(device_probe,		gpioled_probe),
214	DEVMETHOD(device_attach,	gpioled_attach),
215	DEVMETHOD(device_detach,	gpioled_detach),
216
217	DEVMETHOD_END
218};
219
220static driver_t gpioled_driver = {
221	"gpioled",
222	gpioled_methods,
223	sizeof(struct gpioleds_softc),
224};
225
226DRIVER_MODULE(gpioled, ofwbus, gpioled_driver, 0, 0);
227DRIVER_MODULE(gpioled, simplebus, gpioled_driver, 0, 0);
228MODULE_DEPEND(gpioled, gpiobus, 1, 1, 1);
229