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/kernel.h>
# include <linux/types.h>
# include <linux/pci.h>
# include "../pci.h"
# include "pciehp.h"
2006-05-08 13:34:09 +04:00
static void program_hpp_type0 ( struct pci_dev * dev , struct hpp_type0 * hpp )
{
u16 pci_cmd , pci_bctl ;
if ( hpp - > revision > 1 ) {
printk ( KERN_WARNING " %s: Rev.%d type0 record not supported \n " ,
2008-03-04 06:09:46 +03:00
__func__ , hpp - > revision ) ;
2006-05-08 13:34:09 +04:00
return ;
}
pci_write_config_byte ( dev , PCI_CACHE_LINE_SIZE , hpp - > cache_line_size ) ;
pci_write_config_byte ( dev , PCI_LATENCY_TIMER , hpp - > latency_timer ) ;
pci_read_config_word ( dev , PCI_COMMAND , & pci_cmd ) ;
if ( hpp - > enable_serr )
pci_cmd | = PCI_COMMAND_SERR ;
else
pci_cmd & = ~ PCI_COMMAND_SERR ;
if ( hpp - > enable_perr )
pci_cmd | = PCI_COMMAND_PARITY ;
else
pci_cmd & = ~ PCI_COMMAND_PARITY ;
pci_write_config_word ( dev , PCI_COMMAND , pci_cmd ) ;
/* Program bridge control value */
if ( ( dev - > class > > 8 ) = = PCI_CLASS_BRIDGE_PCI ) {
pci_write_config_byte ( dev , PCI_SEC_LATENCY_TIMER ,
hpp - > latency_timer ) ;
pci_read_config_word ( dev , PCI_BRIDGE_CONTROL , & pci_bctl ) ;
if ( hpp - > enable_serr )
pci_bctl | = PCI_BRIDGE_CTL_SERR ;
else
pci_bctl & = ~ PCI_BRIDGE_CTL_SERR ;
if ( hpp - > enable_perr )
pci_bctl | = PCI_BRIDGE_CTL_PARITY ;
else
pci_bctl & = ~ PCI_BRIDGE_CTL_PARITY ;
pci_write_config_word ( dev , PCI_BRIDGE_CONTROL , pci_bctl ) ;
}
}
static void program_hpp_type2 ( struct pci_dev * dev , struct hpp_type2 * hpp )
{
int pos ;
u16 reg16 ;
u32 reg32 ;
if ( hpp - > revision > 1 ) {
printk ( KERN_WARNING " %s: Rev.%d type2 record not supported \n " ,
2008-03-04 06:09:46 +03:00
__func__ , hpp - > revision ) ;
2006-05-08 13:34:09 +04:00
return ;
}
/* Find PCI Express capability */
pos = pci_find_capability ( dev , PCI_CAP_ID_EXP ) ;
if ( ! pos )
return ;
/* Initialize Device Control Register */
pci_read_config_word ( dev , pos + PCI_EXP_DEVCTL , & reg16 ) ;
reg16 = ( reg16 & hpp - > pci_exp_devctl_and ) | hpp - > pci_exp_devctl_or ;
pci_write_config_word ( dev , pos + PCI_EXP_DEVCTL , reg16 ) ;
/* Initialize Link Control Register */
if ( dev - > subordinate ) {
pci_read_config_word ( dev , pos + PCI_EXP_LNKCTL , & reg16 ) ;
reg16 = ( reg16 & hpp - > pci_exp_lnkctl_and )
| hpp - > pci_exp_lnkctl_or ;
pci_write_config_word ( dev , pos + PCI_EXP_LNKCTL , reg16 ) ;
}
/* Find Advanced Error Reporting Enhanced Capability */
2007-11-09 11:28:11 +03:00
pos = pci_find_ext_capability ( dev , PCI_EXT_CAP_ID_ERR ) ;
2006-05-08 13:34:09 +04:00
if ( ! pos )
return ;
/* Initialize Uncorrectable Error Mask Register */
pci_read_config_dword ( dev , pos + PCI_ERR_UNCOR_MASK , & reg32 ) ;
reg32 = ( reg32 & hpp - > unc_err_mask_and ) | hpp - > unc_err_mask_or ;
pci_write_config_dword ( dev , pos + PCI_ERR_UNCOR_MASK , reg32 ) ;
/* Initialize Uncorrectable Error Severity Register */
pci_read_config_dword ( dev , pos + PCI_ERR_UNCOR_SEVER , & reg32 ) ;
reg32 = ( reg32 & hpp - > unc_err_sever_and ) | hpp - > unc_err_sever_or ;
pci_write_config_dword ( dev , pos + PCI_ERR_UNCOR_SEVER , reg32 ) ;
/* Initialize Correctable Error Mask Register */
pci_read_config_dword ( dev , pos + PCI_ERR_COR_MASK , & reg32 ) ;
reg32 = ( reg32 & hpp - > cor_err_mask_and ) | hpp - > cor_err_mask_or ;
pci_write_config_dword ( dev , pos + PCI_ERR_COR_MASK , reg32 ) ;
/* Initialize Advanced Error Capabilities and Control Register */
pci_read_config_dword ( dev , pos + PCI_ERR_CAP , & reg32 ) ;
reg32 = ( reg32 & hpp - > adv_err_cap_and ) | hpp - > adv_err_cap_or ;
pci_write_config_dword ( dev , pos + PCI_ERR_CAP , reg32 ) ;
/*
* FIXME : The following two registers are not supported yet .
*
* o Secondary Uncorrectable Error Severity Register
* o Secondary Uncorrectable Error Mask Register
*/
}
static void program_fw_provided_values ( struct pci_dev * dev )
{
struct pci_dev * cdev ;
struct hotplug_params hpp ;
/* Program hpp values for this device */
if ( ! ( dev - > hdr_type = = PCI_HEADER_TYPE_NORMAL | |
( dev - > hdr_type = = PCI_HEADER_TYPE_BRIDGE & &
( dev - > class > > 8 ) = = PCI_CLASS_BRIDGE_PCI ) ) )
return ;
if ( pciehp_get_hp_params_from_firmware ( dev , & hpp ) ) {
printk ( KERN_WARNING " %s: Could not get hotplug parameters \n " ,
2008-03-04 06:09:46 +03:00
__func__ ) ;
2006-05-08 13:34:09 +04:00
return ;
}
if ( hpp . t2 )
program_hpp_type2 ( dev , hpp . t2 ) ;
if ( hpp . t0 )
program_hpp_type0 ( dev , hpp . t0 ) ;
/* Program child devices */
if ( dev - > subordinate ) {
list_for_each_entry ( cdev , & dev - > subordinate - > devices ,
bus_list )
program_fw_provided_values ( cdev ) ;
}
}
2008-02-17 12:45:28 +03:00
static int __ref pciehp_add_bridge ( struct pci_dev * dev )
2005-12-08 23:12:25 +03:00
{
struct pci_bus * parent = dev - > bus ;
int pass , busnr , start = parent - > secondary ;
int end = parent - > subordinate ;
for ( busnr = start ; busnr < = end ; busnr + + ) {
if ( ! pci_find_bus ( pci_domain_nr ( parent ) , busnr ) )
break ;
}
if ( busnr - - > end ) {
err ( " No bus number available for hot-added bridge %s \n " ,
pci_name ( dev ) ) ;
return - 1 ;
}
for ( pass = 0 ; pass < 2 ; pass + + )
busnr = pci_scan_bridge ( parent , dev , busnr , pass ) ;
if ( ! dev - > subordinate )
return - 1 ;
pci_bus_size_bridges ( dev - > subordinate ) ;
pci_bus_assign_resources ( parent ) ;
pci_enable_bridges ( parent ) ;
pci_bus_add_devices ( parent ) ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
2005-11-01 03:20:06 +03:00
int pciehp_configure_device ( struct slot * p_slot )
2005-04-17 02:20:36 +04:00
{
2005-11-01 03:20:06 +03:00
struct pci_dev * dev ;
struct pci_bus * parent = p_slot - > ctrl - > pci_dev - > subordinate ;
int num , fn ;
2006-05-12 06:22:24 +04:00
dev = pci_get_slot ( parent , PCI_DEVFN ( p_slot - > device , 0 ) ) ;
2005-11-01 03:20:06 +03:00
if ( dev ) {
err ( " Device %s already exists at %x:%x, cannot hot-add \n " ,
pci_name ( dev ) , p_slot - > bus , p_slot - > device ) ;
2006-05-12 06:22:24 +04:00
pci_dev_put ( dev ) ;
2005-11-01 03:20:06 +03:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2005-11-01 03:20:06 +03:00
num = pci_scan_slot ( parent , PCI_DEVFN ( p_slot - > device , 0 ) ) ;
if ( num = = 0 ) {
err ( " No new device found \n " ) ;
return - ENODEV ;
}
2005-04-17 02:20:36 +04:00
2005-11-01 03:20:06 +03:00
for ( fn = 0 ; fn < 8 ; fn + + ) {
2005-12-08 23:12:25 +03:00
dev = pci_get_slot ( parent , PCI_DEVFN ( p_slot - > device , fn ) ) ;
if ( ! dev )
2005-11-01 03:20:06 +03:00
continue ;
if ( ( dev - > class > > 16 ) = = PCI_BASE_CLASS_DISPLAY ) {
err ( " Cannot hot-add display device %s \n " ,
pci_name ( dev ) ) ;
2006-05-12 06:23:39 +04:00
pci_dev_put ( dev ) ;
2005-11-01 03:20:06 +03:00
continue ;
}
if ( ( dev - > hdr_type = = PCI_HEADER_TYPE_BRIDGE ) | |
( dev - > hdr_type = = PCI_HEADER_TYPE_CARDBUS ) ) {
2005-12-08 23:12:25 +03:00
pciehp_add_bridge ( dev ) ;
2005-11-01 03:20:06 +03:00
}
2006-05-08 13:34:09 +04:00
program_fw_provided_values ( dev ) ;
2006-05-12 06:23:39 +04:00
pci_dev_put ( dev ) ;
2005-04-17 02:20:36 +04:00
}
2005-11-01 03:20:06 +03:00
pci_bus_assign_resources ( parent ) ;
pci_bus_add_devices ( parent ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-11-01 03:20:08 +03:00
int pciehp_unconfigure_device ( struct slot * p_slot )
2005-04-17 02:20:36 +04:00
{
2007-08-10 03:09:31 +04:00
int ret , rc = 0 ;
2005-04-17 02:20:36 +04:00
int j ;
2005-11-01 03:20:08 +03:00
u8 bctl = 0 ;
2007-08-10 03:09:31 +04:00
u8 presence = 0 ;
2006-05-12 06:22:24 +04:00
struct pci_bus * parent = p_slot - > ctrl - > pci_dev - > subordinate ;
2007-12-20 13:46:33 +03:00
u16 command ;
2005-04-17 02:20:36 +04:00
2008-03-04 06:09:46 +03:00
dbg ( " %s: bus/dev = %x/%x \n " , __func__ , p_slot - > bus ,
2005-11-01 03:20:08 +03:00
p_slot - > device ) ;
2007-12-20 13:46:33 +03:00
ret = p_slot - > hpc_ops - > get_adapter_status ( p_slot , & presence ) ;
if ( ret )
presence = 0 ;
2005-04-17 02:20:36 +04:00
2007-11-06 15:20:17 +03:00
for ( j = 0 ; j < 8 ; j + + ) {
2006-05-12 06:22:24 +04:00
struct pci_dev * temp = pci_get_slot ( parent ,
2005-11-01 03:20:08 +03:00
( p_slot - > device < < 3 ) | j ) ;
if ( ! temp )
continue ;
if ( ( temp - > class > > 16 ) = = PCI_BASE_CLASS_DISPLAY ) {
err ( " Cannot remove display device %s \n " ,
pci_name ( temp ) ) ;
2006-05-12 06:22:24 +04:00
pci_dev_put ( temp ) ;
2005-11-01 03:20:08 +03:00
continue ;
}
2007-12-20 13:46:33 +03:00
if ( temp - > hdr_type = = PCI_HEADER_TYPE_BRIDGE & & presence ) {
pci_read_config_byte ( temp , PCI_BRIDGE_CONTROL , & bctl ) ;
if ( bctl & PCI_BRIDGE_CTL_VGA ) {
err ( " Cannot remove display device %s \n " ,
pci_name ( temp ) ) ;
pci_dev_put ( temp ) ;
continue ;
2005-11-01 03:20:08 +03:00
}
2005-04-17 02:20:36 +04:00
}
2005-11-01 03:20:08 +03:00
pci_remove_bus_device ( temp ) ;
2007-12-20 13:46:33 +03:00
/*
* Ensure that no new Requests will be generated from
* the device .
*/
if ( presence ) {
pci_read_config_word ( temp , PCI_COMMAND , & command ) ;
command & = ~ ( PCI_COMMAND_MASTER | PCI_COMMAND_SERR ) ;
command | = PCI_COMMAND_INTX_DISABLE ;
pci_write_config_word ( temp , PCI_COMMAND , command ) ;
}
2006-05-12 06:22:24 +04:00
pci_dev_put ( temp ) ;
2005-04-17 02:20:36 +04:00
}
2007-08-10 03:09:37 +04:00
/*
2005-04-17 02:20:36 +04:00
* Some PCI Express root ports require fixup after hot - plug operation .
*/
2007-08-10 03:09:37 +04:00
if ( pcie_mch_quirk )
2005-11-01 03:20:08 +03:00
pci_fixup_device ( pci_fixup_final , p_slot - > ctrl - > pci_dev ) ;
2007-08-10 03:09:37 +04:00
2005-04-17 02:20:36 +04:00
return rc ;
}