2005-04-17 02:20:36 +04:00
/*
* PCI Express 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>
# include "pciehp.h"
# include <linux/interrupt.h>
2007-01-10 00:02:36 +03:00
# include <linux/time.h>
2005-04-17 02:20:36 +04:00
/* Global variables */
int pciehp_debug ;
int pciehp_poll_mode ;
int pciehp_poll_time ;
2005-11-01 03:20:12 +03:00
int pciehp_force ;
2007-03-07 02:02:26 +03:00
struct workqueue_struct * pciehp_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 "PCI Express Hot Plug Controller Driver"
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
module_param ( pciehp_debug , bool , 0644 ) ;
module_param ( pciehp_poll_mode , bool , 0644 ) ;
module_param ( pciehp_poll_time , int , 0644 ) ;
2005-11-01 03:20:12 +03:00
module_param ( pciehp_force , bool , 0644 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( pciehp_debug , " Debugging mode enabled or not " ) ;
MODULE_PARM_DESC ( pciehp_poll_mode , " Using polling mechanism for hot-plug events or not " ) ;
MODULE_PARM_DESC ( pciehp_poll_time , " Polling mechanism frequency, in seconds " ) ;
2005-11-01 03:20:12 +03:00
MODULE_PARM_DESC ( pciehp_force , " Force pciehp, even if _OSC and OSHP are missing " ) ;
2005-04-17 02:20:36 +04:00
# define PCIE_MODULE_NAME "pciehp"
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 ) ;
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 ) ;
2005-04-29 05:08:53 +04:00
/**
* release_slot - free up the memory used by a slot
* @ hotplug_slot : slot to free
*/
static void release_slot ( struct hotplug_slot * hotplug_slot )
{
2008-09-05 07:11:26 +04:00
struct slot * slot = hotplug_slot - > private ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , hotplug_slot_name ( hotplug_slot ) ) ;
2005-04-29 05:08:53 +04:00
2009-10-05 12:41:37 +04:00
kfree ( hotplug_slot - > ops ) ;
2008-06-20 07:07:08 +04:00
kfree ( hotplug_slot - > info ) ;
kfree ( hotplug_slot ) ;
2006-12-22 04:01:02 +03:00
}
2009-09-15 12:24:46 +04:00
static int init_slot ( struct controller * ctrl )
2005-04-17 02:20:36 +04:00
{
2009-09-15 12:24:46 +04:00
struct slot * slot = ctrl - > slot ;
struct hotplug_slot * hotplug = NULL ;
struct hotplug_slot_info * info = NULL ;
2009-10-05 12:41:37 +04:00
struct hotplug_slot_ops * ops = NULL ;
2008-10-21 03:41:38 +04:00
char name [ SLOT_NAME_SIZE ] ;
2006-12-22 04:01:02 +03:00
int retval = - ENOMEM ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:24:46 +04:00
hotplug = kzalloc ( sizeof ( * hotplug ) , GFP_KERNEL ) ;
if ( ! hotplug )
goto out ;
info = kzalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info )
goto out ;
2009-10-05 12:41:37 +04:00
/* Setup hotplug slot ops */
ops = kzalloc ( sizeof ( * ops ) , GFP_KERNEL ) ;
if ( ! ops )
goto out ;
ops - > enable_slot = enable_slot ;
ops - > disable_slot = disable_slot ;
ops - > get_power_status = get_power_status ;
ops - > get_adapter_status = get_adapter_status ;
ops - > get_max_bus_speed = get_max_bus_speed ;
ops - > get_cur_bus_speed = get_cur_bus_speed ;
if ( MRL_SENS ( ctrl ) )
ops - > get_latch_status = get_latch_status ;
if ( ATTN_LED ( ctrl ) ) {
ops - > get_attention_status = get_attention_status ;
ops - > set_attention_status = set_attention_status ;
}
2009-09-15 12:24:46 +04:00
/* register this slot with the hotplug pci core */
hotplug - > info = info ;
hotplug - > private = slot ;
hotplug - > release = & release_slot ;
2009-10-05 12:41:37 +04:00
hotplug - > ops = ops ;
2009-09-15 12:24:46 +04:00
slot - > hotplug_slot = hotplug ;
2009-09-15 12:31:16 +04:00
snprintf ( name , SLOT_NAME_SIZE , " %u " , PSN ( ctrl ) ) ;
2009-09-15 12:24:46 +04:00
2009-09-15 12:28:28 +04:00
ctrl_dbg ( ctrl , " Registering domain:bus:dev=%04x:%02x:00 sun=%x \n " ,
2009-09-15 12:30:14 +04:00
pci_domain_nr ( ctrl - > pcie - > port - > subordinate ) ,
2009-09-15 12:31:16 +04:00
ctrl - > pcie - > port - > subordinate - > number , PSN ( ctrl ) ) ;
2009-09-15 12:24:46 +04:00
retval = pci_hp_register ( hotplug ,
2009-09-15 12:30:14 +04:00
ctrl - > pcie - > port - > subordinate , 0 , name ) ;
2009-09-15 12:24:46 +04:00
if ( retval ) {
ctrl_err ( ctrl ,
" pci_hp_register failed with error %d \n " , retval ) ;
goto out ;
}
get_power_status ( hotplug , & info - > power_status ) ;
get_attention_status ( hotplug , & info - > attention_status ) ;
get_latch_status ( hotplug , & info - > latch_status ) ;
get_adapter_status ( hotplug , & info - > adapter_status ) ;
out :
if ( retval ) {
2009-10-05 12:41:37 +04:00
kfree ( ops ) ;
2009-09-15 12:24:46 +04:00
kfree ( info ) ;
kfree ( hotplug ) ;
2005-04-17 02:20:36 +04:00
}
2006-12-22 04:01:02 +03:00
return retval ;
2005-04-17 02:20:36 +04:00
}
2009-09-15 12:24:46 +04:00
static void cleanup_slot ( struct controller * ctrl )
2005-04-17 02:20:36 +04:00
{
2009-09-15 12:24:46 +04:00
pci_hp_deregister ( ctrl - > slot - > hotplug_slot ) ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 )
{
struct slot * slot = hotplug_slot - > private ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
hotplug_slot - > info - > attention_status = status ;
2009-10-05 12:41:37 +04:00
pciehp_set_attention_status ( slot , status ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int enable_slot ( struct hotplug_slot * hotplug_slot )
{
struct slot * slot = hotplug_slot - > private ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2007-03-07 02:02:26 +03:00
return pciehp_sysfs_enable_slot ( slot ) ;
2005-04-17 02:20:36 +04:00
}
static int disable_slot ( struct hotplug_slot * hotplug_slot )
{
struct slot * slot = hotplug_slot - > private ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2007-03-07 02:02:26 +03:00
return pciehp_sysfs_disable_slot ( slot ) ;
2005-04-17 02:20:36 +04:00
}
static int get_power_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_power_status ( slot , value ) ;
2005-04-17 02:20:36 +04:00
if ( retval < 0 )
* value = hotplug_slot - > info - > power_status ;
return 0 ;
}
static int get_attention_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_attention_status ( slot , value ) ;
2005-04-17 02:20:36 +04:00
if ( retval < 0 )
* value = hotplug_slot - > info - > attention_status ;
return 0 ;
}
static int get_latch_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_latch_status ( slot , value ) ;
2005-04-17 02:20:36 +04:00
if ( retval < 0 )
* value = hotplug_slot - > info - > latch_status ;
return 0 ;
}
static int get_adapter_status ( struct hotplug_slot * hotplug_slot , u8 * value )
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_adapter_status ( slot , value ) ;
2005-04-17 02:20:36 +04:00
if ( retval < 0 )
* value = hotplug_slot - > info - > adapter_status ;
return 0 ;
}
2008-06-11 01:28:50 +04:00
static int get_max_bus_speed ( struct hotplug_slot * hotplug_slot ,
enum pci_bus_speed * value )
2005-04-17 02:20:36 +04:00
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2007-08-10 03:09:35 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_max_link_speed ( slot , value ) ;
2005-04-17 02:20:36 +04:00
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 )
{
struct slot * slot = hotplug_slot - > private ;
int retval ;
2008-10-23 06:47:32 +04:00
ctrl_dbg ( slot - > ctrl , " %s: physical_slot = %s \n " ,
2008-10-21 03:41:38 +04:00
__func__ , slot_name ( slot ) ) ;
2007-08-10 03:09:35 +04:00
2009-09-15 12:30:48 +04:00
retval = pciehp_get_cur_link_speed ( slot , value ) ;
2005-04-17 02:20:36 +04:00
if ( retval < 0 )
* value = PCI_SPEED_UNKNOWN ;
return 0 ;
}
2009-01-13 16:44:19 +03:00
static int pciehp_probe ( struct pcie_device * dev )
2005-04-17 02:20:36 +04:00
{
int rc ;
struct controller * ctrl ;
2009-09-15 12:24:46 +04:00
struct slot * slot ;
2005-04-17 02:20:36 +04:00
u8 value ;
2008-05-28 09:57:30 +04:00
struct pci_dev * pdev = dev - > port ;
if ( pciehp_force )
2008-09-05 07:11:26 +04:00
dev_info ( & dev - > device ,
" Bypassing BIOS check for pciehp use on %s \n " ,
pci_name ( pdev ) ) ;
2008-05-28 09:57:30 +04:00
else if ( pciehp_get_hp_hw_control_from_firmware ( pdev ) )
goto err_out_none ;
2007-08-10 03:09:35 +04:00
2008-06-20 07:07:08 +04:00
ctrl = pcie_init ( dev ) ;
2005-04-17 02:20:36 +04:00
if ( ! ctrl ) {
2008-10-23 06:47:32 +04:00
dev_err ( & dev - > device , " Controller initialization failed \n " ) ;
2008-06-20 07:07:08 +04:00
goto err_out_none ;
2005-04-17 02:20:36 +04:00
}
2008-06-26 15:06:24 +04:00
set_service_data ( dev , ctrl ) ;
2005-04-17 02:20:36 +04:00
/* Setup the slot information structures */
2009-09-15 12:24:46 +04:00
rc = init_slot ( ctrl ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
2008-06-11 01:28:50 +04:00
if ( rc = = - EBUSY )
2008-10-23 06:47:32 +04:00
ctrl_warn ( ctrl , " Slot already registered by another "
2008-09-05 07:11:26 +04:00
" hotplug driver \n " ) ;
2008-06-11 01:28:50 +04:00
else
2008-10-23 06:47:32 +04:00
ctrl_err ( ctrl , " Slot initialization failed \n " ) ;
2006-12-22 04:01:05 +03:00
goto err_out_release_ctlr ;
2005-04-17 02:20:36 +04:00
}
2009-01-29 06:31:18 +03:00
/* Enable events after we have setup the data structures */
rc = pcie_init_notification ( ctrl ) ;
if ( rc ) {
ctrl_err ( ctrl , " Notification initialization failed \n " ) ;
goto err_out_release_ctlr ;
}
2008-12-08 08:30:24 +03:00
/* Check if slot is occupied */
2009-09-15 12:24:46 +04:00
slot = ctrl - > slot ;
2009-09-15 12:30:48 +04:00
pciehp_get_adapter_status ( slot , & value ) ;
2008-12-08 08:30:24 +03:00
if ( value ) {
if ( pciehp_force )
2009-09-15 12:24:46 +04:00
pciehp_enable_slot ( slot ) ;
2008-12-08 08:30:24 +03:00
} else {
/* Power off slot if not occupied */
if ( POWER_CTRL ( ctrl ) ) {
2009-09-15 12:30:48 +04:00
rc = pciehp_power_off_slot ( slot ) ;
2008-12-08 08:30:24 +03:00
if ( rc )
goto err_out_free_ctrl_slot ;
}
2005-04-17 02:20:36 +04:00
}
return 0 ;
err_out_free_ctrl_slot :
2009-09-15 12:24:46 +04:00
cleanup_slot ( ctrl ) ;
2006-12-22 04:01:05 +03:00
err_out_release_ctlr :
2009-09-15 12:30:48 +04:00
pciehp_release_ctrl ( ctrl ) ;
2005-04-17 02:20:36 +04:00
err_out_none :
return - ENODEV ;
}
2009-09-15 12:30:48 +04:00
static void pciehp_remove ( struct pcie_device * dev )
2005-04-17 02:20:36 +04:00
{
2008-06-26 15:06:24 +04:00
struct controller * ctrl = get_service_data ( dev ) ;
2005-04-17 02:20:36 +04:00
2009-09-15 12:24:46 +04:00
cleanup_slot ( ctrl ) ;
2009-09-15 12:30:48 +04:00
pciehp_release_ctrl ( ctrl ) ;
2005-04-17 02:20:36 +04:00
}
# ifdef CONFIG_PM
2009-02-16 00:32:48 +03:00
static int pciehp_suspend ( struct pcie_device * dev )
2005-04-17 02:20:36 +04:00
{
2008-09-05 07:11:26 +04:00
dev_info ( & dev - > device , " %s ENTRY \n " , __func__ ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int pciehp_resume ( struct pcie_device * dev )
{
2008-09-05 07:11:26 +04:00
dev_info ( & dev - > device , " %s ENTRY \n " , __func__ ) ;
2007-11-29 02:12:00 +03:00
if ( pciehp_force ) {
2008-06-26 15:06:24 +04:00
struct controller * ctrl = get_service_data ( dev ) ;
2009-09-15 12:24:46 +04:00
struct slot * slot ;
2007-11-29 02:12:00 +03:00
u8 status ;
/* reinitialize the chipset's event detection logic */
2008-06-20 07:07:08 +04:00
pcie_enable_notification ( ctrl ) ;
2007-11-29 02:12:00 +03:00
2009-09-15 12:24:46 +04:00
slot = ctrl - > slot ;
2007-11-29 02:12:00 +03:00
/* Check if slot is occupied */
2009-09-15 12:30:48 +04:00
pciehp_get_adapter_status ( slot , & status ) ;
2007-11-29 02:12:00 +03:00
if ( status )
2009-09-15 12:24:46 +04:00
pciehp_enable_slot ( slot ) ;
2007-11-29 02:12:00 +03:00
else
2009-09-15 12:24:46 +04:00
pciehp_disable_slot ( slot ) ;
2007-11-29 02:12:00 +03:00
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
2009-02-16 00:32:48 +03:00
# endif /* PM */
2005-04-17 02:20:36 +04:00
static struct pcie_port_service_driver hpdriver_portdrv = {
2008-09-05 07:09:43 +04:00
. name = PCIE_MODULE_NAME ,
2009-01-13 16:46:46 +03:00
. port_type = PCIE_ANY_PORT ,
. service = PCIE_PORT_SERVICE_HP ,
2005-04-17 02:20:36 +04:00
. probe = pciehp_probe ,
. remove = pciehp_remove ,
# ifdef CONFIG_PM
. suspend = pciehp_suspend ,
. resume = pciehp_resume ,
# endif /* PM */
} ;
static int __init pcied_init ( void )
{
int retval = 0 ;
2008-12-17 06:07:38 +03:00
pciehp_firmware_init ( ) ;
2005-11-01 03:20:07 +03:00
retval = pcie_port_service_register ( & hpdriver_portdrv ) ;
dbg ( " pcie_port_service_register = %d \n " , retval ) ;
info ( DRIVER_DESC " version: " DRIVER_VERSION " \n " ) ;
if ( retval )
2008-10-23 06:47:32 +04:00
dbg ( " Failure to register service \n " ) ;
2005-04-17 02:20:36 +04:00
return retval ;
}
static void __exit pcied_cleanup ( void )
{
dbg ( " unload_pciehpd() \n " ) ;
pcie_port_service_unregister ( & hpdriver_portdrv ) ;
info ( DRIVER_DESC " version: " DRIVER_VERSION " unloaded \n " ) ;
}
module_init ( pcied_init ) ;
module_exit ( pcied_cleanup ) ;