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