2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 2004 Matthew Wilcox < matthew @ wil . cx >
* Copyright ( C ) 2004 Intel Corp .
*
* This code is released under the GNU General Public License version 2.
*/
/*
* mmconfig . c - Low - level direct PCI config space access via MMCONFIG
*/
# include <linux/pci.h>
# include <linux/init.h>
2005-06-24 04:35:56 +04:00
# include <linux/acpi.h>
2006-04-07 21:49:30 +04:00
# include <asm/e820.h>
2008-12-27 16:02:28 +03:00
# include <asm/pci_x86.h>
2005-04-17 02:20:36 +04:00
2006-04-07 21:50:12 +04:00
/* Assume systems with more busses have correct MCFG */
2005-04-17 02:20:36 +04:00
# define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG))
/* The base address of the last MMCONFIG device accessed */
static u32 mmcfg_last_accessed_device ;
2006-12-23 04:00:43 +03:00
static int mmcfg_last_accessed_cpu ;
2005-04-17 02:20:36 +04:00
/*
* Functions for accessing PCI configuration space with MMCONFIG accesses
*/
2005-12-13 09:17:11 +03:00
static u32 get_base_addr ( unsigned int seg , int bus , unsigned devfn )
2005-06-24 04:35:56 +04:00
{
2007-02-02 19:48:22 +03:00
struct acpi_mcfg_allocation * cfg ;
2007-02-13 15:26:20 +03:00
int cfg_num ;
2005-06-24 04:35:56 +04:00
2007-02-13 15:26:20 +03:00
for ( cfg_num = 0 ; cfg_num < pci_mmcfg_config_num ; cfg_num + + ) {
2005-06-24 04:35:56 +04:00
cfg = & pci_mmcfg_config [ cfg_num ] ;
2007-02-13 15:26:20 +03:00
if ( cfg - > pci_segment = = seg & &
( cfg - > start_bus_number < = bus ) & &
2005-06-24 04:35:56 +04:00
( cfg - > end_bus_number > = bus ) )
2007-02-02 19:48:22 +03:00
return cfg - > address ;
2005-06-24 04:35:56 +04:00
}
2006-01-27 04:03:50 +03:00
/* Fall back to type 0 */
return 0 ;
2005-06-24 04:35:56 +04:00
}
2005-04-17 02:20:36 +04:00
2006-10-01 10:27:10 +04:00
/*
* This is always called under pci_config_lock
*/
static void pci_exp_set_dev_base ( unsigned int base , int bus , int devfn )
2005-04-17 02:20:36 +04:00
{
2005-12-13 09:17:10 +03:00
u32 dev_base = base | ( bus < < 20 ) | ( devfn < < 12 ) ;
2006-12-23 04:00:43 +03:00
int cpu = smp_processor_id ( ) ;
if ( dev_base ! = mmcfg_last_accessed_device | |
cpu ! = mmcfg_last_accessed_cpu ) {
2005-04-17 02:20:36 +04:00
mmcfg_last_accessed_device = dev_base ;
2006-12-23 04:00:43 +03:00
mmcfg_last_accessed_cpu = cpu ;
2005-04-17 02:20:36 +04:00
set_fixmap_nocache ( FIX_PCIE_MCFG , dev_base ) ;
}
}
static int pci_mmcfg_read ( unsigned int seg , unsigned int bus ,
unsigned int devfn , int reg , int len , u32 * value )
{
unsigned long flags ;
2005-12-13 09:17:10 +03:00
u32 base ;
2005-04-17 02:20:36 +04:00
2006-04-11 14:54:48 +04:00
if ( ( bus > 255 ) | | ( devfn > 255 ) | | ( reg > 4095 ) ) {
2008-01-15 01:31:09 +03:00
err : * value = - 1 ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2006-04-07 21:50:15 +04:00
}
2005-04-17 02:20:36 +04:00
2005-12-13 09:17:11 +03:00
base = get_base_addr ( seg , bus , devfn ) ;
2005-12-13 09:17:10 +03:00
if ( ! base )
2008-01-15 01:31:09 +03:00
goto err ;
2005-12-13 09:17:10 +03:00
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & pci_config_lock , flags ) ;
2005-12-13 09:17:10 +03:00
pci_exp_set_dev_base ( base , bus , devfn ) ;
2005-04-17 02:20:36 +04:00
switch ( len ) {
case 1 :
2007-08-11 00:30:59 +04:00
* value = mmio_config_readb ( mmcfg_virt_addr + reg ) ;
2005-04-17 02:20:36 +04:00
break ;
case 2 :
2007-08-11 00:30:59 +04:00
* value = mmio_config_readw ( mmcfg_virt_addr + reg ) ;
2005-04-17 02:20:36 +04:00
break ;
case 4 :
2007-08-11 00:30:59 +04:00
* value = mmio_config_readl ( mmcfg_virt_addr + reg ) ;
2005-04-17 02:20:36 +04:00
break ;
}
spin_unlock_irqrestore ( & pci_config_lock , flags ) ;
return 0 ;
}
static int pci_mmcfg_write ( unsigned int seg , unsigned int bus ,
unsigned int devfn , int reg , int len , u32 value )
{
unsigned long flags ;
2005-12-13 09:17:10 +03:00
u32 base ;
2005-04-17 02:20:36 +04:00
2007-02-02 19:48:22 +03:00
if ( ( bus > 255 ) | | ( devfn > 255 ) | | ( reg > 4095 ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2005-12-13 09:17:11 +03:00
base = get_base_addr ( seg , bus , devfn ) ;
2005-12-13 09:17:10 +03:00
if ( ! base )
2008-01-15 01:31:09 +03:00
return - EINVAL ;
2005-12-13 09:17:10 +03:00
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & pci_config_lock , flags ) ;
2005-12-13 09:17:10 +03:00
pci_exp_set_dev_base ( base , bus , devfn ) ;
2005-04-17 02:20:36 +04:00
switch ( len ) {
case 1 :
2007-08-12 13:23:16 +04:00
mmio_config_writeb ( mmcfg_virt_addr + reg , value ) ;
2005-04-17 02:20:36 +04:00
break ;
case 2 :
2007-08-12 13:23:16 +04:00
mmio_config_writew ( mmcfg_virt_addr + reg , value ) ;
2005-04-17 02:20:36 +04:00
break ;
case 4 :
2007-08-12 13:23:16 +04:00
mmio_config_writel ( mmcfg_virt_addr + reg , value ) ;
2005-04-17 02:20:36 +04:00
break ;
}
spin_unlock_irqrestore ( & pci_config_lock , flags ) ;
return 0 ;
}
static struct pci_raw_ops pci_mmcfg = {
. read = pci_mmcfg_read ,
. write = pci_mmcfg_write ,
} ;
2007-02-13 15:26:20 +03:00
int __init pci_mmcfg_arch_init ( void )
2005-12-13 09:17:11 +03:00
{
2008-02-10 17:45:28 +03:00
printk ( KERN_INFO " PCI: Using MMCONFIG for extended config space \n " ) ;
raw_pci_ext_ops = & pci_mmcfg ;
2007-02-13 15:26:20 +03:00
return 1 ;
2005-04-17 02:20:36 +04:00
}
2008-02-15 12:28:41 +03:00
void __init pci_mmcfg_arch_free ( void )
{
}