]>
Commit | Line | Data |
---|---|---|
5283ecb5 | 1 | /* |
62c7ae87 | 2 | * Low-Level PCI Support for the SH7780 |
5283ecb5 | 3 | * |
a45635df | 4 | * Copyright (C) 2005 - 2010 Paul Mundt |
5283ecb5 | 5 | * |
62c7ae87 PM |
6 | * This file is subject to the terms and conditions of the GNU General Public |
7 | * License. See the file "COPYING" in the main directory of this archive | |
8 | * for more details. | |
5283ecb5 | 9 | */ |
5283ecb5 PM |
10 | #include <linux/types.h> |
11 | #include <linux/kernel.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/pci.h> | |
ef407bee PM |
14 | #include <linux/interrupt.h> |
15 | #include <linux/timer.h> | |
16 | #include <linux/irq.h> | |
5283ecb5 | 17 | #include <linux/errno.h> |
5283ecb5 | 18 | #include <linux/delay.h> |
aee4467b | 19 | #include <linux/log2.h> |
959f85f8 | 20 | #include "pci-sh4.h" |
a45635df PM |
21 | #include <asm/mmu.h> |
22 | #include <asm/sizes.h> | |
5283ecb5 | 23 | |
e79066a6 PM |
24 | static struct resource sh7785_io_resource = { |
25 | .name = "SH7785_IO", | |
a45635df PM |
26 | .start = 0x1000, |
27 | .end = SH7780_PCI_IO_SIZE - 1, | |
e79066a6 PM |
28 | .flags = IORESOURCE_IO |
29 | }; | |
30 | ||
31 | static struct resource sh7785_mem_resource = { | |
32 | .name = "SH7785_mem", | |
33 | .start = SH7780_PCI_MEMORY_BASE, | |
34 | .end = SH7780_PCI_MEMORY_BASE + SH7780_PCI_MEM_SIZE - 1, | |
35 | .flags = IORESOURCE_MEM | |
36 | }; | |
37 | ||
38 | static struct pci_channel sh7780_pci_controller = { | |
39 | .pci_ops = &sh4_pci_ops, | |
40 | .mem_resource = &sh7785_mem_resource, | |
09cfeb13 | 41 | .mem_offset = 0x00000000, |
e79066a6 | 42 | .io_resource = &sh7785_io_resource, |
09cfeb13 | 43 | .io_offset = 0x00000000, |
5582b064 | 44 | .io_map_base = SH7780_PCI_IO_BASE, |
ef407bee PM |
45 | .serr_irq = evt2irq(0xa00), |
46 | .err_irq = evt2irq(0xaa0), | |
e79066a6 PM |
47 | }; |
48 | ||
ef407bee PM |
49 | struct pci_errors { |
50 | unsigned int mask; | |
51 | const char *str; | |
52 | } pci_arbiter_errors[] = { | |
53 | { SH4_PCIAINT_MBKN, "master broken" }, | |
54 | { SH4_PCIAINT_TBTO, "target bus time out" }, | |
55 | { SH4_PCIAINT_MBTO, "master bus time out" }, | |
56 | { SH4_PCIAINT_TABT, "target abort" }, | |
57 | { SH4_PCIAINT_MABT, "master abort" }, | |
58 | { SH4_PCIAINT_RDPE, "read data parity error" }, | |
59 | { SH4_PCIAINT_WDPE, "write data parity error" }, | |
60 | }, pci_interrupt_errors[] = { | |
61 | { SH4_PCIINT_MLCK, "master lock error" }, | |
62 | { SH4_PCIINT_TABT, "target-target abort" }, | |
63 | { SH4_PCIINT_TRET, "target retry time out" }, | |
64 | { SH4_PCIINT_MFDE, "master function disable erorr" }, | |
65 | { SH4_PCIINT_PRTY, "address parity error" }, | |
66 | { SH4_PCIINT_SERR, "SERR" }, | |
67 | { SH4_PCIINT_TWDP, "data parity error for target write" }, | |
68 | { SH4_PCIINT_TRDP, "PERR detected for target read" }, | |
69 | { SH4_PCIINT_MTABT, "target abort for master" }, | |
70 | { SH4_PCIINT_MMABT, "master abort for master" }, | |
71 | { SH4_PCIINT_MWPD, "master write data parity error" }, | |
72 | { SH4_PCIINT_MRPD, "master read data parity error" }, | |
73 | }; | |
74 | ||
75 | static irqreturn_t sh7780_pci_err_irq(int irq, void *dev_id) | |
76 | { | |
77 | struct pci_channel *hose = dev_id; | |
78 | unsigned long addr; | |
79 | unsigned int status; | |
80 | unsigned int cmd; | |
81 | int i; | |
82 | ||
83 | addr = __raw_readl(hose->reg_base + SH4_PCIALR); | |
84 | ||
85 | /* | |
86 | * Handle status errors. | |
87 | */ | |
88 | status = __raw_readw(hose->reg_base + PCI_STATUS); | |
89 | if (status & (PCI_STATUS_PARITY | | |
90 | PCI_STATUS_DETECTED_PARITY | | |
91 | PCI_STATUS_SIG_TARGET_ABORT | | |
92 | PCI_STATUS_REC_TARGET_ABORT | | |
93 | PCI_STATUS_REC_MASTER_ABORT)) { | |
94 | cmd = pcibios_handle_status_errors(addr, status, hose); | |
95 | if (likely(cmd)) | |
96 | __raw_writew(cmd, hose->reg_base + PCI_STATUS); | |
97 | } | |
98 | ||
99 | /* | |
100 | * Handle arbiter errors. | |
101 | */ | |
102 | status = __raw_readl(hose->reg_base + SH4_PCIAINT); | |
103 | for (i = cmd = 0; i < ARRAY_SIZE(pci_arbiter_errors); i++) { | |
104 | if (status & pci_arbiter_errors[i].mask) { | |
105 | printk(KERN_DEBUG "PCI: %s, addr=%08lx\n", | |
106 | pci_arbiter_errors[i].str, addr); | |
107 | cmd |= pci_arbiter_errors[i].mask; | |
108 | } | |
109 | } | |
110 | __raw_writel(cmd, hose->reg_base + SH4_PCIAINT); | |
111 | ||
112 | /* | |
113 | * Handle the remaining PCI errors. | |
114 | */ | |
115 | status = __raw_readl(hose->reg_base + SH4_PCIINT); | |
116 | for (i = cmd = 0; i < ARRAY_SIZE(pci_interrupt_errors); i++) { | |
117 | if (status & pci_interrupt_errors[i].mask) { | |
118 | printk(KERN_DEBUG "PCI: %s, addr=%08lx\n", | |
119 | pci_interrupt_errors[i].str, addr); | |
120 | cmd |= pci_interrupt_errors[i].mask; | |
121 | } | |
122 | } | |
123 | __raw_writel(cmd, hose->reg_base + SH4_PCIINT); | |
124 | ||
125 | return IRQ_HANDLED; | |
126 | } | |
127 | ||
128 | static irqreturn_t sh7780_pci_serr_irq(int irq, void *dev_id) | |
129 | { | |
130 | struct pci_channel *hose = dev_id; | |
131 | ||
132 | printk(KERN_DEBUG "PCI: system error received: "); | |
133 | pcibios_report_status(PCI_STATUS_SIG_SYSTEM_ERROR, 1); | |
134 | printk("\n"); | |
135 | ||
136 | /* Deassert SERR */ | |
137 | __raw_writel(SH4_PCIINTM_SDIM, hose->reg_base + SH4_PCIINTM); | |
138 | ||
139 | /* Back off the IRQ for awhile */ | |
140 | disable_irq(irq); | |
141 | hose->serr_timer.expires = jiffies + HZ; | |
142 | add_timer(&hose->serr_timer); | |
143 | ||
144 | return IRQ_HANDLED; | |
145 | } | |
146 | ||
147 | static int __init sh7780_pci_setup_irqs(struct pci_channel *hose) | |
148 | { | |
149 | int ret; | |
150 | ||
151 | /* Clear out PCI arbiter IRQs */ | |
152 | __raw_writel(0, hose->reg_base + SH4_PCIAINT); | |
153 | ||
154 | /* Clear all error conditions */ | |
155 | __raw_writew(PCI_STATUS_DETECTED_PARITY | \ | |
156 | PCI_STATUS_SIG_SYSTEM_ERROR | \ | |
157 | PCI_STATUS_REC_MASTER_ABORT | \ | |
158 | PCI_STATUS_REC_TARGET_ABORT | \ | |
159 | PCI_STATUS_SIG_TARGET_ABORT | \ | |
160 | PCI_STATUS_PARITY, hose->reg_base + PCI_STATUS); | |
161 | ||
162 | ret = request_irq(hose->serr_irq, sh7780_pci_serr_irq, IRQF_DISABLED, | |
163 | "PCI SERR interrupt", hose); | |
164 | if (unlikely(ret)) { | |
165 | printk(KERN_ERR "PCI: Failed hooking SERR IRQ\n"); | |
166 | return ret; | |
167 | } | |
168 | ||
169 | /* | |
170 | * The PCI ERR IRQ needs to be IRQF_SHARED since all of the power | |
171 | * down IRQ vectors are routed through the ERR IRQ vector. We | |
172 | * only request_irq() once as there is only a single masking | |
173 | * source for multiple events. | |
174 | */ | |
175 | ret = request_irq(hose->err_irq, sh7780_pci_err_irq, IRQF_SHARED, | |
176 | "PCI ERR interrupt", hose); | |
177 | if (unlikely(ret)) { | |
178 | free_irq(hose->serr_irq, hose); | |
179 | return ret; | |
180 | } | |
181 | ||
182 | /* Unmask all of the arbiter IRQs. */ | |
183 | __raw_writel(SH4_PCIAINT_MBKN | SH4_PCIAINT_TBTO | SH4_PCIAINT_MBTO | \ | |
184 | SH4_PCIAINT_TABT | SH4_PCIAINT_MABT | SH4_PCIAINT_RDPE | \ | |
185 | SH4_PCIAINT_WDPE, hose->reg_base + SH4_PCIAINTM); | |
186 | ||
187 | /* Unmask all of the PCI IRQs */ | |
188 | __raw_writel(SH4_PCIINTM_TTADIM | SH4_PCIINTM_TMTOIM | \ | |
189 | SH4_PCIINTM_MDEIM | SH4_PCIINTM_APEDIM | \ | |
190 | SH4_PCIINTM_SDIM | SH4_PCIINTM_DPEITWM | \ | |
191 | SH4_PCIINTM_PEDITRM | SH4_PCIINTM_TADIMM | \ | |
192 | SH4_PCIINTM_MADIMM | SH4_PCIINTM_MWPDIM | \ | |
193 | SH4_PCIINTM_MRDPEIM, hose->reg_base + SH4_PCIINTM); | |
194 | ||
195 | return ret; | |
196 | } | |
197 | ||
198 | static inline void __init sh7780_pci_teardown_irqs(struct pci_channel *hose) | |
199 | { | |
200 | free_irq(hose->err_irq, hose); | |
201 | free_irq(hose->serr_irq, hose); | |
202 | } | |
203 | ||
85b59f5b PM |
204 | static void __init sh7780_pci66_init(struct pci_channel *hose) |
205 | { | |
206 | unsigned int tmp; | |
207 | ||
208 | if (!pci_is_66mhz_capable(hose, 0, 0)) | |
209 | return; | |
210 | ||
211 | /* Enable register access */ | |
212 | tmp = __raw_readl(hose->reg_base + SH4_PCICR); | |
213 | tmp |= SH4_PCICR_PREFIX; | |
214 | __raw_writel(tmp, hose->reg_base + SH4_PCICR); | |
215 | ||
216 | /* Enable 66MHz operation */ | |
217 | tmp = __raw_readw(hose->reg_base + PCI_STATUS); | |
218 | tmp |= PCI_STATUS_66MHZ; | |
219 | __raw_writew(tmp, hose->reg_base + PCI_STATUS); | |
220 | ||
221 | /* Done */ | |
222 | tmp = __raw_readl(hose->reg_base + SH4_PCICR); | |
223 | tmp |= SH4_PCICR_PREFIX | SH4_PCICR_CFIN; | |
224 | __raw_writel(tmp, hose->reg_base + SH4_PCICR); | |
225 | } | |
226 | ||
e79066a6 | 227 | static int __init sh7780_pci_init(void) |
5283ecb5 | 228 | { |
e79066a6 | 229 | struct pci_channel *chan = &sh7780_pci_controller; |
a45635df PM |
230 | phys_addr_t memphys; |
231 | size_t memsize; | |
959f85f8 | 232 | unsigned int id; |
a45635df | 233 | const char *type; |
bcf39352 | 234 | int ret; |
5283ecb5 | 235 | |
4e7b7fdb | 236 | printk(KERN_NOTICE "PCI: Starting intialization.\n"); |
5283ecb5 | 237 | |
e4c6a360 MD |
238 | chan->reg_base = 0xfe040000; |
239 | ||
4e7b7fdb PM |
240 | /* Enable CPU access to the PCIC registers. */ |
241 | __raw_writel(PCIECR_ENBL, PCIECR); | |
5283ecb5 | 242 | |
a45635df PM |
243 | /* Reset */ |
244 | __raw_writel(SH4_PCICR_PREFIX | SH4_PCICR_PRST, | |
245 | chan->reg_base + SH4_PCICR); | |
246 | ||
aee4467b PM |
247 | /* |
248 | * Wait for it to come back up. The spec says to allow for up to | |
249 | * 1 second after toggling the reset pin, but in practice 100ms | |
250 | * is more than enough. | |
251 | */ | |
a45635df PM |
252 | mdelay(100); |
253 | ||
254 | id = __raw_readw(chan->reg_base + PCI_VENDOR_ID); | |
255 | if (id != PCI_VENDOR_ID_RENESAS) { | |
4e7b7fdb PM |
256 | printk(KERN_ERR "PCI: Unknown vendor ID 0x%04x.\n", id); |
257 | return -ENODEV; | |
32351a28 PM |
258 | } |
259 | ||
a45635df PM |
260 | id = __raw_readw(chan->reg_base + PCI_DEVICE_ID); |
261 | type = (id == PCI_DEVICE_ID_RENESAS_SH7763) ? "SH7763" : | |
262 | (id == PCI_DEVICE_ID_RENESAS_SH7780) ? "SH7780" : | |
263 | (id == PCI_DEVICE_ID_RENESAS_SH7781) ? "SH7781" : | |
264 | (id == PCI_DEVICE_ID_RENESAS_SH7785) ? "SH7785" : | |
4e7b7fdb PM |
265 | NULL; |
266 | if (unlikely(!type)) { | |
267 | printk(KERN_ERR "PCI: Found an unsupported Renesas host " | |
268 | "controller, device id 0x%04x.\n", id); | |
269 | return -EINVAL; | |
5283ecb5 PM |
270 | } |
271 | ||
4e7b7fdb PM |
272 | printk(KERN_NOTICE "PCI: Found a Renesas %s host " |
273 | "controller, revision %d.\n", type, | |
a45635df | 274 | __raw_readb(chan->reg_base + PCI_REVISION_ID)); |
4e7b7fdb | 275 | |
c66c1d79 | 276 | /* |
a45635df PM |
277 | * Now throw it in to register initialization mode and |
278 | * start the real work. | |
c66c1d79 | 279 | */ |
a45635df PM |
280 | __raw_writel(SH4_PCICR_PREFIX, chan->reg_base + SH4_PCICR); |
281 | ||
aee4467b PM |
282 | __raw_writel(0, chan->reg_base + PCI_BASE_ADDRESS_0); |
283 | ||
a45635df | 284 | memphys = __pa(memory_start); |
aee4467b | 285 | memsize = roundup_pow_of_two(memory_end - memory_start); |
0bbc9bc3 | 286 | |
62c7ae87 | 287 | /* |
aee4467b PM |
288 | * If there's more than 512MB of memory, we need to roll over to |
289 | * LAR1/LSR1. | |
5283ecb5 | 290 | */ |
aee4467b PM |
291 | if (memsize > SZ_512M) { |
292 | __raw_writel(memphys + SZ_512M, chan->reg_base + SH4_PCILAR1); | |
293 | __raw_writel((((memsize - SZ_512M) - SZ_1M) & 0x1ff00000) | 1, | |
294 | chan->reg_base + SH4_PCILSR1); | |
295 | memsize = SZ_512M; | |
296 | } else { | |
297 | /* | |
298 | * Otherwise just zero it out and disable it. | |
299 | */ | |
300 | __raw_writel(0, chan->reg_base + SH4_PCILAR1); | |
301 | __raw_writel(0, chan->reg_base + SH4_PCILSR1); | |
302 | } | |
a45635df | 303 | |
aee4467b PM |
304 | /* |
305 | * LAR0/LSR0 covers up to the first 512MB, which is enough to | |
306 | * cover all of lowmem on most platforms. | |
307 | */ | |
a45635df | 308 | __raw_writel(memphys, chan->reg_base + SH4_PCILAR0); |
aee4467b | 309 | __raw_writel(((memsize - SZ_1M) & 0x1ff00000) | 1, |
a45635df PM |
310 | chan->reg_base + SH4_PCILSR0); |
311 | ||
ef407bee PM |
312 | /* |
313 | * Hook up the ERR and SERR IRQs. | |
314 | */ | |
315 | ret = sh7780_pci_setup_irqs(chan); | |
316 | if (unlikely(ret)) | |
317 | return ret; | |
a45635df PM |
318 | |
319 | /* | |
320 | * Disable the cache snoop controller for non-coherent DMA. | |
321 | */ | |
322 | __raw_writel(0, chan->reg_base + SH7780_PCICSCR0); | |
323 | __raw_writel(0, chan->reg_base + SH7780_PCICSAR0); | |
324 | __raw_writel(0, chan->reg_base + SH7780_PCICSCR1); | |
325 | __raw_writel(0, chan->reg_base + SH7780_PCICSAR1); | |
326 | ||
327 | __raw_writel(0xfd000000, chan->reg_base + SH7780_PCIMBR0); | |
328 | __raw_writel(0x00fc0000, chan->reg_base + SH7780_PCIMBMR0); | |
329 | ||
330 | __raw_writel(0, chan->reg_base + SH7780_PCIIOBR); | |
331 | __raw_writel(0, chan->reg_base + SH7780_PCIIOBMR); | |
332 | ||
ef407bee PM |
333 | __raw_writew(PCI_COMMAND_SERR | PCI_COMMAND_WAIT | \ |
334 | PCI_COMMAND_PARITY | PCI_COMMAND_MASTER | \ | |
335 | PCI_COMMAND_MEMORY, chan->reg_base + PCI_COMMAND); | |
336 | ||
a45635df PM |
337 | /* |
338 | * Initialization mode complete, release the control register and | |
339 | * enable round robin mode to stop device overruns/starvation. | |
340 | */ | |
341 | __raw_writel(SH4_PCICR_PREFIX | SH4_PCICR_CFIN | SH4_PCICR_FTO, | |
342 | chan->reg_base + SH4_PCICR); | |
5283ecb5 | 343 | |
bcf39352 PM |
344 | ret = register_pci_controller(chan); |
345 | if (unlikely(ret)) | |
ef407bee | 346 | goto err; |
e79066a6 | 347 | |
85b59f5b PM |
348 | sh7780_pci66_init(chan); |
349 | ||
350 | printk(KERN_NOTICE "PCI: Running at %dMHz.\n", | |
351 | (__raw_readw(chan->reg_base + PCI_STATUS) & PCI_STATUS_66MHZ) ? | |
352 | 66 : 33); | |
353 | ||
d0e3db40 | 354 | return 0; |
ef407bee PM |
355 | |
356 | err: | |
357 | sh7780_pci_teardown_irqs(chan); | |
358 | return ret; | |
5283ecb5 | 359 | } |
e79066a6 | 360 | arch_initcall(sh7780_pci_init); |