Commit | Line | Data |
---|---|---|
014da7ff BH |
1 | /* |
2 | * Copyright (C) 2006 Benjamin Herrenschmidt <benh@kernel.crashing.org> | |
3 | * IBM, Corp. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | #undef DEBUG | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/mm.h> | |
13 | #include <linux/pci.h> | |
14 | #include <asm/io.h> | |
15 | #include <asm/machdep.h> | |
16 | #include <asm/pci-bridge.h> | |
17 | #include <asm/ppc-pci.h> | |
18 | ||
19 | ||
20 | #define SPIDER_PCI_REG_BASE 0xd000 | |
21 | #define SPIDER_PCI_VCI_CNTL_STAT 0x0110 | |
22 | #define SPIDER_PCI_DUMMY_READ 0x0810 | |
23 | #define SPIDER_PCI_DUMMY_READ_BASE 0x0814 | |
24 | ||
25 | /* Undefine that to re-enable bogus prefetch | |
26 | * | |
27 | * Without that workaround, the chip will do bogus prefetch past | |
28 | * page boundary from system memory. This setting will disable that, | |
29 | * though the documentation is unclear as to the consequences of doing | |
30 | * so, either purely performances, or possible misbehaviour... It's not | |
31 | * clear wether the chip can handle unaligned accesses at all without | |
32 | * prefetching enabled. | |
33 | * | |
34 | * For now, things appear to be behaving properly with that prefetching | |
35 | * disabled and IDE, possibly because IDE isn't doing any unaligned | |
36 | * access. | |
37 | */ | |
38 | #define SPIDER_DISABLE_PREFETCH | |
39 | ||
a24e57be | 40 | #define MAX_SPIDERS 3 |
014da7ff BH |
41 | |
42 | static struct spider_pci_bus { | |
43 | void __iomem *regs; | |
44 | unsigned long mmio_start; | |
45 | unsigned long mmio_end; | |
46 | unsigned long pio_vstart; | |
47 | unsigned long pio_vend; | |
48 | } spider_pci_busses[MAX_SPIDERS]; | |
49 | static int spider_pci_count; | |
50 | ||
51 | static struct spider_pci_bus *spider_pci_find(unsigned long vaddr, | |
52 | unsigned long paddr) | |
53 | { | |
54 | int i; | |
55 | ||
56 | for (i = 0; i < spider_pci_count; i++) { | |
57 | struct spider_pci_bus *bus = &spider_pci_busses[i]; | |
58 | if (paddr && paddr >= bus->mmio_start && paddr < bus->mmio_end) | |
59 | return bus; | |
60 | if (vaddr && vaddr >= bus->pio_vstart && vaddr < bus->pio_vend) | |
61 | return bus; | |
62 | } | |
63 | return NULL; | |
64 | } | |
65 | ||
66 | static void spider_io_flush(const volatile void __iomem *addr) | |
67 | { | |
68 | struct spider_pci_bus *bus; | |
69 | int token; | |
70 | ||
71 | /* Get platform token (set by ioremap) from address */ | |
72 | token = PCI_GET_ADDR_TOKEN(addr); | |
73 | ||
74 | /* Fast path if we have a non-0 token, it indicates which bus we | |
75 | * are on. | |
76 | * | |
59c51591 | 77 | * If the token is 0, that means either that the ioremap was done |
014da7ff BH |
78 | * before we initialized this layer, or it's a PIO operation. We |
79 | * fallback to a low path in this case. Hopefully, internal devices | |
80 | * which are ioremap'ed early should use in_XX/out_XX functions | |
81 | * instead of the PCI ones and thus not suffer from the slowdown. | |
82 | * | |
83 | * Also note that currently, the workaround will not work for areas | |
84 | * that are not mapped with PTEs (bolted in the hash table). This | |
85 | * is the case for ioremaps done very early at boot (before | |
86 | * mem_init_done) and includes the mapping of the ISA IO space. | |
87 | * | |
88 | * Fortunately, none of the affected devices is expected to do DMA | |
89 | * and thus there should be no problem in practice. | |
90 | * | |
91 | * In order to improve performances, we only do the PTE search for | |
92 | * addresses falling in the PHB IO space area. That means it will | |
93 | * not work for hotplug'ed PHBs but those don't exist with Spider. | |
94 | */ | |
95 | if (token && token <= spider_pci_count) | |
96 | bus = &spider_pci_busses[token - 1]; | |
97 | else { | |
98 | unsigned long vaddr, paddr; | |
99 | pte_t *ptep; | |
100 | ||
101 | /* Fixup physical address */ | |
102 | vaddr = (unsigned long)PCI_FIX_ADDR(addr); | |
103 | ||
104 | /* Check if it's in allowed range for PIO */ | |
3d5134ee | 105 | if (vaddr < PHB_IO_BASE || vaddr > PHB_IO_END) |
014da7ff BH |
106 | return; |
107 | ||
108 | /* Try to find a PTE. If not, clear the paddr, we'll do | |
109 | * a vaddr only lookup (PIO only) | |
110 | */ | |
111 | ptep = find_linux_pte(init_mm.pgd, vaddr); | |
112 | if (ptep == NULL) | |
113 | paddr = 0; | |
114 | else | |
115 | paddr = pte_pfn(*ptep) << PAGE_SHIFT; | |
116 | ||
117 | bus = spider_pci_find(vaddr, paddr); | |
118 | if (bus == NULL) | |
119 | return; | |
120 | } | |
121 | ||
122 | /* Now do the workaround | |
123 | */ | |
124 | (void)in_be32(bus->regs + SPIDER_PCI_DUMMY_READ); | |
125 | } | |
126 | ||
127 | static u8 spider_readb(const volatile void __iomem *addr) | |
128 | { | |
129 | u8 val = __do_readb(addr); | |
130 | spider_io_flush(addr); | |
131 | return val; | |
132 | } | |
133 | ||
134 | static u16 spider_readw(const volatile void __iomem *addr) | |
135 | { | |
136 | u16 val = __do_readw(addr); | |
137 | spider_io_flush(addr); | |
138 | return val; | |
139 | } | |
140 | ||
141 | static u32 spider_readl(const volatile void __iomem *addr) | |
142 | { | |
143 | u32 val = __do_readl(addr); | |
144 | spider_io_flush(addr); | |
145 | return val; | |
146 | } | |
147 | ||
148 | static u64 spider_readq(const volatile void __iomem *addr) | |
149 | { | |
150 | u64 val = __do_readq(addr); | |
151 | spider_io_flush(addr); | |
152 | return val; | |
153 | } | |
154 | ||
155 | static u16 spider_readw_be(const volatile void __iomem *addr) | |
156 | { | |
157 | u16 val = __do_readw_be(addr); | |
158 | spider_io_flush(addr); | |
159 | return val; | |
160 | } | |
161 | ||
162 | static u32 spider_readl_be(const volatile void __iomem *addr) | |
163 | { | |
164 | u32 val = __do_readl_be(addr); | |
165 | spider_io_flush(addr); | |
166 | return val; | |
167 | } | |
168 | ||
169 | static u64 spider_readq_be(const volatile void __iomem *addr) | |
170 | { | |
171 | u64 val = __do_readq_be(addr); | |
172 | spider_io_flush(addr); | |
173 | return val; | |
174 | } | |
175 | ||
176 | static void spider_readsb(const volatile void __iomem *addr, void *buf, | |
177 | unsigned long count) | |
178 | { | |
179 | __do_readsb(addr, buf, count); | |
180 | spider_io_flush(addr); | |
181 | } | |
182 | ||
183 | static void spider_readsw(const volatile void __iomem *addr, void *buf, | |
184 | unsigned long count) | |
185 | { | |
186 | __do_readsw(addr, buf, count); | |
187 | spider_io_flush(addr); | |
188 | } | |
189 | ||
190 | static void spider_readsl(const volatile void __iomem *addr, void *buf, | |
191 | unsigned long count) | |
192 | { | |
193 | __do_readsl(addr, buf, count); | |
194 | spider_io_flush(addr); | |
195 | } | |
196 | ||
197 | static void spider_memcpy_fromio(void *dest, const volatile void __iomem *src, | |
198 | unsigned long n) | |
199 | { | |
200 | __do_memcpy_fromio(dest, src, n); | |
201 | spider_io_flush(src); | |
202 | } | |
203 | ||
204 | ||
205 | static void __iomem * spider_ioremap(unsigned long addr, unsigned long size, | |
206 | unsigned long flags) | |
207 | { | |
208 | struct spider_pci_bus *bus; | |
209 | void __iomem *res = __ioremap(addr, size, flags); | |
210 | int busno; | |
211 | ||
212 | pr_debug("spider_ioremap(0x%lx, 0x%lx, 0x%lx) -> 0x%p\n", | |
213 | addr, size, flags, res); | |
214 | ||
215 | bus = spider_pci_find(0, addr); | |
216 | if (bus != NULL) { | |
217 | busno = bus - spider_pci_busses; | |
218 | pr_debug(" found bus %d, setting token\n", busno); | |
219 | PCI_SET_ADDR_TOKEN(res, busno + 1); | |
220 | } | |
221 | pr_debug(" result=0x%p\n", res); | |
222 | ||
223 | return res; | |
224 | } | |
225 | ||
226 | static void __init spider_pci_setup_chip(struct spider_pci_bus *bus) | |
227 | { | |
228 | #ifdef SPIDER_DISABLE_PREFETCH | |
229 | u32 val = in_be32(bus->regs + SPIDER_PCI_VCI_CNTL_STAT); | |
230 | pr_debug(" PVCI_Control_Status was 0x%08x\n", val); | |
231 | out_be32(bus->regs + SPIDER_PCI_VCI_CNTL_STAT, val | 0x8); | |
232 | #endif | |
233 | ||
234 | /* Configure the dummy address for the workaround */ | |
235 | out_be32(bus->regs + SPIDER_PCI_DUMMY_READ_BASE, 0x80000000); | |
236 | } | |
237 | ||
238 | static void __init spider_pci_add_one(struct pci_controller *phb) | |
239 | { | |
240 | struct spider_pci_bus *bus = &spider_pci_busses[spider_pci_count]; | |
241 | struct device_node *np = phb->arch_data; | |
242 | struct resource rsrc; | |
243 | void __iomem *regs; | |
244 | ||
245 | if (spider_pci_count >= MAX_SPIDERS) { | |
246 | printk(KERN_ERR "Too many spider bridges, workarounds" | |
247 | " disabled for %s\n", np->full_name); | |
248 | return; | |
249 | } | |
250 | ||
251 | /* Get the registers for the beast */ | |
252 | if (of_address_to_resource(np, 0, &rsrc)) { | |
253 | printk(KERN_ERR "Failed to get registers for spider %s" | |
254 | " workarounds disabled\n", np->full_name); | |
255 | return; | |
256 | } | |
257 | ||
258 | /* Mask out some useless bits in there to get to the base of the | |
259 | * spider chip | |
260 | */ | |
261 | rsrc.start &= ~0xfffffffful; | |
262 | ||
263 | /* Map them */ | |
264 | regs = ioremap(rsrc.start + SPIDER_PCI_REG_BASE, 0x1000); | |
265 | if (regs == NULL) { | |
266 | printk(KERN_ERR "Failed to map registers for spider %s" | |
267 | " workarounds disabled\n", np->full_name); | |
268 | return; | |
269 | } | |
270 | ||
271 | spider_pci_count++; | |
272 | ||
273 | /* We assume spiders only have one MMIO resource */ | |
274 | bus->mmio_start = phb->mem_resources[0].start; | |
275 | bus->mmio_end = phb->mem_resources[0].end + 1; | |
276 | ||
277 | bus->pio_vstart = (unsigned long)phb->io_base_virt; | |
278 | bus->pio_vend = bus->pio_vstart + phb->pci_io_size; | |
279 | ||
280 | bus->regs = regs; | |
281 | ||
282 | printk(KERN_INFO "PCI: Spider MMIO workaround for %s\n",np->full_name); | |
283 | ||
284 | pr_debug(" mmio (P) = 0x%016lx..0x%016lx\n", | |
285 | bus->mmio_start, bus->mmio_end); | |
286 | pr_debug(" pio (V) = 0x%016lx..0x%016lx\n", | |
287 | bus->pio_vstart, bus->pio_vend); | |
288 | pr_debug(" regs (P) = 0x%016lx (V) = 0x%p\n", | |
289 | rsrc.start + SPIDER_PCI_REG_BASE, bus->regs); | |
290 | ||
291 | spider_pci_setup_chip(bus); | |
292 | } | |
293 | ||
294 | static struct ppc_pci_io __initdata spider_pci_io = { | |
295 | .readb = spider_readb, | |
296 | .readw = spider_readw, | |
297 | .readl = spider_readl, | |
298 | .readq = spider_readq, | |
299 | .readw_be = spider_readw_be, | |
300 | .readl_be = spider_readl_be, | |
301 | .readq_be = spider_readq_be, | |
302 | .readsb = spider_readsb, | |
303 | .readsw = spider_readsw, | |
304 | .readsl = spider_readsl, | |
305 | .memcpy_fromio = spider_memcpy_fromio, | |
306 | }; | |
307 | ||
308 | static int __init spider_pci_workaround_init(void) | |
309 | { | |
310 | struct pci_controller *phb; | |
311 | ||
312 | if (!machine_is(cell)) | |
313 | return 0; | |
314 | ||
315 | /* Find spider bridges. We assume they have been all probed | |
316 | * in setup_arch(). If that was to change, we would need to | |
317 | * update this code to cope with dynamically added busses | |
318 | */ | |
319 | list_for_each_entry(phb, &hose_list, list_node) { | |
320 | struct device_node *np = phb->arch_data; | |
e2eb6392 | 321 | const char *model = of_get_property(np, "model", NULL); |
014da7ff BH |
322 | |
323 | /* If no model property or name isn't exactly "pci", skip */ | |
324 | if (model == NULL || strcmp(np->name, "pci")) | |
325 | continue; | |
326 | /* If model is not "Spider", skip */ | |
327 | if (strcmp(model, "Spider")) | |
328 | continue; | |
329 | spider_pci_add_one(phb); | |
330 | } | |
331 | ||
332 | /* No Spider PCI found, exit */ | |
333 | if (spider_pci_count == 0) | |
334 | return 0; | |
335 | ||
336 | /* Setup IO callbacks. We only setup MMIO reads. PIO reads will | |
337 | * fallback to MMIO reads (though without a token, thus slower) | |
338 | */ | |
339 | ppc_pci_io = spider_pci_io; | |
340 | ||
341 | /* Setup ioremap callback */ | |
342 | ppc_md.ioremap = spider_ioremap; | |
343 | ||
344 | return 0; | |
345 | } | |
346 | arch_initcall(spider_pci_workaround_init); |