1/*-
2 * Copyright (c) 2009-2010 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Semihalf under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/ktr.h>
36#include <sys/kernel.h>
37#include <sys/module.h>
38#include <sys/bus.h>
39#include <sys/rman.h>
40#include <sys/malloc.h>
41
42#include <machine/fdt.h>
43
44#include <dev/ofw/openfirm.h>
45
46#include "fdt_common.h"
47#include "ofw_bus_if.h"
48
49#ifdef DEBUG
50#define debugf(fmt, args...) do { printf("%s(): ", __func__);	\
51    printf(fmt,##args); } while (0)
52#else
53#define debugf(fmt, args...)
54#endif
55
56static MALLOC_DEFINE(M_FDTBUS, "fdtbus", "FDTbus devices information");
57
58struct fdtbus_devinfo {
59	phandle_t		di_node;
60	char			*di_name;
61	char			*di_type;
62	char			*di_compat;
63	struct resource_list	di_res;
64
65	/* Interrupts sense-level info for this device */
66	struct fdt_sense_level	di_intr_sl[DI_MAX_INTR_NUM];
67};
68
69struct fdtbus_softc {
70	struct rman	sc_irq;
71	struct rman	sc_mem;
72};
73
74/*
75 * Prototypes.
76 */
77static void fdtbus_identify(driver_t *, device_t);
78static int fdtbus_probe(device_t);
79static int fdtbus_attach(device_t);
80
81static int fdtbus_print_child(device_t, device_t);
82static struct resource *fdtbus_alloc_resource(device_t, device_t, int,
83    int *, u_long, u_long, u_long, u_int);
84static int fdtbus_release_resource(device_t, device_t, int, int,
85    struct resource *);
86static int fdtbus_activate_resource(device_t, device_t, int, int,
87    struct resource *);
88static int fdtbus_deactivate_resource(device_t, device_t, int, int,
89    struct resource *);
90static int fdtbus_setup_intr(device_t, device_t, struct resource *, int,
91    driver_filter_t *, driver_intr_t *, void *, void **);
92
93static const char *fdtbus_ofw_get_name(device_t, device_t);
94static phandle_t fdtbus_ofw_get_node(device_t, device_t);
95static const char *fdtbus_ofw_get_type(device_t, device_t);
96static const char *fdtbus_ofw_get_compat(device_t, device_t);
97
98/*
99 * Local routines.
100 */
101static void newbus_device_from_fdt_node(device_t, phandle_t);
102
103/*
104 * Bus interface definition.
105 */
106static device_method_t fdtbus_methods[] = {
107	/* Device interface */
108	DEVMETHOD(device_identify,	fdtbus_identify),
109	DEVMETHOD(device_probe,		fdtbus_probe),
110	DEVMETHOD(device_attach,	fdtbus_attach),
111	DEVMETHOD(device_detach,	bus_generic_detach),
112	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
113	DEVMETHOD(device_suspend,	bus_generic_suspend),
114	DEVMETHOD(device_resume,	bus_generic_resume),
115
116	/* Bus interface */
117	DEVMETHOD(bus_print_child,	fdtbus_print_child),
118	DEVMETHOD(bus_alloc_resource,	fdtbus_alloc_resource),
119	DEVMETHOD(bus_release_resource,	fdtbus_release_resource),
120	DEVMETHOD(bus_activate_resource, fdtbus_activate_resource),
121	DEVMETHOD(bus_deactivate_resource, fdtbus_deactivate_resource),
122	DEVMETHOD(bus_config_intr,	bus_generic_config_intr),
123	DEVMETHOD(bus_setup_intr,	fdtbus_setup_intr),
124	DEVMETHOD(bus_teardown_intr,	bus_generic_teardown_intr),
125
126	/* OFW bus interface */
127	DEVMETHOD(ofw_bus_get_node,	fdtbus_ofw_get_node),
128	DEVMETHOD(ofw_bus_get_name,	fdtbus_ofw_get_name),
129	DEVMETHOD(ofw_bus_get_type,	fdtbus_ofw_get_type),
130	DEVMETHOD(ofw_bus_get_compat,	fdtbus_ofw_get_compat),
131
132	{ 0, 0 }
133};
134
135static driver_t fdtbus_driver = {
136	"fdtbus",
137	fdtbus_methods,
138	sizeof(struct fdtbus_softc)
139};
140
141devclass_t fdtbus_devclass;
142
143DRIVER_MODULE(fdtbus, nexus, fdtbus_driver, fdtbus_devclass, 0, 0);
144
145static void
146fdtbus_identify(driver_t *driver, device_t parent)
147{
148
149	debugf("%s(driver=%p, parent=%p)\n", __func__, driver, parent);
150
151	if (device_find_child(parent, "fdtbus", -1) == NULL)
152		BUS_ADD_CHILD(parent, 0, "fdtbus", -1);
153}
154
155static int
156fdtbus_probe(device_t dev)
157{
158
159	debugf("%s(dev=%p); pass=%u\n", __func__, dev, bus_current_pass);
160
161	device_set_desc(dev, "FDT main bus");
162	if (!bootverbose)
163		device_quiet(dev);
164	return (BUS_PROBE_DEFAULT);
165}
166
167static int
168fdtbus_attach(device_t dev)
169{
170	phandle_t root;
171	phandle_t child;
172	struct fdtbus_softc *sc;
173	u_long start, end;
174	int error;
175
176	if ((root = OF_finddevice("/")) == -1)
177		panic("fdtbus_attach: no root node.");
178
179	sc = device_get_softc(dev);
180
181	/*
182	 * IRQ rman.
183	 */
184	start = 0;
185	end = FDT_INTR_MAX - 1;
186	sc->sc_irq.rm_start = start;
187	sc->sc_irq.rm_end = end;
188	sc->sc_irq.rm_type = RMAN_ARRAY;
189	sc->sc_irq.rm_descr = "Interrupt request lines";
190	if ((error = rman_init(&sc->sc_irq)) != 0) {
191		device_printf(dev, "could not init IRQ rman, error = %d\n",
192		    error);
193		return (error);
194	}
195	if ((error = rman_manage_region(&sc->sc_irq, start, end)) != 0) {
196		device_printf(dev, "could not manage IRQ region, error = %d\n",
197		    error);
198		return (error);
199	}
200
201	/*
202	 * Mem-mapped I/O space rman.
203	 */
204	start = 0;
205	end = ~0ul;
206	sc->sc_mem.rm_start = start;
207	sc->sc_mem.rm_end = end;
208	sc->sc_mem.rm_type = RMAN_ARRAY;
209	sc->sc_mem.rm_descr = "I/O memory";
210	if ((error = rman_init(&sc->sc_mem)) != 0) {
211		device_printf(dev, "could not init I/O mem rman, error = %d\n",
212		    error);
213		return (error);
214	}
215	if ((error = rman_manage_region(&sc->sc_mem, start, end)) != 0) {
216		device_printf(dev, "could not manage I/O mem region, "
217		    "error = %d\n", error);
218		return (error);
219	}
220
221	/*
222	 * Walk the FDT root node and add top-level devices as our children.
223	 */
224	for (child = OF_child(root); child != 0; child = OF_peer(child)) {
225		/* Check and process 'status' property. */
226		if (!(fdt_is_enabled(child)))
227			continue;
228
229		newbus_device_from_fdt_node(dev, child);
230	}
231
232	return (bus_generic_attach(dev));
233}
234
235static int
236fdtbus_print_child(device_t dev, device_t child)
237{
238	struct fdtbus_devinfo *di;
239	struct resource_list *rl;
240	int rv;
241
242	di = device_get_ivars(child);
243	rl = &di->di_res;
244
245	rv = 0;
246	rv += bus_print_child_header(dev, child);
247	rv += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#lx");
248	rv += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld");
249	rv += bus_print_child_footer(dev, child);
250
251	return (rv);
252}
253
254static void
255newbus_device_destroy(device_t dev)
256{
257	struct fdtbus_devinfo *di;
258
259	di = device_get_ivars(dev);
260	if (di == NULL)
261		return;
262
263	free(di->di_name, M_OFWPROP);
264	free(di->di_type, M_OFWPROP);
265	free(di->di_compat, M_OFWPROP);
266
267	resource_list_free(&di->di_res);
268	free(di, M_FDTBUS);
269}
270
271static device_t
272newbus_device_create(device_t dev_par, phandle_t node, char *name, char *type,
273    char *compat)
274{
275	device_t child;
276	struct fdtbus_devinfo *di;
277
278	child = device_add_child(dev_par, NULL, -1);
279	if (child == NULL) {
280		free(name, M_OFWPROP);
281		free(type, M_OFWPROP);
282		free(compat, M_OFWPROP);
283		return (NULL);
284	}
285
286	di = malloc(sizeof(*di), M_FDTBUS, M_WAITOK);
287	di->di_node = node;
288	di->di_name = name;
289	di->di_type = type;
290	di->di_compat = compat;
291
292	resource_list_init(&di->di_res);
293
294	if (fdt_reg_to_rl(node, &di->di_res)) {
295		device_printf(child, "could not process 'reg' property\n");
296		newbus_device_destroy(child);
297		child = NULL;
298		goto out;
299	}
300
301	if (fdt_intr_to_rl(node, &di->di_res, di->di_intr_sl)) {
302		device_printf(child, "could not process 'interrupts' "
303		    "property\n");
304		newbus_device_destroy(child);
305		child = NULL;
306		goto out;
307	}
308
309	device_set_ivars(child, di);
310	debugf("added child name='%s', node=%p\n", name, (void *)node);
311
312out:
313	return (child);
314}
315
316static device_t
317newbus_pci_create(device_t dev_par, phandle_t dt_node, u_long par_base,
318    u_long par_size)
319{
320	pcell_t reg[3 + 2];
321	device_t dev_child;
322	u_long start, end, count;
323	struct fdtbus_devinfo *di;
324	char *name, *type, *compat;
325	int len;
326
327	OF_getprop_alloc(dt_node, "device_type", 1, (void **)&type);
328	if (!(type != NULL && strcmp(type, "pci") == 0)) {
329		/* Only process 'pci' subnodes. */
330		free(type, M_OFWPROP);
331		return (NULL);
332	}
333
334	OF_getprop_alloc(dt_node, "name", 1, (void **)&name);
335	OF_getprop_alloc(OF_parent(dt_node), "compatible", 1,
336	    (void **)&compat);
337
338	dev_child = device_add_child(dev_par, NULL, -1);
339	if (dev_child == NULL) {
340		free(name, M_OFWPROP);
341		free(type, M_OFWPROP);
342		free(compat, M_OFWPROP);
343		return (NULL);
344	}
345
346	di = malloc(sizeof(*di), M_FDTBUS, M_WAITOK);
347	di->di_node = dt_node;
348	di->di_name = name;
349	di->di_type = type;
350	di->di_compat = compat;
351
352	resource_list_init(&di->di_res);
353
354	/*
355	 * Produce and set SYS_RES_MEMORY resources.
356	 */
357	start = 0;
358	count = 0;
359
360	len = OF_getprop(dt_node, "reg", &reg, sizeof(reg));
361	if (len > 0) {
362		if (fdt_data_verify((void *)&reg[1], 2) != 0) {
363			device_printf(dev_child, "'reg' address value out of "
364			    "range\n");
365			newbus_device_destroy(dev_child);
366			dev_child = NULL;
367			goto out;
368		}
369		start = fdt_data_get((void *)&reg[1], 2);
370
371		if (fdt_data_verify((void *)&reg[3], 2) != 0) {
372			device_printf(dev_child, "'reg' size value out of "
373			    "range\n");
374			newbus_device_destroy(dev_child);
375			dev_child = NULL;
376			goto out;
377		}
378		count = fdt_data_get((void *)&reg[3], 2);
379	}
380
381	/* Calculate address range relative to base. */
382	par_base &= 0x000ffffful;
383	start &= 0x000ffffful;
384	start += par_base + fdt_immr_va;
385	if (count == 0)
386		count = par_size;
387	end = start + count - 1;
388
389	debugf("start = 0x%08lx, end = 0x%08lx, count = 0x%08lx\n",
390	    start, end, count);
391
392	if (count > par_size) {
393		device_printf(dev_child, "'reg' size value out of range\n");
394		newbus_device_destroy(dev_child);
395		dev_child = NULL;
396		goto out;
397	}
398
399	resource_list_add(&di->di_res, SYS_RES_MEMORY, 0, start, end, count);
400
401	/*
402	 * Set SYS_RES_IRQ resources.
403	 */
404	if (fdt_intr_to_rl(OF_parent(dt_node), &di->di_res, di->di_intr_sl)) {
405		device_printf(dev_child, "could not process 'interrupts' "
406		    "property\n");
407		newbus_device_destroy(dev_child);
408		dev_child = NULL;
409		goto out;
410	}
411
412	device_set_ivars(dev_child, di);
413	debugf("added child name='%s', node=%p\n", name,
414	    (void *)dt_node);
415
416out:
417	return (dev_child);
418}
419
420static void
421pci_from_fdt_node(device_t dev_par, phandle_t dt_node, char *name,
422    char *type, char *compat)
423{
424	u_long reg_base, reg_size;
425	phandle_t dt_child;
426
427	/*
428	 * Retrieve 'reg' property.
429	 */
430	if (fdt_regsize(dt_node, &reg_base, &reg_size) != 0) {
431		device_printf(dev_par, "could not retrieve 'reg' prop\n");
432		return;
433	}
434
435	/*
436	 * Walk the PCI node and instantiate newbus devices representing
437	 * logical resources (bridges / ports).
438	 */
439	for (dt_child = OF_child(dt_node); dt_child != 0;
440	    dt_child = OF_peer(dt_child)) {
441
442		if (!(fdt_is_enabled(dt_child)))
443			continue;
444
445		newbus_pci_create(dev_par, dt_child, reg_base, reg_size);
446	}
447}
448
449/*
450 * These FDT nodes do not need a corresponding newbus device object.
451 */
452static char *fdt_devices_skip[] = {
453	"aliases",
454	"chosen",
455	"memory",
456	NULL
457};
458
459static void
460newbus_device_from_fdt_node(device_t dev_par, phandle_t node)
461{
462	char *name, *type, *compat;
463	device_t child;
464	int i;
465
466	OF_getprop_alloc(node, "name", 1, (void **)&name);
467	OF_getprop_alloc(node, "device_type", 1, (void **)&type);
468	OF_getprop_alloc(node, "compatible", 1, (void **)&compat);
469
470	for (i = 0; fdt_devices_skip[i] != NULL; i++)
471		if (name != NULL && strcmp(name, fdt_devices_skip[i]) == 0) {
472			debugf("skipping instantiating FDT device='%s'\n",
473			    name);
474			return;
475		}
476
477	child = newbus_device_create(dev_par, node, name, type, compat);
478	if (type != NULL && strcmp(type, "pci") == 0)
479		pci_from_fdt_node(child, node, name, type, compat);
480}
481
482static struct resource *
483fdtbus_alloc_resource(device_t bus, device_t child, int type, int *rid,
484    u_long start, u_long end, u_long count, u_int flags)
485{
486	struct fdtbus_softc *sc;
487	struct resource *res;
488	struct rman *rm;
489	struct fdtbus_devinfo *di;
490	struct resource_list_entry *rle;
491	int needactivate;
492
493	/*
494	 * Request for the default allocation with a given rid: use resource
495	 * list stored in the local device info.
496	 */
497	if ((start == 0UL) && (end == ~0UL)) {
498		if ((di = device_get_ivars(child)) == NULL)
499			return (NULL);
500
501		if (type == SYS_RES_IOPORT)
502			type = SYS_RES_MEMORY;
503
504		rle = resource_list_find(&di->di_res, type, *rid);
505		if (rle == NULL) {
506			device_printf(bus, "no default resources for "
507			    "rid = %d, type = %d\n", *rid, type);
508			return (NULL);
509		}
510		start = rle->start;
511		end = rle->end;
512		count = rle->count;
513	}
514
515	sc = device_get_softc(bus);
516
517	needactivate = flags & RF_ACTIVE;
518	flags &= ~RF_ACTIVE;
519
520	switch (type) {
521	case SYS_RES_IRQ:
522		rm = &sc->sc_irq;
523		break;
524
525	case SYS_RES_IOPORT:
526	case SYS_RES_MEMORY:
527		rm = &sc->sc_mem;
528		break;
529
530	default:
531		return (NULL);
532	}
533
534	res = rman_reserve_resource(rm, start, end, count, flags, child);
535	if (res == NULL) {
536		device_printf(bus, "failed to reserve resource %#lx - %#lx "
537		    "(%#lx)\n", start, end, count);
538		return (NULL);
539	}
540
541	rman_set_rid(res, *rid);
542
543	if (type == SYS_RES_IOPORT || type == SYS_RES_MEMORY) {
544		/* XXX endianess should be set based on SOC node */
545		rman_set_bustag(res, fdtbus_bs_tag);
546		rman_set_bushandle(res, rman_get_start(res));
547	}
548
549	if (needactivate)
550		if (bus_activate_resource(child, type, *rid, res)) {
551			device_printf(child, "resource activation failed\n");
552			rman_release_resource(res);
553			return (NULL);
554		}
555
556	return (res);
557}
558
559static int
560fdtbus_release_resource(device_t bus, device_t child, int type, int rid,
561    struct resource *res)
562{
563	int err;
564
565	if (rman_get_flags(res) & RF_ACTIVE) {
566		err = bus_deactivate_resource(child, type, rid, res);
567		if (err)
568			return (err);
569	}
570
571	return (rman_release_resource(res));
572}
573
574static int
575fdtbus_setup_intr(device_t bus, device_t child, struct resource *res,
576    int flags, driver_filter_t *filter, driver_intr_t *ihand, void *arg,
577    void **cookiep)
578{
579	struct fdtbus_devinfo *di;
580	enum intr_trigger trig;
581	enum intr_polarity pol;
582	int error, rid;
583
584	if (res == NULL)
585		return (EINVAL);
586
587	/*
588	 * We are responsible for configuring the interrupts of our direct
589	 * children.
590	 */
591	if (device_get_parent(child) == bus) {
592		di = device_get_ivars(child);
593		if (di == NULL)
594			return (ENXIO);
595
596		rid = rman_get_rid(res);
597		if (rid >= DI_MAX_INTR_NUM)
598			return (ENOENT);
599
600		trig = di->di_intr_sl[rid].trig;
601		pol = di->di_intr_sl[rid].pol;
602		if (trig != INTR_TRIGGER_CONFORM ||
603		    pol != INTR_POLARITY_CONFORM) {
604			error = bus_generic_config_intr(bus,
605			    rman_get_start(res), trig, pol);
606			if (error)
607				return (error);
608		}
609	}
610
611	error = bus_generic_setup_intr(bus, child, res, flags, filter, ihand,
612	    arg, cookiep);
613	return (error);
614}
615
616static int
617fdtbus_activate_resource(device_t bus, device_t child, int type, int rid,
618    struct resource *res)
619{
620	bus_space_handle_t p;
621	int error;
622
623	if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) {
624		error = bus_space_map(rman_get_bustag(res),
625		    rman_get_bushandle(res), rman_get_size(res), 0, &p);
626		if (error)
627			return (error);
628		rman_set_bushandle(res, p);
629	}
630
631	return (rman_activate_resource(res));
632}
633
634static int
635fdtbus_deactivate_resource(device_t bus, device_t child, int type, int rid,
636    struct resource *res)
637{
638
639	return (rman_deactivate_resource(res));
640}
641
642static const char *
643fdtbus_ofw_get_name(device_t bus, device_t dev)
644{
645	struct fdtbus_devinfo *di;
646
647	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_name);
648}
649
650static phandle_t
651fdtbus_ofw_get_node(device_t bus, device_t dev)
652{
653	struct fdtbus_devinfo *di;
654
655	return ((di = device_get_ivars(dev)) == NULL ? 0 : di->di_node);
656}
657
658static const char *
659fdtbus_ofw_get_type(device_t bus, device_t dev)
660{
661	struct fdtbus_devinfo *di;
662
663	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_type);
664}
665
666static const char *
667fdtbus_ofw_get_compat(device_t bus, device_t dev)
668{
669	struct fdtbus_devinfo *di;
670
671	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_compat);
672}
673
674
675