2006-01-19 04:44:08 +03:00
/*
* AMD 76 x Memory Controller kernel module
* ( C ) 2003 Linux Networx ( http : //lnxi.com)
* This file may be distributed under the terms of the
* GNU General Public License .
*
* Written by Thayne Harbaugh
* Based on work by Dan Hollis < goemon at anime dot net > and others .
* http : //www.anime.net/~goemon/linux-ecc/
*
* $ Id : edac_amd76x . c , v 1.4 .2 .5 2005 / 10 / 05 00 : 43 : 44 dsp_llnl Exp $
*
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
# include <linux/slab.h>
2008-04-29 12:03:18 +04:00
# include <linux/edac.h>
2007-07-19 12:49:47 +04:00
# include "edac_core.h"
2006-01-19 04:44:08 +03:00
2007-07-19 12:49:47 +04:00
# define AMD76X_REVISION " Ver: 2.0.2 " __DATE__
2006-07-01 15:35:45 +04:00
# define EDAC_MOD_STR "amd76x_edac"
2006-06-30 12:56:07 +04:00
2006-03-26 13:38:40 +04:00
# define amd76x_printk(level, fmt, arg...) \
2006-03-26 13:38:52 +04:00
edac_printk ( level , " amd76x " , fmt , # # arg )
2006-03-26 13:38:40 +04:00
# define amd76x_mc_printk(mci, level, fmt, arg...) \
2006-03-26 13:38:52 +04:00
edac_mc_chipset_printk ( mci , level , " amd76x " , fmt , # # arg )
2006-03-26 13:38:40 +04:00
2006-01-19 04:44:08 +03:00
# define AMD76X_NR_CSROWS 8
# define AMD76X_NR_CHANS 1
# define AMD76X_NR_DIMMS 4
/* AMD 76x register addresses - device 0 function 0 - PCI bridge */
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:08 +03:00
# define AMD76X_ECC_MODE_STATUS 0x48 / * Mode and status of ECC (32b)
*
* 31 : 16 reserved
* 15 : 14 SERR enabled : x1 = ue 1 x = ce
* 13 reserved
* 12 diag : disabled , enabled
* 11 : 10 mode : dis , EC , ECC , ECC + scrub
* 9 : 8 status : x1 = ue 1 x = ce
* 7 : 4 UE cs row
* 3 : 0 CE cs row
*/
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:08 +03:00
# define AMD76X_DRAM_MODE_STATUS 0x58 / * DRAM Mode and status (32b)
*
* 31 : 26 clock disable 5 - 0
* 25 SDRAM init
* 24 reserved
* 23 mode register service
* 22 : 21 suspend to RAM
* 20 burst refresh enable
* 19 refresh disable
* 18 reserved
* 17 : 16 cycles - per - refresh
* 15 : 8 reserved
* 7 : 0 x4 mode enable 7 - 0
*/
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:08 +03:00
# define AMD76X_MEM_BASE_ADDR 0xC0 / * Memory base address (8 x 32b)
*
* 31 : 23 chip - select base
* 22 : 16 reserved
* 15 : 7 chip - select mask
* 6 : 3 reserved
* 2 : 1 address mode
* 0 chip - select enable
*/
struct amd76x_error_info {
u32 ecc_mode_status ;
} ;
enum amd76x_chips {
AMD761 = 0 ,
AMD762
} ;
struct amd76x_dev_info {
const char * ctl_name ;
} ;
static const struct amd76x_dev_info amd76x_devs [ ] = {
2006-03-26 13:38:52 +04:00
[ AMD761 ] = {
2007-07-19 12:50:13 +04:00
. ctl_name = " AMD761 " } ,
2006-03-26 13:38:52 +04:00
[ AMD762 ] = {
2007-07-19 12:50:13 +04:00
. ctl_name = " AMD762 " } ,
2006-01-19 04:44:08 +03:00
} ;
2007-07-19 12:50:10 +04:00
static struct edac_pci_ctl_info * amd76x_pci ;
2006-01-19 04:44:08 +03:00
/**
* amd76x_get_error_info - fetch error information
* @ mci : Memory controller
* @ info : Info to fill in
*
* Fetch and store the AMD76x ECC status . Clear pending status
* on the chip so that further errors will be reported
*/
2006-03-26 13:38:52 +04:00
static void amd76x_get_error_info ( struct mem_ctl_info * mci ,
2007-07-19 12:50:13 +04:00
struct amd76x_error_info * info )
2006-01-19 04:44:08 +03:00
{
2006-06-30 12:56:07 +04:00
struct pci_dev * pdev ;
pdev = to_pci_dev ( mci - > dev ) ;
pci_read_config_dword ( pdev , AMD76X_ECC_MODE_STATUS ,
2007-07-19 12:50:13 +04:00
& info - > ecc_mode_status ) ;
2006-01-19 04:44:08 +03:00
if ( info - > ecc_mode_status & BIT ( 8 ) )
2006-06-30 12:56:07 +04:00
pci_write_bits32 ( pdev , AMD76X_ECC_MODE_STATUS ,
2007-07-19 12:50:02 +04:00
( u32 ) BIT ( 8 ) , ( u32 ) BIT ( 8 ) ) ;
2006-01-19 04:44:08 +03:00
if ( info - > ecc_mode_status & BIT ( 9 ) )
2006-06-30 12:56:07 +04:00
pci_write_bits32 ( pdev , AMD76X_ECC_MODE_STATUS ,
2007-07-19 12:50:02 +04:00
( u32 ) BIT ( 9 ) , ( u32 ) BIT ( 9 ) ) ;
2006-01-19 04:44:08 +03:00
}
/**
* amd76x_process_error_info - Error check
* @ mci : Memory controller
* @ info : Previously fetched information from chip
* @ handle_errors : 1 if we should do recovery
*
* Process the chip state and decide if an error has occurred .
* A return of 1 indicates an error . Also if handle_errors is true
* then attempt to handle and clean up after the error
*/
2006-03-26 13:38:52 +04:00
static int amd76x_process_error_info ( struct mem_ctl_info * mci ,
2007-07-19 12:50:13 +04:00
struct amd76x_error_info * info ,
int handle_errors )
2006-01-19 04:44:08 +03:00
{
int error_found ;
u32 row ;
error_found = 0 ;
/*
2007-07-19 12:50:02 +04:00
* Check for an uncorrectable error
2006-01-19 04:44:08 +03:00
*/
if ( info - > ecc_mode_status & BIT ( 8 ) ) {
error_found = 1 ;
if ( handle_errors ) {
row = ( info - > ecc_mode_status > > 4 ) & 0xf ;
2006-03-26 13:38:52 +04:00
edac_mc_handle_ue ( mci , mci - > csrows [ row ] . first_page , 0 ,
2007-07-19 12:50:13 +04:00
row , mci - > ctl_name ) ;
2006-01-19 04:44:08 +03:00
}
}
/*
2007-07-19 12:50:02 +04:00
* Check for a correctable error
2006-01-19 04:44:08 +03:00
*/
if ( info - > ecc_mode_status & BIT ( 9 ) ) {
error_found = 1 ;
if ( handle_errors ) {
row = info - > ecc_mode_status & 0xf ;
2006-03-26 13:38:52 +04:00
edac_mc_handle_ce ( mci , mci - > csrows [ row ] . first_page , 0 ,
2007-07-19 12:50:13 +04:00
0 , row , 0 , mci - > ctl_name ) ;
2006-01-19 04:44:08 +03:00
}
}
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:08 +03:00
return error_found ;
}
/**
* amd76x_check - Poll the controller
* @ mci : Memory controller
*
* Called by the poll handlers this function reads the status
* from the controller and checks for errors .
*/
static void amd76x_check ( struct mem_ctl_info * mci )
{
struct amd76x_error_info info ;
2006-03-26 13:38:40 +04:00
debugf3 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
amd76x_get_error_info ( mci , & info ) ;
amd76x_process_error_info ( mci , & info , 1 ) ;
}
2006-06-30 12:56:08 +04:00
static void amd76x_init_csrows ( struct mem_ctl_info * mci , struct pci_dev * pdev ,
2007-07-19 12:50:13 +04:00
enum edac_type edac_mode )
2006-06-30 12:56:08 +04:00
{
struct csrow_info * csrow ;
u32 mba , mba_base , mba_mask , dms ;
int index ;
for ( index = 0 ; index < mci - > nr_csrows ; index + + ) {
csrow = & mci - > csrows [ index ] ;
/* find the DRAM Chip Select Base address and mask */
pci_read_config_dword ( pdev ,
2007-07-19 12:50:13 +04:00
AMD76X_MEM_BASE_ADDR + ( index * 4 ) , & mba ) ;
2006-06-30 12:56:08 +04:00
if ( ! ( mba & BIT ( 0 ) ) )
continue ;
mba_base = mba & 0xff800000UL ;
mba_mask = ( ( mba & 0xff80 ) < < 16 ) | 0x7fffffUL ;
pci_read_config_dword ( pdev , AMD76X_DRAM_MODE_STATUS , & dms ) ;
csrow - > first_page = mba_base > > PAGE_SHIFT ;
csrow - > nr_pages = ( mba_mask + 1 ) > > PAGE_SHIFT ;
csrow - > last_page = csrow - > first_page + csrow - > nr_pages - 1 ;
csrow - > page_mask = mba_mask > > PAGE_SHIFT ;
csrow - > grain = csrow - > nr_pages < < PAGE_SHIFT ;
csrow - > mtype = MEM_RDDR ;
csrow - > dtype = ( ( dms > > index ) & 0x1 ) ? DEV_X4 : DEV_UNKNOWN ;
csrow - > edac_mode = edac_mode ;
}
}
2006-01-19 04:44:08 +03:00
/**
* amd76x_probe1 - Perform set up for detected device
* @ pdev ; PCI device detected
* @ dev_idx : Device type index
*
* We have found an AMD76x and now need to set up the memory
* controller status reporting . We configure and set up the
* memory controller reporting and claim the device .
*/
static int amd76x_probe1 ( struct pci_dev * pdev , int dev_idx )
{
2006-06-30 12:56:08 +04:00
static const enum edac_type ems_modes [ ] = {
2006-01-19 04:44:08 +03:00
EDAC_NONE ,
EDAC_EC ,
EDAC_SECDED ,
EDAC_SECDED
} ;
2006-06-30 12:56:08 +04:00
struct mem_ctl_info * mci = NULL ;
2006-01-19 04:44:08 +03:00
u32 ems ;
u32 ems_mode ;
2006-03-26 13:38:45 +04:00
struct amd76x_error_info discard ;
2006-01-19 04:44:08 +03:00
2006-03-26 13:38:40 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
pci_read_config_dword ( pdev , AMD76X_ECC_MODE_STATUS , & ems ) ;
ems_mode = ( ems > > 10 ) & 0x3 ;
2007-07-19 12:50:26 +04:00
mci = edac_mc_alloc ( 0 , AMD76X_NR_CSROWS , AMD76X_NR_CHANS , 0 ) ;
2006-01-19 04:44:08 +03:00
if ( mci = = NULL ) {
2006-06-30 12:56:08 +04:00
return - ENOMEM ;
2006-01-19 04:44:08 +03:00
}
2006-03-26 13:38:40 +04:00
debugf0 ( " %s(): mci = %p \n " , __func__ , mci ) ;
2006-06-30 12:56:07 +04:00
mci - > dev = & pdev - > dev ;
2006-01-19 04:44:08 +03:00
mci - > mtype_cap = MEM_FLAG_RDDR ;
mci - > edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED ;
mci - > edac_cap = ems_mode ?
2007-07-19 12:50:13 +04:00
( EDAC_FLAG_EC | EDAC_FLAG_SECDED ) : EDAC_FLAG_NONE ;
2006-03-26 13:38:41 +04:00
mci - > mod_name = EDAC_MOD_STR ;
2006-06-30 12:56:07 +04:00
mci - > mod_ver = AMD76X_REVISION ;
2006-01-19 04:44:08 +03:00
mci - > ctl_name = amd76x_devs [ dev_idx ] . ctl_name ;
2007-07-19 12:49:47 +04:00
mci - > dev_name = pci_name ( pdev ) ;
2006-01-19 04:44:08 +03:00
mci - > edac_check = amd76x_check ;
mci - > ctl_page_to_phys = NULL ;
2006-06-30 12:56:08 +04:00
amd76x_init_csrows ( mci , pdev , ems_modes [ ems_mode ] ) ;
2007-07-19 12:50:02 +04:00
amd76x_get_error_info ( mci , & discard ) ; /* clear counters */
2006-01-19 04:44:08 +03:00
2006-06-30 12:56:08 +04:00
/* Here we assume that we will never see multiple instances of this
* type of memory controller . The ID is therefore hardcoded to 0.
*/
2007-07-19 12:50:26 +04:00
if ( edac_mc_add_mc ( mci ) ) {
2006-03-26 13:38:40 +04:00
debugf3 ( " %s(): failed edac_mc_add_mc() \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
goto fail ;
}
2007-07-19 12:50:10 +04:00
/* allocating generic PCI control info */
amd76x_pci = edac_pci_create_generic_ctl ( & pdev - > dev , EDAC_MOD_STR ) ;
if ( ! amd76x_pci ) {
printk ( KERN_WARNING
" %s(): Unable to create PCI control \n " ,
__func__ ) ;
printk ( KERN_WARNING
" %s(): PCI error report via EDAC not setup \n " ,
__func__ ) ;
}
2006-01-19 04:44:08 +03:00
/* get this far and it's successful */
2006-03-26 13:38:40 +04:00
debugf3 ( " %s(): success \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
return 0 ;
2007-07-19 12:50:13 +04:00
fail :
2006-06-30 12:56:08 +04:00
edac_mc_free ( mci ) ;
return - ENODEV ;
2006-01-19 04:44:08 +03:00
}
/* returns count (>= 0), or negative on error */
static int __devinit amd76x_init_one ( struct pci_dev * pdev ,
2007-07-19 12:50:13 +04:00
const struct pci_device_id * ent )
2006-01-19 04:44:08 +03:00
{
2006-03-26 13:38:40 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
/* don't need to call pci_device_enable() */
return amd76x_probe1 ( pdev , ent - > driver_data ) ;
}
/**
* amd76x_remove_one - driver shutdown
* @ pdev : PCI device being handed back
*
* Called when the driver is unloaded . Find the matching mci
* structure for the device then delete the mci and free the
* resources .
*/
static void __devexit amd76x_remove_one ( struct pci_dev * pdev )
{
struct mem_ctl_info * mci ;
2006-03-26 13:38:40 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:08 +03:00
2007-07-19 12:50:10 +04:00
if ( amd76x_pci )
edac_pci_release_generic_ctl ( amd76x_pci ) ;
2006-06-30 12:56:07 +04:00
if ( ( mci = edac_mc_del_mc ( & pdev - > dev ) ) = = NULL )
2006-01-19 04:44:08 +03:00
return ;
2006-03-26 13:38:50 +04:00
2006-01-19 04:44:08 +03:00
edac_mc_free ( mci ) ;
}
static const struct pci_device_id amd76x_pci_tbl [ ] __devinitdata = {
2006-03-26 13:38:52 +04:00
{
2007-07-19 12:50:02 +04:00
PCI_VEND_DEV ( AMD , FE_GATE_700C ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
AMD762 } ,
2006-03-26 13:38:52 +04:00
{
2007-07-19 12:50:02 +04:00
PCI_VEND_DEV ( AMD , FE_GATE_700E ) , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 ,
AMD761 } ,
2006-03-26 13:38:52 +04:00
{
2007-07-19 12:50:02 +04:00
0 ,
} /* 0 terminated list. */
2006-01-19 04:44:08 +03:00
} ;
MODULE_DEVICE_TABLE ( pci , amd76x_pci_tbl ) ;
static struct pci_driver amd76x_driver = {
2006-03-26 13:38:41 +04:00
. name = EDAC_MOD_STR ,
2006-01-19 04:44:08 +03:00
. probe = amd76x_init_one ,
. remove = __devexit_p ( amd76x_remove_one ) ,
. id_table = amd76x_pci_tbl ,
} ;
2006-01-19 04:44:13 +03:00
static int __init amd76x_init ( void )
2006-01-19 04:44:08 +03:00
{
2008-04-29 12:03:18 +04:00
/* Ensure that the OPSTATE is set correctly for POLL or NMI */
opstate_init ( ) ;
2006-01-19 04:44:08 +03:00
return pci_register_driver ( & amd76x_driver ) ;
}
static void __exit amd76x_exit ( void )
{
pci_unregister_driver ( & amd76x_driver ) ;
}
module_init ( amd76x_init ) ;
module_exit ( amd76x_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Linux Networx (http://lnxi.com) Thayne Harbaugh " ) ;
MODULE_DESCRIPTION ( " MC support for AMD 76x memory controllers " ) ;
2008-04-29 12:03:18 +04:00
module_param ( edac_op_state , int , 0444 ) ;
MODULE_PARM_DESC ( edac_op_state , " EDAC Error Reporting state: 0=Poll,1=NMI " ) ;