]>
Commit | Line | Data |
---|---|---|
736759ef | 1 | // SPDX-License-Identifier: GPL-2.0+ |
1da177e4 LT |
2 | /* |
3 | * Standard Hot Plug Controller Driver | |
4 | * | |
5 | * Copyright (C) 1995,2001 Compaq Computer Corporation | |
6 | * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) | |
7 | * Copyright (C) 2001 IBM Corp. | |
8 | * Copyright (C) 2003-2004 Intel Corporation | |
9 | * | |
10 | * All rights reserved. | |
11 | * | |
8cf4c195 | 12 | * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> |
1da177e4 LT |
13 | * |
14 | */ | |
15 | ||
1da177e4 LT |
16 | #include <linux/module.h> |
17 | #include <linux/moduleparam.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/types.h> | |
5a0e3ad6 | 20 | #include <linux/slab.h> |
1da177e4 | 21 | #include <linux/pci.h> |
1da177e4 | 22 | #include "shpchp.h" |
1da177e4 LT |
23 | |
24 | /* Global variables */ | |
90ab5ee9 RR |
25 | bool shpchp_debug; |
26 | bool shpchp_poll_mode; | |
1da177e4 | 27 | int shpchp_poll_time; |
1da177e4 LT |
28 | |
29 | #define DRIVER_VERSION "0.4" | |
30 | #define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>" | |
31 | #define DRIVER_DESC "Standard Hot Plug PCI Controller Driver" | |
32 | ||
33 | MODULE_AUTHOR(DRIVER_AUTHOR); | |
34 | MODULE_DESCRIPTION(DRIVER_DESC); | |
35 | MODULE_LICENSE("GPL"); | |
36 | ||
37 | module_param(shpchp_debug, bool, 0644); | |
38 | module_param(shpchp_poll_mode, bool, 0644); | |
39 | module_param(shpchp_poll_time, int, 0644); | |
40 | MODULE_PARM_DESC(shpchp_debug, "Debugging mode enabled or not"); | |
41 | MODULE_PARM_DESC(shpchp_poll_mode, "Using polling mechanism for hot-plug events or not"); | |
42 | MODULE_PARM_DESC(shpchp_poll_time, "Polling mechanism frequency, in seconds"); | |
43 | ||
44 | #define SHPC_MODULE_NAME "shpchp" | |
45 | ||
ff3ce480 BS |
46 | static int set_attention_status(struct hotplug_slot *slot, u8 value); |
47 | static int enable_slot(struct hotplug_slot *slot); | |
48 | static int disable_slot(struct hotplug_slot *slot); | |
49 | static int get_power_status(struct hotplug_slot *slot, u8 *value); | |
50 | static int get_attention_status(struct hotplug_slot *slot, u8 *value); | |
51 | static int get_latch_status(struct hotplug_slot *slot, u8 *value); | |
52 | static int get_adapter_status(struct hotplug_slot *slot, u8 *value); | |
1da177e4 | 53 | |
81c4b5bf | 54 | static const struct hotplug_slot_ops shpchp_hotplug_slot_ops = { |
1da177e4 LT |
55 | .set_attention_status = set_attention_status, |
56 | .enable_slot = enable_slot, | |
57 | .disable_slot = disable_slot, | |
58 | .get_power_status = get_power_status, | |
59 | .get_attention_status = get_attention_status, | |
60 | .get_latch_status = get_latch_status, | |
61 | .get_adapter_status = get_adapter_status, | |
1da177e4 LT |
62 | }; |
63 | ||
1da177e4 LT |
64 | static int init_slots(struct controller *ctrl) |
65 | { | |
926030f6 KK |
66 | struct slot *slot; |
67 | struct hotplug_slot *hotplug_slot; | |
66f17055 | 68 | char name[SLOT_NAME_SIZE]; |
83d05710 | 69 | int retval; |
5fe6cc60 | 70 | int i; |
926030f6 KK |
71 | |
72 | for (i = 0; i < ctrl->num_slots; i++) { | |
57c95c0d | 73 | slot = kzalloc(sizeof(*slot), GFP_KERNEL); |
83d05710 JL |
74 | if (!slot) { |
75 | retval = -ENOMEM; | |
1da177e4 | 76 | goto error; |
83d05710 | 77 | } |
1da177e4 | 78 | |
125450f8 | 79 | hotplug_slot = &slot->hotplug_slot; |
1da177e4 | 80 | |
926030f6 | 81 | slot->hp_slot = i; |
926030f6 | 82 | slot->ctrl = ctrl; |
227b84c7 | 83 | slot->bus = ctrl->pci_dev->subordinate->number; |
926030f6 KK |
84 | slot->device = ctrl->slot_device_offset + i; |
85 | slot->hpc_ops = ctrl->hpc_ops; | |
6f39be2e | 86 | slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); |
f652e7d2 | 87 | |
d8537548 | 88 | slot->wq = alloc_workqueue("shpchp-%d", 0, 0, slot->number); |
f652e7d2 BH |
89 | if (!slot->wq) { |
90 | retval = -ENOMEM; | |
125450f8 | 91 | goto error_slot; |
f652e7d2 BH |
92 | } |
93 | ||
a246fa4e | 94 | mutex_init(&slot->lock); |
e325e1f0 | 95 | INIT_DELAYED_WORK(&slot->work, shpchp_queue_pushbutton_work); |
1da177e4 LT |
96 | |
97 | /* register this slot with the hotplug pci core */ | |
66f17055 | 98 | snprintf(name, SLOT_NAME_SIZE, "%d", slot->number); |
926030f6 KK |
99 | hotplug_slot->ops = &shpchp_hotplug_slot_ops; |
100 | ||
227f0647 | 101 | ctrl_dbg(ctrl, "Registering domain:bus:dev=%04x:%02x:%02x hp_slot=%x sun=%x slot_device_offset=%x\n", |
f7625980 BH |
102 | pci_domain_nr(ctrl->pci_dev->subordinate), |
103 | slot->bus, slot->device, slot->hp_slot, slot->number, | |
104 | ctrl->slot_device_offset); | |
125450f8 | 105 | retval = pci_hp_register(hotplug_slot, |
66f17055 | 106 | ctrl->pci_dev->subordinate, slot->device, name); |
926030f6 | 107 | if (retval) { |
f98ca311 TI |
108 | ctrl_err(ctrl, "pci_hp_register failed with error %d\n", |
109 | retval); | |
f652e7d2 | 110 | goto error_slotwq; |
1da177e4 LT |
111 | } |
112 | ||
a7da2161 LW |
113 | get_power_status(hotplug_slot, &slot->pwr_save); |
114 | get_attention_status(hotplug_slot, &slot->attention_save); | |
115 | get_latch_status(hotplug_slot, &slot->latch_save); | |
116 | get_adapter_status(hotplug_slot, &slot->presence_save); | |
66f17055 | 117 | |
5b1a960d | 118 | list_add(&slot->slot_list, &ctrl->slot_list); |
1da177e4 LT |
119 | } |
120 | ||
121 | return 0; | |
f652e7d2 BH |
122 | error_slotwq: |
123 | destroy_workqueue(slot->wq); | |
1da177e4 | 124 | error_slot: |
926030f6 | 125 | kfree(slot); |
1da177e4 | 126 | error: |
926030f6 | 127 | return retval; |
1da177e4 LT |
128 | } |
129 | ||
f7391f53 | 130 | void cleanup_slots(struct controller *ctrl) |
1da177e4 | 131 | { |
2ac83ccc | 132 | struct slot *slot, *next; |
1da177e4 | 133 | |
2ac83ccc | 134 | list_for_each_entry_safe(slot, next, &ctrl->slot_list, slot_list) { |
5b1a960d | 135 | list_del(&slot->slot_list); |
f7391f53 | 136 | cancel_delayed_work(&slot->work); |
f652e7d2 | 137 | destroy_workqueue(slot->wq); |
125450f8 | 138 | pci_hp_deregister(&slot->hotplug_slot); |
51bbf9be | 139 | kfree(slot); |
1da177e4 LT |
140 | } |
141 | } | |
142 | ||
1da177e4 LT |
143 | /* |
144 | * set_attention_status - Turns the Amber LED for a slot on, off or blink | |
145 | */ | |
ff3ce480 | 146 | static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) |
1da177e4 | 147 | { |
8352e04e | 148 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 | 149 | |
be7bce25 | 150 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 151 | __func__, slot_name(slot)); |
1da177e4 | 152 | |
a7da2161 | 153 | slot->attention_save = status; |
1da177e4 LT |
154 | slot->hpc_ops->set_attention_status(slot, status); |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
ff3ce480 | 159 | static int enable_slot(struct hotplug_slot *hotplug_slot) |
1da177e4 | 160 | { |
8352e04e | 161 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 | 162 | |
be7bce25 | 163 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 164 | __func__, slot_name(slot)); |
1da177e4 | 165 | |
a246fa4e | 166 | return shpchp_sysfs_enable_slot(slot); |
1da177e4 LT |
167 | } |
168 | ||
ff3ce480 | 169 | static int disable_slot(struct hotplug_slot *hotplug_slot) |
1da177e4 | 170 | { |
8352e04e | 171 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 | 172 | |
be7bce25 | 173 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 174 | __func__, slot_name(slot)); |
1da177e4 | 175 | |
a246fa4e | 176 | return shpchp_sysfs_disable_slot(slot); |
1da177e4 LT |
177 | } |
178 | ||
ff3ce480 | 179 | static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) |
1da177e4 | 180 | { |
8352e04e | 181 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 LT |
182 | int retval; |
183 | ||
be7bce25 | 184 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 185 | __func__, slot_name(slot)); |
1da177e4 LT |
186 | |
187 | retval = slot->hpc_ops->get_power_status(slot, value); | |
188 | if (retval < 0) | |
a7da2161 | 189 | *value = slot->pwr_save; |
1da177e4 LT |
190 | |
191 | return 0; | |
192 | } | |
193 | ||
ff3ce480 | 194 | static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) |
1da177e4 | 195 | { |
8352e04e | 196 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 LT |
197 | int retval; |
198 | ||
be7bce25 | 199 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 200 | __func__, slot_name(slot)); |
1da177e4 LT |
201 | |
202 | retval = slot->hpc_ops->get_attention_status(slot, value); | |
203 | if (retval < 0) | |
a7da2161 | 204 | *value = slot->attention_save; |
1da177e4 LT |
205 | |
206 | return 0; | |
207 | } | |
208 | ||
ff3ce480 | 209 | static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) |
1da177e4 | 210 | { |
8352e04e | 211 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 LT |
212 | int retval; |
213 | ||
be7bce25 | 214 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 215 | __func__, slot_name(slot)); |
1da177e4 LT |
216 | |
217 | retval = slot->hpc_ops->get_latch_status(slot, value); | |
218 | if (retval < 0) | |
a7da2161 | 219 | *value = slot->latch_save; |
1da177e4 LT |
220 | |
221 | return 0; | |
222 | } | |
223 | ||
ff3ce480 | 224 | static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) |
1da177e4 | 225 | { |
8352e04e | 226 | struct slot *slot = get_slot(hotplug_slot); |
1da177e4 LT |
227 | int retval; |
228 | ||
be7bce25 | 229 | ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", |
f98ca311 | 230 | __func__, slot_name(slot)); |
1da177e4 LT |
231 | |
232 | retval = slot->hpc_ops->get_adapter_status(slot, value); | |
233 | if (retval < 0) | |
a7da2161 | 234 | *value = slot->presence_save; |
1da177e4 LT |
235 | |
236 | return 0; | |
237 | } | |
238 | ||
b03799b0 BH |
239 | static bool shpc_capable(struct pci_dev *bridge) |
240 | { | |
241 | /* | |
242 | * It is assumed that AMD GOLAM chips support SHPC but they do not | |
243 | * have SHPC capability. | |
244 | */ | |
245 | if (bridge->vendor == PCI_VENDOR_ID_AMD && | |
246 | bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) | |
247 | return true; | |
248 | ||
249 | if (pci_find_capability(bridge, PCI_CAP_ID_SHPC)) | |
250 | return true; | |
251 | ||
252 | return false; | |
253 | } | |
254 | ||
1da177e4 LT |
255 | static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) |
256 | { | |
257 | int rc; | |
258 | struct controller *ctrl; | |
1da177e4 | 259 | |
b03799b0 BH |
260 | if (!shpc_capable(pdev)) |
261 | return -ENODEV; | |
262 | ||
90cc0c3c | 263 | if (acpi_get_hp_hw_control_from_firmware(pdev)) |
1410dc1c RS |
264 | return -ENODEV; |
265 | ||
57c95c0d | 266 | ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); |
c7abb235 | 267 | if (!ctrl) |
1da177e4 | 268 | goto err_out_none; |
c7abb235 | 269 | |
5b1a960d | 270 | INIT_LIST_HEAD(&ctrl->slot_list); |
1da177e4 | 271 | |
ee138334 | 272 | rc = shpc_init(ctrl, pdev); |
1da177e4 | 273 | if (rc) { |
be7bce25 | 274 | ctrl_dbg(ctrl, "Controller initialization failed\n"); |
1da177e4 LT |
275 | goto err_out_free_ctrl; |
276 | } | |
277 | ||
1da177e4 LT |
278 | pci_set_drvdata(pdev, ctrl); |
279 | ||
1da177e4 LT |
280 | /* Setup the slot information structures */ |
281 | rc = init_slots(ctrl); | |
282 | if (rc) { | |
be7bce25 | 283 | ctrl_err(ctrl, "Slot initialization failed\n"); |
f7391f53 | 284 | goto err_out_release_ctlr; |
1da177e4 LT |
285 | } |
286 | ||
e1b95dc6 GKH |
287 | rc = shpchp_create_ctrl_files(ctrl); |
288 | if (rc) | |
289 | goto err_cleanup_slots; | |
1da177e4 | 290 | |
b03799b0 | 291 | pdev->shpc_managed = 1; |
1da177e4 LT |
292 | return 0; |
293 | ||
e1b95dc6 GKH |
294 | err_cleanup_slots: |
295 | cleanup_slots(ctrl); | |
f7391f53 | 296 | err_out_release_ctlr: |
1da177e4 LT |
297 | ctrl->hpc_ops->release_ctlr(ctrl); |
298 | err_out_free_ctrl: | |
299 | kfree(ctrl); | |
300 | err_out_none: | |
301 | return -ENODEV; | |
302 | } | |
303 | ||
e6b82b13 | 304 | static void shpc_remove(struct pci_dev *dev) |
1da177e4 | 305 | { |
e6b82b13 | 306 | struct controller *ctrl = pci_get_drvdata(dev); |
1da177e4 | 307 | |
b03799b0 | 308 | dev->shpc_managed = 0; |
e6b82b13 KK |
309 | shpchp_remove_ctrl_files(ctrl); |
310 | ctrl->hpc_ops->release_ctlr(ctrl); | |
311 | kfree(ctrl); | |
1da177e4 LT |
312 | } |
313 | ||
8394264d | 314 | static const struct pci_device_id shpcd_pci_tbl[] = { |
dfcd5f68 | 315 | {PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0)}, |
1da177e4 LT |
316 | { /* end: all zeroes */ } |
317 | }; | |
1da177e4 LT |
318 | MODULE_DEVICE_TABLE(pci, shpcd_pci_tbl); |
319 | ||
1da177e4 LT |
320 | static struct pci_driver shpc_driver = { |
321 | .name = SHPC_MODULE_NAME, | |
322 | .id_table = shpcd_pci_tbl, | |
323 | .probe = shpc_probe, | |
e6b82b13 | 324 | .remove = shpc_remove, |
1da177e4 LT |
325 | }; |
326 | ||
1da177e4 LT |
327 | static int __init shpcd_init(void) |
328 | { | |
f652e7d2 | 329 | int retval; |
e24dcbef | 330 | |
424600f9 | 331 | retval = pci_register_driver(&shpc_driver); |
66bef8c0 | 332 | dbg("%s: pci_register_driver = %d\n", __func__, retval); |
424600f9 | 333 | info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); |
f652e7d2 | 334 | |
1da177e4 LT |
335 | return retval; |
336 | } | |
337 | ||
338 | static void __exit shpcd_cleanup(void) | |
339 | { | |
340 | dbg("unload_shpchpd()\n"); | |
1da177e4 | 341 | pci_unregister_driver(&shpc_driver); |
1da177e4 LT |
342 | info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); |
343 | } | |
344 | ||
345 | module_init(shpcd_init); | |
346 | module_exit(shpcd_cleanup); |