2005-04-17 02:20:36 +04:00
/*
* Standard Hot Plug Controller Driver
*
* Copyright ( C ) 1995 , 2001 Compaq Computer Corporation
* Copyright ( C ) 2001 Greg Kroah - Hartman ( greg @ kroah . com )
* Copyright ( C ) 2001 IBM Corp .
* Copyright ( C ) 2003 - 2004 Intel Corporation
*
* All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE , GOOD TITLE or
* NON INFRINGEMENT . See the GNU General Public License for more
* details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
2005-08-17 02:16:10 +04:00
* Send feedback to < greg @ kroah . com > , < kristen . c . accardi @ intel . com >
2005-04-17 02:20:36 +04:00
*
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/pci.h>
2006-02-22 02:45:45 +03:00
# include <linux/workqueue.h>
2005-04-17 02:20:36 +04:00
# include "shpchp.h"
/* Global variables */
int shpchp_debug ;
int shpchp_poll_mode ;
int shpchp_poll_time ;
2006-01-26 03:58:30 +03:00
LIST_HEAD ( shpchp_ctrl_list ) ;
2006-02-22 02:45:45 +03:00
struct workqueue_struct * shpchp_wq ;
2005-04-17 02:20:36 +04:00
# define DRIVER_VERSION "0.4"
# define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>, Dely Sy <dely.l.sy@intel.com>"
# define DRIVER_DESC "Standard Hot Plug PCI Controller Driver"
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
module_param ( shpchp_debug , bool , 0644 ) ;
module_param ( shpchp_poll_mode , bool , 0644 ) ;
module_param ( shpchp_poll_time , int , 0644 ) ;
MODULE_PARM_DESC ( shpchp_debug , " Debugging mode enabled or not " ) ;
MODULE_PARM_DESC ( shpchp_poll_mode , " Using polling mechanism for hot-plug events or not " ) ;
MODULE_PARM_DESC ( shpchp_poll_time , " Polling mechanism frequency, in seconds " ) ;
# define SHPC_MODULE_NAME "shpchp"
static int set_attention_status ( struct hotplug_slot * slot , u8 value ) ;
static int enable_slot ( struct hotplug_slot * slot ) ;
static int disable_slot ( struct hotplug_slot * slot ) ;
static int get_power_status ( struct hotplug_slot * slot , u8 * value ) ;
static int get_attention_status ( struct hotplug_slot * slot , u8 * value ) ;
static int get_latch_status ( struct hotplug_slot * slot , u8 * value ) ;
static int get_adapter_status ( struct hotplug_slot * slot , u8 * value ) ;
2005-12-05 13:31:00 +03:00
static int get_address ( struct hotplug_slot * slot , u32 * value ) ;
2005-04-17 02:20:36 +04:00
static int get_max_bus_speed ( struct hotplug_slot * slot , enum pci_bus_speed * value ) ;
static int get_cur_bus_speed ( struct hotplug_slot * slot , enum pci_bus_speed * value ) ;
static struct hotplug_slot_ops shpchp_hotplug_slot_ops = {
. owner = THIS_MODULE ,
. set_attention_status = set_attention_status ,
. enable_slot = enable_slot ,
. disable_slot = disable_slot ,
. get_power_status = get_power_status ,
. get_attention_status = get_attention_status ,
. get_latch_status = get_latch_status ,
. get_adapter_status = get_adapter_status ,
2005-12-05 13:31:00 +03:00
. get_address = get_address ,
2005-04-17 02:20:36 +04:00
. get_max_bus_speed = get_max_bus_speed ,
. get_cur_bus_speed = get_cur_bus_speed ,
} ;
/**
* release_slot - free up the memory used by a slot
* @ hotplug_slot : slot to free
*/
static void release_slot ( struct hotplug_slot * hotplug_slot )
{
2005-05-05 22:57:25 +04:00
struct slot * slot = hotplug_slot - > private ;
2005-04-17 02:20:36 +04:00
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
kfree ( slot - > hotplug_slot - > info ) ;
kfree ( slot - > hotplug_slot ) ;
kfree ( slot ) ;
}
2006-01-26 03:55:35 +03:00
static void make_slot_name ( struct slot * slot )
{
snprintf ( slot - > hotplug_slot - > name , SLOT_NAME_SIZE , " %04d_%04d " ,
slot - > bus , slot - > number ) ;
}
2005-04-17 02:20:36 +04:00
static int init_slots ( struct controller * ctrl )
{
2006-01-26 03:55:35 +03:00
struct slot * slot ;
struct hotplug_slot * hotplug_slot ;
struct hotplug_slot_info * info ;
int retval = - ENOMEM ;
int i ;
u32 sun ;
for ( i = 0 ; i < ctrl - > num_slots ; i + + ) {
2006-01-26 04:02:41 +03:00
slot = kzalloc ( sizeof ( * slot ) , GFP_KERNEL ) ;
2006-01-26 03:55:35 +03:00
if ( ! slot )
2005-04-17 02:20:36 +04:00
goto error ;
2006-01-26 04:02:41 +03:00
hotplug_slot = kzalloc ( sizeof ( * hotplug_slot ) , GFP_KERNEL ) ;
2006-01-26 03:55:35 +03:00
if ( ! hotplug_slot )
2005-04-17 02:20:36 +04:00
goto error_slot ;
2006-01-26 03:55:35 +03:00
slot - > hotplug_slot = hotplug_slot ;
2005-04-17 02:20:36 +04:00
2006-01-26 04:02:41 +03:00
info = kzalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
2006-01-26 03:55:35 +03:00
if ( ! info )
2005-04-17 02:20:36 +04:00
goto error_hpslot ;
2006-01-26 03:55:35 +03:00
hotplug_slot - > info = info ;
2006-01-26 04:04:56 +03:00
hotplug_slot - > name = slot - > name ;
2005-04-17 02:20:36 +04:00
2006-01-26 03:55:35 +03:00
slot - > hp_slot = i ;
slot - > ctrl = ctrl ;
slot - > bus = ctrl - > slot_bus ;
slot - > device = ctrl - > slot_device_offset + i ;
slot - > hpc_ops = ctrl - > hpc_ops ;
2005-04-17 02:20:36 +04:00
if ( shpchprm_get_physical_slot_number ( ctrl , & sun ,
2006-01-26 03:55:35 +03:00
slot - > bus , slot - > device ) )
2006-01-26 04:04:56 +03:00
goto error_info ;
2005-04-17 02:20:36 +04:00
2006-01-26 03:55:35 +03:00
slot - > number = sun ;
2006-02-22 02:45:45 +03:00
INIT_WORK ( & slot - > work , shpchp_pushbutton_thread , slot ) ;
2005-04-17 02:20:36 +04:00
/* register this slot with the hotplug pci core */
2006-01-26 03:55:35 +03:00
hotplug_slot - > private = slot ;
hotplug_slot - > release = & release_slot ;
make_slot_name ( slot ) ;
hotplug_slot - > ops = & shpchp_hotplug_slot_ops ;
get_power_status ( hotplug_slot , & info - > power_status ) ;
get_attention_status ( hotplug_slot , & info - > attention_status ) ;
get_latch_status ( hotplug_slot , & info - > latch_status ) ;
get_adapter_status ( hotplug_slot , & info - > adapter_status ) ;
dbg ( " Registering bus=%x dev=%x hp_slot=%x sun=%x "
" slot_device_offset=%x \n " , slot - > bus , slot - > device ,
slot - > hp_slot , slot - > number , ctrl - > slot_device_offset ) ;
retval = pci_hp_register ( slot - > hotplug_slot ) ;
if ( retval ) {
err ( " pci_hp_register failed with error %d \n " , retval ) ;
2006-01-26 04:04:56 +03:00
goto error_info ;
2005-04-17 02:20:36 +04:00
}
2006-01-26 03:57:40 +03:00
list_add ( & slot - > slot_list , & ctrl - > slot_list ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
error_info :
2006-01-26 03:55:35 +03:00
kfree ( info ) ;
2005-04-17 02:20:36 +04:00
error_hpslot :
2006-01-26 03:55:35 +03:00
kfree ( hotplug_slot ) ;
2005-04-17 02:20:36 +04:00
error_slot :
2006-01-26 03:55:35 +03:00
kfree ( slot ) ;
2005-04-17 02:20:36 +04:00
error :
2006-01-26 03:55:35 +03:00
return retval ;
2005-04-17 02:20:36 +04:00
}
2006-02-22 02:45:45 +03:00
void cleanup_slots ( struct controller * ctrl )
2005-04-17 02:20:36 +04:00
{
2006-01-26 03:57:40 +03:00
struct list_head * tmp ;
struct list_head * next ;
struct slot * slot ;
2005-04-17 02:20:36 +04:00
2006-01-26 03:57:40 +03:00
list_for_each_safe ( tmp , next , & ctrl - > slot_list ) {
slot = list_entry ( tmp , struct slot , slot_list ) ;
list_del ( & slot - > slot_list ) ;
2006-02-22 02:45:45 +03:00
cancel_delayed_work ( & slot - > work ) ;
flush_workqueue ( shpchp_wq ) ;
2006-01-26 03:57:40 +03:00
pci_hp_deregister ( slot - > hotplug_slot ) ;
2005-04-17 02:20:36 +04:00
}
}
static int get_ctlr_slot_config ( struct controller * ctrl )
{
int num_ctlr_slots ;
int first_device_num ;
int physical_slot_num ;
int updown ;
int rc ;
int flags ;
2006-01-26 03:56:53 +03:00
rc = shpc_get_ctlr_slot_config ( ctrl , & num_ctlr_slots ,
& first_device_num , & physical_slot_num ,
& updown , & flags ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
2006-01-26 03:56:53 +03:00
err ( " %s: get_ctlr_slot_config fail for b:d (%x:%x) \n " ,
__FUNCTION__ , ctrl - > bus , ctrl - > device ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
ctrl - > num_slots = num_ctlr_slots ;
ctrl - > slot_device_offset = first_device_num ;
ctrl - > first_slot = physical_slot_num ;
ctrl - > slot_num_inc = updown ; /* either -1 or 1 */
2006-01-26 03:56:53 +03:00
dbg ( " %s: num_slot(0x%x) 1st_dev(0x%x) psn(0x%x) updown(%d) for b:d "
" (%x:%x) \n " , __FUNCTION__ , num_ctlr_slots , first_device_num ,
physical_slot_num , updown , ctrl - > bus , ctrl - > device ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/*
* set_attention_status - Turns the Amber LED for a slot on , off or blink
*/
static int set_attention_status ( struct hotplug_slot * hotplug_slot , u8 status )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
hotplug_slot - > info - > attention_status = status ;
slot - > hpc_ops - > set_attention_status ( slot , status ) ;
return 0 ;
}
static int enable_slot ( struct hotplug_slot * hotplug_slot )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
return shpchp_enable_slot ( slot ) ;
}
static int disable_slot ( struct hotplug_slot * hotplug_slot )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
return shpchp_disable_slot ( slot ) ;
}
static int get_power_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
retval = slot - > hpc_ops - > get_power_status ( slot , value ) ;
if ( retval < 0 )
* value = hotplug_slot - > info - > power_status ;
return 0 ;
}
static int get_attention_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
retval = slot - > hpc_ops - > get_attention_status ( slot , value ) ;
if ( retval < 0 )
* value = hotplug_slot - > info - > attention_status ;
return 0 ;
}
static int get_latch_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
retval = slot - > hpc_ops - > get_latch_status ( slot , value ) ;
if ( retval < 0 )
* value = hotplug_slot - > info - > latch_status ;
return 0 ;
}
static int get_adapter_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
retval = slot - > hpc_ops - > get_adapter_status ( slot , value ) ;
if ( retval < 0 )
* value = hotplug_slot - > info - > adapter_status ;
return 0 ;
}
2005-12-05 13:31:00 +03:00
static int get_address ( struct hotplug_slot * hotplug_slot , u32 * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-12-05 13:31:00 +03:00
struct pci_bus * bus = slot - > ctrl - > pci_dev - > subordinate ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
* value = ( pci_domain_nr ( bus ) < < 16 ) | ( slot - > bus < < 8 ) | slot - > device ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
static int get_max_bus_speed ( struct hotplug_slot * hotplug_slot , enum pci_bus_speed * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
2006-01-26 03:56:53 +03:00
2005-04-17 02:20:36 +04:00
retval = slot - > hpc_ops - > get_max_bus_speed ( slot , value ) ;
if ( retval < 0 )
* value = PCI_SPEED_UNKNOWN ;
return 0 ;
}
static int get_cur_bus_speed ( struct hotplug_slot * hotplug_slot , enum pci_bus_speed * value )
{
2006-01-26 03:56:53 +03:00
struct slot * slot = get_slot ( hotplug_slot , __FUNCTION__ ) ;
2005-04-17 02:20:36 +04:00
int retval ;
dbg ( " %s - physical_slot = %s \n " , __FUNCTION__ , hotplug_slot - > name ) ;
2006-01-26 03:56:53 +03:00
2005-04-17 02:20:36 +04:00
retval = slot - > hpc_ops - > get_cur_bus_speed ( slot , value ) ;
if ( retval < 0 )
* value = PCI_SPEED_UNKNOWN ;
return 0 ;
}
2005-10-13 23:05:39 +04:00
static int is_shpc_capable ( struct pci_dev * dev )
{
if ( ( dev - > vendor = = PCI_VENDOR_ID_AMD ) | | ( dev - > device = =
PCI_DEVICE_ID_AMD_GOLAM_7450 ) )
return 1 ;
if ( pci_find_capability ( dev , PCI_CAP_ID_SHPC ) )
return 1 ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
static int shpc_probe ( struct pci_dev * pdev , const struct pci_device_id * ent )
{
int rc ;
struct controller * ctrl ;
struct slot * t_slot ;
2006-01-26 03:56:53 +03:00
int first_device_num ; /* first PCI device number */
int num_ctlr_slots ; /* number of slots implemented */
2005-04-17 02:20:36 +04:00
2005-10-13 23:05:39 +04:00
if ( ! is_shpc_capable ( pdev ) )
return - ENODEV ;
2006-01-26 04:02:41 +03:00
ctrl = kzalloc ( sizeof ( * ctrl ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! ctrl ) {
err ( " %s : out of memory \n " , __FUNCTION__ ) ;
goto err_out_none ;
}
2006-01-26 03:57:40 +03:00
INIT_LIST_HEAD ( & ctrl - > slot_list ) ;
2005-04-17 02:20:36 +04:00
2005-10-13 23:05:42 +04:00
rc = shpc_init ( ctrl , pdev ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
2006-01-26 03:56:53 +03:00
dbg ( " %s: controller initialization failed \n " ,
SHPC_MODULE_NAME ) ;
2005-04-17 02:20:36 +04:00
goto err_out_free_ctrl ;
}
pci_set_drvdata ( pdev , ctrl ) ;
ctrl - > bus = pdev - > bus - > number ;
ctrl - > slot_bus = pdev - > subordinate - > number ;
ctrl - > device = PCI_SLOT ( pdev - > devfn ) ;
ctrl - > function = PCI_FUNC ( pdev - > devfn ) ;
2006-01-26 03:56:53 +03:00
dbg ( " ctrl bus=0x%x, device=%x, function=%x, irq=%x \n " ,
ctrl - > bus , ctrl - > device , ctrl - > function , pdev - > irq ) ;
2005-04-17 02:20:36 +04:00
/*
2006-01-26 03:56:53 +03:00
* Save configuration headers for this and subordinate PCI buses
2005-04-17 02:20:36 +04:00
*/
rc = get_ctlr_slot_config ( ctrl ) ;
if ( rc ) {
err ( msg_initialization_err , rc ) ;
2006-02-22 02:45:45 +03:00
goto err_out_release_ctlr ;
2005-04-17 02:20:36 +04:00
}
first_device_num = ctrl - > slot_device_offset ;
num_ctlr_slots = ctrl - > num_slots ;
2005-10-13 23:05:36 +04:00
ctrl - > add_support = 1 ;
2006-01-26 03:56:53 +03:00
2005-04-17 02:20:36 +04:00
/* Setup the slot information structures */
rc = init_slots ( ctrl ) ;
if ( rc ) {
err ( msg_initialization_err , 6 ) ;
2006-02-22 02:45:45 +03:00
goto err_out_release_ctlr ;
2005-04-17 02:20:36 +04:00
}
/* Now hpc_functions (slot->hpc_ops->functions) are ready */
t_slot = shpchp_find_slot ( ctrl , first_device_num ) ;
/* Check for operation bus speed */
rc = t_slot - > hpc_ops - > get_cur_bus_speed ( t_slot , & ctrl - > speed ) ;
dbg ( " %s: t_slot->hp_slot %x \n " , __FUNCTION__ , t_slot - > hp_slot ) ;
if ( rc | | ctrl - > speed = = PCI_SPEED_UNKNOWN ) {
2006-01-26 03:56:53 +03:00
err ( SHPC_MODULE_NAME " : Can't get current bus speed. "
" Set to 33MHz PCI. \n " ) ;
2005-04-17 02:20:36 +04:00
ctrl - > speed = PCI_SPEED_33MHz ;
}
2006-01-26 03:58:30 +03:00
list_add ( & ctrl - > ctrl_list , & shpchp_ctrl_list ) ;
2005-04-17 02:20:36 +04:00
shpchp_create_ctrl_files ( ctrl ) ;
return 0 ;
2006-02-22 02:45:45 +03:00
err_out_release_ctlr :
2005-04-17 02:20:36 +04:00
ctrl - > hpc_ops - > release_ctlr ( ctrl ) ;
err_out_free_ctrl :
kfree ( ctrl ) ;
err_out_none :
return - ENODEV ;
}
static void __exit unload_shpchpd ( void )
{
2006-01-26 03:58:30 +03:00
struct list_head * tmp ;
struct list_head * next ;
2005-04-17 02:20:36 +04:00
struct controller * ctrl ;
2006-01-26 03:58:30 +03:00
list_for_each_safe ( tmp , next , & shpchp_ctrl_list ) {
ctrl = list_entry ( tmp , struct controller , ctrl_list ) ;
2005-10-13 23:05:44 +04:00
shpchp_remove_ctrl_files ( ctrl ) ;
2005-04-17 02:20:36 +04:00
ctrl - > hpc_ops - > release_ctlr ( ctrl ) ;
2006-01-26 03:58:30 +03:00
kfree ( ctrl ) ;
2005-04-17 02:20:36 +04:00
}
2006-02-22 02:45:45 +03:00
destroy_workqueue ( shpchp_wq ) ;
2005-04-17 02:20:36 +04:00
}
static struct pci_device_id shpcd_pci_tbl [ ] = {
2006-01-26 03:56:53 +03:00
{ PCI_DEVICE_CLASS ( ( ( PCI_CLASS_BRIDGE_PCI < < 8 ) | 0x00 ) , ~ 0 ) } ,
2005-04-17 02:20:36 +04:00
{ /* end: all zeroes */ }
} ;
MODULE_DEVICE_TABLE ( pci , shpcd_pci_tbl ) ;
static struct pci_driver shpc_driver = {
. name = SHPC_MODULE_NAME ,
. id_table = shpcd_pci_tbl ,
. probe = shpc_probe ,
/* remove: shpc_remove_one, */
} ;
static int __init shpcd_init ( void )
{
int retval = 0 ;
# ifdef CONFIG_HOTPLUG_PCI_SHPC_POLL_EVENT_MODE
shpchp_poll_mode = 1 ;
# endif
2006-02-22 02:45:45 +03:00
shpchp_wq = create_singlethread_workqueue ( " shpchpd " ) ;
if ( ! shpchp_wq )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2005-10-13 23:05:38 +04:00
retval = pci_register_driver ( & shpc_driver ) ;
dbg ( " %s: pci_register_driver = %d \n " , __FUNCTION__ , retval ) ;
info ( DRIVER_DESC " version: " DRIVER_VERSION " \n " ) ;
2005-04-17 02:20:36 +04:00
if ( retval ) {
2006-02-22 02:45:45 +03:00
destroy_workqueue ( shpchp_wq ) ;
2005-10-13 23:05:36 +04:00
}
2005-04-17 02:20:36 +04:00
return retval ;
}
static void __exit shpcd_cleanup ( void )
{
dbg ( " unload_shpchpd() \n " ) ;
unload_shpchpd ( ) ;
pci_unregister_driver ( & shpc_driver ) ;
info ( DRIVER_DESC " version: " DRIVER_VERSION " unloaded \n " ) ;
}
module_init ( shpcd_init ) ;
module_exit ( shpcd_cleanup ) ;