diff options
Diffstat (limited to 'arch/powerpc/platforms/celleb/scc_epci.c')
-rw-r--r-- | arch/powerpc/platforms/celleb/scc_epci.c | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/celleb/scc_epci.c b/arch/powerpc/platforms/celleb/scc_epci.c new file mode 100644 index 000000000000..c11b39c3776a --- /dev/null +++ b/arch/powerpc/platforms/celleb/scc_epci.c | |||
@@ -0,0 +1,409 @@ | |||
1 | /* | ||
2 | * Support for SCC external PCI | ||
3 | * | ||
4 | * (C) Copyright 2004-2007 TOSHIBA CORPORATION | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #undef DEBUG | ||
22 | |||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/threads.h> | ||
25 | #include <linux/pci.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/pci_regs.h> | ||
28 | #include <linux/bootmem.h> | ||
29 | |||
30 | #include <asm/io.h> | ||
31 | #include <asm/irq.h> | ||
32 | #include <asm/prom.h> | ||
33 | #include <asm/machdep.h> | ||
34 | #include <asm/pci-bridge.h> | ||
35 | #include <asm/ppc-pci.h> | ||
36 | |||
37 | #include "scc.h" | ||
38 | #include "pci.h" | ||
39 | #include "interrupt.h" | ||
40 | |||
41 | #define MAX_PCI_DEVICES 32 | ||
42 | #define MAX_PCI_FUNCTIONS 8 | ||
43 | |||
44 | #define iob() __asm__ __volatile__("eieio; sync":::"memory") | ||
45 | |||
46 | |||
47 | #if 0 /* test code for epci dummy read */ | ||
48 | static void celleb_epci_dummy_read(struct pci_dev *dev) | ||
49 | { | ||
50 | void __iomem *epci_base; | ||
51 | struct device_node *node; | ||
52 | struct pci_controller *hose; | ||
53 | u32 val; | ||
54 | |||
55 | node = (struct device_node *)dev->bus->sysdata; | ||
56 | hose = pci_find_hose_for_OF_device(node); | ||
57 | |||
58 | if (!hose) | ||
59 | return; | ||
60 | |||
61 | epci_base = hose->cfg_addr; | ||
62 | |||
63 | val = in_be32(epci_base + SCC_EPCI_WATRP); | ||
64 | iosync(); | ||
65 | |||
66 | return; | ||
67 | } | ||
68 | #endif | ||
69 | |||
70 | static inline void clear_and_disable_master_abort_interrupt( | ||
71 | struct pci_controller *hose) | ||
72 | { | ||
73 | void __iomem *addr; | ||
74 | addr = hose->cfg_addr + PCI_COMMAND; | ||
75 | out_be32(addr, in_be32(addr) | (PCI_STATUS_REC_MASTER_ABORT << 16)); | ||
76 | } | ||
77 | |||
78 | static int celleb_epci_check_abort(struct pci_controller *hose, | ||
79 | void __iomem *addr) | ||
80 | { | ||
81 | void __iomem *reg, *epci_base; | ||
82 | u32 val; | ||
83 | |||
84 | iob(); | ||
85 | epci_base = hose->cfg_addr; | ||
86 | |||
87 | reg = epci_base + PCI_COMMAND; | ||
88 | val = in_be32(reg); | ||
89 | |||
90 | if (val & (PCI_STATUS_REC_MASTER_ABORT << 16)) { | ||
91 | out_be32(reg, | ||
92 | (val & 0xffff) | (PCI_STATUS_REC_MASTER_ABORT << 16)); | ||
93 | |||
94 | /* clear PCI Controller error, FRE, PMFE */ | ||
95 | reg = epci_base + SCC_EPCI_STATUS; | ||
96 | out_be32(reg, SCC_EPCI_INT_PAI); | ||
97 | |||
98 | reg = epci_base + SCC_EPCI_VCSR; | ||
99 | val = in_be32(reg) & 0xffff; | ||
100 | val |= SCC_EPCI_VCSR_FRE; | ||
101 | out_be32(reg, val); | ||
102 | |||
103 | reg = epci_base + SCC_EPCI_VISTAT; | ||
104 | out_be32(reg, SCC_EPCI_VISTAT_PMFE); | ||
105 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
106 | } | ||
107 | |||
108 | return PCIBIOS_SUCCESSFUL; | ||
109 | } | ||
110 | |||
111 | static void __iomem *celleb_epci_make_config_addr(struct pci_controller *hose, | ||
112 | unsigned int devfn, int where) | ||
113 | { | ||
114 | void __iomem *addr; | ||
115 | struct pci_bus *bus = hose->bus; | ||
116 | |||
117 | if (bus->self) | ||
118 | addr = hose->cfg_data + | ||
119 | (((bus->number & 0xff) << 16) | ||
120 | | ((devfn & 0xff) << 8) | ||
121 | | (where & 0xff) | ||
122 | | 0x01000000); | ||
123 | else | ||
124 | addr = hose->cfg_data + | ||
125 | (((devfn & 0xff) << 8) | (where & 0xff)); | ||
126 | |||
127 | pr_debug("EPCI: config_addr = 0x%p\n", addr); | ||
128 | |||
129 | return addr; | ||
130 | } | ||
131 | |||
132 | static int celleb_epci_read_config(struct pci_bus *bus, | ||
133 | unsigned int devfn, int where, int size, u32 * val) | ||
134 | { | ||
135 | void __iomem *addr; | ||
136 | struct device_node *node; | ||
137 | struct pci_controller *hose; | ||
138 | |||
139 | /* allignment check */ | ||
140 | BUG_ON(where % size); | ||
141 | |||
142 | node = (struct device_node *)bus->sysdata; | ||
143 | hose = pci_find_hose_for_OF_device(node); | ||
144 | |||
145 | if (!hose->cfg_data) | ||
146 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
147 | |||
148 | if (bus->number == hose->first_busno && devfn == 0) { | ||
149 | /* EPCI controller self */ | ||
150 | |||
151 | addr = hose->cfg_addr + where; | ||
152 | |||
153 | switch (size) { | ||
154 | case 1: | ||
155 | *val = in_8(addr); | ||
156 | break; | ||
157 | case 2: | ||
158 | *val = in_be16(addr); | ||
159 | break; | ||
160 | case 4: | ||
161 | *val = in_be32(addr); | ||
162 | break; | ||
163 | default: | ||
164 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
165 | } | ||
166 | |||
167 | } else { | ||
168 | |||
169 | clear_and_disable_master_abort_interrupt(hose); | ||
170 | addr = celleb_epci_make_config_addr(hose, devfn, where); | ||
171 | |||
172 | switch (size) { | ||
173 | case 1: | ||
174 | *val = in_8(addr); | ||
175 | break; | ||
176 | case 2: | ||
177 | *val = in_le16(addr); | ||
178 | break; | ||
179 | case 4: | ||
180 | *val = in_le32(addr); | ||
181 | break; | ||
182 | default: | ||
183 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | pr_debug("EPCI: " | ||
188 | "addr=0x%lx, devfn=0x%x, where=0x%x, size=0x%x, val=0x%x\n", | ||
189 | addr, devfn, where, size, *val); | ||
190 | |||
191 | return celleb_epci_check_abort(hose, NULL); | ||
192 | } | ||
193 | |||
194 | static int celleb_epci_write_config(struct pci_bus *bus, | ||
195 | unsigned int devfn, int where, int size, u32 val) | ||
196 | { | ||
197 | void __iomem *addr; | ||
198 | struct device_node *node; | ||
199 | struct pci_controller *hose; | ||
200 | |||
201 | /* allignment check */ | ||
202 | BUG_ON(where % size); | ||
203 | |||
204 | node = (struct device_node *)bus->sysdata; | ||
205 | hose = pci_find_hose_for_OF_device(node); | ||
206 | |||
207 | if (!hose->cfg_data) | ||
208 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
209 | |||
210 | if (bus->number == hose->first_busno && devfn == 0) { | ||
211 | /* EPCI controller self */ | ||
212 | |||
213 | addr = hose->cfg_addr + where; | ||
214 | |||
215 | switch (size) { | ||
216 | case 1: | ||
217 | out_8(addr, val); | ||
218 | break; | ||
219 | case 2: | ||
220 | out_be16(addr, val); | ||
221 | break; | ||
222 | case 4: | ||
223 | out_be32(addr, val); | ||
224 | break; | ||
225 | default: | ||
226 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
227 | } | ||
228 | |||
229 | } else { | ||
230 | |||
231 | clear_and_disable_master_abort_interrupt(hose); | ||
232 | addr = celleb_epci_make_config_addr(hose, devfn, where); | ||
233 | |||
234 | switch (size) { | ||
235 | case 1: | ||
236 | out_8(addr, val); | ||
237 | break; | ||
238 | case 2: | ||
239 | out_le16(addr, val); | ||
240 | break; | ||
241 | case 4: | ||
242 | out_le32(addr, val); | ||
243 | break; | ||
244 | default: | ||
245 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | return celleb_epci_check_abort(hose, addr); | ||
250 | } | ||
251 | |||
252 | struct pci_ops celleb_epci_ops = { | ||
253 | celleb_epci_read_config, | ||
254 | celleb_epci_write_config, | ||
255 | }; | ||
256 | |||
257 | /* to be moved in FW */ | ||
258 | static int __devinit celleb_epci_init(struct pci_controller *hose) | ||
259 | { | ||
260 | u32 val; | ||
261 | void __iomem *reg, *epci_base; | ||
262 | int hwres = 0; | ||
263 | |||
264 | epci_base = hose->cfg_addr; | ||
265 | |||
266 | /* PCI core reset(Internal bus and PCI clock) */ | ||
267 | reg = epci_base + SCC_EPCI_CKCTRL; | ||
268 | val = in_be32(reg); | ||
269 | if (val == 0x00030101) | ||
270 | hwres = 1; | ||
271 | else { | ||
272 | val &= ~(SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1); | ||
273 | out_be32(reg, val); | ||
274 | |||
275 | /* set PCI core clock */ | ||
276 | val = in_be32(reg); | ||
277 | val |= (SCC_EPCI_CKCTRL_OCLKEN | SCC_EPCI_CKCTRL_LCLKEN); | ||
278 | out_be32(reg, val); | ||
279 | |||
280 | /* release PCI core reset (internal bus) */ | ||
281 | val = in_be32(reg); | ||
282 | val |= SCC_EPCI_CKCTRL_CRST0; | ||
283 | out_be32(reg, val); | ||
284 | |||
285 | /* set PCI clock select */ | ||
286 | reg = epci_base + SCC_EPCI_CLKRST; | ||
287 | val = in_be32(reg); | ||
288 | val &= ~SCC_EPCI_CLKRST_CKS_MASK; | ||
289 | val |= SCC_EPCI_CLKRST_CKS_2; | ||
290 | out_be32(reg, val); | ||
291 | |||
292 | /* set arbiter */ | ||
293 | reg = epci_base + SCC_EPCI_ABTSET; | ||
294 | out_be32(reg, 0x0f1f001f); /* temporary value */ | ||
295 | |||
296 | /* buffer on */ | ||
297 | reg = epci_base + SCC_EPCI_CLKRST; | ||
298 | val = in_be32(reg); | ||
299 | val |= SCC_EPCI_CLKRST_BC; | ||
300 | out_be32(reg, val); | ||
301 | |||
302 | /* PCI clock enable */ | ||
303 | val = in_be32(reg); | ||
304 | val |= SCC_EPCI_CLKRST_PCKEN; | ||
305 | out_be32(reg, val); | ||
306 | |||
307 | /* release PCI core reset (all) */ | ||
308 | reg = epci_base + SCC_EPCI_CKCTRL; | ||
309 | val = in_be32(reg); | ||
310 | val |= (SCC_EPCI_CKCTRL_CRST0 | SCC_EPCI_CKCTRL_CRST1); | ||
311 | out_be32(reg, val); | ||
312 | |||
313 | /* set base translation registers. (already set by Beat) */ | ||
314 | |||
315 | /* set base address masks. (already set by Beat) */ | ||
316 | } | ||
317 | |||
318 | /* release interrupt masks and clear all interrupts */ | ||
319 | reg = epci_base + SCC_EPCI_INTSET; | ||
320 | out_be32(reg, 0x013f011f); /* all interrupts enable */ | ||
321 | reg = epci_base + SCC_EPCI_VIENAB; | ||
322 | val = SCC_EPCI_VIENAB_PMPEE | SCC_EPCI_VIENAB_PMFEE; | ||
323 | out_be32(reg, val); | ||
324 | reg = epci_base + SCC_EPCI_STATUS; | ||
325 | out_be32(reg, 0xffffffff); | ||
326 | reg = epci_base + SCC_EPCI_VISTAT; | ||
327 | out_be32(reg, 0xffffffff); | ||
328 | |||
329 | /* disable PCI->IB address translation */ | ||
330 | reg = epci_base + SCC_EPCI_VCSR; | ||
331 | val = in_be32(reg); | ||
332 | val &= ~(SCC_EPCI_VCSR_DR | SCC_EPCI_VCSR_AT); | ||
333 | out_be32(reg, val); | ||
334 | |||
335 | /* set base addresses. (no need to set?) */ | ||
336 | |||
337 | /* memory space, bus master enable */ | ||
338 | reg = epci_base + PCI_COMMAND; | ||
339 | val = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; | ||
340 | out_be32(reg, val); | ||
341 | |||
342 | /* endian mode setup */ | ||
343 | reg = epci_base + SCC_EPCI_ECMODE; | ||
344 | val = 0x00550155; | ||
345 | out_be32(reg, val); | ||
346 | |||
347 | /* set control option */ | ||
348 | reg = epci_base + SCC_EPCI_CNTOPT; | ||
349 | val = in_be32(reg); | ||
350 | val |= SCC_EPCI_CNTOPT_O2PMB; | ||
351 | out_be32(reg, val); | ||
352 | |||
353 | /* XXX: temporay: set registers for address conversion setup */ | ||
354 | reg = epci_base + SCC_EPCI_CNF10_REG; | ||
355 | out_be32(reg, 0x80000008); | ||
356 | reg = epci_base + SCC_EPCI_CNF14_REG; | ||
357 | out_be32(reg, 0x40000008); | ||
358 | |||
359 | reg = epci_base + SCC_EPCI_BAM0; | ||
360 | out_be32(reg, 0x80000000); | ||
361 | reg = epci_base + SCC_EPCI_BAM1; | ||
362 | out_be32(reg, 0xe0000000); | ||
363 | |||
364 | reg = epci_base + SCC_EPCI_PVBAT; | ||
365 | out_be32(reg, 0x80000000); | ||
366 | |||
367 | if (!hwres) { | ||
368 | /* release external PCI reset */ | ||
369 | reg = epci_base + SCC_EPCI_CLKRST; | ||
370 | val = in_be32(reg); | ||
371 | val |= SCC_EPCI_CLKRST_PCIRST; | ||
372 | out_be32(reg, val); | ||
373 | } | ||
374 | |||
375 | return 0; | ||
376 | } | ||
377 | |||
378 | int __devinit celleb_setup_epci(struct device_node *node, | ||
379 | struct pci_controller *hose) | ||
380 | { | ||
381 | struct resource r; | ||
382 | |||
383 | pr_debug("PCI: celleb_setup_epci()\n"); | ||
384 | |||
385 | if (of_address_to_resource(node, 0, &r)) | ||
386 | goto error; | ||
387 | hose->cfg_addr = ioremap(r.start, (r.end - r.start + 1)); | ||
388 | if (!hose->cfg_addr) | ||
389 | goto error; | ||
390 | pr_debug("EPCI: cfg_addr map 0x%016lx->0x%016lx + 0x%016lx\n", | ||
391 | r.start, (unsigned long)hose->cfg_addr, | ||
392 | (r.end - r.start + 1)); | ||
393 | |||
394 | if (of_address_to_resource(node, 2, &r)) | ||
395 | goto error; | ||
396 | hose->cfg_data = ioremap(r.start, (r.end - r.start + 1)); | ||
397 | if (!hose->cfg_data) | ||
398 | goto error; | ||
399 | pr_debug("EPCI: cfg_data map 0x%016lx->0x%016lx + 0x%016lx\n", | ||
400 | r.start, (unsigned long)hose->cfg_data, | ||
401 | (r.end - r.start + 1)); | ||
402 | |||
403 | celleb_epci_init(hose); | ||
404 | |||
405 | return 0; | ||
406 | |||
407 | error: | ||
408 | return 1; | ||
409 | } | ||