aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/cavium-octeon/msi.c
diff options
context:
space:
mode:
authorDavid Daney <ddaney@caviumnetworks.com>2009-04-23 20:44:38 -0400
committerRalf Baechle <ralf@linux-mips.org>2009-06-17 06:06:25 -0400
commite8635b484f644c7873e6091f15330c49396f2cbc (patch)
treea12d2a4f22cd29d7b88c4df251eced3b43ea47a7 /arch/mips/cavium-octeon/msi.c
parent8860fb8210b06720d5fe3c23b2803a211c26feb1 (diff)
MIPS: Add Cavium OCTEON PCI support.
This patch adds support for PCI and PCIe to the base Cavium OCTEON processor support. Signed-off-by: David Daney <ddaney@caviumnetworks.com> Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/cavium-octeon/msi.c')
-rw-r--r--arch/mips/cavium-octeon/msi.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/arch/mips/cavium-octeon/msi.c b/arch/mips/cavium-octeon/msi.c
new file mode 100644
index 000000000000..964b03b75a8f
--- /dev/null
+++ b/arch/mips/cavium-octeon/msi.c
@@ -0,0 +1,288 @@
1/*
2 * This file is subject to the terms and conditions of the GNU General Public
3 * License. See the file "COPYING" in the main directory of this archive
4 * for more details.
5 *
6 * Copyright (C) 2005-2007 Cavium Networks
7 */
8#include <linux/kernel.h>
9#include <linux/init.h>
10#include <linux/msi.h>
11#include <linux/spinlock.h>
12#include <linux/interrupt.h>
13
14#include <asm/octeon/octeon.h>
15#include <asm/octeon/cvmx-npi-defs.h>
16#include <asm/octeon/cvmx-pci-defs.h>
17#include <asm/octeon/cvmx-npei-defs.h>
18#include <asm/octeon/cvmx-pexp-defs.h>
19
20#include "pci-common.h"
21
22/*
23 * Each bit in msi_free_irq_bitmask represents a MSI interrupt that is
24 * in use.
25 */
26static uint64_t msi_free_irq_bitmask;
27
28/*
29 * Each bit in msi_multiple_irq_bitmask tells that the device using
30 * this bit in msi_free_irq_bitmask is also using the next bit. This
31 * is used so we can disable all of the MSI interrupts when a device
32 * uses multiple.
33 */
34static uint64_t msi_multiple_irq_bitmask;
35
36/*
37 * This lock controls updates to msi_free_irq_bitmask and
38 * msi_multiple_irq_bitmask.
39 */
40static DEFINE_SPINLOCK(msi_free_irq_bitmask_lock);
41
42
43/**
44 * Called when a driver request MSI interrupts instead of the
45 * legacy INT A-D. This routine will allocate multiple interrupts
46 * for MSI devices that support them. A device can override this by
47 * programming the MSI control bits [6:4] before calling
48 * pci_enable_msi().
49 *
50 * @param dev Device requesting MSI interrupts
51 * @param desc MSI descriptor
52 *
53 * Returns 0 on success.
54 */
55int arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc)
56{
57 struct msi_msg msg;
58 uint16_t control;
59 int configured_private_bits;
60 int request_private_bits;
61 int irq;
62 int irq_step;
63 uint64_t search_mask;
64
65 /*
66 * Read the MSI config to figure out how many IRQs this device
67 * wants. Most devices only want 1, which will give
68 * configured_private_bits and request_private_bits equal 0.
69 */
70 pci_read_config_word(dev, desc->msi_attrib.pos + PCI_MSI_FLAGS,
71 &control);
72
73 /*
74 * If the number of private bits has been configured then use
75 * that value instead of the requested number. This gives the
76 * driver the chance to override the number of interrupts
77 * before calling pci_enable_msi().
78 */
79 configured_private_bits = (control & PCI_MSI_FLAGS_QSIZE) >> 4;
80 if (configured_private_bits == 0) {
81 /* Nothing is configured, so use the hardware requested size */
82 request_private_bits = (control & PCI_MSI_FLAGS_QMASK) >> 1;
83 } else {
84 /*
85 * Use the number of configured bits, assuming the
86 * driver wanted to override the hardware request
87 * value.
88 */
89 request_private_bits = configured_private_bits;
90 }
91
92 /*
93 * The PCI 2.3 spec mandates that there are at most 32
94 * interrupts. If this device asks for more, only give it one.
95 */
96 if (request_private_bits > 5)
97 request_private_bits = 0;
98
99try_only_one:
100 /*
101 * The IRQs have to be aligned on a power of two based on the
102 * number being requested.
103 */
104 irq_step = 1 << request_private_bits;
105
106 /* Mask with one bit for each IRQ */
107 search_mask = (1 << irq_step) - 1;
108
109 /*
110 * We're going to search msi_free_irq_bitmask_lock for zero
111 * bits. This represents an MSI interrupt number that isn't in
112 * use.
113 */
114 spin_lock(&msi_free_irq_bitmask_lock);
115 for (irq = 0; irq < 64; irq += irq_step) {
116 if ((msi_free_irq_bitmask & (search_mask << irq)) == 0) {
117 msi_free_irq_bitmask |= search_mask << irq;
118 msi_multiple_irq_bitmask |= (search_mask >> 1) << irq;
119 break;
120 }
121 }
122 spin_unlock(&msi_free_irq_bitmask_lock);
123
124 /* Make sure the search for available interrupts didn't fail */
125 if (irq >= 64) {
126 if (request_private_bits) {
127 pr_err("arch_setup_msi_irq: Unable to find %d free "
128 "interrupts, trying just one",
129 1 << request_private_bits);
130 request_private_bits = 0;
131 goto try_only_one;
132 } else
133 panic("arch_setup_msi_irq: Unable to find a free MSI "
134 "interrupt");
135 }
136
137 /* MSI interrupts start at logical IRQ OCTEON_IRQ_MSI_BIT0 */
138 irq += OCTEON_IRQ_MSI_BIT0;
139
140 switch (octeon_dma_bar_type) {
141 case OCTEON_DMA_BAR_TYPE_SMALL:
142 /* When not using big bar, Bar 0 is based at 128MB */
143 msg.address_lo =
144 ((128ul << 20) + CVMX_PCI_MSI_RCV) & 0xffffffff;
145 msg.address_hi = ((128ul << 20) + CVMX_PCI_MSI_RCV) >> 32;
146 case OCTEON_DMA_BAR_TYPE_BIG:
147 /* When using big bar, Bar 0 is based at 0 */
148 msg.address_lo = (0 + CVMX_PCI_MSI_RCV) & 0xffffffff;
149 msg.address_hi = (0 + CVMX_PCI_MSI_RCV) >> 32;
150 break;
151 case OCTEON_DMA_BAR_TYPE_PCIE:
152 /* When using PCIe, Bar 0 is based at 0 */
153 /* FIXME CVMX_NPEI_MSI_RCV* other than 0? */
154 msg.address_lo = (0 + CVMX_NPEI_PCIE_MSI_RCV) & 0xffffffff;
155 msg.address_hi = (0 + CVMX_NPEI_PCIE_MSI_RCV) >> 32;
156 break;
157 default:
158 panic("arch_setup_msi_irq: Invalid octeon_dma_bar_type\n");
159 }
160 msg.data = irq - OCTEON_IRQ_MSI_BIT0;
161
162 /* Update the number of IRQs the device has available to it */
163 control &= ~PCI_MSI_FLAGS_QSIZE;
164 control |= request_private_bits << 4;
165 pci_write_config_word(dev, desc->msi_attrib.pos + PCI_MSI_FLAGS,
166 control);
167
168 set_irq_msi(irq, desc);
169 write_msi_msg(irq, &msg);
170 return 0;
171}
172
173
174/**
175 * Called when a device no longer needs its MSI interrupts. All
176 * MSI interrupts for the device are freed.
177 *
178 * @irq: The devices first irq number. There may be multple in sequence.
179 */
180void arch_teardown_msi_irq(unsigned int irq)
181{
182 int number_irqs;
183 uint64_t bitmask;
184
185 if ((irq < OCTEON_IRQ_MSI_BIT0) || (irq > OCTEON_IRQ_MSI_BIT63))
186 panic("arch_teardown_msi_irq: Attempted to teardown illegal "
187 "MSI interrupt (%d)", irq);
188 irq -= OCTEON_IRQ_MSI_BIT0;
189
190 /*
191 * Count the number of IRQs we need to free by looking at the
192 * msi_multiple_irq_bitmask. Each bit set means that the next
193 * IRQ is also owned by this device.
194 */
195 number_irqs = 0;
196 while ((irq+number_irqs < 64) &&
197 (msi_multiple_irq_bitmask & (1ull << (irq + number_irqs))))
198 number_irqs++;
199 number_irqs++;
200 /* Mask with one bit for each IRQ */
201 bitmask = (1 << number_irqs) - 1;
202 /* Shift the mask to the correct bit location */
203 bitmask <<= irq;
204 if ((msi_free_irq_bitmask & bitmask) != bitmask)
205 panic("arch_teardown_msi_irq: Attempted to teardown MSI "
206 "interrupt (%d) not in use", irq);
207
208 /* Checks are done, update the in use bitmask */
209 spin_lock(&msi_free_irq_bitmask_lock);
210 msi_free_irq_bitmask &= ~bitmask;
211 msi_multiple_irq_bitmask &= ~bitmask;
212 spin_unlock(&msi_free_irq_bitmask_lock);
213}
214
215
216/**
217 * Called by the interrupt handling code when an MSI interrupt
218 * occurs.
219 *
220 * @param cpl
221 * @param dev_id
222 *
223 * @return
224 */
225static irqreturn_t octeon_msi_interrupt(int cpl, void *dev_id)
226{
227 uint64_t msi_bits;
228 int irq;
229
230 if (octeon_dma_bar_type == OCTEON_DMA_BAR_TYPE_PCIE)
231 msi_bits = cvmx_read_csr(CVMX_PEXP_NPEI_MSI_RCV0);
232 else
233 msi_bits = cvmx_read_csr(CVMX_NPI_NPI_MSI_RCV);
234 irq = fls64(msi_bits);
235 if (irq) {
236 irq += OCTEON_IRQ_MSI_BIT0 - 1;
237 if (irq_desc[irq].action) {
238 do_IRQ(irq);
239 return IRQ_HANDLED;
240 } else {
241 pr_err("Spurious MSI interrupt %d\n", irq);
242 if (octeon_has_feature(OCTEON_FEATURE_PCIE)) {
243 /* These chips have PCIe */
244 cvmx_write_csr(CVMX_PEXP_NPEI_MSI_RCV0,
245 1ull << (irq -
246 OCTEON_IRQ_MSI_BIT0));
247 } else {
248 /* These chips have PCI */
249 cvmx_write_csr(CVMX_NPI_NPI_MSI_RCV,
250 1ull << (irq -
251 OCTEON_IRQ_MSI_BIT0));
252 }
253 }
254 }
255 return IRQ_NONE;
256}
257
258
259/**
260 * Initializes the MSI interrupt handling code
261 *
262 * @return
263 */
264int octeon_msi_initialize(void)
265{
266 int r;
267 if (octeon_has_feature(OCTEON_FEATURE_PCIE)) {
268 r = request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt,
269 IRQF_SHARED,
270 "MSI[0:63]", octeon_msi_interrupt);
271 } else if (octeon_is_pci_host()) {
272 r = request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt,
273 IRQF_SHARED,
274 "MSI[0:15]", octeon_msi_interrupt);
275 r += request_irq(OCTEON_IRQ_PCI_MSI1, octeon_msi_interrupt,
276 IRQF_SHARED,
277 "MSI[16:31]", octeon_msi_interrupt);
278 r += request_irq(OCTEON_IRQ_PCI_MSI2, octeon_msi_interrupt,
279 IRQF_SHARED,
280 "MSI[32:47]", octeon_msi_interrupt);
281 r += request_irq(OCTEON_IRQ_PCI_MSI3, octeon_msi_interrupt,
282 IRQF_SHARED,
283 "MSI[48:63]", octeon_msi_interrupt);
284 }
285 return 0;
286}
287
288subsys_initcall(octeon_msi_initialize);