1308428Sgonzo/*-
2308428Sgonzo * Copyright (c) 2009 Oleksandr Tymoshenko <gonzo@freebsd.org>
3308428Sgonzo * All rights reserved.
4308428Sgonzo *
5308428Sgonzo * Redistribution and use in source and binary forms, with or without
6308428Sgonzo * modification, are permitted provided that the following conditions
7308428Sgonzo * are met:
8308428Sgonzo * 1. Redistributions of source code must retain the above copyright
9308428Sgonzo *    notice, this list of conditions and the following disclaimer.
10308428Sgonzo * 2. Redistributions in binary form must reproduce the above copyright
11308428Sgonzo *    notice, this list of conditions and the following disclaimer in the
12308428Sgonzo *    documentation and/or other materials provided with the distribution.
13308428Sgonzo *
14308428Sgonzo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15308428Sgonzo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16308428Sgonzo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17308428Sgonzo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18308428Sgonzo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19308428Sgonzo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20308428Sgonzo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21308428Sgonzo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22308428Sgonzo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23308428Sgonzo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24308428Sgonzo * SUCH DAMAGE.
25308428Sgonzo */
26308428Sgonzo
27308428Sgonzo#include <sys/cdefs.h>
28308428Sgonzo__FBSDID("$FreeBSD: stable/11/sys/dev/gpio/gpioled_fdt.c 308658 2016-11-15 00:28:07Z gonzo $");
29308428Sgonzo
30308428Sgonzo#include "opt_platform.h"
31308428Sgonzo
32308428Sgonzo#include <sys/param.h>
33308428Sgonzo#include <sys/systm.h>
34308428Sgonzo#include <sys/bus.h>
35308428Sgonzo#include <sys/gpio.h>
36308428Sgonzo#include <sys/kernel.h>
37308428Sgonzo#include <sys/lock.h>
38308428Sgonzo#include <sys/malloc.h>
39308428Sgonzo#include <sys/module.h>
40308428Sgonzo#include <sys/mutex.h>
41308428Sgonzo
42308428Sgonzo#include <dev/fdt/fdt_common.h>
43308428Sgonzo#include <dev/ofw/ofw_bus.h>
44308428Sgonzo
45308428Sgonzo#include <dev/gpio/gpiobusvar.h>
46308428Sgonzo#include <dev/led/led.h>
47308428Sgonzo
48308428Sgonzo#include "gpiobus_if.h"
49308428Sgonzo
50308428Sgonzostruct gpioled
51308428Sgonzo{
52308428Sgonzo	struct gpioleds_softc	*parent_sc;
53308428Sgonzo	gpio_pin_t		pin;
54308428Sgonzo	struct cdev		*leddev;
55308428Sgonzo};
56308428Sgonzo
57308428Sgonzostruct gpioleds_softc
58308428Sgonzo{
59308428Sgonzo	device_t	sc_dev;
60308428Sgonzo	device_t	sc_busdev;
61308428Sgonzo	struct gpioled	*sc_leds;
62308428Sgonzo	int		sc_total_leds;
63308428Sgonzo};
64308428Sgonzo
65308428Sgonzostatic void gpioled_control(void *, int);
66308428Sgonzostatic int gpioled_probe(device_t);
67308428Sgonzostatic int gpioled_attach(device_t);
68308428Sgonzostatic int gpioled_detach(device_t);
69308428Sgonzo
70308428Sgonzostatic void
71308428Sgonzogpioled_control(void *priv, int onoff)
72308428Sgonzo{
73308428Sgonzo	struct gpioled *led;
74308428Sgonzo
75308428Sgonzo	led = (struct gpioled *)priv;
76308428Sgonzo	if (led->pin)
77308428Sgonzo		gpio_pin_set_active(led->pin, onoff);
78308428Sgonzo}
79308428Sgonzo
80308428Sgonzostatic void
81308428Sgonzogpioleds_attach_led(struct gpioleds_softc *sc, phandle_t node,
82308428Sgonzo    struct gpioled *led)
83308428Sgonzo{
84308428Sgonzo	char *name;
85308428Sgonzo	int state, err;
86308428Sgonzo	char *default_state;
87308428Sgonzo
88308428Sgonzo	led->parent_sc = sc;
89308428Sgonzo
90308428Sgonzo	state = 0;
91308428Sgonzo	if (OF_getprop_alloc(node, "default-state",
92308428Sgonzo	    sizeof(char), (void **)&default_state) != -1) {
93308428Sgonzo		if (strcasecmp(default_state, "on") == 0)
94308428Sgonzo			state = 1;
95308428Sgonzo		else if (strcasecmp(default_state, "off") == 0)
96308428Sgonzo			state = 0;
97308428Sgonzo		else if (strcasecmp(default_state, "keep") == 0)
98308428Sgonzo			state = -1;
99308428Sgonzo		else {
100308428Sgonzo			state = -1;
101308428Sgonzo			device_printf(sc->sc_dev,
102308428Sgonzo			    "unknown value for default-state in FDT\n");
103308428Sgonzo		}
104308428Sgonzo		OF_prop_free(default_state);
105308428Sgonzo	}
106308428Sgonzo
107308428Sgonzo	name = NULL;
108308428Sgonzo	if (OF_getprop_alloc(node, "label", 1, (void **)&name) == -1)
109308428Sgonzo		OF_getprop_alloc(node, "name", 1, (void **)&name);
110308428Sgonzo
111308428Sgonzo	if (name == NULL) {
112308428Sgonzo		device_printf(sc->sc_dev,
113308428Sgonzo		    "no name provided for gpio LED, skipping\n");
114308428Sgonzo		return;
115308428Sgonzo	}
116308428Sgonzo
117308428Sgonzo	err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &led->pin);
118308428Sgonzo	if (err) {
119308428Sgonzo		device_printf(sc->sc_dev, "<%s> failed to map pin\n", name);
120308428Sgonzo		if (name)
121308428Sgonzo			OF_prop_free(name);
122308428Sgonzo		return;
123308428Sgonzo	}
124308428Sgonzo	gpio_pin_setflags(led->pin, GPIO_PIN_OUTPUT);
125308428Sgonzo
126308428Sgonzo	led->leddev = led_create_state(gpioled_control, led, name,
127308428Sgonzo	    state);
128308428Sgonzo
129308428Sgonzo	if (name != NULL)
130308428Sgonzo		OF_prop_free(name);
131308428Sgonzo}
132308428Sgonzo
133308428Sgonzostatic void
134308428Sgonzogpioleds_detach_led(struct gpioled *led)
135308428Sgonzo{
136308428Sgonzo
137308428Sgonzo	if (led->leddev != NULL)
138308428Sgonzo		led_destroy(led->leddev);
139308428Sgonzo
140308428Sgonzo	if (led->pin)
141308428Sgonzo		gpio_pin_release(led->pin);
142308428Sgonzo}
143308428Sgonzo
144308428Sgonzostatic int
145308428Sgonzogpioled_probe(device_t dev)
146308428Sgonzo{
147308428Sgonzo	if (!ofw_bus_is_compatible(dev, "gpio-leds"))
148308428Sgonzo		return (ENXIO);
149308428Sgonzo
150308428Sgonzo	device_set_desc(dev, "GPIO LEDs");
151308428Sgonzo
152308428Sgonzo	return (BUS_PROBE_DEFAULT);
153308428Sgonzo}
154308428Sgonzo
155308428Sgonzostatic int
156308428Sgonzogpioled_attach(device_t dev)
157308428Sgonzo{
158308428Sgonzo	struct gpioleds_softc *sc;
159308428Sgonzo	phandle_t child, leds;
160308428Sgonzo	int total_leds;
161308428Sgonzo
162308428Sgonzo	if ((leds = ofw_bus_get_node(dev)) == -1)
163308428Sgonzo		return (ENXIO);
164308428Sgonzo
165308428Sgonzo	sc = device_get_softc(dev);
166308428Sgonzo	sc->sc_dev = dev;
167308428Sgonzo	sc->sc_busdev = device_get_parent(dev);
168308428Sgonzo
169308428Sgonzo	/* Traverse the 'gpio-leds' node and count leds */
170308428Sgonzo	total_leds = 0;
171308428Sgonzo	for (child = OF_child(leds); child != 0; child = OF_peer(child)) {
172308428Sgonzo		if (!OF_hasprop(child, "gpios"))
173308428Sgonzo			continue;
174308428Sgonzo		total_leds++;
175308428Sgonzo	}
176308428Sgonzo
177308428Sgonzo	if (total_leds) {
178308428Sgonzo		sc->sc_leds =  malloc(sizeof(struct gpioled) * total_leds,
179308428Sgonzo		    M_DEVBUF, M_WAITOK | M_ZERO);
180308428Sgonzo
181308428Sgonzo		sc->sc_total_leds = 0;
182308428Sgonzo		/* Traverse the 'gpio-leds' node and count leds */
183308428Sgonzo		for (child = OF_child(leds); child != 0; child = OF_peer(child)) {
184308428Sgonzo			if (!OF_hasprop(child, "gpios"))
185308428Sgonzo				continue;
186308428Sgonzo			gpioleds_attach_led(sc, child, &sc->sc_leds[sc->sc_total_leds]);
187308428Sgonzo			sc->sc_total_leds++;
188308428Sgonzo		}
189308428Sgonzo	}
190308428Sgonzo
191308428Sgonzo	return (0);
192308428Sgonzo}
193308428Sgonzo
194308428Sgonzostatic int
195308428Sgonzogpioled_detach(device_t dev)
196308428Sgonzo{
197308428Sgonzo	struct gpioleds_softc *sc;
198308428Sgonzo	int i;
199308428Sgonzo
200308428Sgonzo	sc = device_get_softc(dev);
201308428Sgonzo
202308428Sgonzo	for (i = 0; i < sc->sc_total_leds; i++)
203308428Sgonzo		gpioleds_detach_led(&sc->sc_leds[i]);
204308428Sgonzo
205308428Sgonzo	if (sc->sc_leds)
206308428Sgonzo		free(sc->sc_leds, M_DEVBUF);
207308428Sgonzo
208308428Sgonzo	return (0);
209308428Sgonzo}
210308428Sgonzo
211308428Sgonzostatic devclass_t gpioled_devclass;
212308428Sgonzo
213308428Sgonzostatic device_method_t gpioled_methods[] = {
214308428Sgonzo	/* Device interface */
215308428Sgonzo	DEVMETHOD(device_probe,		gpioled_probe),
216308428Sgonzo	DEVMETHOD(device_attach,	gpioled_attach),
217308428Sgonzo	DEVMETHOD(device_detach,	gpioled_detach),
218308428Sgonzo
219308428Sgonzo	DEVMETHOD_END
220308428Sgonzo};
221308428Sgonzo
222308428Sgonzostatic driver_t gpioled_driver = {
223308428Sgonzo	"gpioled",
224308428Sgonzo	gpioled_methods,
225308428Sgonzo	sizeof(struct gpioleds_softc),
226308428Sgonzo};
227308428Sgonzo
228308428SgonzoDRIVER_MODULE(gpioled, ofwbus, gpioled_driver, gpioled_devclass, 0, 0);
229308428SgonzoDRIVER_MODULE(gpioled, simplebus, gpioled_driver, gpioled_devclass, 0, 0);
230308428SgonzoMODULE_DEPEND(gpioled, gpiobus, 1, 1, 1);
231