aboutsummaryrefslogtreecommitdiffstats
path: root/recipes/linux/linux-2.6.29/canyonlands/0001-powerpc-4xx-Add-PPC4xx-PCIe-MSI-support.patch
blob: 177b5d54a1f548ff7f3ee8bad935ebc0c02ddb15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
From aa1671e0b1a5b72dbf714a1c13cb9850f9ecaee7 Mon Sep 17 00:00:00 2001
From: Stefan Roese <sr@denx.de>
Date: Wed, 10 Sep 2008 06:02:17 +0200
Subject: [PATCH] powerpc/4xx: Add PPC4xx PCIe MSI support

This MSI driver can be used on all PCIe enabled PPC4xx variants.
This is currently 405EX, 440SPe and 460EX/GT.

This driver version is a testing version and no release. It still
has some known problems which need to be solved:

- Longshine LCS-8337TXR
  MSI's are successfully generated on 405EX (Kilauea) and 460EX
  (Canyonlands). But the MSI generation stops at some time and
  not further MSI's are generated anymore.

- Intel PRO/1000 PT Desktop
  No MSI is generated at all

Signed-off-by: Stefan Roese <sr@denx.de>
---
 arch/powerpc/boot/dts/canyonlands.dts |   25 +++
 arch/powerpc/boot/dts/katmai.dts      |   12 +
 arch/powerpc/boot/dts/kilauea.dts     |   21 ++
 arch/powerpc/include/asm/dcr-regs.h   |    5 +
 arch/powerpc/sysdev/Makefile          |    3 +-
 arch/powerpc/sysdev/ppc4xx_msi.c      |  358 +++++++++++++++++++++++++++++++++
 arch/powerpc/sysdev/ppc4xx_pci.c      |   46 ++++-
 arch/powerpc/sysdev/ppc4xx_pci.h      |   11 +
 8 files changed, 479 insertions(+), 2 deletions(-)
 create mode 100644 arch/powerpc/sysdev/ppc4xx_msi.c

diff --git a/arch/powerpc/boot/dts/canyonlands.dts b/arch/powerpc/boot/dts/canyonlands.dts
index 79fe412..5c8b419 100644
--- a/arch/powerpc/boot/dts/canyonlands.dts
+++ b/arch/powerpc/boot/dts/canyonlands.dts
@@ -111,6 +111,11 @@
 		ranges;
 		clock-frequency = <0>; /* Filled in by U-Boot */
 
+		MQ0: memqueue {
+			compatible = "ibm,mq-460ex";
+			dcr-reg = <0x040 0x011>;
+		};
+
 		SDRAM0: sdram {
 			compatible = "ibm,sdram-460ex", "ibm,sdram-405gp";
 			dcr-reg = <0x010 0x002>;
@@ -395,6 +400,8 @@
 				0x0 0x0 0x0 0x2 &UIC3 0xd 0x4 /* swizzled int B */
 				0x0 0x0 0x0 0x3 &UIC3 0xe 0x4 /* swizzled int C */
 				0x0 0x0 0x0 0x4 &UIC3 0xf 0x4 /* swizzled int D */>;
+
+			mq-device = <&MQ0>;
 		};
 
 		PCIE1: pciex@d20000000 {
@@ -436,6 +443,24 @@
 				0x0 0x0 0x0 0x2 &UIC3 0x11 0x4 /* swizzled int B */
 				0x0 0x0 0x0 0x3 &UIC3 0x12 0x4 /* swizzled int C */
 				0x0 0x0 0x0 0x4 &UIC3 0x13 0x4 /* swizzled int D */>;
+
+			mq-device = <&MQ0>;
+		};
+
+		MSI: msi@c10000000 {
+			compatible = "ibm,ppc4xx-msi-460ex", "ibm,ppc4xx-msi";
+			reg = <0xc 0x10000000 0x100>;
+			sdr-base = <0x36c>;
+			interrupts =   <24 1
+					25 1
+					26 1
+					27 1
+					28 1
+					29 1
+					30 1
+					31 1>;
+			interrupt-parent = <&UIC3>;
+			mq-device = <&MQ0>;
 		};
 	};
 };
diff --git a/arch/powerpc/boot/dts/katmai.dts b/arch/powerpc/boot/dts/katmai.dts
index 077819b..b70bdcb 100644
--- a/arch/powerpc/boot/dts/katmai.dts
+++ b/arch/powerpc/boot/dts/katmai.dts
@@ -392,6 +392,18 @@
 				0x0 0x0 0x0 0x3 &UIC3 0xa 0x4 /* swizzled int C */
 				0x0 0x0 0x0 0x4 &UIC3 0xb 0x4 /* swizzled int D */>;
 		};
+
+		MSI: msi@400300000 {
+			compatible = "ibm,ppc4xx-msi-440spe", "ibm,ppc4xx-msi";
+			reg = <0x4 0x00300000 0x100>;
+			sdr-base = <0x3b0>;
+			/* test-only: right only 4 UIC interrupts are mapped on 440SPe */
+			interrupts =   <12 1
+					13 1
+					14 1
+					15 1>;
+			interrupt-parent = <&UIC0>;
+		};
 	};
 
 	chosen {
diff --git a/arch/powerpc/boot/dts/kilauea.dts b/arch/powerpc/boot/dts/kilauea.dts
index dececc4..56fad7c 100644
--- a/arch/powerpc/boot/dts/kilauea.dts
+++ b/arch/powerpc/boot/dts/kilauea.dts
@@ -342,5 +342,26 @@
 				0x0 0x0 0x0 0x3 &UIC2 0xd 0x4 /* swizzled int C */
 				0x0 0x0 0x0 0x4 &UIC2 0xe 0x4 /* swizzled int D */>;
 		};
+
+		MSI: msi@0ef620000 {
+			compatible = "ibm,ppc4xx-msi-405ex", "ibm,ppc4xx-msi";
+			reg = <0xef620000 0x100>;
+			sdr-base = <0x4b0>;
+			interrupts =   <15 1
+					16 1
+					17 1
+					18 1
+					19 1
+					20 1
+					21 1
+					22 1
+					23 1
+					24 1
+					25 1
+					26 1
+					27 1
+					28 1>;
+			interrupt-parent = <&UIC2>;
+		};
 	};
 };
diff --git a/arch/powerpc/include/asm/dcr-regs.h b/arch/powerpc/include/asm/dcr-regs.h
index 828e3aa..c69dfc2 100644
--- a/arch/powerpc/include/asm/dcr-regs.h
+++ b/arch/powerpc/include/asm/dcr-regs.h
@@ -157,4 +157,9 @@
 #define  L2C_SNP_SSR_32G	0x0000f000
 #define  L2C_SNP_ESR		0x00000800
 
+/*
+ * Memory Queue Modules DCR offsets
+ */
+#define MQ0_BAUH		0x10
+
 #endif /* __DCR_REGS_H__ */
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 5afce11..034a989 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -6,6 +6,7 @@ mpic-msi-obj-$(CONFIG_PCI_MSI)	+= mpic_msi.o mpic_u3msi.o mpic_pasemi_msi.o
 obj-$(CONFIG_MPIC)		+= mpic.o $(mpic-msi-obj-y)
 fsl-msi-obj-$(CONFIG_PCI_MSI)	+= fsl_msi.o
 obj-$(CONFIG_PPC_MSI_BITMAP)	+= msi_bitmap.o
+ppc4xx-msi-obj-$(CONFIG_PCI_MSI) += ppc4xx_msi.o
 
 obj-$(CONFIG_PPC_MPC106)	+= grackle.o
 obj-$(CONFIG_PPC_DCR_NATIVE)	+= dcr-low.o
@@ -35,7 +36,7 @@ obj-$(CONFIG_4xx_SOC)		+= ppc4xx_soc.o
 obj-$(CONFIG_XILINX_VIRTEX)	+= xilinx_intc.o
 obj-$(CONFIG_OF_RTC)		+= of_rtc.o
 ifeq ($(CONFIG_PCI),y)
-obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
+obj-$(CONFIG_4xx)		+= ppc4xx_pci.o $(ppc4xx-msi-obj-y)
 endif
 obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o
 
diff --git a/arch/powerpc/sysdev/ppc4xx_msi.c b/arch/powerpc/sysdev/ppc4xx_msi.c
new file mode 100644
index 0000000..7c95499
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_msi.c
@@ -0,0 +1,358 @@
+/*
+ * IBM/AMCC PPC4xx PCIe MSI handling
+ *
+ * Copyright 2008 Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * Loosly based on a PPC4xx MSI version posted to linuxppc-dev from
+ * Preetesh Parekh <pparekh@amcc.com>
+ *
+ * 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.
+ */
+
+#undef DEBUG
+
+#include <linux/irq.h>
+#include <linux/bootmem.h>
+#include <linux/bitmap.h>
+#include <linux/msi.h>
+#include <linux/pci.h>
+#include <linux/of_platform.h>
+#include <asm/ppc-pci.h>
+
+#include <asm/dcr.h>
+#include <asm/dcr-regs.h>
+#include <asm/reg.h>
+
+#define U64_TO_U32_LOW(val)	((u32)((val) & 0x00000000ffffffffULL))
+#define U64_TO_U32_HIGH(val)	((u32)((val) >> 32))
+
+#define RES_TO_U32_LOW(val)    \
+	((sizeof(resource_size_t) > sizeof(u32)) ? U64_TO_U32_LOW(val) : (val))
+#define RES_TO_U32_HIGH(val)   \
+	((sizeof(resource_size_t) > sizeof(u32)) ? U64_TO_U32_HIGH(val) : (0))
+
+/*
+ * Byte reversal for PEIH message handling is handled differently
+ * on 4xx PPC variants:
+ *
+ * 405EX	One bit for both directions (in- and outbound)
+ * 440SPe	No byte-reversal configuration bit at all
+ * 460EX/GT	2 bits, one for inbound and one for outbount messages
+ */
+
+/* 405EX */
+#define SDR0_PEIHS2_BREV	(0x80000000 >> 30)
+
+/* 460EX/GT */
+#define SDR0_PEIHS2_OMBR	(0x80000000 >> 29)
+#define SDR0_PEIHS2_IMBR	(0x80000000 >> 30)
+
+#define PEIH_TERMADH		0x00
+#define PEIH_TERMADL		0x08
+#define PEIH_MSIED		0x10
+#define PEIH_MSIMK		0x18
+#define PEIH_MSIASS		0x20
+#define PEIH_FLUSH0		0x30
+#define PEIH_FLUSH1		0x38
+#define PEIH_CNTRST		0x48
+
+#define PPC4XX_MSI_DATA		0x00000000
+#define PPC4XX_MSI_DATA_MASK	0xFFFFFFE0
+
+#define NR_MSI_IRQS		32
+
+struct ppc4xx_msi {
+	u32 msi_addr_lo;
+	u32 msi_addr_hi;
+	int virq[NR_MSI_IRQS];
+	int irqs;
+
+	unsigned long *irq_bitmap;
+	spinlock_t bitmap_lock;
+
+	dma_addr_t paddr;
+};
+
+static struct ppc4xx_msi *ppc4xx_msi;
+
+static int ppc4xx_msi_alloc_irqs(struct ppc4xx_msi *msi, int num)
+{
+	unsigned long flags;
+	int order = get_count_order(num);
+	int offset;
+
+	spin_lock_irqsave(&msi->bitmap_lock, flags);
+	offset = bitmap_find_free_region(msi->irq_bitmap, NR_MSI_IRQS, order);
+	spin_unlock_irqrestore(&msi->bitmap_lock, flags);
+
+	pr_debug("%s: allocated 0x%x (2^%d) at offset 0x%x\n",
+		__func__, num, order, offset);
+
+	return offset;
+}
+
+static void ppc4xx_msi_free_irqs(struct ppc4xx_msi *msi, int offset, int num)
+{
+	unsigned long flags;
+	int order = get_count_order(num);
+
+	pr_debug("%s: freeing 0x%x (2^%d) at offset 0x%x\n",
+		__func__, num, order, offset);
+
+	spin_lock_irqsave(&msi->bitmap_lock, flags);
+	bitmap_release_region(msi->irq_bitmap, offset, order);
+	spin_unlock_irqrestore(&msi->bitmap_lock, flags);
+}
+
+static int ppc4xx_msi_init_allocator(struct ppc4xx_msi *msi)
+{
+	int size = BITS_TO_LONGS(NR_MSI_IRQS) * sizeof(u32);
+
+	msi->irq_bitmap = kzalloc(size, GFP_KERNEL);
+	if (msi->irq_bitmap == NULL) {
+		pr_debug("%s: ENOMEM allocating allocator bitmap!\n", __func__);
+		return -ENOMEM;
+	}
+
+	bitmap_allocate_region(msi->irq_bitmap, 0, get_count_order(NR_MSI_IRQS));
+	ppc4xx_msi_free_irqs(msi, 0, NR_MSI_IRQS);
+
+	return 0;
+}
+
+static int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
+{
+	struct msi_desc *entry;
+	struct msi_msg msg;
+	int msi_irq;
+	struct ppc4xx_msi *msi = ppc4xx_msi;
+	int rc;
+
+	msg.address_hi = ppc4xx_msi->msi_addr_hi;
+	msg.address_lo = ppc4xx_msi->msi_addr_lo;
+
+	list_for_each_entry(entry, &dev->msi_list, list) {
+		msi_irq = ppc4xx_msi_alloc_irqs(msi, 1);
+		if ((msi_irq < 0) || (msi_irq >= msi->irqs)) {
+			pr_debug("%s: fail allocating msi interrupt\n", __func__);
+			rc = -ENOSPC;
+			goto out_free;
+		}
+
+		set_irq_msi(ppc4xx_msi->virq[msi_irq], entry);
+		pr_debug("%s: allocated virq %d (hw %d) addr 0x%08x\n",
+			 __func__, ppc4xx_msi->virq[msi_irq], msi_irq, msg.address_lo);
+
+		/* Write message to PCI device */
+		msg.data = PPC4XX_MSI_DATA | msi_irq;
+		write_msi_msg(ppc4xx_msi->virq[msi_irq], &msg);
+	}
+
+	return 0;
+
+out_free:
+	return rc;
+}
+
+static int ppc4xx_msi_check_device(struct pci_dev *pdev, int nvec, int type)
+{
+	if (type == PCI_CAP_ID_MSIX)
+		pr_debug("ppc4xx_msi: MSI-X untested, trying anyway\n");
+
+	return 0;
+}
+
+static void ppc4xx_teardown_msi_irqs(struct pci_dev *dev)
+{
+	struct msi_desc *entry;
+	struct ppc4xx_msi *msi = ppc4xx_msi;
+
+	list_for_each_entry(entry, &dev->msi_list, list) {
+		if (entry->irq == NO_IRQ)
+			continue;
+
+		pr_debug("%s: freeing virq %d\n", __func__, entry->irq);
+		set_irq_msi(entry->irq, NULL);
+		ppc4xx_msi_free_irqs(msi, entry->irq, 1);
+		irq_dispose_mapping(entry->irq);
+	}
+
+	return;
+}
+
+static int __devinit ppc4xx_of_msi_probe(struct of_device *dev,
+					 const struct of_device_id *match)
+{
+	struct device_node *np = dev->node;
+	struct device_node *mq_np;
+	struct resource res;
+	struct ppc4xx_msi *msi = NULL;
+	void __iomem *peih_regs = NULL;
+	void *vaddr = NULL;
+	const u32 *pval;
+	u32 sdr_base;
+	int count;
+	int i;
+	int rc;
+	u64 msi_addr = 0;
+	u32 val = 0;
+
+	printk(KERN_INFO "Setting up PPC4xx MSI support\n");
+
+	msi = kzalloc(sizeof(struct ppc4xx_msi), GFP_KERNEL);
+	if (!msi) {
+		printk(KERN_ERR "No memory for MSI structure!\n");
+		rc = -ENOMEM;
+		goto error_out;
+	}
+
+	/* Fetch PCIe interrupt handler registers address */
+	if (of_address_to_resource(np, 0, &res)) {
+		printk(KERN_ERR "%s: Can't get PCI-E interrupt handler space!\n",
+		       np->full_name);
+		rc = -ENOMEM;
+		goto error_out;
+	}
+
+	peih_regs = ioremap(res.start, res.end - res.start + 1);
+	if (!peih_regs) {
+		printk(KERN_ERR "%s: ioremap failed!\n", np->full_name);
+		rc = -ENOMEM;
+		goto error_out;
+	}
+
+	pval = of_get_property(np, "sdr-base", NULL);
+	if (pval == NULL) {
+		printk(KERN_ERR "%s: Missing sdr-base!\n", np->full_name);
+		rc = -ENOMEM;
+		goto error_out;
+	}
+	sdr_base = *pval;
+
+	/* Set byte reversal bit(s) if necessary */
+	if (of_device_is_compatible(np, "ibm,ppc4xx-msi-405ex"))
+		val = SDR0_PEIHS2_BREV;
+	if ((of_device_is_compatible(np, "ibm,ppc4xx-msi-460ex")) ||
+	    (of_device_is_compatible(np, "ibm,ppc4xx-msi-460gt")))
+		val = SDR0_PEIHS2_IMBR | SDR0_PEIHS2_OMBR;
+
+	/* Set base address for PEIH */
+	mtdcri(SDR0, sdr_base + 0, RES_TO_U32_HIGH(res.start));
+	mtdcri(SDR0, sdr_base + 1, RES_TO_U32_LOW(res.start) | val);
+	pr_debug("%s: MSI PEIH physical address at %llx\n", __func__, (u64)res.start);
+
+	/*
+	 * MSI termintaion address needs to be located in an local
+	 * area mapped to the PCIe bus via a PIM (PCI Inbound Message Window).
+	 * Only this way the accesses get forwarded to the PLB where they are
+	 * decoded.
+	 */
+	vaddr = dma_alloc_coherent(&dev->dev, PAGE_SIZE, &msi->paddr, GFP_KERNEL);
+
+	/*
+	 * On 460EX/GT the PEIH (PCIe Interrupt Handler) logic is implemented
+	 * on the HB (High Bandwidth) segment of the PLB. This implies that the
+	 * target address must reside in the HB segment range.
+	 * The DCR MQ0_BAUH (PLB Base Address, upper 32 bits (HB)) configures
+	 * the offset for this HB access window. The optional "mq-device"
+	 * points to the Memory-Queue Module device node, which configures
+	 * the base address of the HB PLB segment.
+	 */
+	pval = of_get_property(np, "mq-device", NULL);
+	if (pval) {
+		mq_np = of_find_node_by_phandle(*pval);
+		if (mq_np) {
+			pval = of_get_property(mq_np, "dcr-reg", NULL);
+			if (pval)
+				msi_addr = (u64)mfdcr(*pval + MQ0_BAUH) << 32;
+		}
+	}
+
+	/* Now add physical address of the cache coherent area */
+	msi_addr += msi->paddr;
+	msi->msi_addr_hi = U64_TO_U32_HIGH(msi_addr);
+	msi->msi_addr_lo = U64_TO_U32_LOW(msi_addr);
+	pr_debug("%s: MSI termination address: vaddr=%p paddr=%llx\n",
+		 __func__, vaddr, msi_addr);
+
+	/* Progam the Interrupt handler Termination addr registers */
+	out_be32(peih_regs + PEIH_TERMADH, msi->msi_addr_hi);
+	out_be32(peih_regs + PEIH_TERMADL, msi->msi_addr_lo);
+
+	/* Program MSI Expected data and Mask bits */
+	out_be32(peih_regs + PEIH_MSIED, PPC4XX_MSI_DATA);
+	out_be32(peih_regs + PEIH_MSIMK, PPC4XX_MSI_DATA_MASK);
+
+	rc = ppc4xx_msi_init_allocator(msi);
+	if (rc) {
+		printk(KERN_ERR "Error allocating MSI bitmap!\n");
+		goto error_out;
+	}
+
+	pval = of_get_property(np, "interrupts", &count);
+	if (!pval) {
+		printk(KERN_ERR "No interrupts property found on %s!\n",
+		       np->full_name);
+		rc = -ENODEV;
+		goto error_out;
+	}
+	if (count % 8 != 0) {
+		printk(KERN_ERR "Malformed interrupts property on %s!\n",
+		       np->full_name);
+		rc = -EINVAL;
+		goto error_out;
+	}
+
+	count /= sizeof(u32);
+	for (i = 0; i < count / 2; i++) {
+		msi->virq[i] = irq_of_parse_and_map(np, i);
+		pr_debug("%s: virq[%d] = %d\n", __func__, i, msi->virq[i]);
+	}
+
+	iounmap(peih_regs);
+
+	msi->irqs = count;
+	ppc4xx_msi = msi;
+
+	WARN_ON(ppc_md.setup_msi_irqs);
+	ppc_md.setup_msi_irqs = ppc4xx_setup_msi_irqs;
+	ppc_md.teardown_msi_irqs = ppc4xx_teardown_msi_irqs;
+	ppc_md.msi_check_device = ppc4xx_msi_check_device;
+
+	return 0;
+
+error_out:
+	if (vaddr)
+		dma_free_coherent(&dev->dev, PAGE_SIZE, vaddr, msi->paddr);
+
+	if (peih_regs)
+		iounmap(peih_regs);
+
+	if (msi)
+		kfree(msi);
+
+	return rc;
+}
+
+static const struct of_device_id ppc4xx_of_msi_ids[] = {
+	{
+		.compatible = "ibm,ppc4xx-msi",
+	},
+	{}
+};
+
+static struct of_platform_driver ppc4xx_of_msi_driver = {
+	.name = "ibm-msi",
+	.match_table = ppc4xx_of_msi_ids,
+	.probe = ppc4xx_of_msi_probe,
+};
+
+static __init int ppc4xx_of_msi_init(void)
+{
+	return of_register_platform_driver(&ppc4xx_of_msi_driver);
+}
+
+subsys_initcall(ppc4xx_of_msi_init);
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.c b/arch/powerpc/sysdev/ppc4xx_pci.c
index d3e4d61..03e5c63 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.c
+++ b/arch/powerpc/sysdev/ppc4xx_pci.c
@@ -1393,6 +1393,10 @@ static void __init ppc4xx_configure_pciex_PIMs(struct ppc4xx_pciex_port *port,
 {
 	resource_size_t size = res->end - res->start + 1;
 	u64 sa;
+	struct device_node *np = port->node;
+	struct device_node *mq_np;
+	const u32 *pval;
+	u32 bauh = 0;
 
 	if (port->endpoint) {
 		resource_size_t ep_addr = 0;
@@ -1442,10 +1446,50 @@ static void __init ppc4xx_configure_pciex_PIMs(struct ppc4xx_pciex_port *port,
 
 		out_le32(mbase + PCI_BASE_ADDRESS_0, RES_TO_U32_LOW(res->start));
 		out_le32(mbase + PCI_BASE_ADDRESS_1, RES_TO_U32_HIGH(res->start));
+
+		/*
+		 * For MSI support some 4xx platforms (e.g. 460EX/GT) need
+		 * to configure a 2nd PIM to enable access to the HB PLB
+		 * segment where the PEIH MSI termination address is located
+		 * and snooped from the PEIH.
+		 * The optional "mq-device" points to the Memory-Queue Module
+		 * device node, which configures the base address of the HB PLB
+		 * segment.
+		 */
+		pval = of_get_property(np, "mq-device", NULL);
+		if (pval) {
+			mq_np = of_find_node_by_phandle(*pval);
+			if (mq_np) {
+				pval = of_get_property(mq_np, "dcr-reg", NULL);
+				if (pval)
+					bauh = mfdcr(*pval + MQ0_BAUH);
+			}
+		}
+
+		if (bauh) {
+			out_le32(mbase + PECFG_BAR2HMPA, RES_TO_U32_HIGH(sa));
+			out_le32(mbase + PECFG_BAR2LMPA, RES_TO_U32_LOW(sa));
+
+			/* The setup of the split looks weird to me ... let's see
+			 * if it works
+			 */
+			out_le32(mbase + PECFG_PIM3LAL, 0x00000000);
+			out_le32(mbase + PECFG_PIM3LAH, 0x00000000);
+			out_le32(mbase + PECFG_PIM4LAL, 0x00000000);
+			out_le32(mbase + PECFG_PIM4LAH, 0x00000000);
+			out_le32(mbase + PECFG_PIM34SAH, 0xffff0000);
+			out_le32(mbase + PECFG_PIM34SAL, 0x00000000);
+
+			out_le32(mbase + PECFG_RTBAR2L, RES_TO_U32_LOW(res->start));
+			out_le32(mbase + PECFG_RTBAR2H, RES_TO_U32_HIGH(res->start) + bauh);
+		}
 	}
 
 	/* Enable inbound mapping */
-	out_le32(mbase + PECFG_PIMEN, 0x1);
+	if (bauh == 0)
+		out_le32(mbase + PECFG_PIMEN, 0x1);
+	else
+		out_le32(mbase + PECFG_PIMEN, 0x5);
 
 	/* Enable I/O, Mem, and Busmaster cycles */
 	out_le16(mbase + PCI_COMMAND,
diff --git a/arch/powerpc/sysdev/ppc4xx_pci.h b/arch/powerpc/sysdev/ppc4xx_pci.h
index d04e40b..b1d7d31 100644
--- a/arch/powerpc/sysdev/ppc4xx_pci.h
+++ b/arch/powerpc/sysdev/ppc4xx_pci.h
@@ -398,6 +398,14 @@
 #define PECFG_PIM1LAH		0x34c
 #define PECFG_PIM01SAL		0x350
 #define PECFG_PIM01SAH		0x354
+#define PECFG_PIM2LAL		0x358
+#define PECFG_PIM2LAH		0x35c
+#define PECFG_PIM3LAL		0x360
+#define PECFG_PIM3LAH		0x364
+#define PECFG_PIM4LAL		0x368
+#define PECFG_PIM4LAH		0x36c
+#define PECFG_PIM34SAL		0x370
+#define PECFG_PIM34SAH		0x374
 
 #define PECFG_POM0LAL		0x380
 #define PECFG_POM0LAH		0x384
@@ -406,6 +414,9 @@
 #define PECFG_POM2LAL		0x390
 #define PECFG_POM2LAH		0x394
 
+#define PECFG_RTBAR2L		0x3b8
+#define PECFG_RTBAR2H		0x3bc
+
 /* SDR Bit Mappings */
 #define PESDRx_RCSSET_HLDPLB	0x10000000
 #define PESDRx_RCSSET_RSTGU	0x01000000
-- 
1.6.2.1