diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2011-09-19 13:45:06 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2011-09-20 02:12:44 -0400 |
commit | c1a2562ac5edcb3965760f4a37368122d85657af (patch) | |
tree | 294311d74c126c18220c897fcaf04cf29effda61 /arch/powerpc/platforms/powernv/pci.c | |
parent | 61305a96fad622ae0f0e78cb06f67ad721d378f9 (diff) |
powerpc/powernv: Implement MSI support for p5ioc2 PCIe
This implements support for MSIs on p5ioc2 PHBs. We only support
MSIs on the PCIe PHBs, not the PCI-X ones as the later hasn't been
properly verified in HW.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/powernv/pci.c')
-rw-r--r-- | arch/powerpc/platforms/powernv/pci.c | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/powernv/pci.c b/arch/powerpc/platforms/powernv/pci.c index 746ce5e51208..5c175519cf9e 100644 --- a/arch/powerpc/platforms/powernv/pci.c +++ b/arch/powerpc/platforms/powernv/pci.c | |||
@@ -19,6 +19,7 @@ | |||
19 | #include <linux/bootmem.h> | 19 | #include <linux/bootmem.h> |
20 | #include <linux/irq.h> | 20 | #include <linux/irq.h> |
21 | #include <linux/io.h> | 21 | #include <linux/io.h> |
22 | #include <linux/msi.h> | ||
22 | 23 | ||
23 | #include <asm/sections.h> | 24 | #include <asm/sections.h> |
24 | #include <asm/io.h> | 25 | #include <asm/io.h> |
@@ -38,6 +39,108 @@ | |||
38 | #define cfg_dbg(fmt...) do { } while(0) | 39 | #define cfg_dbg(fmt...) do { } while(0) |
39 | //#define cfg_dbg(fmt...) printk(fmt) | 40 | //#define cfg_dbg(fmt...) printk(fmt) |
40 | 41 | ||
42 | #ifdef CONFIG_PCI_MSI | ||
43 | static int pnv_msi_check_device(struct pci_dev* pdev, int nvec, int type) | ||
44 | { | ||
45 | struct pci_controller *hose = pci_bus_to_host(pdev->bus); | ||
46 | struct pnv_phb *phb = hose->private_data; | ||
47 | |||
48 | return (phb && phb->msi_map) ? 0 : -ENODEV; | ||
49 | } | ||
50 | |||
51 | static unsigned int pnv_get_one_msi(struct pnv_phb *phb) | ||
52 | { | ||
53 | unsigned int id; | ||
54 | |||
55 | spin_lock(&phb->lock); | ||
56 | id = find_next_zero_bit(phb->msi_map, phb->msi_count, phb->msi_next); | ||
57 | if (id >= phb->msi_count && phb->msi_next) | ||
58 | id = find_next_zero_bit(phb->msi_map, phb->msi_count, 0); | ||
59 | if (id >= phb->msi_count) { | ||
60 | spin_unlock(&phb->lock); | ||
61 | return 0; | ||
62 | } | ||
63 | __set_bit(id, phb->msi_map); | ||
64 | spin_unlock(&phb->lock); | ||
65 | return id + phb->msi_base; | ||
66 | } | ||
67 | |||
68 | static void pnv_put_msi(struct pnv_phb *phb, unsigned int hwirq) | ||
69 | { | ||
70 | unsigned int id; | ||
71 | |||
72 | if (WARN_ON(hwirq < phb->msi_base || | ||
73 | hwirq >= (phb->msi_base + phb->msi_count))) | ||
74 | return; | ||
75 | id = hwirq - phb->msi_base; | ||
76 | spin_lock(&phb->lock); | ||
77 | __clear_bit(id, phb->msi_map); | ||
78 | spin_unlock(&phb->lock); | ||
79 | } | ||
80 | |||
81 | static int pnv_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) | ||
82 | { | ||
83 | struct pci_controller *hose = pci_bus_to_host(pdev->bus); | ||
84 | struct pnv_phb *phb = hose->private_data; | ||
85 | struct msi_desc *entry; | ||
86 | struct msi_msg msg; | ||
87 | unsigned int hwirq, virq; | ||
88 | int rc; | ||
89 | |||
90 | if (WARN_ON(!phb)) | ||
91 | return -ENODEV; | ||
92 | |||
93 | list_for_each_entry(entry, &pdev->msi_list, list) { | ||
94 | if (!entry->msi_attrib.is_64 && !phb->msi32_support) { | ||
95 | pr_warn("%s: Supports only 64-bit MSIs\n", | ||
96 | pci_name(pdev)); | ||
97 | return -ENXIO; | ||
98 | } | ||
99 | hwirq = pnv_get_one_msi(phb); | ||
100 | if (!hwirq) { | ||
101 | pr_warn("%s: Failed to find a free MSI\n", | ||
102 | pci_name(pdev)); | ||
103 | return -ENOSPC; | ||
104 | } | ||
105 | virq = irq_create_mapping(NULL, hwirq); | ||
106 | if (virq == NO_IRQ) { | ||
107 | pr_warn("%s: Failed to map MSI to linux irq\n", | ||
108 | pci_name(pdev)); | ||
109 | pnv_put_msi(phb, hwirq); | ||
110 | return -ENOMEM; | ||
111 | } | ||
112 | rc = phb->msi_setup(phb, pdev, hwirq, entry->msi_attrib.is_64, | ||
113 | &msg); | ||
114 | if (rc) { | ||
115 | pr_warn("%s: Failed to setup MSI\n", pci_name(pdev)); | ||
116 | irq_dispose_mapping(virq); | ||
117 | pnv_put_msi(phb, hwirq); | ||
118 | return rc; | ||
119 | } | ||
120 | irq_set_msi_desc(virq, entry); | ||
121 | write_msi_msg(virq, &msg); | ||
122 | } | ||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | static void pnv_teardown_msi_irqs(struct pci_dev *pdev) | ||
127 | { | ||
128 | struct pci_controller *hose = pci_bus_to_host(pdev->bus); | ||
129 | struct pnv_phb *phb = hose->private_data; | ||
130 | struct msi_desc *entry; | ||
131 | |||
132 | if (WARN_ON(!phb)) | ||
133 | return; | ||
134 | |||
135 | list_for_each_entry(entry, &pdev->msi_list, list) { | ||
136 | if (entry->irq == NO_IRQ) | ||
137 | continue; | ||
138 | irq_set_msi_desc(entry->irq, NULL); | ||
139 | pnv_put_msi(phb, virq_to_hw(entry->irq)); | ||
140 | irq_dispose_mapping(entry->irq); | ||
141 | } | ||
142 | } | ||
143 | #endif /* CONFIG_PCI_MSI */ | ||
41 | 144 | ||
42 | static void pnv_pci_config_check_eeh(struct pnv_phb *phb, struct pci_bus *bus, | 145 | static void pnv_pci_config_check_eeh(struct pnv_phb *phb, struct pci_bus *bus, |
43 | u32 bdfn) | 146 | u32 bdfn) |
@@ -283,4 +386,10 @@ void __init pnv_pci_init(void) | |||
283 | ppc_md.tce_free = pnv_tce_free; | 386 | ppc_md.tce_free = pnv_tce_free; |
284 | set_pci_dma_ops(&dma_iommu_ops); | 387 | set_pci_dma_ops(&dma_iommu_ops); |
285 | 388 | ||
389 | /* Configure MSIs */ | ||
390 | #ifdef CONFIG_PCI_MSI | ||
391 | ppc_md.msi_check_device = pnv_msi_check_device; | ||
392 | ppc_md.setup_msi_irqs = pnv_setup_msi_irqs; | ||
393 | ppc_md.teardown_msi_irqs = pnv_teardown_msi_irqs; | ||
394 | #endif | ||
286 | } | 395 | } |