diff options
Diffstat (limited to 'arch/powerpc/sysdev/mpic_pasemi_msi.c')
-rw-r--r-- | arch/powerpc/sysdev/mpic_pasemi_msi.c | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/mpic_pasemi_msi.c b/arch/powerpc/sysdev/mpic_pasemi_msi.c new file mode 100644 index 000000000000..d6bfda30ac87 --- /dev/null +++ b/arch/powerpc/sysdev/mpic_pasemi_msi.c | |||
@@ -0,0 +1,172 @@ | |||
1 | /* | ||
2 | * Copyright 2007, Olof Johansson, PA Semi | ||
3 | * | ||
4 | * Based on arch/powerpc/sysdev/mpic_u3msi.c: | ||
5 | * | ||
6 | * Copyright 2006, Segher Boessenkool, IBM Corporation. | ||
7 | * Copyright 2006-2007, Michael Ellerman, IBM Corporation. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; version 2 of the | ||
12 | * License. | ||
13 | * | ||
14 | */ | ||
15 | |||
16 | #undef DEBUG | ||
17 | |||
18 | #include <linux/irq.h> | ||
19 | #include <linux/bootmem.h> | ||
20 | #include <linux/msi.h> | ||
21 | #include <asm/mpic.h> | ||
22 | #include <asm/prom.h> | ||
23 | #include <asm/hw_irq.h> | ||
24 | #include <asm/ppc-pci.h> | ||
25 | |||
26 | #include "mpic.h" | ||
27 | |||
28 | /* Allocate 16 interrupts per device, to give an alignment of 16, | ||
29 | * since that's the size of the grouping w.r.t. affinity. If someone | ||
30 | * needs more than 32 MSI's down the road we'll have to rethink this, | ||
31 | * but it should be OK for now. | ||
32 | */ | ||
33 | #define ALLOC_CHUNK 16 | ||
34 | |||
35 | #define PASEMI_MSI_ADDR 0xfc080000 | ||
36 | |||
37 | /* A bit ugly, can we get this from the pci_dev somehow? */ | ||
38 | static struct mpic *msi_mpic; | ||
39 | |||
40 | |||
41 | static void mpic_pasemi_msi_mask_irq(unsigned int irq) | ||
42 | { | ||
43 | pr_debug("mpic_pasemi_msi_mask_irq %d\n", irq); | ||
44 | mask_msi_irq(irq); | ||
45 | mpic_mask_irq(irq); | ||
46 | } | ||
47 | |||
48 | static void mpic_pasemi_msi_unmask_irq(unsigned int irq) | ||
49 | { | ||
50 | pr_debug("mpic_pasemi_msi_unmask_irq %d\n", irq); | ||
51 | mpic_unmask_irq(irq); | ||
52 | unmask_msi_irq(irq); | ||
53 | } | ||
54 | |||
55 | static struct irq_chip mpic_pasemi_msi_chip = { | ||
56 | .shutdown = mpic_pasemi_msi_mask_irq, | ||
57 | .mask = mpic_pasemi_msi_mask_irq, | ||
58 | .unmask = mpic_pasemi_msi_unmask_irq, | ||
59 | .eoi = mpic_end_irq, | ||
60 | .set_type = mpic_set_irq_type, | ||
61 | .set_affinity = mpic_set_affinity, | ||
62 | .typename = "PASEMI-MSI ", | ||
63 | }; | ||
64 | |||
65 | static int pasemi_msi_check_device(struct pci_dev *pdev, int nvec, int type) | ||
66 | { | ||
67 | if (type == PCI_CAP_ID_MSIX) | ||
68 | pr_debug("pasemi_msi: MSI-X untested, trying anyway\n"); | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static void pasemi_msi_teardown_msi_irqs(struct pci_dev *pdev) | ||
74 | { | ||
75 | struct msi_desc *entry; | ||
76 | |||
77 | pr_debug("pasemi_msi_teardown_msi_irqs, pdev %p\n", pdev); | ||
78 | |||
79 | list_for_each_entry(entry, &pdev->msi_list, list) { | ||
80 | if (entry->irq == NO_IRQ) | ||
81 | continue; | ||
82 | |||
83 | set_irq_msi(entry->irq, NULL); | ||
84 | mpic_msi_free_hwirqs(msi_mpic, virq_to_hw(entry->irq), | ||
85 | ALLOC_CHUNK); | ||
86 | irq_dispose_mapping(entry->irq); | ||
87 | } | ||
88 | |||
89 | return; | ||
90 | } | ||
91 | |||
92 | static int pasemi_msi_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) | ||
93 | { | ||
94 | irq_hw_number_t hwirq; | ||
95 | unsigned int virq; | ||
96 | struct msi_desc *entry; | ||
97 | struct msi_msg msg; | ||
98 | u64 addr; | ||
99 | |||
100 | pr_debug("pasemi_msi_setup_msi_irqs, pdev %p nvec %d type %d\n", | ||
101 | pdev, nvec, type); | ||
102 | |||
103 | msg.address_hi = 0; | ||
104 | msg.address_lo = PASEMI_MSI_ADDR; | ||
105 | |||
106 | list_for_each_entry(entry, &pdev->msi_list, list) { | ||
107 | /* Allocate 16 interrupts for now, since that's the grouping for | ||
108 | * affinity. This can be changed later if it turns out 32 is too | ||
109 | * few MSIs for someone, but restrictions will apply to how the | ||
110 | * sources can be changed independently. | ||
111 | */ | ||
112 | hwirq = mpic_msi_alloc_hwirqs(msi_mpic, ALLOC_CHUNK); | ||
113 | if (hwirq < 0) { | ||
114 | pr_debug("pasemi_msi: failed allocating hwirq\n"); | ||
115 | return hwirq; | ||
116 | } | ||
117 | |||
118 | virq = irq_create_mapping(msi_mpic->irqhost, hwirq); | ||
119 | if (virq == NO_IRQ) { | ||
120 | pr_debug("pasemi_msi: failed mapping hwirq 0x%lx\n", hwirq); | ||
121 | mpic_msi_free_hwirqs(msi_mpic, hwirq, ALLOC_CHUNK); | ||
122 | return -ENOSPC; | ||
123 | } | ||
124 | |||
125 | /* Vector on MSI is really an offset, the hardware adds | ||
126 | * it to the value written at the magic address. So set | ||
127 | * it to 0 to remain sane. | ||
128 | */ | ||
129 | mpic_set_vector(virq, 0); | ||
130 | |||
131 | set_irq_msi(virq, entry); | ||
132 | set_irq_chip(virq, &mpic_pasemi_msi_chip); | ||
133 | set_irq_type(virq, IRQ_TYPE_EDGE_RISING); | ||
134 | |||
135 | pr_debug("pasemi_msi: allocated virq 0x%x (hw 0x%lx) addr 0x%lx\n", | ||
136 | virq, hwirq, addr); | ||
137 | |||
138 | /* Likewise, the device writes [0...511] into the target | ||
139 | * register to generate MSI [512...1023] | ||
140 | */ | ||
141 | msg.data = hwirq-0x200; | ||
142 | write_msi_msg(virq, &msg); | ||
143 | } | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | int mpic_pasemi_msi_init(struct mpic *mpic) | ||
149 | { | ||
150 | int rc; | ||
151 | |||
152 | if (!mpic->irqhost->of_node || | ||
153 | !of_device_is_compatible(mpic->irqhost->of_node, | ||
154 | "pasemi,pwrficient-openpic")) | ||
155 | return -ENODEV; | ||
156 | |||
157 | rc = mpic_msi_init_allocator(mpic); | ||
158 | if (rc) { | ||
159 | pr_debug("pasemi_msi: Error allocating bitmap!\n"); | ||
160 | return rc; | ||
161 | } | ||
162 | |||
163 | pr_debug("pasemi_msi: Registering PA Semi MPIC MSI callbacks\n"); | ||
164 | |||
165 | msi_mpic = mpic; | ||
166 | WARN_ON(ppc_md.setup_msi_irqs); | ||
167 | ppc_md.setup_msi_irqs = pasemi_msi_setup_msi_irqs; | ||
168 | ppc_md.teardown_msi_irqs = pasemi_msi_teardown_msi_irqs; | ||
169 | ppc_md.msi_check_device = pasemi_msi_check_device; | ||
170 | |||
171 | return 0; | ||
172 | } | ||