diff options
Diffstat (limited to 'drivers/of/of_pci_irq.c')
| -rw-r--r-- | drivers/of/of_pci_irq.c | 116 | 
1 files changed, 116 insertions, 0 deletions
diff --git a/drivers/of/of_pci_irq.c b/drivers/of/of_pci_irq.c new file mode 100644 index 00000000000..1710d9dc7fc --- /dev/null +++ b/drivers/of/of_pci_irq.c @@ -0,0 +1,116 @@ +#include <linux/kernel.h> +#include <linux/of_pci.h> +#include <linux/of_irq.h> +#include <linux/export.h> + +/** + * of_irq_parse_pci - Resolve the interrupt for a PCI device + * @pdev:       the device whose interrupt is to be resolved + * @out_irq:    structure of_irq filled by this function + * + * This function resolves the PCI interrupt for a given PCI device. If a + * device-node exists for a given pci_dev, it will use normal OF tree + * walking. If not, it will implement standard swizzling and walk up the + * PCI tree until an device-node is found, at which point it will finish + * resolving using the OF tree walking. + */ +int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq) +{ +	struct device_node *dn, *ppnode; +	struct pci_dev *ppdev; +	__be32 laddr[3]; +	u8 pin; +	int rc; + +	/* Check if we have a device node, if yes, fallback to standard +	 * device tree parsing +	 */ +	dn = pci_device_to_OF_node(pdev); +	if (dn) { +		rc = of_irq_parse_one(dn, 0, out_irq); +		if (!rc) +			return rc; +	} + +	/* Ok, we don't, time to have fun. Let's start by building up an +	 * interrupt spec.  we assume #interrupt-cells is 1, which is standard +	 * for PCI. If you do different, then don't use that routine. +	 */ +	rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin); +	if (rc != 0) +		return rc; +	/* No pin, exit */ +	if (pin == 0) +		return -ENODEV; + +	/* Now we walk up the PCI tree */ +	for (;;) { +		/* Get the pci_dev of our parent */ +		ppdev = pdev->bus->self; + +		/* Ouch, it's a host bridge... */ +		if (ppdev == NULL) { +			ppnode = pci_bus_to_OF_node(pdev->bus); + +			/* No node for host bridge ? give up */ +			if (ppnode == NULL) +				return -EINVAL; +		} else { +			/* We found a P2P bridge, check if it has a node */ +			ppnode = pci_device_to_OF_node(ppdev); +		} + +		/* Ok, we have found a parent with a device-node, hand over to +		 * the OF parsing code. +		 * We build a unit address from the linux device to be used for +		 * resolution. Note that we use the linux bus number which may +		 * not match your firmware bus numbering. +		 * Fortunately, in most cases, interrupt-map-mask doesn't +		 * include the bus number as part of the matching. +		 * You should still be careful about that though if you intend +		 * to rely on this function (you ship  a firmware that doesn't +		 * create device nodes for all PCI devices). +		 */ +		if (ppnode) +			break; + +		/* We can only get here if we hit a P2P bridge with no node, +		 * let's do standard swizzling and try again +		 */ +		pin = pci_swizzle_interrupt_pin(pdev, pin); +		pdev = ppdev; +	} + +	out_irq->np = ppnode; +	out_irq->args_count = 1; +	out_irq->args[0] = pin; +	laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8)); +	laddr[1] = laddr[2] = cpu_to_be32(0); +	return of_irq_parse_raw(laddr, out_irq); +} +EXPORT_SYMBOL_GPL(of_irq_parse_pci); + +/** + * of_irq_parse_and_map_pci() - Decode a PCI irq from the device tree and map to a virq + * @dev: The pci device needing an irq + * @slot: PCI slot number; passed when used as map_irq callback. Unused + * @pin: PCI irq pin number; passed when used as map_irq callback. Unused + * + * @slot and @pin are unused, but included in the function so that this + * function can be used directly as the map_irq callback to pci_fixup_irqs(). + */ +int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin) +{ +	struct of_phandle_args oirq; +	int ret; + +	ret = of_irq_parse_pci(dev, &oirq); +	if (ret) { +		dev_err(&dev->dev, "of_irq_parse_pci() failed with rc=%d\n", ret); +		return 0; /* Proper return code 0 == NO_IRQ */ +	} + +	return irq_create_of_mapping(&oirq); +} +EXPORT_SYMBOL_GPL(of_irq_parse_and_map_pci); +  | 
