xref: /freebsd/sys/dev/gpio/ofw_gpiobus.c (revision ddfc9c4c)
16d866ed3SLuiz Otavio O Souza /*-
2718cf2ccSPedro F. Giffuni  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3718cf2ccSPedro F. Giffuni  *
46d866ed3SLuiz Otavio O Souza  * Copyright (c) 2009, Nathan Whitehorn <nwhitehorn@FreeBSD.org>
56d866ed3SLuiz Otavio O Souza  * Copyright (c) 2013, Luiz Otavio O Souza <loos@FreeBSD.org>
66d866ed3SLuiz Otavio O Souza  * Copyright (c) 2013 The FreeBSD Foundation
76d866ed3SLuiz Otavio O Souza  * All rights reserved.
86d866ed3SLuiz Otavio O Souza  *
96d866ed3SLuiz Otavio O Souza  * Redistribution and use in source and binary forms, with or without
106d866ed3SLuiz Otavio O Souza  * modification, are permitted provided that the following conditions
116d866ed3SLuiz Otavio O Souza  * are met:
126d866ed3SLuiz Otavio O Souza  * 1. Redistributions of source code must retain the above copyright
136d866ed3SLuiz Otavio O Souza  *    notice unmodified, this list of conditions, and the following
146d866ed3SLuiz Otavio O Souza  *    disclaimer.
156d866ed3SLuiz Otavio O Souza  * 2. Redistributions in binary form must reproduce the above copyright
166d866ed3SLuiz Otavio O Souza  *    notice, this list of conditions and the following disclaimer in the
176d866ed3SLuiz Otavio O Souza  *    documentation and/or other materials provided with the distribution.
186d866ed3SLuiz Otavio O Souza  *
196d866ed3SLuiz Otavio O Souza  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
206d866ed3SLuiz Otavio O Souza  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
216d866ed3SLuiz Otavio O Souza  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
226d866ed3SLuiz Otavio O Souza  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
236d866ed3SLuiz Otavio O Souza  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
246d866ed3SLuiz Otavio O Souza  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
256d866ed3SLuiz Otavio O Souza  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
266d866ed3SLuiz Otavio O Souza  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
276d866ed3SLuiz Otavio O Souza  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
286d866ed3SLuiz Otavio O Souza  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
296d866ed3SLuiz Otavio O Souza  */
306d866ed3SLuiz Otavio O Souza 
316d866ed3SLuiz Otavio O Souza #include <sys/cdefs.h>
326d866ed3SLuiz Otavio O Souza __FBSDID("$FreeBSD$");
336d866ed3SLuiz Otavio O Souza 
346d866ed3SLuiz Otavio O Souza #include <sys/param.h>
35160ca719SLuiz Otavio O Souza #include <sys/systm.h>
36002381dfSLuiz Otavio O Souza #include <sys/bus.h>
37002381dfSLuiz Otavio O Souza #include <sys/kernel.h>
38002381dfSLuiz Otavio O Souza #include <sys/malloc.h>
39002381dfSLuiz Otavio O Souza #include <sys/module.h>
406d866ed3SLuiz Otavio O Souza 
416d866ed3SLuiz Otavio O Souza #include <dev/gpio/gpiobusvar.h>
426d866ed3SLuiz Otavio O Souza #include <dev/ofw/ofw_bus.h>
436d866ed3SLuiz Otavio O Souza 
44d752f0f6SLuiz Otavio O Souza #include "gpiobus_if.h"
45d752f0f6SLuiz Otavio O Souza 
466d866ed3SLuiz Otavio O Souza static struct ofw_gpiobus_devinfo *ofw_gpiobus_setup_devinfo(device_t,
47e8da3e8aSLuiz Otavio O Souza 	device_t, phandle_t);
486ad7f491SLuiz Otavio O Souza static void ofw_gpiobus_destroy_devinfo(device_t, struct ofw_gpiobus_devinfo *);
49e8da3e8aSLuiz Otavio O Souza static int ofw_gpiobus_parse_gpios_impl(device_t, phandle_t, char *,
50e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_softc *, struct gpiobus_pin **);
516d866ed3SLuiz Otavio O Souza 
528a4ba038SMichal Meloun /*
538a4ba038SMichal Meloun  * Utility functions for easier handling of OFW GPIO pins.
548a4ba038SMichal Meloun  *
558a4ba038SMichal Meloun  * !!! BEWARE !!!
568a4ba038SMichal Meloun  * GPIOBUS uses children's IVARs, so we cannot use this interface for cross
578a4ba038SMichal Meloun  * tree consumers.
588a4ba038SMichal Meloun  *
598a4ba038SMichal Meloun  */
60dc027dc6SIan Lepore int
61dc027dc6SIan Lepore gpio_pin_get_by_ofw_propidx(device_t consumer, phandle_t cnode,
6251702162SOleksandr Tymoshenko     char *prop_name, int idx, gpio_pin_t *out_pin)
638a4ba038SMichal Meloun {
6451702162SOleksandr Tymoshenko 	phandle_t xref;
658a4ba038SMichal Meloun 	pcell_t *cells;
668a4ba038SMichal Meloun 	device_t busdev;
678a4ba038SMichal Meloun 	struct gpiobus_pin pin;
688a4ba038SMichal Meloun 	int ncells, rv;
698a4ba038SMichal Meloun 
7051702162SOleksandr Tymoshenko 	KASSERT(consumer != NULL && cnode > 0,
7151702162SOleksandr Tymoshenko 	    ("both consumer and cnode required"));
728a4ba038SMichal Meloun 
738a4ba038SMichal Meloun 	rv = ofw_bus_parse_xref_list_alloc(cnode, prop_name, "#gpio-cells",
748a4ba038SMichal Meloun 	    idx, &xref, &ncells, &cells);
758a4ba038SMichal Meloun 	if (rv != 0)
768a4ba038SMichal Meloun 		return (rv);
778a4ba038SMichal Meloun 
788a4ba038SMichal Meloun 	/* Translate provider to device. */
798a4ba038SMichal Meloun 	pin.dev = OF_device_from_xref(xref);
808a4ba038SMichal Meloun 	if (pin.dev == NULL) {
81bc90a48cSOleksandr Tymoshenko 		OF_prop_free(cells);
828a4ba038SMichal Meloun 		return (ENODEV);
838a4ba038SMichal Meloun 	}
848a4ba038SMichal Meloun 
858a4ba038SMichal Meloun 	/* Test if GPIO bus already exist. */
868a4ba038SMichal Meloun 	busdev = GPIO_GET_BUS(pin.dev);
878a4ba038SMichal Meloun 	if (busdev == NULL) {
88bc90a48cSOleksandr Tymoshenko 		OF_prop_free(cells);
898a4ba038SMichal Meloun 		return (ENODEV);
908a4ba038SMichal Meloun 	}
918a4ba038SMichal Meloun 
928a4ba038SMichal Meloun 	/* Map GPIO pin. */
938a4ba038SMichal Meloun 	rv = gpio_map_gpios(pin.dev, cnode, OF_node_from_xref(xref), ncells,
948a4ba038SMichal Meloun 	    cells, &pin.pin, &pin.flags);
95bc90a48cSOleksandr Tymoshenko 	OF_prop_free(cells);
9651702162SOleksandr Tymoshenko 	if (rv != 0)
978a4ba038SMichal Meloun 		return (ENXIO);
988a4ba038SMichal Meloun 
998a4ba038SMichal Meloun 	/* Reserve GPIO pin. */
100c45b8422SIan Lepore 	rv = gpiobus_acquire_pin(busdev, pin.pin);
10151702162SOleksandr Tymoshenko 	if (rv != 0)
1028a4ba038SMichal Meloun 		return (EBUSY);
1038a4ba038SMichal Meloun 
1048a4ba038SMichal Meloun 	*out_pin = malloc(sizeof(struct gpiobus_pin), M_DEVBUF,
1058a4ba038SMichal Meloun 	    M_WAITOK | M_ZERO);
1068a4ba038SMichal Meloun 	**out_pin = pin;
1078a4ba038SMichal Meloun 	return (0);
1088a4ba038SMichal Meloun }
1098a4ba038SMichal Meloun 
1108a4ba038SMichal Meloun int
11151702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_idx(device_t consumer, phandle_t node,
11251702162SOleksandr Tymoshenko     int idx, gpio_pin_t *pin)
1138a4ba038SMichal Meloun {
1148a4ba038SMichal Meloun 
115dc027dc6SIan Lepore 	return (gpio_pin_get_by_ofw_propidx(consumer, node, "gpios", idx, pin));
1168a4ba038SMichal Meloun }
1178a4ba038SMichal Meloun 
1188a4ba038SMichal Meloun int
11951702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_property(device_t consumer, phandle_t node,
12051702162SOleksandr Tymoshenko     char *name, gpio_pin_t *pin)
1218a4ba038SMichal Meloun {
1228a4ba038SMichal Meloun 
123dc027dc6SIan Lepore 	return (gpio_pin_get_by_ofw_propidx(consumer, node, name, 0, pin));
1248a4ba038SMichal Meloun }
1258a4ba038SMichal Meloun 
1268a4ba038SMichal Meloun int
12751702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_name(device_t consumer, phandle_t node,
12851702162SOleksandr Tymoshenko     char *name, gpio_pin_t *pin)
1298a4ba038SMichal Meloun {
1308a4ba038SMichal Meloun 	int rv, idx;
1318a4ba038SMichal Meloun 
13251702162SOleksandr Tymoshenko 	KASSERT(consumer != NULL && node > 0,
13351702162SOleksandr Tymoshenko 	    ("both consumer and node required"));
13451702162SOleksandr Tymoshenko 
13551702162SOleksandr Tymoshenko 	rv = ofw_bus_find_string_index(node, "gpio-names", name, &idx);
1368a4ba038SMichal Meloun 	if (rv != 0)
1378a4ba038SMichal Meloun 		return (rv);
13851702162SOleksandr Tymoshenko 	return (gpio_pin_get_by_ofw_idx(consumer, node, idx, pin));
1398a4ba038SMichal Meloun }
1408a4ba038SMichal Meloun 
1418a4ba038SMichal Meloun /*
1428a4ba038SMichal Meloun  * OFW_GPIOBUS driver.
1438a4ba038SMichal Meloun  */
1446d866ed3SLuiz Otavio O Souza device_t
145e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_add_fdt_child(device_t bus, const char *drvname, phandle_t child)
1466d866ed3SLuiz Otavio O Souza {
1476d866ed3SLuiz Otavio O Souza 	device_t childdev;
148d752f0f6SLuiz Otavio O Souza 	int i;
149d752f0f6SLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
150e8da3e8aSLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
1516d866ed3SLuiz Otavio O Souza 
1526d866ed3SLuiz Otavio O Souza 	/*
1533dede9f3SWarner Losh 	 * Check to see if we already have a child for @p child, and if so
1543dede9f3SWarner Losh 	 * return it.
1553dede9f3SWarner Losh 	 */
1563dede9f3SWarner Losh 	childdev = ofw_bus_find_child_device_by_phandle(bus, child);
1573dede9f3SWarner Losh 	if (childdev != NULL)
1583dede9f3SWarner Losh 		return (childdev);
1593dede9f3SWarner Losh 
1603dede9f3SWarner Losh 	/*
1616d866ed3SLuiz Otavio O Souza 	 * Set up the GPIO child and OFW bus layer devinfo and add it to bus.
1626d866ed3SLuiz Otavio O Souza 	 */
163e8da3e8aSLuiz Otavio O Souza 	childdev = device_add_child(bus, drvname, -1);
164e8da3e8aSLuiz Otavio O Souza 	if (childdev == NULL)
1656d866ed3SLuiz Otavio O Souza 		return (NULL);
166e8da3e8aSLuiz Otavio O Souza 	dinfo = ofw_gpiobus_setup_devinfo(bus, childdev, child);
167e8da3e8aSLuiz Otavio O Souza 	if (dinfo == NULL) {
168e8da3e8aSLuiz Otavio O Souza 		device_delete_child(bus, childdev);
1696d866ed3SLuiz Otavio O Souza 		return (NULL);
1706d866ed3SLuiz Otavio O Souza 	}
171e8da3e8aSLuiz Otavio O Souza 	if (device_probe_and_attach(childdev) != 0) {
1726ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
173e8da3e8aSLuiz Otavio O Souza 		device_delete_child(bus, childdev);
174e8da3e8aSLuiz Otavio O Souza 		return (NULL);
175e8da3e8aSLuiz Otavio O Souza 	}
176d752f0f6SLuiz Otavio O Souza 	/* Use the child name as pin name. */
177d752f0f6SLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
178d752f0f6SLuiz Otavio O Souza 	for (i = 0; i < devi->npins; i++)
179d752f0f6SLuiz Otavio O Souza 		GPIOBUS_PIN_SETNAME(bus, devi->pins[i],
180d752f0f6SLuiz Otavio O Souza 		    device_get_nameunit(childdev));
1816d866ed3SLuiz Otavio O Souza 
1826d866ed3SLuiz Otavio O Souza 	return (childdev);
1836d866ed3SLuiz Otavio O Souza }
1846d866ed3SLuiz Otavio O Souza 
185e8da3e8aSLuiz Otavio O Souza int
186e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_parse_gpios(device_t consumer, char *pname,
187e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_pin **pins)
1886d866ed3SLuiz Otavio O Souza {
1896d866ed3SLuiz Otavio O Souza 
190e8da3e8aSLuiz Otavio O Souza 	return (ofw_gpiobus_parse_gpios_impl(consumer,
191e8da3e8aSLuiz Otavio O Souza 	    ofw_bus_get_node(consumer), pname, NULL, pins));
1926d866ed3SLuiz Otavio O Souza }
1936d866ed3SLuiz Otavio O Souza 
194dbdb1205SLuiz Otavio O Souza void
195dbdb1205SLuiz Otavio O Souza ofw_gpiobus_register_provider(device_t provider)
196dbdb1205SLuiz Otavio O Souza {
197dbdb1205SLuiz Otavio O Souza 	phandle_t node;
198dbdb1205SLuiz Otavio O Souza 
199dbdb1205SLuiz Otavio O Souza 	node = ofw_bus_get_node(provider);
200f5e4e915SAndrew Turner 	if (node != -1)
201dbdb1205SLuiz Otavio O Souza 		OF_device_register_xref(OF_xref_from_node(node), provider);
202dbdb1205SLuiz Otavio O Souza }
203dbdb1205SLuiz Otavio O Souza 
204dbdb1205SLuiz Otavio O Souza void
205dbdb1205SLuiz Otavio O Souza ofw_gpiobus_unregister_provider(device_t provider)
206dbdb1205SLuiz Otavio O Souza {
207dbdb1205SLuiz Otavio O Souza 	phandle_t node;
208dbdb1205SLuiz Otavio O Souza 
209dbdb1205SLuiz Otavio O Souza 	node = ofw_bus_get_node(provider);
210f5e4e915SAndrew Turner 	if (node != -1)
211dbdb1205SLuiz Otavio O Souza 		OF_device_register_xref(OF_xref_from_node(node), NULL);
212dbdb1205SLuiz Otavio O Souza }
213dbdb1205SLuiz Otavio O Souza 
2146d866ed3SLuiz Otavio O Souza static struct ofw_gpiobus_devinfo *
215e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_setup_devinfo(device_t bus, device_t child, phandle_t node)
2166d866ed3SLuiz Otavio O Souza {
217e8da3e8aSLuiz Otavio O Souza 	int i, npins;
218e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
219e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_pin *pins;
2206d866ed3SLuiz Otavio O Souza 	struct gpiobus_softc *sc;
2216d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
2226d866ed3SLuiz Otavio O Souza 
223e8da3e8aSLuiz Otavio O Souza 	sc = device_get_softc(bus);
2246d866ed3SLuiz Otavio O Souza 	dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
2256d866ed3SLuiz Otavio O Souza 	if (dinfo == NULL)
2266d866ed3SLuiz Otavio O Souza 		return (NULL);
2276d866ed3SLuiz Otavio O Souza 	if (ofw_bus_gen_setup_devinfo(&dinfo->opd_obdinfo, node) != 0) {
2286d866ed3SLuiz Otavio O Souza 		free(dinfo, M_DEVBUF);
2296d866ed3SLuiz Otavio O Souza 		return (NULL);
2306d866ed3SLuiz Otavio O Souza 	}
2316d866ed3SLuiz Otavio O Souza 	/* Parse the gpios property for the child. */
232e8da3e8aSLuiz Otavio O Souza 	npins = ofw_gpiobus_parse_gpios_impl(child, node, "gpios", sc, &pins);
2336ad7f491SLuiz Otavio O Souza 	if (npins <= 0) {
2346ad7f491SLuiz Otavio O Souza 		ofw_bus_gen_destroy_devinfo(&dinfo->opd_obdinfo);
2356ad7f491SLuiz Otavio O Souza 		free(dinfo, M_DEVBUF);
2366ad7f491SLuiz Otavio O Souza 		return (NULL);
2376ad7f491SLuiz Otavio O Souza 	}
2386ad7f491SLuiz Otavio O Souza 	/* Initialize the irq resource list. */
2396ad7f491SLuiz Otavio O Souza 	resource_list_init(&dinfo->opd_dinfo.rl);
2406ad7f491SLuiz Otavio O Souza 	/* Allocate the child ivars and copy the parsed pin data. */
241e8da3e8aSLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
242e8da3e8aSLuiz Otavio O Souza 	devi->npins = (uint32_t)npins;
243e8da3e8aSLuiz Otavio O Souza 	if (gpiobus_alloc_ivars(devi) != 0) {
244e8da3e8aSLuiz Otavio O Souza 		free(pins, M_DEVBUF);
2456ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
2466ad7f491SLuiz Otavio O Souza 		return (NULL);
2476d866ed3SLuiz Otavio O Souza 	}
248404e6469SLi-Wen Hsu 	for (i = 0; i < devi->npins; i++)
249e8da3e8aSLuiz Otavio O Souza 		devi->pins[i] = pins[i].pin;
250e8da3e8aSLuiz Otavio O Souza 	free(pins, M_DEVBUF);
2516ad7f491SLuiz Otavio O Souza 	/* Parse the interrupt resources. */
252a8c5ea04SRuslan Bukin 	if (ofw_bus_intr_to_rl(bus, node, &dinfo->opd_dinfo.rl, NULL) != 0) {
2536ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
2546ad7f491SLuiz Otavio O Souza 		return (NULL);
255e8da3e8aSLuiz Otavio O Souza 	}
256e8da3e8aSLuiz Otavio O Souza 	device_set_ivars(child, dinfo);
257e8da3e8aSLuiz Otavio O Souza 
258e8da3e8aSLuiz Otavio O Souza 	return (dinfo);
259138bf909SLuiz Otavio O Souza }
2606d866ed3SLuiz Otavio O Souza 
2616d866ed3SLuiz Otavio O Souza static void
2626ad7f491SLuiz Otavio O Souza ofw_gpiobus_destroy_devinfo(device_t bus, struct ofw_gpiobus_devinfo *dinfo)
2636d866ed3SLuiz Otavio O Souza {
2646ad7f491SLuiz Otavio O Souza 	int i;
265e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
2666ad7f491SLuiz Otavio O Souza 	struct gpiobus_softc *sc;
2676d866ed3SLuiz Otavio O Souza 
2686ad7f491SLuiz Otavio O Souza 	sc = device_get_softc(bus);
269e8da3e8aSLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
2706ad7f491SLuiz Otavio O Souza 	for (i = 0; i < devi->npins; i++) {
2716ad7f491SLuiz Otavio O Souza 		if (devi->pins[i] > sc->sc_npins)
2726ad7f491SLuiz Otavio O Souza 			continue;
273d752f0f6SLuiz Otavio O Souza 		sc->sc_pins[devi->pins[i]].mapped = 0;
2746ad7f491SLuiz Otavio O Souza 	}
275e8da3e8aSLuiz Otavio O Souza 	gpiobus_free_ivars(devi);
276138bf909SLuiz Otavio O Souza 	resource_list_free(&dinfo->opd_dinfo.rl);
2776d866ed3SLuiz Otavio O Souza 	ofw_bus_gen_destroy_devinfo(&dinfo->opd_obdinfo);
2786d866ed3SLuiz Otavio O Souza 	free(dinfo, M_DEVBUF);
2796d866ed3SLuiz Otavio O Souza }
2806d866ed3SLuiz Otavio O Souza 
2816d866ed3SLuiz Otavio O Souza static int
282e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_parse_gpios_impl(device_t consumer, phandle_t cnode, char *pname,
283e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_softc *bussc, struct gpiobus_pin **pins)
284e8da3e8aSLuiz Otavio O Souza {
285e8da3e8aSLuiz Otavio O Souza 	int gpiocells, i, j, ncells, npins;
286e8da3e8aSLuiz Otavio O Souza 	pcell_t *gpios;
287e8da3e8aSLuiz Otavio O Souza 	phandle_t gpio;
288e8da3e8aSLuiz Otavio O Souza 
289f7604b1bSOleksandr Tymoshenko 	ncells = OF_getencprop_alloc_multi(cnode, pname, sizeof(*gpios),
290e8da3e8aSLuiz Otavio O Souza             (void **)&gpios);
291e8da3e8aSLuiz Otavio O Souza 	if (ncells == -1) {
292e8da3e8aSLuiz Otavio O Souza 		device_printf(consumer,
293e8da3e8aSLuiz Otavio O Souza 		    "Warning: No %s specified in fdt data; "
294e8da3e8aSLuiz Otavio O Souza 		    "device may not function.\n", pname);
295e8da3e8aSLuiz Otavio O Souza 		return (-1);
296e8da3e8aSLuiz Otavio O Souza 	}
297e8da3e8aSLuiz Otavio O Souza 	/*
298e8da3e8aSLuiz Otavio O Souza 	 * The gpio-specifier is controller independent, the first pcell has
299e8da3e8aSLuiz Otavio O Souza 	 * the reference to the GPIO controller phandler.
300e8da3e8aSLuiz Otavio O Souza 	 * Count the number of encoded gpio-specifiers on the first pass.
301e8da3e8aSLuiz Otavio O Souza 	 */
302e8da3e8aSLuiz Otavio O Souza 	i = 0;
303e8da3e8aSLuiz Otavio O Souza 	npins = 0;
304e8da3e8aSLuiz Otavio O Souza 	while (i < ncells) {
305e8da3e8aSLuiz Otavio O Souza 		/* Allow NULL specifiers. */
306e8da3e8aSLuiz Otavio O Souza 		if (gpios[i] == 0) {
307e8da3e8aSLuiz Otavio O Souza 			npins++;
308e8da3e8aSLuiz Otavio O Souza 			i++;
309e8da3e8aSLuiz Otavio O Souza 			continue;
310e8da3e8aSLuiz Otavio O Souza 		}
311e8da3e8aSLuiz Otavio O Souza 		gpio = OF_node_from_xref(gpios[i]);
312e8da3e8aSLuiz Otavio O Souza 		/* If we have bussc, ignore devices from other gpios. */
313e8da3e8aSLuiz Otavio O Souza 		if (bussc != NULL)
314e8da3e8aSLuiz Otavio O Souza 			if (ofw_bus_get_node(bussc->sc_dev) != gpio)
315e8da3e8aSLuiz Otavio O Souza 				return (0);
316e8da3e8aSLuiz Otavio O Souza 		/*
317e8da3e8aSLuiz Otavio O Souza 		 * Check for gpio-controller property and read the #gpio-cells
318e8da3e8aSLuiz Otavio O Souza 		 * for this GPIO controller.
319e8da3e8aSLuiz Otavio O Souza 		 */
320e8da3e8aSLuiz Otavio O Souza 		if (!OF_hasprop(gpio, "gpio-controller") ||
321e8da3e8aSLuiz Otavio O Souza 		    OF_getencprop(gpio, "#gpio-cells", &gpiocells,
322e8da3e8aSLuiz Otavio O Souza 		    sizeof(gpiocells)) < 0) {
323e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
324e8da3e8aSLuiz Otavio O Souza 			    "gpio reference is not a gpio-controller.\n");
325bc90a48cSOleksandr Tymoshenko 			OF_prop_free(gpios);
326e8da3e8aSLuiz Otavio O Souza 			return (-1);
327e8da3e8aSLuiz Otavio O Souza 		}
328e8da3e8aSLuiz Otavio O Souza 		if (ncells - i < gpiocells + 1) {
329e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
330e8da3e8aSLuiz Otavio O Souza 			    "%s cells doesn't match #gpio-cells.\n", pname);
331e8da3e8aSLuiz Otavio O Souza 			return (-1);
332e8da3e8aSLuiz Otavio O Souza 		}
333e8da3e8aSLuiz Otavio O Souza 		npins++;
334e8da3e8aSLuiz Otavio O Souza 		i += gpiocells + 1;
335e8da3e8aSLuiz Otavio O Souza 	}
336e8da3e8aSLuiz Otavio O Souza 	if (npins == 0 || pins == NULL) {
337e8da3e8aSLuiz Otavio O Souza 		if (npins == 0)
338e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer, "no pin specified in %s.\n",
339e8da3e8aSLuiz Otavio O Souza 			    pname);
340bc90a48cSOleksandr Tymoshenko 		OF_prop_free(gpios);
341e8da3e8aSLuiz Otavio O Souza 		return (npins);
342e8da3e8aSLuiz Otavio O Souza 	}
343e8da3e8aSLuiz Otavio O Souza 	*pins = malloc(sizeof(struct gpiobus_pin) * npins, M_DEVBUF,
344e8da3e8aSLuiz Otavio O Souza 	    M_NOWAIT | M_ZERO);
345e8da3e8aSLuiz Otavio O Souza 	if (*pins == NULL) {
346bc90a48cSOleksandr Tymoshenko 		OF_prop_free(gpios);
347e8da3e8aSLuiz Otavio O Souza 		return (-1);
348e8da3e8aSLuiz Otavio O Souza 	}
349e8da3e8aSLuiz Otavio O Souza 	/* Decode the gpio specifier on the second pass. */
350e8da3e8aSLuiz Otavio O Souza 	i = 0;
351e8da3e8aSLuiz Otavio O Souza 	j = 0;
352e8da3e8aSLuiz Otavio O Souza 	while (i < ncells) {
353e8da3e8aSLuiz Otavio O Souza 		/* Allow NULL specifiers. */
354e8da3e8aSLuiz Otavio O Souza 		if (gpios[i] == 0) {
355e8da3e8aSLuiz Otavio O Souza 			j++;
356e8da3e8aSLuiz Otavio O Souza 			i++;
357e8da3e8aSLuiz Otavio O Souza 			continue;
358e8da3e8aSLuiz Otavio O Souza 		}
359e8da3e8aSLuiz Otavio O Souza 		gpio = OF_node_from_xref(gpios[i]);
360e8da3e8aSLuiz Otavio O Souza 		/* Read gpio-cells property for this GPIO controller. */
361e8da3e8aSLuiz Otavio O Souza 		if (OF_getencprop(gpio, "#gpio-cells", &gpiocells,
362e8da3e8aSLuiz Otavio O Souza 		    sizeof(gpiocells)) < 0) {
363e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
364e8da3e8aSLuiz Otavio O Souza 			    "gpio does not have the #gpio-cells property.\n");
365e8da3e8aSLuiz Otavio O Souza 			goto fail;
366e8da3e8aSLuiz Otavio O Souza 		}
367e8da3e8aSLuiz Otavio O Souza 		/* Return the device reference for the GPIO controller. */
368e8da3e8aSLuiz Otavio O Souza 		(*pins)[j].dev = OF_device_from_xref(gpios[i]);
369e8da3e8aSLuiz Otavio O Souza 		if ((*pins)[j].dev == NULL) {
370e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
371e8da3e8aSLuiz Otavio O Souza 			    "no device registered for the gpio controller.\n");
372e8da3e8aSLuiz Otavio O Souza 			goto fail;
373e8da3e8aSLuiz Otavio O Souza 		}
374e8da3e8aSLuiz Otavio O Souza 		/*
375e8da3e8aSLuiz Otavio O Souza 		 * If the gpiobus softc is NULL we use the GPIO_GET_BUS() to
376e8da3e8aSLuiz Otavio O Souza 		 * retrieve it.  The GPIO_GET_BUS() method is only valid after
377e8da3e8aSLuiz Otavio O Souza 		 * the child is probed and attached.
378e8da3e8aSLuiz Otavio O Souza 		 */
379e8da3e8aSLuiz Otavio O Souza 		if (bussc == NULL) {
380e8da3e8aSLuiz Otavio O Souza 			if (GPIO_GET_BUS((*pins)[j].dev) == NULL) {
381e8da3e8aSLuiz Otavio O Souza 				device_printf(consumer,
382e8da3e8aSLuiz Otavio O Souza 				    "no gpiobus reference for %s.\n",
383e8da3e8aSLuiz Otavio O Souza 				    device_get_nameunit((*pins)[j].dev));
384e8da3e8aSLuiz Otavio O Souza 				goto fail;
385e8da3e8aSLuiz Otavio O Souza 			}
386e8da3e8aSLuiz Otavio O Souza 			bussc = device_get_softc(GPIO_GET_BUS((*pins)[j].dev));
387e8da3e8aSLuiz Otavio O Souza 		}
388e8da3e8aSLuiz Otavio O Souza 		/* Get the GPIO pin number and flags. */
389e8da3e8aSLuiz Otavio O Souza 		if (gpio_map_gpios((*pins)[j].dev, cnode, gpio, gpiocells,
390e8da3e8aSLuiz Otavio O Souza 		    &gpios[i + 1], &(*pins)[j].pin, &(*pins)[j].flags) != 0) {
391e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
392e8da3e8aSLuiz Otavio O Souza 			    "cannot map the gpios specifier.\n");
393e8da3e8aSLuiz Otavio O Souza 			goto fail;
394e8da3e8aSLuiz Otavio O Souza 		}
395da3b488dSLuiz Otavio O Souza 		/* Reserve the GPIO pin. */
396c45b8422SIan Lepore 		if (gpiobus_acquire_pin(bussc->sc_busdev, (*pins)[j].pin) != 0)
397e8da3e8aSLuiz Otavio O Souza 			goto fail;
398e8da3e8aSLuiz Otavio O Souza 		j++;
399e8da3e8aSLuiz Otavio O Souza 		i += gpiocells + 1;
400e8da3e8aSLuiz Otavio O Souza 	}
401bc90a48cSOleksandr Tymoshenko 	OF_prop_free(gpios);
402e8da3e8aSLuiz Otavio O Souza 
403e8da3e8aSLuiz Otavio O Souza 	return (npins);
404e8da3e8aSLuiz Otavio O Souza 
405e8da3e8aSLuiz Otavio O Souza fail:
406bc90a48cSOleksandr Tymoshenko 	OF_prop_free(gpios);
407e8da3e8aSLuiz Otavio O Souza 	free(*pins, M_DEVBUF);
408e8da3e8aSLuiz Otavio O Souza 	return (-1);
409e8da3e8aSLuiz Otavio O Souza }
410e8da3e8aSLuiz Otavio O Souza 
411e8da3e8aSLuiz Otavio O Souza static int
4126d866ed3SLuiz Otavio O Souza ofw_gpiobus_probe(device_t dev)
4136d866ed3SLuiz Otavio O Souza {
4146d866ed3SLuiz Otavio O Souza 
4156d866ed3SLuiz Otavio O Souza 	if (ofw_bus_get_node(dev) == -1)
4166d866ed3SLuiz Otavio O Souza 		return (ENXIO);
4176d866ed3SLuiz Otavio O Souza 	device_set_desc(dev, "OFW GPIO bus");
4186d866ed3SLuiz Otavio O Souza 
419846419f8SIan Lepore 	return (BUS_PROBE_DEFAULT);
4206d866ed3SLuiz Otavio O Souza }
4216d866ed3SLuiz Otavio O Souza 
4226d866ed3SLuiz Otavio O Souza static int
4236d866ed3SLuiz Otavio O Souza ofw_gpiobus_attach(device_t dev)
4246d866ed3SLuiz Otavio O Souza {
42588516632SLuiz Otavio O Souza 	int err;
4266d866ed3SLuiz Otavio O Souza 	phandle_t child;
4276d866ed3SLuiz Otavio O Souza 
42888516632SLuiz Otavio O Souza 	err = gpiobus_init_softc(dev);
42988516632SLuiz Otavio O Souza 	if (err != 0)
43088516632SLuiz Otavio O Souza 		return (err);
4316d866ed3SLuiz Otavio O Souza 	bus_generic_probe(dev);
4326d866ed3SLuiz Otavio O Souza 	bus_enumerate_hinted_children(dev);
4336d866ed3SLuiz Otavio O Souza 	/*
4346d866ed3SLuiz Otavio O Souza 	 * Attach the children represented in the device tree.
4356d866ed3SLuiz Otavio O Souza 	 */
4366d866ed3SLuiz Otavio O Souza 	for (child = OF_child(ofw_bus_get_node(dev)); child != 0;
437e8da3e8aSLuiz Otavio O Souza 	    child = OF_peer(child)) {
438846419f8SIan Lepore 		if (OF_hasprop(child, "gpio-hog"))
439846419f8SIan Lepore 			continue;
440e8da3e8aSLuiz Otavio O Souza 		if (!OF_hasprop(child, "gpios"))
4416d866ed3SLuiz Otavio O Souza 			continue;
442e8da3e8aSLuiz Otavio O Souza 		if (ofw_gpiobus_add_fdt_child(dev, NULL, child) == NULL)
443e8da3e8aSLuiz Otavio O Souza 			continue;
444e8da3e8aSLuiz Otavio O Souza 	}
4456d866ed3SLuiz Otavio O Souza 
4466d866ed3SLuiz Otavio O Souza 	return (bus_generic_attach(dev));
4476d866ed3SLuiz Otavio O Souza }
4486d866ed3SLuiz Otavio O Souza 
4496d866ed3SLuiz Otavio O Souza static device_t
4506d866ed3SLuiz Otavio O Souza ofw_gpiobus_add_child(device_t dev, u_int order, const char *name, int unit)
4516d866ed3SLuiz Otavio O Souza {
4526d866ed3SLuiz Otavio O Souza 	device_t child;
4536d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *devi;
4546d866ed3SLuiz Otavio O Souza 
4556d866ed3SLuiz Otavio O Souza 	child = device_add_child_ordered(dev, order, name, unit);
4566d866ed3SLuiz Otavio O Souza 	if (child == NULL)
4576d866ed3SLuiz Otavio O Souza 		return (child);
4586d866ed3SLuiz Otavio O Souza 	devi = malloc(sizeof(struct ofw_gpiobus_devinfo), M_DEVBUF,
4596d866ed3SLuiz Otavio O Souza 	    M_NOWAIT | M_ZERO);
4606d866ed3SLuiz Otavio O Souza 	if (devi == NULL) {
4616d866ed3SLuiz Otavio O Souza 		device_delete_child(dev, child);
4626d866ed3SLuiz Otavio O Souza 		return (0);
4636d866ed3SLuiz Otavio O Souza 	}
4646d866ed3SLuiz Otavio O Souza 
4656d866ed3SLuiz Otavio O Souza 	/*
4666d866ed3SLuiz Otavio O Souza 	 * NULL all the OFW-related parts of the ivars for non-OFW
4676d866ed3SLuiz Otavio O Souza 	 * children.
4686d866ed3SLuiz Otavio O Souza 	 */
4696d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_node = -1;
4706d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_name = NULL;
4716d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_compat = NULL;
4726d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_type = NULL;
4736d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_model = NULL;
4746d866ed3SLuiz Otavio O Souza 
4756d866ed3SLuiz Otavio O Souza 	device_set_ivars(child, devi);
4766d866ed3SLuiz Otavio O Souza 
4776d866ed3SLuiz Otavio O Souza 	return (child);
4786d866ed3SLuiz Otavio O Souza }
4796d866ed3SLuiz Otavio O Souza 
4806d866ed3SLuiz Otavio O Souza static const struct ofw_bus_devinfo *
4816d866ed3SLuiz Otavio O Souza ofw_gpiobus_get_devinfo(device_t bus, device_t dev)
4826d866ed3SLuiz Otavio O Souza {
4836d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
4846d866ed3SLuiz Otavio O Souza 
4856d866ed3SLuiz Otavio O Souza 	dinfo = device_get_ivars(dev);
4866d866ed3SLuiz Otavio O Souza 
4876d866ed3SLuiz Otavio O Souza 	return (&dinfo->opd_obdinfo);
4886d866ed3SLuiz Otavio O Souza }
4896d866ed3SLuiz Otavio O Souza 
4906d866ed3SLuiz Otavio O Souza static device_method_t ofw_gpiobus_methods[] = {
4916d866ed3SLuiz Otavio O Souza 	/* Device interface */
4926d866ed3SLuiz Otavio O Souza 	DEVMETHOD(device_probe,		ofw_gpiobus_probe),
4936d866ed3SLuiz Otavio O Souza 	DEVMETHOD(device_attach,	ofw_gpiobus_attach),
4946d866ed3SLuiz Otavio O Souza 
4956d866ed3SLuiz Otavio O Souza 	/* Bus interface */
496ddfc9c4cSWarner Losh 	DEVMETHOD(bus_child_pnpinfo,	ofw_bus_gen_child_pnpinfo),
4976d866ed3SLuiz Otavio O Souza 	DEVMETHOD(bus_add_child,	ofw_gpiobus_add_child),
4986d866ed3SLuiz Otavio O Souza 
4996d866ed3SLuiz Otavio O Souza 	/* ofw_bus interface */
5006d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_devinfo,	ofw_gpiobus_get_devinfo),
5016d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_compat,	ofw_bus_gen_get_compat),
5026d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_model,	ofw_bus_gen_get_model),
5036d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_name,	ofw_bus_gen_get_name),
5046d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_node,	ofw_bus_gen_get_node),
5056d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_type,	ofw_bus_gen_get_type),
5066d866ed3SLuiz Otavio O Souza 
5076d866ed3SLuiz Otavio O Souza 	DEVMETHOD_END
5086d866ed3SLuiz Otavio O Souza };
5096d866ed3SLuiz Otavio O Souza 
510e3da01a3SMichal Meloun devclass_t ofwgpiobus_devclass;
5116d866ed3SLuiz Otavio O Souza 
5126d866ed3SLuiz Otavio O Souza DEFINE_CLASS_1(gpiobus, ofw_gpiobus_driver, ofw_gpiobus_methods,
5136d866ed3SLuiz Otavio O Souza     sizeof(struct gpiobus_softc), gpiobus_driver);
51412bc2abbSMichal Meloun EARLY_DRIVER_MODULE(ofw_gpiobus, gpio, ofw_gpiobus_driver, ofwgpiobus_devclass,
51512bc2abbSMichal Meloun     0, 0, BUS_PASS_BUS);
5166d866ed3SLuiz Otavio O Souza MODULE_VERSION(ofw_gpiobus, 1);
5176d866ed3SLuiz Otavio O Souza MODULE_DEPEND(ofw_gpiobus, gpiobus, 1, 1, 1);
518