Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * CompactPCI Hot Plug Driver PCI functions | |
3 | * | |
bcc488ab | 4 | * Copyright (C) 2002,2005 by SOMA Networks, Inc. |
1da177e4 LT |
5 | * |
6 | * All rights reserved. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or (at | |
11 | * your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
16 | * NON INFRINGEMENT. See the GNU General Public License for more | |
17 | * details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, write to the Free Software | |
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | * | |
23 | * Send feedback to <scottm@somanetworks.com> | |
24 | */ | |
25 | ||
1da177e4 LT |
26 | #include <linux/module.h> |
27 | #include <linux/kernel.h> | |
28 | #include <linux/pci.h> | |
7a54f25c | 29 | #include <linux/pci_hotplug.h> |
1da177e4 LT |
30 | #include <linux/proc_fs.h> |
31 | #include "../pci.h" | |
1da177e4 LT |
32 | #include "cpci_hotplug.h" |
33 | ||
1da177e4 | 34 | #define MY_NAME "cpci_hotplug" |
1da177e4 LT |
35 | |
36 | extern int cpci_debug; | |
37 | ||
38 | #define dbg(format, arg...) \ | |
39 | do { \ | |
bcc488ab | 40 | if (cpci_debug) \ |
1da177e4 LT |
41 | printk (KERN_DEBUG "%s: " format "\n", \ |
42 | MY_NAME , ## arg); \ | |
bcc488ab | 43 | } while (0) |
1da177e4 LT |
44 | #define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME , ## arg) |
45 | #define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) | |
46 | #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) | |
47 | ||
1da177e4 LT |
48 | |
49 | u8 cpci_get_attention_status(struct slot* slot) | |
50 | { | |
51 | int hs_cap; | |
52 | u16 hs_csr; | |
53 | ||
54 | hs_cap = pci_bus_find_capability(slot->bus, | |
55 | slot->devfn, | |
56 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 57 | if (!hs_cap) |
1da177e4 | 58 | return 0; |
1da177e4 | 59 | |
bcc488ab | 60 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
61 | slot->devfn, |
62 | hs_cap + 2, | |
bcc488ab | 63 | &hs_csr)) |
1da177e4 | 64 | return 0; |
bcc488ab | 65 | |
1da177e4 LT |
66 | return hs_csr & 0x0008 ? 1 : 0; |
67 | } | |
68 | ||
69 | int cpci_set_attention_status(struct slot* slot, int status) | |
70 | { | |
71 | int hs_cap; | |
72 | u16 hs_csr; | |
73 | ||
74 | hs_cap = pci_bus_find_capability(slot->bus, | |
75 | slot->devfn, | |
76 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 77 | if (!hs_cap) |
1da177e4 | 78 | return 0; |
bcc488ab | 79 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
80 | slot->devfn, |
81 | hs_cap + 2, | |
bcc488ab | 82 | &hs_csr)) |
1da177e4 | 83 | return 0; |
bcc488ab | 84 | if (status) |
1da177e4 | 85 | hs_csr |= HS_CSR_LOO; |
bcc488ab | 86 | else |
1da177e4 | 87 | hs_csr &= ~HS_CSR_LOO; |
bcc488ab | 88 | if (pci_bus_write_config_word(slot->bus, |
1da177e4 LT |
89 | slot->devfn, |
90 | hs_cap + 2, | |
bcc488ab | 91 | hs_csr)) |
1da177e4 | 92 | return 0; |
1da177e4 LT |
93 | return 1; |
94 | } | |
95 | ||
96 | u16 cpci_get_hs_csr(struct slot* slot) | |
97 | { | |
98 | int hs_cap; | |
99 | u16 hs_csr; | |
100 | ||
101 | hs_cap = pci_bus_find_capability(slot->bus, | |
102 | slot->devfn, | |
103 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 104 | if (!hs_cap) |
1da177e4 | 105 | return 0xFFFF; |
bcc488ab | 106 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
107 | slot->devfn, |
108 | hs_cap + 2, | |
bcc488ab | 109 | &hs_csr)) |
1da177e4 | 110 | return 0xFFFF; |
1da177e4 LT |
111 | return hs_csr; |
112 | } | |
113 | ||
1da177e4 LT |
114 | int cpci_check_and_clear_ins(struct slot* slot) |
115 | { | |
116 | int hs_cap; | |
117 | u16 hs_csr; | |
118 | int ins = 0; | |
119 | ||
120 | hs_cap = pci_bus_find_capability(slot->bus, | |
121 | slot->devfn, | |
122 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 123 | if (!hs_cap) |
1da177e4 | 124 | return 0; |
bcc488ab | 125 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
126 | slot->devfn, |
127 | hs_cap + 2, | |
bcc488ab | 128 | &hs_csr)) |
1da177e4 | 129 | return 0; |
bcc488ab | 130 | if (hs_csr & HS_CSR_INS) { |
1da177e4 | 131 | /* Clear INS (by setting it) */ |
bcc488ab | 132 | if (pci_bus_write_config_word(slot->bus, |
1da177e4 LT |
133 | slot->devfn, |
134 | hs_cap + 2, | |
bcc488ab | 135 | hs_csr)) |
1da177e4 | 136 | ins = 0; |
bcc488ab SM |
137 | else |
138 | ins = 1; | |
1da177e4 LT |
139 | } |
140 | return ins; | |
141 | } | |
142 | ||
143 | int cpci_check_ext(struct slot* slot) | |
144 | { | |
145 | int hs_cap; | |
146 | u16 hs_csr; | |
147 | int ext = 0; | |
148 | ||
149 | hs_cap = pci_bus_find_capability(slot->bus, | |
150 | slot->devfn, | |
151 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 152 | if (!hs_cap) |
1da177e4 | 153 | return 0; |
bcc488ab | 154 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
155 | slot->devfn, |
156 | hs_cap + 2, | |
bcc488ab | 157 | &hs_csr)) |
1da177e4 | 158 | return 0; |
bcc488ab | 159 | if (hs_csr & HS_CSR_EXT) |
1da177e4 | 160 | ext = 1; |
1da177e4 LT |
161 | return ext; |
162 | } | |
163 | ||
164 | int cpci_clear_ext(struct slot* slot) | |
165 | { | |
166 | int hs_cap; | |
167 | u16 hs_csr; | |
168 | ||
169 | hs_cap = pci_bus_find_capability(slot->bus, | |
170 | slot->devfn, | |
171 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 172 | if (!hs_cap) |
1da177e4 | 173 | return -ENODEV; |
bcc488ab | 174 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
175 | slot->devfn, |
176 | hs_cap + 2, | |
bcc488ab | 177 | &hs_csr)) |
1da177e4 | 178 | return -ENODEV; |
bcc488ab | 179 | if (hs_csr & HS_CSR_EXT) { |
1da177e4 | 180 | /* Clear EXT (by setting it) */ |
bcc488ab | 181 | if (pci_bus_write_config_word(slot->bus, |
1da177e4 LT |
182 | slot->devfn, |
183 | hs_cap + 2, | |
bcc488ab | 184 | hs_csr)) |
1da177e4 | 185 | return -ENODEV; |
1da177e4 LT |
186 | } |
187 | return 0; | |
188 | } | |
189 | ||
190 | int cpci_led_on(struct slot* slot) | |
191 | { | |
192 | int hs_cap; | |
193 | u16 hs_csr; | |
194 | ||
195 | hs_cap = pci_bus_find_capability(slot->bus, | |
196 | slot->devfn, | |
197 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 198 | if (!hs_cap) |
1da177e4 | 199 | return -ENODEV; |
bcc488ab | 200 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
201 | slot->devfn, |
202 | hs_cap + 2, | |
bcc488ab | 203 | &hs_csr)) |
1da177e4 | 204 | return -ENODEV; |
bcc488ab | 205 | if ((hs_csr & HS_CSR_LOO) != HS_CSR_LOO) { |
1da177e4 | 206 | hs_csr |= HS_CSR_LOO; |
bcc488ab | 207 | if (pci_bus_write_config_word(slot->bus, |
1da177e4 LT |
208 | slot->devfn, |
209 | hs_cap + 2, | |
210 | hs_csr)) { | |
211 | err("Could not set LOO for slot %s", | |
d6c479e0 | 212 | hotplug_slot_name(slot->hotplug_slot)); |
1da177e4 LT |
213 | return -ENODEV; |
214 | } | |
215 | } | |
216 | return 0; | |
217 | } | |
218 | ||
219 | int cpci_led_off(struct slot* slot) | |
220 | { | |
221 | int hs_cap; | |
222 | u16 hs_csr; | |
223 | ||
224 | hs_cap = pci_bus_find_capability(slot->bus, | |
225 | slot->devfn, | |
226 | PCI_CAP_ID_CHSWP); | |
bcc488ab | 227 | if (!hs_cap) |
1da177e4 | 228 | return -ENODEV; |
bcc488ab | 229 | if (pci_bus_read_config_word(slot->bus, |
1da177e4 LT |
230 | slot->devfn, |
231 | hs_cap + 2, | |
bcc488ab | 232 | &hs_csr)) |
1da177e4 | 233 | return -ENODEV; |
bcc488ab | 234 | if (hs_csr & HS_CSR_LOO) { |
1da177e4 | 235 | hs_csr &= ~HS_CSR_LOO; |
bcc488ab | 236 | if (pci_bus_write_config_word(slot->bus, |
1da177e4 LT |
237 | slot->devfn, |
238 | hs_cap + 2, | |
239 | hs_csr)) { | |
240 | err("Could not clear LOO for slot %s", | |
d6c479e0 | 241 | hotplug_slot_name(slot->hotplug_slot)); |
1da177e4 LT |
242 | return -ENODEV; |
243 | } | |
244 | } | |
245 | return 0; | |
246 | } | |
247 | ||
248 | ||
249 | /* | |
250 | * Device configuration functions | |
251 | */ | |
252 | ||
0ab2b57f | 253 | int __ref cpci_configure_slot(struct slot *slot) |
1da177e4 | 254 | { |
cc702c2c SM |
255 | struct pci_bus *parent; |
256 | int fn; | |
1da177e4 | 257 | |
66bef8c0 | 258 | dbg("%s - enter", __func__); |
1da177e4 | 259 | |
bcc488ab | 260 | if (slot->dev == NULL) { |
1da177e4 LT |
261 | dbg("pci_dev null, finding %02x:%02x:%x", |
262 | slot->bus->number, PCI_SLOT(slot->devfn), PCI_FUNC(slot->devfn)); | |
bcc488ab | 263 | slot->dev = pci_get_slot(slot->bus, slot->devfn); |
1da177e4 LT |
264 | } |
265 | ||
266 | /* Still NULL? Well then scan for it! */ | |
bcc488ab | 267 | if (slot->dev == NULL) { |
1da177e4 LT |
268 | int n; |
269 | dbg("pci_dev still null"); | |
270 | ||
271 | /* | |
272 | * This will generate pci_dev structures for all functions, but | |
273 | * we will only call this case when lookup fails. | |
274 | */ | |
275 | n = pci_scan_slot(slot->bus, slot->devfn); | |
66bef8c0 | 276 | dbg("%s: pci_scan_slot returned %d", __func__, n); |
bcc488ab SM |
277 | slot->dev = pci_get_slot(slot->bus, slot->devfn); |
278 | if (slot->dev == NULL) { | |
1da177e4 | 279 | err("Could not find PCI device for slot %02x", slot->number); |
cc702c2c | 280 | return -ENODEV; |
1da177e4 LT |
281 | } |
282 | } | |
cc702c2c SM |
283 | parent = slot->dev->bus; |
284 | ||
285 | for (fn = 0; fn < 8; fn++) { | |
286 | struct pci_dev *dev; | |
287 | ||
288 | dev = pci_get_slot(parent, PCI_DEVFN(PCI_SLOT(slot->devfn), fn)); | |
289 | if (!dev) | |
290 | continue; | |
291 | if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || | |
292 | (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) { | |
293 | /* Find an unused bus number for the new bridge */ | |
294 | struct pci_bus *child; | |
295 | unsigned char busnr, start = parent->secondary; | |
296 | unsigned char end = parent->subordinate; | |
297 | ||
298 | for (busnr = start; busnr <= end; busnr++) { | |
299 | if (!pci_find_bus(pci_domain_nr(parent), | |
300 | busnr)) | |
301 | break; | |
302 | } | |
303 | if (busnr >= end) { | |
304 | err("No free bus for hot-added bridge\n"); | |
305 | pci_dev_put(dev); | |
306 | continue; | |
307 | } | |
308 | child = pci_add_new_bus(parent, dev, busnr); | |
309 | if (!child) { | |
310 | err("Cannot add new bus for %s\n", | |
311 | pci_name(dev)); | |
312 | pci_dev_put(dev); | |
313 | continue; | |
314 | } | |
315 | child->subordinate = pci_do_scan_bus(child); | |
316 | pci_bus_size_bridges(child); | |
317 | } | |
318 | pci_dev_put(dev); | |
1da177e4 LT |
319 | } |
320 | ||
cc702c2c SM |
321 | pci_bus_assign_resources(parent); |
322 | pci_bus_add_devices(parent); | |
323 | pci_enable_bridges(parent); | |
43b7d7cf | 324 | |
66bef8c0 | 325 | dbg("%s - exit", __func__); |
43b7d7cf | 326 | return 0; |
1da177e4 LT |
327 | } |
328 | ||
329 | int cpci_unconfigure_slot(struct slot* slot) | |
330 | { | |
1da177e4 | 331 | int i; |
1da177e4 LT |
332 | struct pci_dev *dev; |
333 | ||
66bef8c0 | 334 | dbg("%s - enter", __func__); |
bcc488ab | 335 | if (!slot->dev) { |
1da177e4 LT |
336 | err("No device for slot %02x\n", slot->number); |
337 | return -ENODEV; | |
338 | } | |
339 | ||
1da177e4 | 340 | for (i = 0; i < 8; i++) { |
bcc488ab | 341 | dev = pci_get_slot(slot->bus, |
1da177e4 | 342 | PCI_DEVFN(PCI_SLOT(slot->devfn), i)); |
bcc488ab | 343 | if (dev) { |
43b7d7cf | 344 | pci_remove_bus_device(dev); |
03e49d40 | 345 | pci_dev_put(dev); |
1da177e4 LT |
346 | } |
347 | } | |
03e49d40 SM |
348 | pci_dev_put(slot->dev); |
349 | slot->dev = NULL; | |
350 | ||
66bef8c0 | 351 | dbg("%s - exit", __func__); |
43b7d7cf | 352 | return 0; |
1da177e4 | 353 | } |