aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86
diff options
context:
space:
mode:
authorJesse Barnes <jbarnes@virtuousgeek.org>2010-02-04 13:59:27 -0500
committerH. Peter Anvin <hpa@zytor.com>2010-02-24 02:14:47 -0500
commita712ffbc199849364c46e9112b93b66de08e2c26 (patch)
treeaf5c32acfcbd84a1069490ed6951e5d3bd7ff079 /arch/x86
parent4966e1affb45c5fc402969e10e979407b972a7df (diff)
x86/PCI: Moorestown PCI support
The Moorestown platform only has a few devices that actually support PCI config cycles. The rest of the devices use an in-RAM MCFG space for the purposes of device enumeration and initialization. There are a few uglies in the fake support, like BAR sizes that aren't a power of two, sizing detection, and writes to the real devices, but other than that it's pretty straightforward. Another way to think of this is not really as PCI at all, but just a table in RAM describing which devices are present, their capabilities and their offsets in MMIO space. This could have been done with a special new firmware table on this platform, but given that we do have some real PCI devices too, simply describing things in an MCFG type space was pretty simple. Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org> LKML-Reference: <43F901BD926A4E43B106BF17856F07559FB80D08@orsmsx508.amr.corp.intel.com> Signed-off-by: Jacob Pan <jacob.jun.pan@intel.com> Signed-off-by: H. Peter Anvin <hpa@zytor.com>
Diffstat (limited to 'arch/x86')
-rw-r--r--arch/x86/pci/Makefile2
-rw-r--r--arch/x86/pci/mrst.c258
2 files changed, 259 insertions, 1 deletions
diff --git a/arch/x86/pci/Makefile b/arch/x86/pci/Makefile
index 39fba37f702f..4753ebc19cae 100644
--- a/arch/x86/pci/Makefile
+++ b/arch/x86/pci/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_X86_VISWS) += visws.o
13 13
14obj-$(CONFIG_X86_NUMAQ) += numaq_32.o 14obj-$(CONFIG_X86_NUMAQ) += numaq_32.o
15 15
16obj-y += common.o early.o 16obj-y += common.o early.o mrst.o
17obj-y += amd_bus.o 17obj-y += amd_bus.o
18obj-$(CONFIG_X86_64) += bus_numa.o 18obj-$(CONFIG_X86_64) += bus_numa.o
19 19
diff --git a/arch/x86/pci/mrst.c b/arch/x86/pci/mrst.c
new file mode 100644
index 000000000000..6e9e1a35a5d7
--- /dev/null
+++ b/arch/x86/pci/mrst.c
@@ -0,0 +1,258 @@
1/*
2 * Moorestown PCI support
3 * Copyright (c) 2008 Intel Corporation
4 * Jesse Barnes <jesse.barnes@intel.com>
5 *
6 * Moorestown has an interesting PCI implementation:
7 * - configuration space is memory mapped (as defined by MCFG)
8 * - Lincroft devices also have a real, type 1 configuration space
9 * - Early Lincroft silicon has a type 1 access bug that will cause
10 * a hang if non-existent devices are accessed
11 * - some devices have the "fixed BAR" capability, which means
12 * they can't be relocated or modified; check for that during
13 * BAR sizing
14 *
15 * So, we use the MCFG space for all reads and writes, but also send
16 * Lincroft writes to type 1 space. But only read/write if the device
17 * actually exists, otherwise return all 1s for reads and bit bucket
18 * the writes.
19 */
20
21#include <linux/sched.h>
22#include <linux/pci.h>
23#include <linux/ioport.h>
24#include <linux/init.h>
25#include <linux/dmi.h>
26
27#include <asm/acpi.h>
28#include <asm/segment.h>
29#include <asm/io.h>
30#include <asm/smp.h>
31#include <asm/pci_x86.h>
32#include <asm/hw_irq.h>
33#include <asm/io_apic.h>
34
35#define PCIE_CAP_OFFSET 0x100
36
37/* Fixed BAR fields */
38#define PCIE_VNDR_CAP_ID_FIXED_BAR 0x00 /* Fixed BAR (TBD) */
39#define PCI_FIXED_BAR_0_SIZE 0x04
40#define PCI_FIXED_BAR_1_SIZE 0x08
41#define PCI_FIXED_BAR_2_SIZE 0x0c
42#define PCI_FIXED_BAR_3_SIZE 0x10
43#define PCI_FIXED_BAR_4_SIZE 0x14
44#define PCI_FIXED_BAR_5_SIZE 0x1c
45
46/**
47 * fixed_bar_cap - return the offset of the fixed BAR cap if found
48 * @bus: PCI bus
49 * @devfn: device in question
50 *
51 * Look for the fixed BAR cap on @bus and @devfn, returning its offset
52 * if found or 0 otherwise.
53 */
54static int fixed_bar_cap(struct pci_bus *bus, unsigned int devfn)
55{
56 int pos;
57 u32 pcie_cap = 0, cap_data;
58
59 pos = PCIE_CAP_OFFSET;
60 while (pos) {
61 if (raw_pci_ext_ops->read(pci_domain_nr(bus), bus->number,
62 devfn, pos, 4, &pcie_cap))
63 return 0;
64
65 if (pcie_cap == 0xffffffff)
66 return 0;
67
68 if (PCI_EXT_CAP_ID(pcie_cap) == PCI_EXT_CAP_ID_VNDR) {
69 raw_pci_ext_ops->read(pci_domain_nr(bus), bus->number,
70 devfn, pos + 4, 4, &cap_data);
71 if ((cap_data & 0xffff) == PCIE_VNDR_CAP_ID_FIXED_BAR)
72 return pos;
73 }
74
75 pos = pcie_cap >> 20;
76 }
77
78 return 0;
79}
80
81static int pci_device_update_fixed(struct pci_bus *bus, unsigned int devfn,
82 int reg, int len, u32 val, int offset)
83{
84 u32 size;
85 unsigned int domain, busnum;
86 int bar = (reg - PCI_BASE_ADDRESS_0) >> 2;
87
88 domain = pci_domain_nr(bus);
89 busnum = bus->number;
90
91 if (val == ~0 && len == 4) {
92 unsigned long decode;
93
94 raw_pci_ext_ops->read(domain, busnum, devfn,
95 offset + 8 + (bar * 4), 4, &size);
96
97 /* Turn the size into a decode pattern for the sizing code */
98 if (size) {
99 decode = size - 1;
100 decode |= decode >> 1;
101 decode |= decode >> 2;
102 decode |= decode >> 4;
103 decode |= decode >> 8;
104 decode |= decode >> 16;
105 decode++;
106 decode = ~(decode - 1);
107 } else {
108 decode = ~0;
109 }
110
111 /*
112 * If val is all ones, the core code is trying to size the reg,
113 * so update the mmconfig space with the real size.
114 *
115 * Note: this assumes the fixed size we got is a power of two.
116 */
117 return raw_pci_ext_ops->write(domain, busnum, devfn, reg, 4,
118 decode);
119 }
120
121 /* This is some other kind of BAR write, so just do it. */
122 return raw_pci_ext_ops->write(domain, busnum, devfn, reg, len, val);
123}
124
125/**
126 * type1_access_ok - check whether to use type 1
127 * @bus: bus number
128 * @devfn: device & function in question
129 *
130 * If the bus is on a Lincroft chip and it exists, or is not on a Lincroft at
131 * all, the we can go ahead with any reads & writes. If it's on a Lincroft,
132 * but doesn't exist, avoid the access altogether to keep the chip from
133 * hanging.
134 */
135static bool type1_access_ok(unsigned int bus, unsigned int devfn, int reg)
136{
137 /* This is a workaround for A0 LNC bug where PCI status register does
138 * not have new CAP bit set. can not be written by SW either.
139 *
140 * PCI header type in real LNC indicates a single function device, this
141 * will prevent probing other devices under the same function in PCI
142 * shim. Therefore, use the header type in shim instead.
143 */
144 if (reg >= 0x100 || reg == PCI_STATUS || reg == PCI_HEADER_TYPE)
145 return 0;
146 if (bus == 0 && (devfn == PCI_DEVFN(2, 0) || devfn == PCI_DEVFN(0, 0)))
147 return 1;
148 return 0; /* langwell on others */
149}
150
151static int pci_read(struct pci_bus *bus, unsigned int devfn, int where,
152 int size, u32 *value)
153{
154 if (type1_access_ok(bus->number, devfn, where))
155 return pci_direct_conf1.read(pci_domain_nr(bus), bus->number,
156 devfn, where, size, value);
157 return raw_pci_ext_ops->read(pci_domain_nr(bus), bus->number,
158 devfn, where, size, value);
159}
160
161static int pci_write(struct pci_bus *bus, unsigned int devfn, int where,
162 int size, u32 value)
163{
164 int offset;
165
166 /* On MRST, there is no PCI ROM BAR, this will cause a subsequent read
167 * to ROM BAR return 0 then being ignored.
168 */
169 if (where == PCI_ROM_ADDRESS)
170 return 0;
171
172 /*
173 * Devices with fixed BARs need special handling:
174 * - BAR sizing code will save, write ~0, read size, restore
175 * - so writes to fixed BARs need special handling
176 * - other writes to fixed BAR devices should go through mmconfig
177 */
178 offset = fixed_bar_cap(bus, devfn);
179 if (offset &&
180 (where >= PCI_BASE_ADDRESS_0 && where <= PCI_BASE_ADDRESS_5)) {
181 return pci_device_update_fixed(bus, devfn, where, size, value,
182 offset);
183 }
184
185 /*
186 * On Moorestown update both real & mmconfig space
187 * Note: early Lincroft silicon can't handle type 1 accesses to
188 * non-existent devices, so just eat the write in that case.
189 */
190 if (type1_access_ok(bus->number, devfn, where))
191 return pci_direct_conf1.write(pci_domain_nr(bus), bus->number,
192 devfn, where, size, value);
193 return raw_pci_ext_ops->write(pci_domain_nr(bus), bus->number, devfn,
194 where, size, value);
195}
196
197static int mrst_pci_irq_enable(struct pci_dev *dev)
198{
199 u8 pin;
200 struct io_apic_irq_attr irq_attr;
201
202 pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
203
204 /* MRST only have IOAPIC, the PCI irq lines are 1:1 mapped to
205 * IOAPIC RTE entries, so we just enable RTE for the device.
206 */
207 irq_attr.ioapic = mp_find_ioapic(dev->irq);
208 irq_attr.ioapic_pin = dev->irq;
209 irq_attr.trigger = 1; /* level */
210 irq_attr.polarity = 1; /* active low */
211 io_apic_set_pci_routing(&dev->dev, dev->irq, &irq_attr);
212
213 return 0;
214}
215
216struct pci_ops pci_mrst_ops = {
217 .read = pci_read,
218 .write = pci_write,
219};
220
221/**
222 * pci_mrst_init - installs pci_mrst_ops
223 *
224 * Moorestown has an interesting PCI implementation (see above).
225 * Called when the early platform detection installs it.
226 */
227int __init pci_mrst_init(void)
228{
229 printk(KERN_INFO "Moorestown platform detected, using MRST PCI ops\n");
230 pci_mmcfg_late_init();
231 pcibios_enable_irq = mrst_pci_irq_enable;
232 pci_root_ops = pci_mrst_ops;
233 /* Continue with standard init */
234 return 1;
235}
236
237/*
238 * Langwell devices reside at fixed offsets, don't try to move them.
239 */
240static void __devinit pci_fixed_bar_fixup(struct pci_dev *dev)
241{
242 unsigned long offset;
243 u32 size;
244 int i;
245
246 /* Fixup the BAR sizes for fixed BAR devices and make them unmoveable */
247 offset = fixed_bar_cap(dev->bus, dev->devfn);
248 if (!offset || PCI_DEVFN(2, 0) == dev->devfn ||
249 PCI_DEVFN(2, 2) == dev->devfn)
250 return;
251
252 for (i = 0; i < PCI_ROM_RESOURCE; i++) {
253 pci_read_config_dword(dev, offset + 8 + (i * 4), &size);
254 dev->resource[i].end = dev->resource[i].start + size - 1;
255 dev->resource[i].flags |= IORESOURCE_PCI_FIXED;
256 }
257}
258DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_fixed_bar_fixup);