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 #include <sys/taskqueue.h> 40 41 #include <machine/atomic.h> 42 43 #include <dev/fdt/fdt_common.h> 44 #include <dev/ofw/ofw_bus.h> 45 46 #include <dev/gpio/gpiobusvar.h> 47 #include <dev/led/led.h> 48 49 #include "gpiobus_if.h" 50 51 struct gpioled 52 { 53 struct gpioleds_softc *parent_sc; 54 gpio_pin_t pin; 55 struct cdev *leddev; 56 struct task task; 57 int state; 58 }; 59 60 struct gpioleds_softc 61 { 62 device_t sc_dev; 63 device_t sc_busdev; 64 struct gpioled *sc_leds; 65 int sc_total_leds; 66 }; 67 68 static void gpioled_control(void *, int); 69 static int gpioled_probe(device_t); 70 static int gpioled_attach(device_t); 71 static int gpioled_detach(device_t); 72 73 /* Writes the most recently requested state, not every state update. */ 74 static void 75 gpioled_update(void *arg, int pending __unused) 76 { 77 struct gpioled *led; 78 79 led = arg; 80 gpio_pin_set_active(led->pin, atomic_load_int(&led->state) != 0); 81 } 82 83 static void 84 gpioled_control(void *priv, int onoff) 85 { 86 struct gpioled *led; 87 88 led = (struct gpioled *)priv; 89 if (led->pin == NULL) 90 return; 91 92 /* Runs under the led(4) mutex, so the pin write is deferred. */ 93 atomic_store_int(&led->state, onoff); 94 taskqueue_enqueue(taskqueue_thread, &led->task); 95 } 96 97 static void 98 gpioleds_attach_led(struct gpioleds_softc *sc, phandle_t node, 99 struct gpioled *led) 100 { 101 char *name; 102 int state, err; 103 char *default_state; 104 105 led->parent_sc = sc; 106 107 state = 0; 108 if (OF_getprop_alloc(node, "default-state", 109 (void **)&default_state) != -1) { 110 if (strcasecmp(default_state, "on") == 0) 111 state = 1; 112 else if (strcasecmp(default_state, "off") == 0) 113 state = 0; 114 else if (strcasecmp(default_state, "keep") == 0) 115 state = -1; 116 else { 117 state = -1; 118 device_printf(sc->sc_dev, 119 "unknown value for default-state in FDT\n"); 120 } 121 OF_prop_free(default_state); 122 } 123 124 name = NULL; 125 if (OF_getprop_alloc(node, "label", (void **)&name) == -1) 126 OF_getprop_alloc(node, "name", (void **)&name); 127 128 if (name == NULL) { 129 device_printf(sc->sc_dev, 130 "no name provided for gpio LED, skipping\n"); 131 return; 132 } 133 134 err = gpio_pin_get_by_ofw_idx(sc->sc_dev, node, 0, &led->pin); 135 if (err) { 136 device_printf(sc->sc_dev, "<%s> failed to map pin\n", name); 137 if (name) 138 OF_prop_free(name); 139 return; 140 } 141 gpio_pin_setflags(led->pin, GPIO_PIN_OUTPUT); 142 143 TASK_INIT(&led->task, 0, gpioled_update, led); 144 145 led->leddev = led_create_state(gpioled_control, led, name, 146 state); 147 148 if (name != NULL) 149 OF_prop_free(name); 150 } 151 152 static void 153 gpioleds_detach_led(struct gpioled *led) 154 { 155 156 if (led->leddev != NULL) { 157 led_destroy(led->leddev); 158 taskqueue_drain(taskqueue_thread, &led->task); 159 } 160 161 if (led->pin) 162 gpio_pin_release(led->pin); 163 } 164 165 static int 166 gpioled_probe(device_t dev) 167 { 168 if (!ofw_bus_status_okay(dev)) 169 return (ENXIO); 170 if (!ofw_bus_is_compatible(dev, "gpio-leds")) 171 return (ENXIO); 172 173 device_set_desc(dev, "GPIO LEDs"); 174 175 return (BUS_PROBE_DEFAULT); 176 } 177 178 static int 179 gpioled_attach(device_t dev) 180 { 181 struct gpioleds_softc *sc; 182 phandle_t child, leds; 183 int total_leds; 184 185 if ((leds = ofw_bus_get_node(dev)) == -1) 186 return (ENXIO); 187 188 sc = device_get_softc(dev); 189 sc->sc_dev = dev; 190 sc->sc_busdev = device_get_parent(dev); 191 192 /* Traverse the 'gpio-leds' node and count leds */ 193 total_leds = 0; 194 for (child = OF_child(leds); child != 0; child = OF_peer(child)) { 195 if (!OF_hasprop(child, "gpios")) 196 continue; 197 total_leds++; 198 } 199 200 if (total_leds) { 201 sc->sc_leds = malloc(sizeof(struct gpioled) * total_leds, 202 M_DEVBUF, M_WAITOK | M_ZERO); 203 204 sc->sc_total_leds = 0; 205 /* Traverse the 'gpio-leds' node and count leds */ 206 for (child = OF_child(leds); child != 0; child = OF_peer(child)) { 207 if (!OF_hasprop(child, "gpios")) 208 continue; 209 gpioleds_attach_led(sc, child, &sc->sc_leds[sc->sc_total_leds]); 210 sc->sc_total_leds++; 211 } 212 } 213 214 return (0); 215 } 216 217 static int 218 gpioled_detach(device_t dev) 219 { 220 struct gpioleds_softc *sc; 221 int i; 222 223 sc = device_get_softc(dev); 224 225 for (i = 0; i < sc->sc_total_leds; i++) 226 gpioleds_detach_led(&sc->sc_leds[i]); 227 228 if (sc->sc_leds) 229 free(sc->sc_leds, M_DEVBUF); 230 231 return (0); 232 } 233 234 static device_method_t gpioled_methods[] = { 235 /* Device interface */ 236 DEVMETHOD(device_probe, gpioled_probe), 237 DEVMETHOD(device_attach, gpioled_attach), 238 DEVMETHOD(device_detach, gpioled_detach), 239 240 DEVMETHOD_END 241 }; 242 243 static driver_t gpioled_driver = { 244 "gpioled", 245 gpioled_methods, 246 sizeof(struct gpioleds_softc), 247 }; 248 249 DRIVER_MODULE(gpioled, ofwbus, gpioled_driver, 0, 0); 250 DRIVER_MODULE(gpioled, simplebus, gpioled_driver, 0, 0); 251 MODULE_DEPEND(gpioled, gpiobus, 1, 1, 1); 252