diff options
Diffstat (limited to 'arch/powerpc/platforms/wsp/msi.c')
| -rw-r--r-- | arch/powerpc/platforms/wsp/msi.c | 102 | 
1 files changed, 102 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/wsp/msi.c b/arch/powerpc/platforms/wsp/msi.c new file mode 100644 index 00000000000..380882f27ad --- /dev/null +++ b/arch/powerpc/platforms/wsp/msi.c @@ -0,0 +1,102 @@ +/* + * Copyright 2011 Michael Ellerman, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/msi.h> +#include <linux/irq.h> +#include <linux/interrupt.h> + +#include "msi.h" +#include "ics.h" +#include "wsp_pci.h" + +/* Magic addresses for 32 & 64-bit MSIs with hardcoded MVE 0 */ +#define MSI_ADDR_32		0xFFFF0000ul +#define MSI_ADDR_64		0x1000000000000000ul + +int wsp_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) +{ +	struct pci_controller *phb; +	struct msi_desc *entry; +	struct msi_msg msg; +	unsigned int virq; +	int hwirq; + +	phb = pci_bus_to_host(dev->bus); +	if (!phb) +		return -ENOENT; + +	entry = list_first_entry(&dev->msi_list, struct msi_desc, list); +	if (entry->msi_attrib.is_64) { +		msg.address_lo = 0; +		msg.address_hi = MSI_ADDR_64 >> 32; +	} else { +		msg.address_lo = MSI_ADDR_32; +		msg.address_hi = 0; +	} + +	list_for_each_entry(entry, &dev->msi_list, list) { +		hwirq = wsp_ics_alloc_irq(phb->dn, 1); +		if (hwirq < 0) { +			dev_warn(&dev->dev, "wsp_msi: hwirq alloc failed!\n"); +			return hwirq; +		} + +		virq = irq_create_mapping(NULL, hwirq); +		if (virq == NO_IRQ) { +			dev_warn(&dev->dev, "wsp_msi: virq alloc failed!\n"); +			return -1; +		} + +		dev_dbg(&dev->dev, "wsp_msi: allocated irq %#x/%#x\n", +			hwirq, virq); + +		wsp_ics_set_msi_chip(virq); +		irq_set_msi_desc(virq, entry); +		msg.data = hwirq & XIVE_ADDR_MASK; +		write_msi_msg(virq, &msg); +	} + +	return 0; +} + +void wsp_teardown_msi_irqs(struct pci_dev *dev) +{ +	struct pci_controller *phb; +	struct msi_desc *entry; +	int hwirq; + +	phb = pci_bus_to_host(dev->bus); + +	dev_dbg(&dev->dev, "wsp_msi: tearing down msi irqs\n"); + +	list_for_each_entry(entry, &dev->msi_list, list) { +		if (entry->irq == NO_IRQ) +			continue; + +		irq_set_msi_desc(entry->irq, NULL); +		wsp_ics_set_std_chip(entry->irq); + +		hwirq = virq_to_hw(entry->irq); +		/* In this order to avoid racing with irq_create_mapping() */ +		irq_dispose_mapping(entry->irq); +		wsp_ics_free_irq(phb->dn, hwirq); +	} +} + +void wsp_setup_phb_msi(struct pci_controller *phb) +{ +	/* Create a single MVE at offset 0 that matches everything */ +	out_be64(phb->cfg_data + PCIE_REG_IODA_ADDR, PCIE_REG_IODA_AD_TBL_MVT); +	out_be64(phb->cfg_data + PCIE_REG_IODA_DATA0, 1ull << 63); + +	ppc_md.setup_msi_irqs = wsp_setup_msi_irqs; +	ppc_md.teardown_msi_irqs = wsp_teardown_msi_irqs; +}  |