2019-05-30 02:57:47 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2007-07-19 12:50:24 +04:00
/*
* Copyright ( C ) 2006 - 2007 PA Semi , Inc
*
* Author : Egor Martovetsky < egor @ pasemi . com >
* Maintained by : Olof Johansson < olof @ lixom . net >
*
* Driver for the PWRficient onchip memory controllers
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
2008-04-30 05:16:16 +04:00
# include <linux/edac.h>
2016-10-29 20:16:34 +03:00
# include "edac_module.h"
2007-07-19 12:50:24 +04:00
# define MODULE_NAME "pasemi_edac"
# define MCCFG_MCEN 0x300
# define MCCFG_MCEN_MMC_EN 0x00000001
# define MCCFG_ERRCOR 0x388
# define MCCFG_ERRCOR_RNK_FAIL_DET_EN 0x00000100
# define MCCFG_ERRCOR_ECC_GEN_EN 0x00000010
# define MCCFG_ERRCOR_ECC_CRR_EN 0x00000001
# define MCCFG_SCRUB 0x384
# define MCCFG_SCRUB_RGLR_SCRB_EN 0x00000001
# define MCDEBUG_ERRCTL1 0x728
# define MCDEBUG_ERRCTL1_RFL_LOG_EN 0x00080000
# define MCDEBUG_ERRCTL1_MBE_LOG_EN 0x00040000
# define MCDEBUG_ERRCTL1_SBE_LOG_EN 0x00020000
# define MCDEBUG_ERRSTA 0x730
# define MCDEBUG_ERRSTA_RFL_STATUS 0x00000004
# define MCDEBUG_ERRSTA_MBE_STATUS 0x00000002
# define MCDEBUG_ERRSTA_SBE_STATUS 0x00000001
# define MCDEBUG_ERRCNT1 0x734
# define MCDEBUG_ERRCNT1_SBE_CNT_OVRFLO 0x00000080
# define MCDEBUG_ERRLOG1A 0x738
# define MCDEBUG_ERRLOG1A_MERR_TYPE_M 0x30000000
# define MCDEBUG_ERRLOG1A_MERR_TYPE_NONE 0x00000000
# define MCDEBUG_ERRLOG1A_MERR_TYPE_SBE 0x10000000
# define MCDEBUG_ERRLOG1A_MERR_TYPE_MBE 0x20000000
# define MCDEBUG_ERRLOG1A_MERR_TYPE_RFL 0x30000000
# define MCDEBUG_ERRLOG1A_MERR_BA_M 0x00700000
# define MCDEBUG_ERRLOG1A_MERR_BA_S 20
# define MCDEBUG_ERRLOG1A_MERR_CS_M 0x00070000
# define MCDEBUG_ERRLOG1A_MERR_CS_S 16
# define MCDEBUG_ERRLOG1A_SYNDROME_M 0x0000ffff
# define MCDRAM_RANKCFG 0x114
# define MCDRAM_RANKCFG_EN 0x00000001
# define MCDRAM_RANKCFG_TYPE_SIZE_M 0x000001c0
# define MCDRAM_RANKCFG_TYPE_SIZE_S 6
# define PASEMI_EDAC_NR_CSROWS 8
# define PASEMI_EDAC_NR_CHANS 1
# define PASEMI_EDAC_ERROR_GRAIN 64
static int last_page_in_mmc ;
static int system_mmc_id ;
static u32 pasemi_edac_get_error_info ( struct mem_ctl_info * mci )
{
2012-03-16 14:44:18 +04:00
struct pci_dev * pdev = to_pci_dev ( mci - > pdev ) ;
2007-07-19 12:50:24 +04:00
u32 tmp ;
pci_read_config_dword ( pdev , MCDEBUG_ERRSTA ,
& tmp ) ;
tmp & = ( MCDEBUG_ERRSTA_RFL_STATUS | MCDEBUG_ERRSTA_MBE_STATUS
| MCDEBUG_ERRSTA_SBE_STATUS ) ;
if ( tmp ) {
if ( tmp & MCDEBUG_ERRSTA_SBE_STATUS )
pci_write_config_dword ( pdev , MCDEBUG_ERRCNT1 ,
MCDEBUG_ERRCNT1_SBE_CNT_OVRFLO ) ;
pci_write_config_dword ( pdev , MCDEBUG_ERRSTA , tmp ) ;
}
return tmp ;
}
static void pasemi_edac_process_error_info ( struct mem_ctl_info * mci , u32 errsta )
{
2012-03-16 14:44:18 +04:00
struct pci_dev * pdev = to_pci_dev ( mci - > pdev ) ;
2007-07-19 12:50:24 +04:00
u32 errlog1a ;
u32 cs ;
if ( ! errsta )
return ;
pci_read_config_dword ( pdev , MCDEBUG_ERRLOG1A , & errlog1a ) ;
cs = ( errlog1a & MCDEBUG_ERRLOG1A_MERR_CS_M ) > >
MCDEBUG_ERRLOG1A_MERR_CS_S ;
/* uncorrectable/multi-bit errors */
if ( errsta & ( MCDEBUG_ERRSTA_MBE_STATUS |
MCDEBUG_ERRSTA_RFL_STATUS ) ) {
2012-06-04 20:27:43 +04:00
edac_mc_handle_error ( HW_EVENT_ERR_UNCORRECTED , mci , 1 ,
2012-04-24 22:05:43 +04:00
mci - > csrows [ cs ] - > first_page , 0 , 0 ,
2012-06-04 18:29:25 +04:00
cs , 0 , - 1 , mci - > ctl_name , " " ) ;
2007-07-19 12:50:24 +04:00
}
/* correctable/single-bit errors */
2012-04-16 22:11:36 +04:00
if ( errsta & MCDEBUG_ERRSTA_SBE_STATUS )
2012-06-04 20:27:43 +04:00
edac_mc_handle_error ( HW_EVENT_ERR_CORRECTED , mci , 1 ,
2012-04-24 22:05:43 +04:00
mci - > csrows [ cs ] - > first_page , 0 , 0 ,
2012-06-04 18:29:25 +04:00
cs , 0 , - 1 , mci - > ctl_name , " " ) ;
2007-07-19 12:50:24 +04:00
}
static void pasemi_edac_check ( struct mem_ctl_info * mci )
{
u32 errsta ;
errsta = pasemi_edac_get_error_info ( mci ) ;
if ( errsta )
pasemi_edac_process_error_info ( mci , errsta ) ;
}
static int pasemi_edac_init_csrows ( struct mem_ctl_info * mci ,
struct pci_dev * pdev ,
enum edac_type edac_mode )
{
struct csrow_info * csrow ;
2012-01-28 01:38:08 +04:00
struct dimm_info * dimm ;
2007-07-19 12:50:24 +04:00
u32 rankcfg ;
int index ;
for ( index = 0 ; index < mci - > nr_csrows ; index + + ) {
2012-04-24 22:05:43 +04:00
csrow = mci - > csrows [ index ] ;
dimm = csrow - > channels [ 0 ] - > dimm ;
2007-07-19 12:50:24 +04:00
pci_read_config_dword ( pdev ,
MCDRAM_RANKCFG + ( index * 12 ) ,
& rankcfg ) ;
if ( ! ( rankcfg & MCDRAM_RANKCFG_EN ) )
continue ;
switch ( ( rankcfg & MCDRAM_RANKCFG_TYPE_SIZE_M ) > >
MCDRAM_RANKCFG_TYPE_SIZE_S ) {
case 0 :
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = 128 < < ( 20 - PAGE_SHIFT ) ;
2007-07-19 12:50:24 +04:00
break ;
case 1 :
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = 256 < < ( 20 - PAGE_SHIFT ) ;
2007-07-19 12:50:24 +04:00
break ;
case 2 :
case 3 :
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = 512 < < ( 20 - PAGE_SHIFT ) ;
2007-07-19 12:50:24 +04:00
break ;
case 4 :
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = 1024 < < ( 20 - PAGE_SHIFT ) ;
2007-07-19 12:50:24 +04:00
break ;
case 5 :
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = 2048 < < ( 20 - PAGE_SHIFT ) ;
2007-07-19 12:50:24 +04:00
break ;
default :
edac_mc_printk ( mci , KERN_ERR ,
" Unrecognized Rank Config. rankcfg=%u \n " ,
rankcfg ) ;
return - EINVAL ;
}
csrow - > first_page = last_page_in_mmc ;
2012-01-28 16:09:38 +04:00
csrow - > last_page = csrow - > first_page + dimm - > nr_pages - 1 ;
last_page_in_mmc + = dimm - > nr_pages ;
2007-07-19 12:50:24 +04:00
csrow - > page_mask = 0 ;
2012-01-28 01:38:08 +04:00
dimm - > grain = PASEMI_EDAC_ERROR_GRAIN ;
dimm - > mtype = MEM_DDR ;
dimm - > dtype = DEV_UNKNOWN ;
dimm - > edac_mode = edac_mode ;
2007-07-19 12:50:24 +04:00
}
return 0 ;
}
2012-12-22 01:23:51 +04:00
static int pasemi_edac_probe ( struct pci_dev * pdev ,
const struct pci_device_id * ent )
2007-07-19 12:50:24 +04:00
{
struct mem_ctl_info * mci = NULL ;
2012-04-16 22:11:36 +04:00
struct edac_mc_layer layers [ 2 ] ;
2007-07-19 12:50:24 +04:00
u32 errctl1 , errcor , scrub , mcen ;
pci_read_config_dword ( pdev , MCCFG_MCEN , & mcen ) ;
if ( ! ( mcen & MCCFG_MCEN_MMC_EN ) )
return - ENODEV ;
/*
* We should think about enabling other error detection later on
*/
pci_read_config_dword ( pdev , MCDEBUG_ERRCTL1 , & errctl1 ) ;
errctl1 | = MCDEBUG_ERRCTL1_SBE_LOG_EN |
MCDEBUG_ERRCTL1_MBE_LOG_EN |
MCDEBUG_ERRCTL1_RFL_LOG_EN ;
pci_write_config_dword ( pdev , MCDEBUG_ERRCTL1 , errctl1 ) ;
2012-04-16 22:11:36 +04:00
layers [ 0 ] . type = EDAC_MC_LAYER_CHIP_SELECT ;
layers [ 0 ] . size = PASEMI_EDAC_NR_CSROWS ;
layers [ 0 ] . is_virt_csrow = true ;
layers [ 1 ] . type = EDAC_MC_LAYER_CHANNEL ;
layers [ 1 ] . size = PASEMI_EDAC_NR_CHANS ;
layers [ 1 ] . is_virt_csrow = false ;
2012-05-02 21:37:00 +04:00
mci = edac_mc_alloc ( system_mmc_id + + , ARRAY_SIZE ( layers ) , layers ,
2012-04-16 22:11:36 +04:00
0 ) ;
2007-07-19 12:50:24 +04:00
if ( mci = = NULL )
return - ENOMEM ;
pci_read_config_dword ( pdev , MCCFG_ERRCOR , & errcor ) ;
errcor | = MCCFG_ERRCOR_RNK_FAIL_DET_EN |
MCCFG_ERRCOR_ECC_GEN_EN |
MCCFG_ERRCOR_ECC_CRR_EN ;
2012-03-16 14:44:18 +04:00
mci - > pdev = & pdev - > dev ;
2007-07-19 12:50:24 +04:00
mci - > mtype_cap = MEM_FLAG_DDR | MEM_FLAG_RDDR ;
mci - > edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED ;
mci - > edac_cap = ( errcor & MCCFG_ERRCOR_ECC_GEN_EN ) ?
( ( errcor & MCCFG_ERRCOR_ECC_CRR_EN ) ?
( EDAC_FLAG_EC | EDAC_FLAG_SECDED ) : EDAC_FLAG_EC ) :
EDAC_FLAG_NONE ;
mci - > mod_name = MODULE_NAME ;
mci - > dev_name = pci_name ( pdev ) ;
2007-11-05 05:57:45 +03:00
mci - > ctl_name = " pasemi,pwrficient-mc " ;
2007-07-19 12:50:24 +04:00
mci - > edac_check = pasemi_edac_check ;
mci - > ctl_page_to_phys = NULL ;
pci_read_config_dword ( pdev , MCCFG_SCRUB , & scrub ) ;
mci - > scrub_cap = SCRUB_FLAG_HW_PROG | SCRUB_FLAG_HW_SRC ;
mci - > scrub_mode =
( ( errcor & MCCFG_ERRCOR_ECC_CRR_EN ) ? SCRUB_FLAG_HW_SRC : 0 ) |
( ( scrub & MCCFG_SCRUB_RGLR_SCRB_EN ) ? SCRUB_FLAG_HW_PROG : 0 ) ;
if ( pasemi_edac_init_csrows ( mci , pdev ,
( mci - > edac_cap & EDAC_FLAG_SECDED ) ?
EDAC_SECDED :
( ( mci - > edac_cap & EDAC_FLAG_EC ) ?
EDAC_EC : EDAC_NONE ) ) )
goto fail ;
/*
* Clear status
*/
pasemi_edac_get_error_info ( mci ) ;
2007-07-19 12:50:26 +04:00
if ( edac_mc_add_mc ( mci ) )
2007-07-19 12:50:24 +04:00
goto fail ;
/* get this far and it's successful */
return 0 ;
fail :
edac_mc_free ( mci ) ;
return - ENODEV ;
}
2012-12-22 01:23:51 +04:00
static void pasemi_edac_remove ( struct pci_dev * pdev )
2007-07-19 12:50:24 +04:00
{
struct mem_ctl_info * mci = edac_mc_del_mc ( & pdev - > dev ) ;
if ( ! mci )
return ;
edac_mc_free ( mci ) ;
}
static const struct pci_device_id pasemi_edac_pci_tbl [ ] = {
{ PCI_DEVICE ( PCI_VENDOR_ID_PASEMI , 0xa00a ) } ,
2007-10-19 10:41:11 +04:00
{ }
2007-07-19 12:50:24 +04:00
} ;
MODULE_DEVICE_TABLE ( pci , pasemi_edac_pci_tbl ) ;
static struct pci_driver pasemi_edac_driver = {
. name = MODULE_NAME ,
. probe = pasemi_edac_probe ,
2012-12-22 01:23:51 +04:00
. remove = pasemi_edac_remove ,
2007-07-19 12:50:24 +04:00
. id_table = pasemi_edac_pci_tbl ,
} ;
static int __init pasemi_edac_init ( void )
{
2008-04-29 12:03:18 +04:00
/* Ensure that the OPSTATE is set correctly for POLL or NMI */
opstate_init ( ) ;
2007-07-19 12:50:24 +04:00
return pci_register_driver ( & pasemi_edac_driver ) ;
}
static void __exit pasemi_edac_exit ( void )
{
pci_unregister_driver ( & pasemi_edac_driver ) ;
}
module_init ( pasemi_edac_init ) ;
module_exit ( pasemi_edac_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Egor Martovetsky <egor@pasemi.com> " ) ;
2007-11-05 05:57:45 +03:00
MODULE_DESCRIPTION ( " MC support for PA Semi PWRficient memory controller " ) ;
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 " ) ;