NetBSD-5.0.2/sys/arch/powerpc/pci/pci_machdep_ofw.c

Compare this file to the similar file:
Show the results in this format:

/* $NetBSD: pci_machdep_ofw.c,v 1.12 2008/04/28 20:23:32 martin Exp $ */

/*-
 * Copyright (c) 2007 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tim Rightnour
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Generic OFW routines for pci_machdep
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pci_machdep_ofw.c,v 1.12 2008/04/28 20:23:32 martin Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <uvm/uvm_extern.h>

#include <machine/bus.h>

#include <machine/autoconf.h>
#include <machine/pio.h>
#include <machine/intr.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/ppbreg.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pciconf.h>

#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_pci.h>

pcitag_t genppc_pci_indirect_make_tag(void *, int, int, int);
void genppc_pci_indirect_decompose_tag(void *, pcitag_t, int *, int *, int *);

ofw_pic_node_t picnodes[8];
int nrofpics = 0;

int
genofw_find_picnode(int node)
{
	int i;

	for (i = 0; i < 8; i++)
		if (node == picnodes[i].node)
			return i;
	return -1;
}

void
genofw_find_ofpics(int startnode)
{
	int node, iparent, child, iranges[6], irgot=0, i;
	uint32_t reg[12];
	char name[32];

	for (node = startnode; node; node = OF_peer(node)) {
		if ((child = OF_child(node)) != 0)
			genofw_find_ofpics(child);
		memset(name, 0, sizeof(name));
		if (OF_getprop(node, "name", name, sizeof(name)) == -1)
			continue;
		if (strncmp(name, "interrupt-controller", 20) == 0)
			goto foundic;

		if (OF_getprop(node, "interrupt-controller", name,
		    sizeof(name)) > -1)
			goto foundic;

		if (OF_getprop(node, "device_type", name, sizeof(name)) == -1)
			continue;
		if (strncmp(name, "interrupt-controller", 20) == 0)
			goto foundic;

		/* if we didn't find one, skip to the next */
		continue;
foundic:
		picnodes[nrofpics].node = node;
		if (OF_getprop(node, "interrupt-parent", &iparent,
		    sizeof(iparent)) == sizeof(iparent))
			picnodes[nrofpics].parent = iparent;
		if (OF_getprop(node, "#interrupt-cells", &iparent,
		    sizeof(iparent)) == sizeof(iparent))
			picnodes[nrofpics].cells = iparent;
		else
			picnodes[nrofpics].cells = 1;

		picnodes[nrofpics].intrs = 0;
		irgot = OF_getprop(node, "interrupt-ranges", iranges,
		    sizeof(int)*6); /* XXX is this ok? */
		if (irgot >= sizeof(int)) {
			for (i=0; i < irgot/4; i++)
				if (!picnodes[nrofpics].intrs)
					picnodes[nrofpics].intrs = iranges[i];
		}

		irgot = OF_getprop(node, "reg", reg, sizeof(reg));
		
		if (!picnodes[nrofpics].intrs)
			picnodes[nrofpics].intrs = 16;
		
		if (nrofpics > 0)
			picnodes[nrofpics].offset = picnodes[nrofpics-1].offset
			    + picnodes[nrofpics-1].intrs;
		else
			picnodes[nrofpics].offset = 0;
		OF_getprop(node, "device_type", name, sizeof(name));
		if (strcmp(name, "open-pic") == 0)
			picnodes[nrofpics].type = PICNODE_TYPE_OPENPIC;
		if (strcmp(name, "interrupt-controller") == 0) {
			OF_getprop(node, "compatible", name, sizeof(name));
			if (strcmp(name, "heathrow") == 0)
				picnodes[nrofpics].type = PICNODE_TYPE_HEATHROW;
			if (strcmp(name, "pnpPNP,0") == 0)
				picnodes[nrofpics].type = PICNODE_TYPE_8259;
			if (strcmp(name, "chrp,iic") == 0) {
				picnodes[nrofpics].type = PICNODE_TYPE_8259;
				if (irgot >= 9 * sizeof(uint32_t) &&
				    reg[7] == 0x4d0)
					picnodes[nrofpics].type =
					    PICNODE_TYPE_IVR;
			}
		}
		if (strlen(name) == 0) {
			/* probably a Pegasos, assume 8259 */
			picnodes[nrofpics].type = PICNODE_TYPE_8259;
		}
		nrofpics++;
	}
}

/* Fix up the various picnode offsets */
void
genofw_fixup_picnode_offsets(void)
{
	int i, curoff;

	curoff=0;

	for (i=0; i < nrofpics; i++) {
		if (picnodes[i].type == PICNODE_TYPE_8259 ||
		    picnodes[i].type == PICNODE_TYPE_IVR) {
			picnodes[i].offset = 0;
			curoff = picnodes[i].intrs;
		}
	}
	for (i=0; i < nrofpics; i++) {
		/* now skip the 8259 */
		if (picnodes[i].type == PICNODE_TYPE_8259)
			continue;
		if (picnodes[i].type == PICNODE_TYPE_IVR)
			continue;
		
		picnodes[i].offset = curoff;
		curoff += picnodes[i].intrs;
	}
}

/* we are given a pci devnode, and dig from there */
void
genofw_setup_pciintr_map(void *v, struct genppc_pci_chipset_businfo *pbi,
    int pcinode)
{
	int node;
	u_int32_t map[160];
	int parent, len;
	int curdev, foundirqs=0;
	int i, reclen, nrofpcidevs=0;
	u_int32_t acells, icells, pcells;
	prop_dictionary_t dict;
	prop_dictionary_t sub=0;
	pci_chipset_tag_t pc = (pci_chipset_tag_t)v;

	len = OF_getprop(pcinode, "interrupt-map", map, sizeof(map));
	if (len == -1)
		goto nomap;

	if (OF_getprop(pcinode, "#address-cells", &acells,
	    sizeof(acells)) == -1)
		acells = 1;
	if (OF_getprop(pcinode, "#interrupt-cells", &icells,
	    sizeof(icells)) == -1)
		icells = 1;

	parent = map[acells+icells];
	if (OF_getprop(parent, "#interrupt-cells", &pcells,
	    sizeof(pcells)) == -1)
		pcells = 1;

	reclen = acells+pcells+icells+1;
	nrofpcidevs = len / (reclen * sizeof(int));

	dict = prop_dictionary_create_with_capacity(nrofpcidevs*2);
	KASSERT(dict != NULL);

	curdev = -1;
	prop_dictionary_set(pbi->pbi_properties, "ofw-pci-intrmap", dict);
	for (i = 0; i < nrofpcidevs; i++) {
		prop_number_t intr_num;
		int dev, pin, pic, func;
		char key[20];

		pic = genofw_find_picnode(map[i*reclen + acells + icells]);
		KASSERT(pic != -1);
		dev = (map[i*reclen] >> 8) / 0x8;
		func = (map[i*reclen] >> 8) % 0x8;
		if (curdev != dev)
			sub = prop_dictionary_create_with_capacity(4);
		pin = map[i*reclen + acells];
		intr_num = prop_number_create_integer(map[i*reclen + acells + icells + 1] + picnodes[pic].offset);
		sprintf(key, "pin-%c", 'A' + (pin-1));
		prop_dictionary_set(sub, key, intr_num);
		prop_object_release(intr_num);
		/* should we care about level? */

		sprintf(key, "devfunc-%d", dev*0x8 + func);
		prop_dictionary_set(dict, key, sub);
		if (curdev != dev) {
			prop_object_release(sub);
			curdev = dev;
		}
	}
	/* the mapping is complete */
	prop_object_release(dict);
	aprint_debug("%s\n", prop_dictionary_externalize(pbi->pbi_properties));
	return;

nomap:
	/* so, we have one of those annoying machines that doesn't provide
	 * a nice simple map of interrupts.  We get to do this the hard
	 * way instead.  Lucky us.
	 */
	for (node = OF_child(pcinode), nrofpcidevs=0; node;
	    node = OF_peer(node))
		nrofpcidevs++;
	dict = prop_dictionary_create_with_capacity(nrofpcidevs*2);
	KASSERT(dict != NULL);
	prop_dictionary_set(pbi->pbi_properties, "ofw-pci-intrmap", dict);

	for (node = OF_child(pcinode); node; node = OF_peer(node)) {
		uint32_t irqs[4], reg[5];
		prop_number_t intr_num;
		int dev, pin, func;
		char key[20];

		/* walk the bus looking for pci devices and map them */
		if (OF_getprop(node, "AAPL,interrupts", irqs, 4) > 0) {
			dev = 0;
			if (OF_getprop(node, "reg", reg, 5) > 0) {
				dev = ((reg[0] & 0x0000ff00) >> 8) / 0x8;
				func = ((reg[0] & 0x0000ff00) >> 8) % 0x8;
			} else if (OF_getprop(node, "assigned-addresses",
				       reg, 5) > 0) {
				dev = ((reg[0] & 0x0000ff00) >> 8) / 0x8;
				func = ((reg[0] & 0x0000ff00) >> 8) % 0x8;
			}
			if (dev == 0) {
				aprint_error("cannot figure out device num "
				    "for node 0x%x\n", node);
				continue;
			}
			sub = prop_dictionary_create_with_capacity(4);
			if (OF_getprop(node, "interrupts", &pin, 4) < 0)
				pin = 1;
			intr_num = prop_number_create_integer(irqs[0]);
			sprintf(key, "pin-%c", 'A' + (pin-1));
			prop_dictionary_set(sub, key, intr_num);
			prop_object_release(intr_num);
			sprintf(key, "devfunc-%d", dev*0x8 + func);
			prop_dictionary_set(dict, key, sub);
			prop_object_release(sub);
			foundirqs++;
		}
	}
	if (foundirqs)
		return;

	/*
	 * If we got this far, we have a super-annoying OFW.
	 * They didn't bother to fill in any interrupt properties anywhere,
	 * so we pray that they filled in the ones on the pci devices.
	 */
	for (node = OF_child(pcinode); node; node = OF_peer(node)) {
		uint32_t reg[5], irq;
		prop_number_t intr_num;
		pcitag_t tag;
		int dev, pin, func;
		char key[20];

		if (OF_getprop(node, "reg", reg, 5) > 0) {
			dev = ((reg[0] & 0x0000ff00) >> 8) / 0x8;
			func = ((reg[0] & 0x0000ff00) >> 8) % 0x8;

			tag = pci_make_tag(pc, pc->pc_bus, dev, func);
			irq = PCI_INTERRUPT_LINE(pci_conf_read(pc, tag,
			    PCI_INTERRUPT_REG));
			if (irq == 255)
				irq = 0;

			sub = prop_dictionary_create_with_capacity(4);
			if (OF_getprop(node, "interrupts", &pin, 4) < 0)
				pin = 1;
			intr_num = prop_number_create_integer(irq);
			sprintf(key, "pin-%c", 'A' + (pin-1));
			prop_dictionary_set(sub, key, intr_num);
			prop_object_release(intr_num);
			sprintf(key, "devfunc-%d", dev*0x8 + func);
			prop_dictionary_set(dict, key, sub);
			prop_object_release(sub);
		}
	}
	aprint_debug("%s\n", prop_dictionary_externalize(pbi->pbi_properties));
}

int
genofw_find_node_by_devfunc(int startnode, int bus, int dev, int func)
{
	int node, sz, p=0;
	uint32_t reg;
	
	for (node = startnode; node; node = p) {
		sz = OF_getprop(node, "reg", &reg, sizeof(reg));
		if (sz != sizeof(reg))
			continue;
		if (OFW_PCI_PHYS_HI_BUS(reg) == bus &&
		    OFW_PCI_PHYS_HI_DEVICE(reg) == dev &&
		    OFW_PCI_PHYS_HI_FUNCTION(reg) == func)
			return node;
		if ((p = OF_child(node)))
			continue;
		while (node) {
			if ((p = OF_peer(node)))
				break;
			node = OF_parent(node);
		}
	}
	/* couldn't find it */
	return -1;
}

int
genofw_pci_intr_map(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
{
	struct genppc_pci_chipset_businfo *pbi;
	prop_dictionary_t dict, devsub;
	prop_object_t pinsub;
	prop_number_t pbus;
	int busno, bus, pin, line, swiz, dev, origdev, func, i;
	char key[20];

	pin = pa->pa_intrpin;
	line = pa->pa_intrline;
	bus = busno = pa->pa_bus;
	swiz = pa->pa_intrswiz;
	origdev = dev = pa->pa_device;
	func = pa->pa_function;
	i = 0;

	pbi = SIMPLEQ_FIRST(&pa->pa_pc->pc_pbi);
	while (busno--)
		pbi = SIMPLEQ_NEXT(pbi, next);
	KASSERT(pbi != NULL);

	dict = prop_dictionary_get(pbi->pbi_properties, "ofw-pci-intrmap");

	if (dict != NULL)
		i = prop_dictionary_count(dict);

	if (dict == NULL || i == 0) {
		/* We have an unmapped bus, now it gets hard */
		pbus = prop_dictionary_get(pbi->pbi_properties,
		    "ofw-pcibus-parent");
		if (pbus == NULL)
			goto bad;
		busno = prop_number_integer_value(pbus);
		pbus = prop_dictionary_get(pbi->pbi_properties,
		    "ofw-pcibus-rawdevnum");
		dev = prop_number_integer_value(pbus);

		/* now that we know the parent bus, we need to find it's pbi */
		pbi = SIMPLEQ_FIRST(&pa->pa_pc->pc_pbi);
		while (busno--)
			pbi = SIMPLEQ_NEXT(pbi, next);
		KASSERT(pbi != NULL);

		/* swizzle the pin */
		pin = ((pin + origdev - 1) & 3) + 1;

		/* now we have the pbi, ask for dict again */
		dict = prop_dictionary_get(pbi->pbi_properties,
		    "ofw-pci-intrmap");
		if (dict == NULL)
			goto bad;
	}

	/* No IRQ used. */
	if (pin == 0)
		goto bad;
	if (pin > 4) {
		aprint_error("pci_intr_map: bad interrupt pin %d\n", pin);
		goto bad;
	}

	sprintf(key, "devfunc-%d", dev*0x8 + func);
	devsub = prop_dictionary_get(dict, key);
	if (devsub == NULL)
		goto bad;
	sprintf(key, "pin-%c", 'A' + (pin-1));
	pinsub = prop_dictionary_get(devsub, key);
	if (pinsub == NULL)
		goto bad;
	line = prop_number_integer_value(pinsub);

	if (line == 0 || line == 255) {
		aprint_error("pci_intr_map: no mapping for pin %c\n",'@' + pin);
		goto bad;
	}

	*ihp = line;
	return 0;

bad:
	*ihp = -1;
	return 1;
}

int
genofw_pci_conf_hook(pci_chipset_tag_t pct, int bus, int dev, int func,
	pcireg_t id)
{
	struct genppc_pci_chipset_businfo *pbi;
	prop_number_t pbus;
	pcitag_t tag;
	pcireg_t class;
	int node;

	/* We have already mapped MPIC's if we have them, so leave them alone */
	if (PCI_VENDOR(id) == PCI_VENDOR_IBM &&
	    PCI_PRODUCT(id) == PCI_PRODUCT_IBM_MPIC2)
		return 0;

	if (PCI_VENDOR(id) == PCI_VENDOR_IBM &&
	    PCI_PRODUCT(id) == PCI_PRODUCT_IBM_MPIC)
		return 0;

	/* I highly doubt there are any CHRP ravens, but just in case */
	if (PCI_VENDOR(id) == PCI_VENDOR_MOT &&
	    PCI_PRODUCT(id) == PCI_PRODUCT_MOT_RAVEN)
		return (PCI_CONF_ALL & ~PCI_CONF_MAP_MEM);

	/*
	 * Pegasos2 specific stuff.
	 */
	if (strncmp(model_name, "Pegasos2", 8) == 0) {

		/* never reconfigure the MV64361 host bridge */
		if (PCI_VENDOR(id) == PCI_VENDOR_MARVELL &&
		    PCI_PRODUCT(id) == PCI_PRODUCT_MARVELL_GT64360)
			return 0;

		/* we want to leave viaide(4) alone */
		if (PCI_VENDOR(id) == PCI_VENDOR_VIATECH &&
		    PCI_PRODUCT(id) == PCI_PRODUCT_VIATECH_VT82C586A_IDE)
			return 0;

		/* leave the audio IO alone */
		if (PCI_VENDOR(id) == PCI_VENDOR_VIATECH &&
		    PCI_PRODUCT(id) == PCI_PRODUCT_VIATECH_VT82C686A_AC97)
			return (PCI_CONF_ALL & ~PCI_CONF_MAP_IO);

	}
	
	tag = pci_make_tag(pct, bus, dev, func);
	class = pci_conf_read(pct, tag, PCI_CLASS_REG);

	/* leave video cards alone */
	if (PCI_CLASS(class) == PCI_CLASS_DISPLAY)
		return 0;
	
	/* NOTE, all device specific stuff must be above this line */
	/* don't do this on the primary host bridge */
	if (bus == 0 && dev == 0 && func == 0)
		return PCI_CONF_DEFAULT;

	/*
	 * PCI bridges have special needs.  We need to discover where they
	 * came from, and wire them appropriately.
	 */
	if (PCI_CLASS(class) == PCI_CLASS_BRIDGE &&
	    PCI_SUBCLASS(class) == PCI_SUBCLASS_BRIDGE_PCI) {
		pbi = malloc(sizeof(struct genppc_pci_chipset_businfo),
		    M_DEVBUF, M_NOWAIT);
		KASSERT(pbi != NULL);
		pbi->pbi_properties = prop_dictionary_create();
		KASSERT(pbi->pbi_properties != NULL);
		node = genofw_find_node_by_devfunc(pct->pc_node, bus, dev,
		    func);
		if (node == -1) {
			aprint_error("Cannot find node for device "
			    "bus %d dev %d func %d\n", bus, dev, func);
			prop_object_release(pbi->pbi_properties);
			free(pbi, M_DEVBUF);
			return (PCI_CONF_DEFAULT);
		}
		genofw_setup_pciintr_map((void *)pct, pbi, node);

		/* record the parent bus, and the parent device number */
		pbus = prop_number_create_integer(bus);
		prop_dictionary_set(pbi->pbi_properties, "ofw-pcibus-parent",
		    pbus);
		prop_object_release(pbus);
		pbus = prop_number_create_integer(dev);
		prop_dictionary_set(pbi->pbi_properties, "ofw-pcibus-rawdevnum",
		    pbus);
		prop_object_release(pbus);

		SIMPLEQ_INSERT_TAIL(&pct->pc_pbi, pbi, next);
	}

	return (PCI_CONF_DEFAULT);
}