diff options
Diffstat (limited to 'arch/arm/mach-ixp2000/pci.c')
-rw-r--r-- | arch/arm/mach-ixp2000/pci.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/arch/arm/mach-ixp2000/pci.c b/arch/arm/mach-ixp2000/pci.c new file mode 100644 index 000000000000..831f8ffb6b61 --- /dev/null +++ b/arch/arm/mach-ixp2000/pci.c | |||
@@ -0,0 +1,235 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-ixp2000/pci.c | ||
3 | * | ||
4 | * PCI routines for IXDP2400/IXDP2800 boards | ||
5 | * | ||
6 | * Original Author: Naeem Afzal <naeem.m.afzal@intel.com> | ||
7 | * Maintained by: Deepak Saxena <dsaxena@plexity.net> | ||
8 | * | ||
9 | * Copyright 2002 Intel Corp. | ||
10 | * Copyright (C) 2003-2004 MontaVista Software, Inc. | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify it | ||
13 | * under the terms of the GNU General Public License as published by the | ||
14 | * Free Software Foundation; either version 2 of the License, or (at your | ||
15 | * option) any later version. | ||
16 | */ | ||
17 | |||
18 | #include <linux/sched.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/pci.h> | ||
21 | #include <linux/interrupt.h> | ||
22 | #include <linux/mm.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/ioport.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/delay.h> | ||
27 | |||
28 | #include <asm/io.h> | ||
29 | #include <asm/irq.h> | ||
30 | #include <asm/system.h> | ||
31 | #include <asm/mach-types.h> | ||
32 | #include <asm/hardware.h> | ||
33 | |||
34 | #include <asm/mach/pci.h> | ||
35 | |||
36 | static int pci_master_aborts = 0; | ||
37 | |||
38 | static int clear_master_aborts(void); | ||
39 | |||
40 | static u32 * | ||
41 | ixp2000_pci_config_addr(unsigned int bus_nr, unsigned int devfn, int where) | ||
42 | { | ||
43 | u32 *paddress; | ||
44 | |||
45 | if (PCI_SLOT(devfn) > 7) | ||
46 | return 0; | ||
47 | |||
48 | /* Must be dword aligned */ | ||
49 | where &= ~3; | ||
50 | |||
51 | /* | ||
52 | * For top bus, generate type 0, else type 1 | ||
53 | */ | ||
54 | if (!bus_nr) { | ||
55 | /* only bits[23:16] are used for IDSEL */ | ||
56 | paddress = (u32 *) (IXP2000_PCI_CFG0_VIRT_BASE | ||
57 | | (1 << (PCI_SLOT(devfn) + 16)) | ||
58 | | (PCI_FUNC(devfn) << 8) | where); | ||
59 | } else { | ||
60 | paddress = (u32 *) (IXP2000_PCI_CFG1_VIRT_BASE | ||
61 | | (bus_nr << 16) | ||
62 | | (PCI_SLOT(devfn) << 11) | ||
63 | | (PCI_FUNC(devfn) << 8) | where); | ||
64 | } | ||
65 | |||
66 | return paddress; | ||
67 | } | ||
68 | |||
69 | /* | ||
70 | * Mask table, bits to mask for quantity of size 1, 2 or 4 bytes. | ||
71 | * 0 and 3 are not valid indexes... | ||
72 | */ | ||
73 | static u32 bytemask[] = { | ||
74 | /*0*/ 0, | ||
75 | /*1*/ 0xff, | ||
76 | /*2*/ 0xffff, | ||
77 | /*3*/ 0, | ||
78 | /*4*/ 0xffffffff, | ||
79 | }; | ||
80 | |||
81 | |||
82 | int ixp2000_pci_read_config(struct pci_bus *bus, unsigned int devfn, int where, | ||
83 | int size, u32 *value) | ||
84 | { | ||
85 | u32 n; | ||
86 | u32 *addr; | ||
87 | |||
88 | n = where % 4; | ||
89 | |||
90 | addr = ixp2000_pci_config_addr(bus->number, devfn, where); | ||
91 | if (!addr) | ||
92 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
93 | |||
94 | pci_master_aborts = 0; | ||
95 | *value = (*addr >> (8*n)) & bytemask[size]; | ||
96 | if (pci_master_aborts) { | ||
97 | pci_master_aborts = 0; | ||
98 | *value = 0xffffffff; | ||
99 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
100 | } | ||
101 | |||
102 | return PCIBIOS_SUCCESSFUL; | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * We don't do error checks by callling clear_master_aborts() b/c the | ||
107 | * assumption is that the caller did a read first to make sure a device | ||
108 | * exists. | ||
109 | */ | ||
110 | int ixp2000_pci_write_config(struct pci_bus *bus, unsigned int devfn, int where, | ||
111 | int size, u32 value) | ||
112 | { | ||
113 | u32 mask; | ||
114 | u32 *addr; | ||
115 | u32 temp; | ||
116 | |||
117 | mask = ~(bytemask[size] << ((where % 0x4) * 8)); | ||
118 | addr = ixp2000_pci_config_addr(bus->number, devfn, where); | ||
119 | if (!addr) | ||
120 | return PCIBIOS_DEVICE_NOT_FOUND; | ||
121 | temp = (u32) (value) << ((where % 0x4) * 8); | ||
122 | *addr = (*addr & mask) | temp; | ||
123 | |||
124 | clear_master_aborts(); | ||
125 | |||
126 | return PCIBIOS_SUCCESSFUL; | ||
127 | } | ||
128 | |||
129 | |||
130 | static struct pci_ops ixp2000_pci_ops = { | ||
131 | .read = ixp2000_pci_read_config, | ||
132 | .write = ixp2000_pci_write_config | ||
133 | }; | ||
134 | |||
135 | struct pci_bus *ixp2000_pci_scan_bus(int nr, struct pci_sys_data *sysdata) | ||
136 | { | ||
137 | return pci_scan_bus(sysdata->busnr, &ixp2000_pci_ops, sysdata); | ||
138 | } | ||
139 | |||
140 | |||
141 | int ixp2000_pci_abort_handler(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | ||
142 | { | ||
143 | |||
144 | volatile u32 temp; | ||
145 | unsigned long flags; | ||
146 | |||
147 | pci_master_aborts = 1; | ||
148 | |||
149 | local_irq_save(flags); | ||
150 | temp = *(IXP2000_PCI_CONTROL); | ||
151 | if (temp & ((1 << 8) | (1 << 5))) { | ||
152 | ixp2000_reg_write(IXP2000_PCI_CONTROL, temp); | ||
153 | } | ||
154 | |||
155 | temp = *(IXP2000_PCI_CMDSTAT); | ||
156 | if (temp & (1 << 29)) { | ||
157 | while (temp & (1 << 29)) { | ||
158 | ixp2000_reg_write(IXP2000_PCI_CMDSTAT, temp); | ||
159 | temp = *(IXP2000_PCI_CMDSTAT); | ||
160 | } | ||
161 | } | ||
162 | local_irq_restore(flags); | ||
163 | |||
164 | /* | ||
165 | * If it was an imprecise abort, then we need to correct the | ||
166 | * return address to be _after_ the instruction. | ||
167 | */ | ||
168 | if (fsr & (1 << 10)) | ||
169 | regs->ARM_pc += 4; | ||
170 | |||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | int | ||
175 | clear_master_aborts(void) | ||
176 | { | ||
177 | volatile u32 temp; | ||
178 | unsigned long flags; | ||
179 | |||
180 | local_irq_save(flags); | ||
181 | temp = *(IXP2000_PCI_CONTROL); | ||
182 | if (temp & ((1 << 8) | (1 << 5))) { | ||
183 | ixp2000_reg_write(IXP2000_PCI_CONTROL, temp); | ||
184 | } | ||
185 | |||
186 | temp = *(IXP2000_PCI_CMDSTAT); | ||
187 | if (temp & (1 << 29)) { | ||
188 | while (temp & (1 << 29)) { | ||
189 | ixp2000_reg_write(IXP2000_PCI_CMDSTAT, temp); | ||
190 | temp = *(IXP2000_PCI_CMDSTAT); | ||
191 | } | ||
192 | } | ||
193 | local_irq_restore(flags); | ||
194 | |||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | void __init | ||
199 | ixp2000_pci_preinit(void) | ||
200 | { | ||
201 | hook_fault_code(16+6, ixp2000_pci_abort_handler, SIGBUS, | ||
202 | "PCI config cycle to non-existent device"); | ||
203 | } | ||
204 | |||
205 | |||
206 | /* | ||
207 | * IXP2000 systems often have large resource requirements, so we just | ||
208 | * use our own resource space. | ||
209 | */ | ||
210 | static struct resource ixp2000_pci_mem_space = { | ||
211 | .start = 0x00000000, | ||
212 | .end = 0xffffffff, | ||
213 | .flags = IORESOURCE_MEM, | ||
214 | .name = "PCI Mem Space" | ||
215 | }; | ||
216 | |||
217 | static struct resource ixp2000_pci_io_space = { | ||
218 | .start = 0x00000000, | ||
219 | .end = 0xffffffff, | ||
220 | .flags = IORESOURCE_IO, | ||
221 | .name = "PCI I/O Space" | ||
222 | }; | ||
223 | |||
224 | int ixp2000_pci_setup(int nr, struct pci_sys_data *sys) | ||
225 | { | ||
226 | if (nr >= 1) | ||
227 | return 0; | ||
228 | |||
229 | sys->resource[0] = &ixp2000_pci_io_space; | ||
230 | sys->resource[1] = &ixp2000_pci_mem_space; | ||
231 | sys->resource[2] = NULL; | ||
232 | |||
233 | return 1; | ||
234 | } | ||
235 | |||