xref: /freebsd/sys/dev/gpio/ofw_gpiobus.c (revision fdafd315)
16d866ed3SLuiz Otavio O Souza /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
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/param.h>
32160ca719SLuiz Otavio O Souza #include <sys/systm.h>
33002381dfSLuiz Otavio O Souza #include <sys/bus.h>
34002381dfSLuiz Otavio O Souza #include <sys/kernel.h>
35002381dfSLuiz Otavio O Souza #include <sys/malloc.h>
36002381dfSLuiz Otavio O Souza #include <sys/module.h>
376d866ed3SLuiz Otavio O Souza 
386d866ed3SLuiz Otavio O Souza #include <dev/gpio/gpiobusvar.h>
396d866ed3SLuiz Otavio O Souza #include <dev/ofw/ofw_bus.h>
406d866ed3SLuiz Otavio O Souza 
41d752f0f6SLuiz Otavio O Souza #include "gpiobus_if.h"
42d752f0f6SLuiz Otavio O Souza 
436d866ed3SLuiz Otavio O Souza static struct ofw_gpiobus_devinfo *ofw_gpiobus_setup_devinfo(device_t,
44e8da3e8aSLuiz Otavio O Souza 	device_t, phandle_t);
456ad7f491SLuiz Otavio O Souza static void ofw_gpiobus_destroy_devinfo(device_t, struct ofw_gpiobus_devinfo *);
46e8da3e8aSLuiz Otavio O Souza static int ofw_gpiobus_parse_gpios_impl(device_t, phandle_t, char *,
47e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_softc *, struct gpiobus_pin **);
486d866ed3SLuiz Otavio O Souza 
498a4ba038SMichal Meloun /*
508a4ba038SMichal Meloun  * Utility functions for easier handling of OFW GPIO pins.
518a4ba038SMichal Meloun  *
528a4ba038SMichal Meloun  * !!! BEWARE !!!
538a4ba038SMichal Meloun  * GPIOBUS uses children's IVARs, so we cannot use this interface for cross
548a4ba038SMichal Meloun  * tree consumers.
558a4ba038SMichal Meloun  *
568a4ba038SMichal Meloun  */
57dc027dc6SIan Lepore int
gpio_pin_get_by_ofw_propidx(device_t consumer,phandle_t cnode,char * prop_name,int idx,gpio_pin_t * out_pin)58dc027dc6SIan Lepore gpio_pin_get_by_ofw_propidx(device_t consumer, phandle_t cnode,
5951702162SOleksandr Tymoshenko     char *prop_name, int idx, gpio_pin_t *out_pin)
608a4ba038SMichal Meloun {
6151702162SOleksandr Tymoshenko 	phandle_t xref;
628a4ba038SMichal Meloun 	pcell_t *cells;
638a4ba038SMichal Meloun 	device_t busdev;
648a4ba038SMichal Meloun 	struct gpiobus_pin pin;
658a4ba038SMichal Meloun 	int ncells, rv;
668a4ba038SMichal Meloun 
6751702162SOleksandr Tymoshenko 	KASSERT(consumer != NULL && cnode > 0,
6851702162SOleksandr Tymoshenko 	    ("both consumer and cnode required"));
698a4ba038SMichal Meloun 
708a4ba038SMichal Meloun 	rv = ofw_bus_parse_xref_list_alloc(cnode, prop_name, "#gpio-cells",
718a4ba038SMichal Meloun 	    idx, &xref, &ncells, &cells);
728a4ba038SMichal Meloun 	if (rv != 0)
738a4ba038SMichal Meloun 		return (rv);
748a4ba038SMichal Meloun 
758a4ba038SMichal Meloun 	/* Translate provider to device. */
768a4ba038SMichal Meloun 	pin.dev = OF_device_from_xref(xref);
778a4ba038SMichal Meloun 	if (pin.dev == NULL) {
78bc90a48cSOleksandr Tymoshenko 		OF_prop_free(cells);
798a4ba038SMichal Meloun 		return (ENODEV);
808a4ba038SMichal Meloun 	}
818a4ba038SMichal Meloun 
828a4ba038SMichal Meloun 	/* Test if GPIO bus already exist. */
838a4ba038SMichal Meloun 	busdev = GPIO_GET_BUS(pin.dev);
848a4ba038SMichal Meloun 	if (busdev == NULL) {
85bc90a48cSOleksandr Tymoshenko 		OF_prop_free(cells);
868a4ba038SMichal Meloun 		return (ENODEV);
878a4ba038SMichal Meloun 	}
888a4ba038SMichal Meloun 
898a4ba038SMichal Meloun 	/* Map GPIO pin. */
908a4ba038SMichal Meloun 	rv = gpio_map_gpios(pin.dev, cnode, OF_node_from_xref(xref), ncells,
918a4ba038SMichal Meloun 	    cells, &pin.pin, &pin.flags);
92bc90a48cSOleksandr Tymoshenko 	OF_prop_free(cells);
9351702162SOleksandr Tymoshenko 	if (rv != 0)
948a4ba038SMichal Meloun 		return (ENXIO);
958a4ba038SMichal Meloun 
968a4ba038SMichal Meloun 	/* Reserve GPIO pin. */
97c45b8422SIan Lepore 	rv = gpiobus_acquire_pin(busdev, pin.pin);
9851702162SOleksandr Tymoshenko 	if (rv != 0)
998a4ba038SMichal Meloun 		return (EBUSY);
1008a4ba038SMichal Meloun 
1018a4ba038SMichal Meloun 	*out_pin = malloc(sizeof(struct gpiobus_pin), M_DEVBUF,
1028a4ba038SMichal Meloun 	    M_WAITOK | M_ZERO);
1038a4ba038SMichal Meloun 	**out_pin = pin;
1048a4ba038SMichal Meloun 	return (0);
1058a4ba038SMichal Meloun }
1068a4ba038SMichal Meloun 
1078a4ba038SMichal Meloun int
gpio_pin_get_by_ofw_idx(device_t consumer,phandle_t node,int idx,gpio_pin_t * pin)10851702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_idx(device_t consumer, phandle_t node,
10951702162SOleksandr Tymoshenko     int idx, gpio_pin_t *pin)
1108a4ba038SMichal Meloun {
1118a4ba038SMichal Meloun 
112dc027dc6SIan Lepore 	return (gpio_pin_get_by_ofw_propidx(consumer, node, "gpios", idx, pin));
1138a4ba038SMichal Meloun }
1148a4ba038SMichal Meloun 
1158a4ba038SMichal Meloun int
gpio_pin_get_by_ofw_property(device_t consumer,phandle_t node,char * name,gpio_pin_t * pin)11651702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_property(device_t consumer, phandle_t node,
11751702162SOleksandr Tymoshenko     char *name, gpio_pin_t *pin)
1188a4ba038SMichal Meloun {
1198a4ba038SMichal Meloun 
120dc027dc6SIan Lepore 	return (gpio_pin_get_by_ofw_propidx(consumer, node, name, 0, pin));
1218a4ba038SMichal Meloun }
1228a4ba038SMichal Meloun 
1238a4ba038SMichal Meloun int
gpio_pin_get_by_ofw_name(device_t consumer,phandle_t node,char * name,gpio_pin_t * pin)12451702162SOleksandr Tymoshenko gpio_pin_get_by_ofw_name(device_t consumer, phandle_t node,
12551702162SOleksandr Tymoshenko     char *name, gpio_pin_t *pin)
1268a4ba038SMichal Meloun {
1278a4ba038SMichal Meloun 	int rv, idx;
1288a4ba038SMichal Meloun 
12951702162SOleksandr Tymoshenko 	KASSERT(consumer != NULL && node > 0,
13051702162SOleksandr Tymoshenko 	    ("both consumer and node required"));
13151702162SOleksandr Tymoshenko 
13251702162SOleksandr Tymoshenko 	rv = ofw_bus_find_string_index(node, "gpio-names", name, &idx);
1338a4ba038SMichal Meloun 	if (rv != 0)
1348a4ba038SMichal Meloun 		return (rv);
13551702162SOleksandr Tymoshenko 	return (gpio_pin_get_by_ofw_idx(consumer, node, idx, pin));
1368a4ba038SMichal Meloun }
1378a4ba038SMichal Meloun 
1388a4ba038SMichal Meloun /*
1398a4ba038SMichal Meloun  * OFW_GPIOBUS driver.
1408a4ba038SMichal Meloun  */
1416d866ed3SLuiz Otavio O Souza device_t
ofw_gpiobus_add_fdt_child(device_t bus,const char * drvname,phandle_t child)142e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_add_fdt_child(device_t bus, const char *drvname, phandle_t child)
1436d866ed3SLuiz Otavio O Souza {
1446d866ed3SLuiz Otavio O Souza 	device_t childdev;
145d752f0f6SLuiz Otavio O Souza 	int i;
146d752f0f6SLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
147e8da3e8aSLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
1486d866ed3SLuiz Otavio O Souza 
1496d866ed3SLuiz Otavio O Souza 	/*
1503dede9f3SWarner Losh 	 * Check to see if we already have a child for @p child, and if so
1513dede9f3SWarner Losh 	 * return it.
1523dede9f3SWarner Losh 	 */
1533dede9f3SWarner Losh 	childdev = ofw_bus_find_child_device_by_phandle(bus, child);
1543dede9f3SWarner Losh 	if (childdev != NULL)
1553dede9f3SWarner Losh 		return (childdev);
1563dede9f3SWarner Losh 
1573dede9f3SWarner Losh 	/*
1586d866ed3SLuiz Otavio O Souza 	 * Set up the GPIO child and OFW bus layer devinfo and add it to bus.
1596d866ed3SLuiz Otavio O Souza 	 */
160e8da3e8aSLuiz Otavio O Souza 	childdev = device_add_child(bus, drvname, -1);
161e8da3e8aSLuiz Otavio O Souza 	if (childdev == NULL)
1626d866ed3SLuiz Otavio O Souza 		return (NULL);
163e8da3e8aSLuiz Otavio O Souza 	dinfo = ofw_gpiobus_setup_devinfo(bus, childdev, child);
164e8da3e8aSLuiz Otavio O Souza 	if (dinfo == NULL) {
165e8da3e8aSLuiz Otavio O Souza 		device_delete_child(bus, childdev);
1666d866ed3SLuiz Otavio O Souza 		return (NULL);
1676d866ed3SLuiz Otavio O Souza 	}
168e8da3e8aSLuiz Otavio O Souza 	if (device_probe_and_attach(childdev) != 0) {
1696ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
170e8da3e8aSLuiz Otavio O Souza 		device_delete_child(bus, childdev);
171e8da3e8aSLuiz Otavio O Souza 		return (NULL);
172e8da3e8aSLuiz Otavio O Souza 	}
173d752f0f6SLuiz Otavio O Souza 	/* Use the child name as pin name. */
174d752f0f6SLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
175d752f0f6SLuiz Otavio O Souza 	for (i = 0; i < devi->npins; i++)
176d752f0f6SLuiz Otavio O Souza 		GPIOBUS_PIN_SETNAME(bus, devi->pins[i],
177d752f0f6SLuiz Otavio O Souza 		    device_get_nameunit(childdev));
1786d866ed3SLuiz Otavio O Souza 
1796d866ed3SLuiz Otavio O Souza 	return (childdev);
1806d866ed3SLuiz Otavio O Souza }
1816d866ed3SLuiz Otavio O Souza 
182e8da3e8aSLuiz Otavio O Souza int
ofw_gpiobus_parse_gpios(device_t consumer,char * pname,struct gpiobus_pin ** pins)183e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_parse_gpios(device_t consumer, char *pname,
184e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_pin **pins)
1856d866ed3SLuiz Otavio O Souza {
1866d866ed3SLuiz Otavio O Souza 
187e8da3e8aSLuiz Otavio O Souza 	return (ofw_gpiobus_parse_gpios_impl(consumer,
188e8da3e8aSLuiz Otavio O Souza 	    ofw_bus_get_node(consumer), pname, NULL, pins));
1896d866ed3SLuiz Otavio O Souza }
1906d866ed3SLuiz Otavio O Souza 
191dbdb1205SLuiz Otavio O Souza void
ofw_gpiobus_register_provider(device_t provider)192dbdb1205SLuiz Otavio O Souza ofw_gpiobus_register_provider(device_t provider)
193dbdb1205SLuiz Otavio O Souza {
194dbdb1205SLuiz Otavio O Souza 	phandle_t node;
195dbdb1205SLuiz Otavio O Souza 
196dbdb1205SLuiz Otavio O Souza 	node = ofw_bus_get_node(provider);
197f5e4e915SAndrew Turner 	if (node != -1)
198dbdb1205SLuiz Otavio O Souza 		OF_device_register_xref(OF_xref_from_node(node), provider);
199dbdb1205SLuiz Otavio O Souza }
200dbdb1205SLuiz Otavio O Souza 
201dbdb1205SLuiz Otavio O Souza void
ofw_gpiobus_unregister_provider(device_t provider)202dbdb1205SLuiz Otavio O Souza ofw_gpiobus_unregister_provider(device_t provider)
203dbdb1205SLuiz Otavio O Souza {
204dbdb1205SLuiz Otavio O Souza 	phandle_t node;
205dbdb1205SLuiz Otavio O Souza 
206dbdb1205SLuiz Otavio O Souza 	node = ofw_bus_get_node(provider);
207f5e4e915SAndrew Turner 	if (node != -1)
208dbdb1205SLuiz Otavio O Souza 		OF_device_register_xref(OF_xref_from_node(node), NULL);
209dbdb1205SLuiz Otavio O Souza }
210dbdb1205SLuiz Otavio O Souza 
2116d866ed3SLuiz Otavio O Souza static struct ofw_gpiobus_devinfo *
ofw_gpiobus_setup_devinfo(device_t bus,device_t child,phandle_t node)212e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_setup_devinfo(device_t bus, device_t child, phandle_t node)
2136d866ed3SLuiz Otavio O Souza {
214e8da3e8aSLuiz Otavio O Souza 	int i, npins;
215e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
216e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_pin *pins;
2176d866ed3SLuiz Otavio O Souza 	struct gpiobus_softc *sc;
2186d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
2196d866ed3SLuiz Otavio O Souza 
220e8da3e8aSLuiz Otavio O Souza 	sc = device_get_softc(bus);
2216d866ed3SLuiz Otavio O Souza 	dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_NOWAIT | M_ZERO);
2226d866ed3SLuiz Otavio O Souza 	if (dinfo == NULL)
2236d866ed3SLuiz Otavio O Souza 		return (NULL);
2246d866ed3SLuiz Otavio O Souza 	if (ofw_bus_gen_setup_devinfo(&dinfo->opd_obdinfo, node) != 0) {
2256d866ed3SLuiz Otavio O Souza 		free(dinfo, M_DEVBUF);
2266d866ed3SLuiz Otavio O Souza 		return (NULL);
2276d866ed3SLuiz Otavio O Souza 	}
2286d866ed3SLuiz Otavio O Souza 	/* Parse the gpios property for the child. */
229e8da3e8aSLuiz Otavio O Souza 	npins = ofw_gpiobus_parse_gpios_impl(child, node, "gpios", sc, &pins);
2306ad7f491SLuiz Otavio O Souza 	if (npins <= 0) {
2316ad7f491SLuiz Otavio O Souza 		ofw_bus_gen_destroy_devinfo(&dinfo->opd_obdinfo);
2326ad7f491SLuiz Otavio O Souza 		free(dinfo, M_DEVBUF);
2336ad7f491SLuiz Otavio O Souza 		return (NULL);
2346ad7f491SLuiz Otavio O Souza 	}
2356ad7f491SLuiz Otavio O Souza 	/* Initialize the irq resource list. */
2366ad7f491SLuiz Otavio O Souza 	resource_list_init(&dinfo->opd_dinfo.rl);
2376ad7f491SLuiz Otavio O Souza 	/* Allocate the child ivars and copy the parsed pin data. */
238e8da3e8aSLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
239e8da3e8aSLuiz Otavio O Souza 	devi->npins = (uint32_t)npins;
240e8da3e8aSLuiz Otavio O Souza 	if (gpiobus_alloc_ivars(devi) != 0) {
241e8da3e8aSLuiz Otavio O Souza 		free(pins, M_DEVBUF);
2426ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
2436ad7f491SLuiz Otavio O Souza 		return (NULL);
2446d866ed3SLuiz Otavio O Souza 	}
245404e6469SLi-Wen Hsu 	for (i = 0; i < devi->npins; i++)
246e8da3e8aSLuiz Otavio O Souza 		devi->pins[i] = pins[i].pin;
247e8da3e8aSLuiz Otavio O Souza 	free(pins, M_DEVBUF);
2486ad7f491SLuiz Otavio O Souza 	/* Parse the interrupt resources. */
249a8c5ea04SRuslan Bukin 	if (ofw_bus_intr_to_rl(bus, node, &dinfo->opd_dinfo.rl, NULL) != 0) {
2506ad7f491SLuiz Otavio O Souza 		ofw_gpiobus_destroy_devinfo(bus, dinfo);
2516ad7f491SLuiz Otavio O Souza 		return (NULL);
252e8da3e8aSLuiz Otavio O Souza 	}
253e8da3e8aSLuiz Otavio O Souza 	device_set_ivars(child, dinfo);
254e8da3e8aSLuiz Otavio O Souza 
255e8da3e8aSLuiz Otavio O Souza 	return (dinfo);
256138bf909SLuiz Otavio O Souza }
2576d866ed3SLuiz Otavio O Souza 
2586d866ed3SLuiz Otavio O Souza static void
ofw_gpiobus_destroy_devinfo(device_t bus,struct ofw_gpiobus_devinfo * dinfo)2596ad7f491SLuiz Otavio O Souza ofw_gpiobus_destroy_devinfo(device_t bus, struct ofw_gpiobus_devinfo *dinfo)
2606d866ed3SLuiz Otavio O Souza {
2616ad7f491SLuiz Otavio O Souza 	int i;
262e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_ivar *devi;
2636ad7f491SLuiz Otavio O Souza 	struct gpiobus_softc *sc;
2646d866ed3SLuiz Otavio O Souza 
2656ad7f491SLuiz Otavio O Souza 	sc = device_get_softc(bus);
266e8da3e8aSLuiz Otavio O Souza 	devi = &dinfo->opd_dinfo;
2676ad7f491SLuiz Otavio O Souza 	for (i = 0; i < devi->npins; i++) {
2686ad7f491SLuiz Otavio O Souza 		if (devi->pins[i] > sc->sc_npins)
2696ad7f491SLuiz Otavio O Souza 			continue;
270d752f0f6SLuiz Otavio O Souza 		sc->sc_pins[devi->pins[i]].mapped = 0;
2716ad7f491SLuiz Otavio O Souza 	}
272e8da3e8aSLuiz Otavio O Souza 	gpiobus_free_ivars(devi);
273138bf909SLuiz Otavio O Souza 	resource_list_free(&dinfo->opd_dinfo.rl);
2746d866ed3SLuiz Otavio O Souza 	ofw_bus_gen_destroy_devinfo(&dinfo->opd_obdinfo);
2756d866ed3SLuiz Otavio O Souza 	free(dinfo, M_DEVBUF);
2766d866ed3SLuiz Otavio O Souza }
2776d866ed3SLuiz Otavio O Souza 
2786d866ed3SLuiz Otavio O Souza static int
ofw_gpiobus_parse_gpios_impl(device_t consumer,phandle_t cnode,char * pname,struct gpiobus_softc * bussc,struct gpiobus_pin ** pins)279e8da3e8aSLuiz Otavio O Souza ofw_gpiobus_parse_gpios_impl(device_t consumer, phandle_t cnode, char *pname,
280e8da3e8aSLuiz Otavio O Souza 	struct gpiobus_softc *bussc, struct gpiobus_pin **pins)
281e8da3e8aSLuiz Otavio O Souza {
282e8da3e8aSLuiz Otavio O Souza 	int gpiocells, i, j, ncells, npins;
283e8da3e8aSLuiz Otavio O Souza 	pcell_t *gpios;
284e8da3e8aSLuiz Otavio O Souza 	phandle_t gpio;
285e8da3e8aSLuiz Otavio O Souza 
286f7604b1bSOleksandr Tymoshenko 	ncells = OF_getencprop_alloc_multi(cnode, pname, sizeof(*gpios),
287e8da3e8aSLuiz Otavio O Souza             (void **)&gpios);
288e8da3e8aSLuiz Otavio O Souza 	if (ncells == -1) {
289e8da3e8aSLuiz Otavio O Souza 		device_printf(consumer,
290e8da3e8aSLuiz Otavio O Souza 		    "Warning: No %s specified in fdt data; "
291e8da3e8aSLuiz Otavio O Souza 		    "device may not function.\n", pname);
292e8da3e8aSLuiz Otavio O Souza 		return (-1);
293e8da3e8aSLuiz Otavio O Souza 	}
294e8da3e8aSLuiz Otavio O Souza 	/*
295e8da3e8aSLuiz Otavio O Souza 	 * The gpio-specifier is controller independent, the first pcell has
296e8da3e8aSLuiz Otavio O Souza 	 * the reference to the GPIO controller phandler.
297e8da3e8aSLuiz Otavio O Souza 	 * Count the number of encoded gpio-specifiers on the first pass.
298e8da3e8aSLuiz Otavio O Souza 	 */
299e8da3e8aSLuiz Otavio O Souza 	i = 0;
300e8da3e8aSLuiz Otavio O Souza 	npins = 0;
301e8da3e8aSLuiz Otavio O Souza 	while (i < ncells) {
302e8da3e8aSLuiz Otavio O Souza 		/* Allow NULL specifiers. */
303e8da3e8aSLuiz Otavio O Souza 		if (gpios[i] == 0) {
304e8da3e8aSLuiz Otavio O Souza 			npins++;
305e8da3e8aSLuiz Otavio O Souza 			i++;
306e8da3e8aSLuiz Otavio O Souza 			continue;
307e8da3e8aSLuiz Otavio O Souza 		}
308e8da3e8aSLuiz Otavio O Souza 		gpio = OF_node_from_xref(gpios[i]);
309e8da3e8aSLuiz Otavio O Souza 		/* If we have bussc, ignore devices from other gpios. */
310e8da3e8aSLuiz Otavio O Souza 		if (bussc != NULL)
311e8da3e8aSLuiz Otavio O Souza 			if (ofw_bus_get_node(bussc->sc_dev) != gpio)
312e8da3e8aSLuiz Otavio O Souza 				return (0);
313e8da3e8aSLuiz Otavio O Souza 		/*
314e8da3e8aSLuiz Otavio O Souza 		 * Check for gpio-controller property and read the #gpio-cells
315e8da3e8aSLuiz Otavio O Souza 		 * for this GPIO controller.
316e8da3e8aSLuiz Otavio O Souza 		 */
317e8da3e8aSLuiz Otavio O Souza 		if (!OF_hasprop(gpio, "gpio-controller") ||
318e8da3e8aSLuiz Otavio O Souza 		    OF_getencprop(gpio, "#gpio-cells", &gpiocells,
319e8da3e8aSLuiz Otavio O Souza 		    sizeof(gpiocells)) < 0) {
320e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
321e8da3e8aSLuiz Otavio O Souza 			    "gpio reference is not a gpio-controller.\n");
322bc90a48cSOleksandr Tymoshenko 			OF_prop_free(gpios);
323e8da3e8aSLuiz Otavio O Souza 			return (-1);
324e8da3e8aSLuiz Otavio O Souza 		}
325e8da3e8aSLuiz Otavio O Souza 		if (ncells - i < gpiocells + 1) {
326e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
327e8da3e8aSLuiz Otavio O Souza 			    "%s cells doesn't match #gpio-cells.\n", pname);
328e8da3e8aSLuiz Otavio O Souza 			return (-1);
329e8da3e8aSLuiz Otavio O Souza 		}
330e8da3e8aSLuiz Otavio O Souza 		npins++;
331e8da3e8aSLuiz Otavio O Souza 		i += gpiocells + 1;
332e8da3e8aSLuiz Otavio O Souza 	}
333e8da3e8aSLuiz Otavio O Souza 	if (npins == 0 || pins == NULL) {
334e8da3e8aSLuiz Otavio O Souza 		if (npins == 0)
335e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer, "no pin specified in %s.\n",
336e8da3e8aSLuiz Otavio O Souza 			    pname);
337bc90a48cSOleksandr Tymoshenko 		OF_prop_free(gpios);
338e8da3e8aSLuiz Otavio O Souza 		return (npins);
339e8da3e8aSLuiz Otavio O Souza 	}
340e8da3e8aSLuiz Otavio O Souza 	*pins = malloc(sizeof(struct gpiobus_pin) * npins, M_DEVBUF,
341e8da3e8aSLuiz Otavio O Souza 	    M_NOWAIT | M_ZERO);
342e8da3e8aSLuiz Otavio O Souza 	if (*pins == NULL) {
343bc90a48cSOleksandr Tymoshenko 		OF_prop_free(gpios);
344e8da3e8aSLuiz Otavio O Souza 		return (-1);
345e8da3e8aSLuiz Otavio O Souza 	}
346e8da3e8aSLuiz Otavio O Souza 	/* Decode the gpio specifier on the second pass. */
347e8da3e8aSLuiz Otavio O Souza 	i = 0;
348e8da3e8aSLuiz Otavio O Souza 	j = 0;
349e8da3e8aSLuiz Otavio O Souza 	while (i < ncells) {
350e8da3e8aSLuiz Otavio O Souza 		/* Allow NULL specifiers. */
351e8da3e8aSLuiz Otavio O Souza 		if (gpios[i] == 0) {
352e8da3e8aSLuiz Otavio O Souza 			j++;
353e8da3e8aSLuiz Otavio O Souza 			i++;
354e8da3e8aSLuiz Otavio O Souza 			continue;
355e8da3e8aSLuiz Otavio O Souza 		}
356e8da3e8aSLuiz Otavio O Souza 		gpio = OF_node_from_xref(gpios[i]);
357e8da3e8aSLuiz Otavio O Souza 		/* Read gpio-cells property for this GPIO controller. */
358e8da3e8aSLuiz Otavio O Souza 		if (OF_getencprop(gpio, "#gpio-cells", &gpiocells,
359e8da3e8aSLuiz Otavio O Souza 		    sizeof(gpiocells)) < 0) {
360e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
361e8da3e8aSLuiz Otavio O Souza 			    "gpio does not have the #gpio-cells property.\n");
362e8da3e8aSLuiz Otavio O Souza 			goto fail;
363e8da3e8aSLuiz Otavio O Souza 		}
364e8da3e8aSLuiz Otavio O Souza 		/* Return the device reference for the GPIO controller. */
365e8da3e8aSLuiz Otavio O Souza 		(*pins)[j].dev = OF_device_from_xref(gpios[i]);
366e8da3e8aSLuiz Otavio O Souza 		if ((*pins)[j].dev == NULL) {
367e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
368e8da3e8aSLuiz Otavio O Souza 			    "no device registered for the gpio controller.\n");
369e8da3e8aSLuiz Otavio O Souza 			goto fail;
370e8da3e8aSLuiz Otavio O Souza 		}
371e8da3e8aSLuiz Otavio O Souza 		/*
372e8da3e8aSLuiz Otavio O Souza 		 * If the gpiobus softc is NULL we use the GPIO_GET_BUS() to
373e8da3e8aSLuiz Otavio O Souza 		 * retrieve it.  The GPIO_GET_BUS() method is only valid after
374e8da3e8aSLuiz Otavio O Souza 		 * the child is probed and attached.
375e8da3e8aSLuiz Otavio O Souza 		 */
376e8da3e8aSLuiz Otavio O Souza 		if (bussc == NULL) {
377e8da3e8aSLuiz Otavio O Souza 			if (GPIO_GET_BUS((*pins)[j].dev) == NULL) {
378e8da3e8aSLuiz Otavio O Souza 				device_printf(consumer,
379e8da3e8aSLuiz Otavio O Souza 				    "no gpiobus reference for %s.\n",
380e8da3e8aSLuiz Otavio O Souza 				    device_get_nameunit((*pins)[j].dev));
381e8da3e8aSLuiz Otavio O Souza 				goto fail;
382e8da3e8aSLuiz Otavio O Souza 			}
383e8da3e8aSLuiz Otavio O Souza 			bussc = device_get_softc(GPIO_GET_BUS((*pins)[j].dev));
384e8da3e8aSLuiz Otavio O Souza 		}
385e8da3e8aSLuiz Otavio O Souza 		/* Get the GPIO pin number and flags. */
386e8da3e8aSLuiz Otavio O Souza 		if (gpio_map_gpios((*pins)[j].dev, cnode, gpio, gpiocells,
387e8da3e8aSLuiz Otavio O Souza 		    &gpios[i + 1], &(*pins)[j].pin, &(*pins)[j].flags) != 0) {
388e8da3e8aSLuiz Otavio O Souza 			device_printf(consumer,
389e8da3e8aSLuiz Otavio O Souza 			    "cannot map the gpios specifier.\n");
390e8da3e8aSLuiz Otavio O Souza 			goto fail;
391e8da3e8aSLuiz Otavio O Souza 		}
392da3b488dSLuiz Otavio O Souza 		/* Reserve the GPIO pin. */
393c45b8422SIan Lepore 		if (gpiobus_acquire_pin(bussc->sc_busdev, (*pins)[j].pin) != 0)
394e8da3e8aSLuiz Otavio O Souza 			goto fail;
395e8da3e8aSLuiz Otavio O Souza 		j++;
396e8da3e8aSLuiz Otavio O Souza 		i += gpiocells + 1;
397e8da3e8aSLuiz Otavio O Souza 	}
398bc90a48cSOleksandr Tymoshenko 	OF_prop_free(gpios);
399e8da3e8aSLuiz Otavio O Souza 
400e8da3e8aSLuiz Otavio O Souza 	return (npins);
401e8da3e8aSLuiz Otavio O Souza 
402e8da3e8aSLuiz Otavio O Souza fail:
403bc90a48cSOleksandr Tymoshenko 	OF_prop_free(gpios);
404e8da3e8aSLuiz Otavio O Souza 	free(*pins, M_DEVBUF);
405e8da3e8aSLuiz Otavio O Souza 	return (-1);
406e8da3e8aSLuiz Otavio O Souza }
407e8da3e8aSLuiz Otavio O Souza 
408e8da3e8aSLuiz Otavio O Souza static int
ofw_gpiobus_probe(device_t dev)4096d866ed3SLuiz Otavio O Souza ofw_gpiobus_probe(device_t dev)
4106d866ed3SLuiz Otavio O Souza {
4116d866ed3SLuiz Otavio O Souza 
4126d866ed3SLuiz Otavio O Souza 	if (ofw_bus_get_node(dev) == -1)
4136d866ed3SLuiz Otavio O Souza 		return (ENXIO);
4146d866ed3SLuiz Otavio O Souza 	device_set_desc(dev, "OFW GPIO bus");
4156d866ed3SLuiz Otavio O Souza 
416846419f8SIan Lepore 	return (BUS_PROBE_DEFAULT);
4176d866ed3SLuiz Otavio O Souza }
4186d866ed3SLuiz Otavio O Souza 
4196d866ed3SLuiz Otavio O Souza static int
ofw_gpiobus_attach(device_t dev)4206d866ed3SLuiz Otavio O Souza ofw_gpiobus_attach(device_t dev)
4216d866ed3SLuiz Otavio O Souza {
42288516632SLuiz Otavio O Souza 	int err;
4236d866ed3SLuiz Otavio O Souza 	phandle_t child;
4246d866ed3SLuiz Otavio O Souza 
42588516632SLuiz Otavio O Souza 	err = gpiobus_init_softc(dev);
42688516632SLuiz Otavio O Souza 	if (err != 0)
42788516632SLuiz Otavio O Souza 		return (err);
4286d866ed3SLuiz Otavio O Souza 	bus_generic_probe(dev);
4296d866ed3SLuiz Otavio O Souza 	bus_enumerate_hinted_children(dev);
4306d866ed3SLuiz Otavio O Souza 	/*
4316d866ed3SLuiz Otavio O Souza 	 * Attach the children represented in the device tree.
4326d866ed3SLuiz Otavio O Souza 	 */
4336d866ed3SLuiz Otavio O Souza 	for (child = OF_child(ofw_bus_get_node(dev)); child != 0;
434e8da3e8aSLuiz Otavio O Souza 	    child = OF_peer(child)) {
435846419f8SIan Lepore 		if (OF_hasprop(child, "gpio-hog"))
436846419f8SIan Lepore 			continue;
437e8da3e8aSLuiz Otavio O Souza 		if (!OF_hasprop(child, "gpios"))
4386d866ed3SLuiz Otavio O Souza 			continue;
439e8da3e8aSLuiz Otavio O Souza 		if (ofw_gpiobus_add_fdt_child(dev, NULL, child) == NULL)
440e8da3e8aSLuiz Otavio O Souza 			continue;
441e8da3e8aSLuiz Otavio O Souza 	}
4426d866ed3SLuiz Otavio O Souza 
4436d866ed3SLuiz Otavio O Souza 	return (bus_generic_attach(dev));
4446d866ed3SLuiz Otavio O Souza }
4456d866ed3SLuiz Otavio O Souza 
4466d866ed3SLuiz Otavio O Souza static device_t
ofw_gpiobus_add_child(device_t dev,u_int order,const char * name,int unit)4476d866ed3SLuiz Otavio O Souza ofw_gpiobus_add_child(device_t dev, u_int order, const char *name, int unit)
4486d866ed3SLuiz Otavio O Souza {
4496d866ed3SLuiz Otavio O Souza 	device_t child;
4506d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *devi;
4516d866ed3SLuiz Otavio O Souza 
4526d866ed3SLuiz Otavio O Souza 	child = device_add_child_ordered(dev, order, name, unit);
4536d866ed3SLuiz Otavio O Souza 	if (child == NULL)
4546d866ed3SLuiz Otavio O Souza 		return (child);
4556d866ed3SLuiz Otavio O Souza 	devi = malloc(sizeof(struct ofw_gpiobus_devinfo), M_DEVBUF,
4566d866ed3SLuiz Otavio O Souza 	    M_NOWAIT | M_ZERO);
4576d866ed3SLuiz Otavio O Souza 	if (devi == NULL) {
4586d866ed3SLuiz Otavio O Souza 		device_delete_child(dev, child);
4596d866ed3SLuiz Otavio O Souza 		return (0);
4606d866ed3SLuiz Otavio O Souza 	}
4616d866ed3SLuiz Otavio O Souza 
4626d866ed3SLuiz Otavio O Souza 	/*
4636d866ed3SLuiz Otavio O Souza 	 * NULL all the OFW-related parts of the ivars for non-OFW
4646d866ed3SLuiz Otavio O Souza 	 * children.
4656d866ed3SLuiz Otavio O Souza 	 */
4666d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_node = -1;
4676d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_name = NULL;
4686d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_compat = NULL;
4696d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_type = NULL;
4706d866ed3SLuiz Otavio O Souza 	devi->opd_obdinfo.obd_model = NULL;
4716d866ed3SLuiz Otavio O Souza 
4726d866ed3SLuiz Otavio O Souza 	device_set_ivars(child, devi);
4736d866ed3SLuiz Otavio O Souza 
4746d866ed3SLuiz Otavio O Souza 	return (child);
4756d866ed3SLuiz Otavio O Souza }
4766d866ed3SLuiz Otavio O Souza 
4776d866ed3SLuiz Otavio O Souza static const struct ofw_bus_devinfo *
ofw_gpiobus_get_devinfo(device_t bus,device_t dev)4786d866ed3SLuiz Otavio O Souza ofw_gpiobus_get_devinfo(device_t bus, device_t dev)
4796d866ed3SLuiz Otavio O Souza {
4806d866ed3SLuiz Otavio O Souza 	struct ofw_gpiobus_devinfo *dinfo;
4816d866ed3SLuiz Otavio O Souza 
4826d866ed3SLuiz Otavio O Souza 	dinfo = device_get_ivars(dev);
4836d866ed3SLuiz Otavio O Souza 
4846d866ed3SLuiz Otavio O Souza 	return (&dinfo->opd_obdinfo);
4856d866ed3SLuiz Otavio O Souza }
4866d866ed3SLuiz Otavio O Souza 
4876d866ed3SLuiz Otavio O Souza static device_method_t ofw_gpiobus_methods[] = {
4886d866ed3SLuiz Otavio O Souza 	/* Device interface */
4896d866ed3SLuiz Otavio O Souza 	DEVMETHOD(device_probe,		ofw_gpiobus_probe),
4906d866ed3SLuiz Otavio O Souza 	DEVMETHOD(device_attach,	ofw_gpiobus_attach),
4916d866ed3SLuiz Otavio O Souza 
4926d866ed3SLuiz Otavio O Souza 	/* Bus interface */
493ddfc9c4cSWarner Losh 	DEVMETHOD(bus_child_pnpinfo,	ofw_bus_gen_child_pnpinfo),
4946d866ed3SLuiz Otavio O Souza 	DEVMETHOD(bus_add_child,	ofw_gpiobus_add_child),
4956d866ed3SLuiz Otavio O Souza 
4966d866ed3SLuiz Otavio O Souza 	/* ofw_bus interface */
4976d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_devinfo,	ofw_gpiobus_get_devinfo),
4986d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_compat,	ofw_bus_gen_get_compat),
4996d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_model,	ofw_bus_gen_get_model),
5006d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_name,	ofw_bus_gen_get_name),
5016d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_node,	ofw_bus_gen_get_node),
5026d866ed3SLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_type,	ofw_bus_gen_get_type),
5036d866ed3SLuiz Otavio O Souza 
5046d866ed3SLuiz Otavio O Souza 	DEVMETHOD_END
5056d866ed3SLuiz Otavio O Souza };
5066d866ed3SLuiz Otavio O Souza 
5076d866ed3SLuiz Otavio O Souza DEFINE_CLASS_1(gpiobus, ofw_gpiobus_driver, ofw_gpiobus_methods,
5086d866ed3SLuiz Otavio O Souza     sizeof(struct gpiobus_softc), gpiobus_driver);
509e8590c97SJohn Baldwin EARLY_DRIVER_MODULE(ofw_gpiobus, gpio, ofw_gpiobus_driver, 0, 0, BUS_PASS_BUS);
5106d866ed3SLuiz Otavio O Souza MODULE_VERSION(ofw_gpiobus, 1);
5116d866ed3SLuiz Otavio O Souza MODULE_DEPEND(ofw_gpiobus, gpiobus, 1, 1, 1);
512