1213237Sgonzo/*-
2213237Sgonzo * Copyright (c) 2009 Oleksandr Tymoshenko <gonzo@freebsd.org>
3213237Sgonzo * All rights reserved.
4213237Sgonzo *
5213237Sgonzo * Redistribution and use in source and binary forms, with or without
6213237Sgonzo * modification, are permitted provided that the following conditions
7213237Sgonzo * are met:
8213237Sgonzo * 1. Redistributions of source code must retain the above copyright
9213237Sgonzo *    notice, this list of conditions and the following disclaimer.
10213237Sgonzo * 2. Redistributions in binary form must reproduce the above copyright
11213237Sgonzo *    notice, this list of conditions and the following disclaimer in the
12213237Sgonzo *    documentation and/or other materials provided with the distribution.
13213237Sgonzo *
14213277Sgonzo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15213277Sgonzo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16213277Sgonzo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17213277Sgonzo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18213277Sgonzo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19213277Sgonzo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20213277Sgonzo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21213277Sgonzo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22213277Sgonzo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23213277Sgonzo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24213277Sgonzo * SUCH DAMAGE.
25213237Sgonzo */
26213237Sgonzo
27213237Sgonzo#include <sys/cdefs.h>
28213237Sgonzo__FBSDID("$FreeBSD$");
29213237Sgonzo
30266105Sloos#include "opt_platform.h"
31266105Sloos
32213237Sgonzo#include <sys/param.h>
33213237Sgonzo#include <sys/systm.h>
34213237Sgonzo#include <sys/bus.h>
35278783Sloos#include <sys/gpio.h>
36213237Sgonzo#include <sys/kernel.h>
37213237Sgonzo#include <sys/lock.h>
38213237Sgonzo#include <sys/malloc.h>
39213237Sgonzo#include <sys/module.h>
40213237Sgonzo#include <sys/mutex.h>
41213237Sgonzo
42266105Sloos#ifdef FDT
43266105Sloos#include <dev/fdt/fdt_common.h>
44266105Sloos#include <dev/ofw/ofw_bus.h>
45266105Sloos#endif
46266105Sloos
47278783Sloos#include <dev/gpio/gpiobusvar.h>
48213237Sgonzo#include <dev/led/led.h>
49278783Sloos
50213237Sgonzo#include "gpiobus_if.h"
51213237Sgonzo
52213237Sgonzo/*
53213237Sgonzo * Only one pin for led
54213237Sgonzo */
55213237Sgonzo#define	GPIOLED_PIN	0
56213237Sgonzo
57213237Sgonzo#define GPIOLED_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
58213237Sgonzo#define	GPIOLED_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
59213237Sgonzo#define GPIOLED_LOCK_INIT(_sc) \
60213237Sgonzo	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
61213237Sgonzo	    "gpioled", MTX_DEF)
62213237Sgonzo#define GPIOLED_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
63213237Sgonzo
64213237Sgonzostruct gpioled_softc
65213237Sgonzo{
66213237Sgonzo	device_t	sc_dev;
67213237Sgonzo	device_t	sc_busdev;
68213237Sgonzo	struct mtx	sc_mtx;
69213237Sgonzo	struct cdev	*sc_leddev;
70213237Sgonzo};
71213237Sgonzo
72213237Sgonzostatic void gpioled_control(void *, int);
73213237Sgonzostatic int gpioled_probe(device_t);
74213237Sgonzostatic int gpioled_attach(device_t);
75213237Sgonzostatic int gpioled_detach(device_t);
76213237Sgonzo
77213237Sgonzostatic void
78213237Sgonzogpioled_control(void *priv, int onoff)
79213237Sgonzo{
80278783Sloos	int error;
81278783Sloos	struct gpioled_softc *sc;
82278783Sloos
83278783Sloos	sc = (struct gpioled_softc *)priv;
84213237Sgonzo	GPIOLED_LOCK(sc);
85278783Sloos	error = GPIOBUS_ACQUIRE_BUS(sc->sc_busdev, sc->sc_dev,
86278783Sloos	    GPIOBUS_DONTWAIT);
87278783Sloos	if (error != 0) {
88278783Sloos		GPIOLED_UNLOCK(sc);
89278783Sloos		return;
90278783Sloos	}
91278783Sloos	error = GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev,
92278783Sloos	    GPIOLED_PIN, GPIO_PIN_OUTPUT);
93278783Sloos	if (error == 0)
94278783Sloos		GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, GPIOLED_PIN,
95278783Sloos		    onoff ? GPIO_PIN_HIGH : GPIO_PIN_LOW);
96213237Sgonzo	GPIOBUS_RELEASE_BUS(sc->sc_busdev, sc->sc_dev);
97213237Sgonzo	GPIOLED_UNLOCK(sc);
98213237Sgonzo}
99213237Sgonzo
100266105Sloos#ifdef FDT
101266105Sloosstatic void
102266105Sloosgpioled_identify(driver_t *driver, device_t bus)
103266105Sloos{
104266105Sloos	phandle_t child, leds, root;
105266105Sloos
106266105Sloos	root = OF_finddevice("/");
107266105Sloos	if (root == 0)
108266105Sloos		return;
109266105Sloos	leds = fdt_find_compatible(root, "gpio-leds", 1);
110266105Sloos	if (leds == 0)
111266105Sloos		return;
112266105Sloos
113266105Sloos	/* Traverse the 'gpio-leds' node and add its children. */
114266105Sloos	for (child = OF_child(leds); child != 0; child = OF_peer(child))
115266105Sloos		if (ofw_gpiobus_add_fdt_child(bus, child) == NULL)
116266105Sloos			continue;
117266105Sloos}
118266105Sloos#endif
119266105Sloos
120213237Sgonzostatic int
121213237Sgonzogpioled_probe(device_t dev)
122213237Sgonzo{
123266105Sloos#ifdef FDT
124266105Sloos	int match;
125266105Sloos	phandle_t node;
126266105Sloos	char *compat;
127266105Sloos
128266105Sloos	/*
129266105Sloos	 * We can match against our own node compatible string and also against
130266105Sloos	 * our parent node compatible string.  The first is normally used to
131266105Sloos	 * describe leds on a gpiobus and the later when there is a common node
132266105Sloos	 * compatible with 'gpio-leds' which is used to concentrate all the
133266105Sloos	 * leds nodes on the dts.
134266105Sloos	 */
135266105Sloos	match = 0;
136266105Sloos	if (ofw_bus_is_compatible(dev, "gpioled"))
137266105Sloos		match = 1;
138266105Sloos
139266105Sloos	if (match == 0) {
140266105Sloos		if ((node = ofw_bus_get_node(dev)) == -1)
141266105Sloos			return (ENXIO);
142266105Sloos		if ((node = OF_parent(node)) == -1)
143266105Sloos			return (ENXIO);
144266105Sloos		if (OF_getprop_alloc(node, "compatible", 1,
145266105Sloos		    (void **)&compat) == -1)
146266105Sloos			return (ENXIO);
147266105Sloos
148266105Sloos		if (strcasecmp(compat, "gpio-leds") == 0)
149266105Sloos			match = 1;
150266105Sloos
151266105Sloos		free(compat, M_OFWPROP);
152266105Sloos	}
153266105Sloos
154266105Sloos	if (match == 0)
155266105Sloos		return (ENXIO);
156266105Sloos#endif
157213237Sgonzo	device_set_desc(dev, "GPIO led");
158266105Sloos
159213237Sgonzo	return (0);
160213237Sgonzo}
161213237Sgonzo
162213237Sgonzostatic int
163213237Sgonzogpioled_attach(device_t dev)
164213237Sgonzo{
165213237Sgonzo	struct gpioled_softc *sc;
166266105Sloos#ifdef FDT
167266105Sloos	phandle_t node;
168266105Sloos	char *name;
169266105Sloos#else
170213237Sgonzo	const char *name;
171266105Sloos#endif
172213237Sgonzo
173213237Sgonzo	sc = device_get_softc(dev);
174213237Sgonzo	sc->sc_dev = dev;
175213237Sgonzo	sc->sc_busdev = device_get_parent(dev);
176213237Sgonzo	GPIOLED_LOCK_INIT(sc);
177266105Sloos#ifdef FDT
178266105Sloos	name = NULL;
179266105Sloos	if ((node = ofw_bus_get_node(dev)) == -1)
180266105Sloos		return (ENXIO);
181266105Sloos	if (OF_getprop_alloc(node, "label", 1, (void **)&name) == -1)
182266105Sloos		OF_getprop_alloc(node, "name", 1, (void **)&name);
183266105Sloos#else
184213237Sgonzo	if (resource_string_value(device_get_name(dev),
185213237Sgonzo	    device_get_unit(dev), "name", &name))
186213237Sgonzo		name = NULL;
187266105Sloos#endif
188213237Sgonzo
189213237Sgonzo	sc->sc_leddev = led_create(gpioled_control, sc, name ? name :
190213237Sgonzo	    device_get_nameunit(dev));
191266105Sloos#ifdef FDT
192266105Sloos	if (name != NULL)
193266105Sloos		free(name, M_OFWPROP);
194266105Sloos#endif
195213237Sgonzo
196213237Sgonzo	return (0);
197213237Sgonzo}
198213237Sgonzo
199213237Sgonzostatic int
200213237Sgonzogpioled_detach(device_t dev)
201213237Sgonzo{
202213237Sgonzo	struct gpioled_softc *sc;
203213237Sgonzo
204213237Sgonzo	sc = device_get_softc(dev);
205213237Sgonzo	if (sc->sc_leddev) {
206213237Sgonzo		led_destroy(sc->sc_leddev);
207213237Sgonzo		sc->sc_leddev = NULL;
208213237Sgonzo	}
209213237Sgonzo	GPIOLED_LOCK_DESTROY(sc);
210213237Sgonzo	return (0);
211213237Sgonzo}
212213237Sgonzo
213213237Sgonzostatic devclass_t gpioled_devclass;
214213237Sgonzo
215213237Sgonzostatic device_method_t gpioled_methods[] = {
216213237Sgonzo	/* Device interface */
217266105Sloos#ifdef FDT
218266105Sloos	DEVMETHOD(device_identify,	gpioled_identify),
219266105Sloos#endif
220213237Sgonzo	DEVMETHOD(device_probe,		gpioled_probe),
221213237Sgonzo	DEVMETHOD(device_attach,	gpioled_attach),
222213237Sgonzo	DEVMETHOD(device_detach,	gpioled_detach),
223213237Sgonzo
224213237Sgonzo	{ 0, 0 }
225213237Sgonzo};
226213237Sgonzo
227213237Sgonzostatic driver_t gpioled_driver = {
228213237Sgonzo	"gpioled",
229213237Sgonzo	gpioled_methods,
230213237Sgonzo	sizeof(struct gpioled_softc),
231213237Sgonzo};
232213237Sgonzo
233213237SgonzoDRIVER_MODULE(gpioled, gpiobus, gpioled_driver, gpioled_devclass, 0, 0);
234