2008-02-07 11:14:53 +03:00
/*
* Cell MIC driver for ECC counting
*
* Copyright 2007 Benjamin Herrenschmidt , IBM Corp .
* < benh @ kernel . crashing . org >
*
* This file may be distributed under the terms of the
* GNU General Public License .
*/
# undef DEBUG
2008-10-30 00:01:00 +03:00
# include <linux/edac.h>
2008-02-07 11:14:53 +03:00
# include <linux/module.h>
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/stop_machine.h>
# include <linux/io.h>
2013-09-17 23:28:33 +04:00
# include <linux/of_address.h>
2008-02-07 11:14:53 +03:00
# include <asm/machdep.h>
# include <asm/cell-regs.h>
2016-10-29 20:16:34 +03:00
# include "edac_module.h"
2008-02-07 11:14:53 +03:00
struct cell_edac_priv
{
struct cbe_mic_tm_regs __iomem * regs ;
int node ;
int chanmask ;
# ifdef DEBUG
u64 prev_fir ;
# endif
} ;
static void cell_edac_count_ce ( struct mem_ctl_info * mci , int chan , u64 ar )
{
struct cell_edac_priv * priv = mci - > pvt_info ;
2012-04-24 22:05:43 +04:00
struct csrow_info * csrow = mci - > csrows [ 0 ] ;
2008-07-15 23:51:40 +04:00
unsigned long address , pfn , offset , syndrome ;
2008-02-07 11:14:53 +03:00
2012-03-16 14:44:18 +04:00
dev_dbg ( mci - > pdev , " ECC CE err on node %d, channel %d, ar = 0x%016llx \n " ,
2008-02-07 11:14:53 +03:00
priv - > node , chan , ar ) ;
/* Address decoding is likely a bit bogus, to dbl check */
address = ( ar & 0xffffffffe0000000ul ) > > 29 ;
if ( priv - > chanmask = = 0x3 )
address = ( address < < 1 ) | chan ;
pfn = address > > PAGE_SHIFT ;
offset = address & ~ PAGE_MASK ;
2008-07-15 23:51:40 +04:00
syndrome = ( ar & 0x000000001fe00000ul ) > > 21 ;
2008-02-07 11:14:53 +03:00
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-01 22:38:34 +03:00
/* TODO: Decoding of the error address */
2012-06-04 20:27:43 +04:00
edac_mc_handle_error ( HW_EVENT_ERR_CORRECTED , mci , 1 ,
2012-04-16 22:06:25 +04:00
csrow - > first_page + pfn , offset , syndrome ,
2012-06-04 18:29:25 +04:00
0 , chan , - 1 , " " , " " ) ;
2008-02-07 11:14:53 +03:00
}
static void cell_edac_count_ue ( struct mem_ctl_info * mci , int chan , u64 ar )
{
struct cell_edac_priv * priv = mci - > pvt_info ;
2012-04-24 22:05:43 +04:00
struct csrow_info * csrow = mci - > csrows [ 0 ] ;
2008-02-07 11:14:53 +03:00
unsigned long address , pfn , offset ;
2012-03-16 14:44:18 +04:00
dev_dbg ( mci - > pdev , " ECC UE err on node %d, channel %d, ar = 0x%016llx \n " ,
2008-02-07 11:14:53 +03:00
priv - > node , chan , ar ) ;
/* Address decoding is likely a bit bogus, to dbl check */
address = ( ar & 0xffffffffe0000000ul ) > > 29 ;
if ( priv - > chanmask = = 0x3 )
address = ( address < < 1 ) | chan ;
pfn = address > > PAGE_SHIFT ;
offset = address & ~ PAGE_MASK ;
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-01 22:38:34 +03:00
/* TODO: Decoding of the error address */
2012-06-04 20:27:43 +04:00
edac_mc_handle_error ( HW_EVENT_ERR_UNCORRECTED , mci , 1 ,
2012-04-16 22:06:25 +04:00
csrow - > first_page + pfn , offset , 0 ,
2012-06-04 18:29:25 +04:00
0 , chan , - 1 , " " , " " ) ;
2008-02-07 11:14:53 +03:00
}
static void cell_edac_check ( struct mem_ctl_info * mci )
{
struct cell_edac_priv * priv = mci - > pvt_info ;
u64 fir , addreg , clear = 0 ;
fir = in_be64 ( & priv - > regs - > mic_fir ) ;
# ifdef DEBUG
if ( fir ! = priv - > prev_fir ) {
2012-03-16 14:44:18 +04:00
dev_dbg ( mci - > pdev , " fir change : 0x%016lx \n " , fir ) ;
2008-02-07 11:14:53 +03:00
priv - > prev_fir = fir ;
}
# endif
if ( ( priv - > chanmask & 0x1 ) & & ( fir & CBE_MIC_FIR_ECC_SINGLE_0_ERR ) ) {
addreg = in_be64 ( & priv - > regs - > mic_df_ecc_address_0 ) ;
clear | = CBE_MIC_FIR_ECC_SINGLE_0_RESET ;
cell_edac_count_ce ( mci , 0 , addreg ) ;
}
if ( ( priv - > chanmask & 0x2 ) & & ( fir & CBE_MIC_FIR_ECC_SINGLE_1_ERR ) ) {
addreg = in_be64 ( & priv - > regs - > mic_df_ecc_address_1 ) ;
clear | = CBE_MIC_FIR_ECC_SINGLE_1_RESET ;
cell_edac_count_ce ( mci , 1 , addreg ) ;
}
if ( ( priv - > chanmask & 0x1 ) & & ( fir & CBE_MIC_FIR_ECC_MULTI_0_ERR ) ) {
addreg = in_be64 ( & priv - > regs - > mic_df_ecc_address_0 ) ;
clear | = CBE_MIC_FIR_ECC_MULTI_0_RESET ;
cell_edac_count_ue ( mci , 0 , addreg ) ;
}
if ( ( priv - > chanmask & 0x2 ) & & ( fir & CBE_MIC_FIR_ECC_MULTI_1_ERR ) ) {
addreg = in_be64 ( & priv - > regs - > mic_df_ecc_address_1 ) ;
clear | = CBE_MIC_FIR_ECC_MULTI_1_RESET ;
cell_edac_count_ue ( mci , 1 , addreg ) ;
}
/* The procedure for clearing FIR bits is a bit ... weird */
if ( clear ) {
fir & = ~ ( CBE_MIC_FIR_ECC_ERR_MASK | CBE_MIC_FIR_ECC_SET_MASK ) ;
fir | = CBE_MIC_FIR_ECC_RESET_MASK ;
fir & = ~ clear ;
out_be64 ( & priv - > regs - > mic_fir , fir ) ;
( void ) in_be64 ( & priv - > regs - > mic_fir ) ;
mb ( ) ; /* sync up */
# ifdef DEBUG
fir = in_be64 ( & priv - > regs - > mic_fir ) ;
2012-03-16 14:44:18 +04:00
dev_dbg ( mci - > pdev , " fir clear : 0x%016lx \n " , fir ) ;
2008-02-07 11:14:53 +03:00
# endif
}
}
2012-12-22 01:23:51 +04:00
static void cell_edac_init_csrows ( struct mem_ctl_info * mci )
2008-02-07 11:14:53 +03:00
{
2012-04-24 22:05:43 +04:00
struct csrow_info * csrow = mci - > csrows [ 0 ] ;
2012-01-28 01:38:08 +04:00
struct dimm_info * dimm ;
2008-02-07 11:14:53 +03:00
struct cell_edac_priv * priv = mci - > pvt_info ;
struct device_node * np ;
2012-01-28 01:38:08 +04:00
int j ;
2012-01-28 16:09:38 +04:00
u32 nr_pages ;
2008-02-07 11:14:53 +03:00
2014-06-04 19:42:26 +04:00
for_each_node_by_name ( np , " memory " ) {
2008-02-07 11:14:53 +03:00
struct resource r ;
/* We "know" that the Cell firmware only creates one entry
* in the " memory " nodes . If that changes , this code will
* need to be adapted .
*/
if ( of_address_to_resource ( np , 0 , & r ) )
continue ;
if ( of_node_to_nid ( np ) ! = priv - > node )
continue ;
csrow - > first_page = r . start > > PAGE_SHIFT ;
2012-01-28 16:09:38 +04:00
nr_pages = resource_size ( & r ) > > PAGE_SHIFT ;
csrow - > last_page = csrow - > first_page + nr_pages - 1 ;
2012-01-28 01:38:08 +04:00
for ( j = 0 ; j < csrow - > nr_channels ; j + + ) {
2012-04-24 22:05:43 +04:00
dimm = csrow - > channels [ j ] - > dimm ;
2012-01-28 01:38:08 +04:00
dimm - > mtype = MEM_XDR ;
dimm - > edac_mode = EDAC_SECDED ;
2012-01-28 16:09:38 +04:00
dimm - > nr_pages = nr_pages / csrow - > nr_channels ;
2012-01-28 01:38:08 +04:00
}
2012-03-16 14:44:18 +04:00
dev_dbg ( mci - > pdev ,
2008-02-07 11:14:53 +03:00
" Initialized on node %d, chanmask=0x%x, "
" first_page=0x%lx, nr_pages=0x%x \n " ,
priv - > node , priv - > chanmask ,
2012-04-16 22:06:25 +04:00
csrow - > first_page , nr_pages ) ;
2008-02-07 11:14:53 +03:00
break ;
}
2013-08-26 10:53:43 +04:00
of_node_put ( np ) ;
2008-02-07 11:14:53 +03:00
}
2012-12-22 01:23:51 +04:00
static int cell_edac_probe ( struct platform_device * pdev )
2008-02-07 11:14:53 +03:00
{
struct cbe_mic_tm_regs __iomem * regs ;
struct mem_ctl_info * mci ;
2012-04-16 22:06:25 +04:00
struct edac_mc_layer layers [ 2 ] ;
2008-02-07 11:14:53 +03:00
struct cell_edac_priv * priv ;
u64 reg ;
2012-04-16 22:06:25 +04:00
int rc , chanmask , num_chans ;
2008-02-07 11:14:53 +03:00
regs = cbe_get_cpu_mic_tm_regs ( cbe_node_to_cpu ( pdev - > id ) ) ;
if ( regs = = NULL )
return - ENODEV ;
2008-10-30 00:01:00 +03:00
edac_op_state = EDAC_OPSTATE_POLL ;
2008-02-07 11:14:53 +03:00
/* Get channel population */
reg = in_be64 ( & regs - > mic_mnt_cfg ) ;
2009-01-21 16:16:28 +03:00
dev_dbg ( & pdev - > dev , " MIC_MNT_CFG = 0x%016llx \n " , reg ) ;
2008-02-07 11:14:53 +03:00
chanmask = 0 ;
if ( reg & CBE_MIC_MNT_CFG_CHAN_0_POP )
chanmask | = 0x1 ;
if ( reg & CBE_MIC_MNT_CFG_CHAN_1_POP )
chanmask | = 0x2 ;
if ( chanmask = = 0 ) {
dev_warn ( & pdev - > dev ,
" Yuck ! No channel populated ? Aborting ! \n " ) ;
return - ENODEV ;
}
2009-01-21 16:16:28 +03:00
dev_dbg ( & pdev - > dev , " Initial FIR = 0x%016llx \n " ,
2008-02-07 11:14:53 +03:00
in_be64 ( & regs - > mic_fir ) ) ;
/* Allocate & init EDAC MC data structure */
2012-04-16 22:06:25 +04:00
num_chans = chanmask = = 3 ? 2 : 1 ;
layers [ 0 ] . type = EDAC_MC_LAYER_CHIP_SELECT ;
layers [ 0 ] . size = 1 ;
layers [ 0 ] . is_virt_csrow = true ;
layers [ 1 ] . type = EDAC_MC_LAYER_CHANNEL ;
layers [ 1 ] . size = num_chans ;
layers [ 1 ] . is_virt_csrow = false ;
2012-05-02 21:37:00 +04:00
mci = edac_mc_alloc ( pdev - > id , ARRAY_SIZE ( layers ) , layers ,
2012-04-16 22:06:25 +04:00
sizeof ( struct cell_edac_priv ) ) ;
2008-02-07 11:14:53 +03:00
if ( mci = = NULL )
return - ENOMEM ;
priv = mci - > pvt_info ;
priv - > regs = regs ;
priv - > node = pdev - > id ;
priv - > chanmask = chanmask ;
2012-03-16 14:44:18 +04:00
mci - > pdev = & pdev - > dev ;
2008-02-07 11:14:53 +03:00
mci - > mtype_cap = MEM_FLAG_XDR ;
mci - > edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_EC | EDAC_FLAG_SECDED ;
mci - > edac_cap = EDAC_FLAG_EC | EDAC_FLAG_SECDED ;
mci - > mod_name = " cell_edac " ;
mci - > ctl_name = " MIC " ;
2009-03-25 02:38:21 +03:00
mci - > dev_name = dev_name ( & pdev - > dev ) ;
2008-02-07 11:14:53 +03:00
mci - > edac_check = cell_edac_check ;
cell_edac_init_csrows ( mci ) ;
/* Register with EDAC core */
rc = edac_mc_add_mc ( mci ) ;
if ( rc ) {
dev_err ( & pdev - > dev , " failed to register with EDAC core \n " ) ;
edac_mc_free ( mci ) ;
return rc ;
}
return 0 ;
}
2012-12-22 01:23:51 +04:00
static int cell_edac_remove ( struct platform_device * pdev )
2008-02-07 11:14:53 +03:00
{
struct mem_ctl_info * mci = edac_mc_del_mc ( & pdev - > dev ) ;
if ( mci )
edac_mc_free ( mci ) ;
return 0 ;
}
static struct platform_driver cell_edac_driver = {
. driver = {
. name = " cbe-mic " ,
} ,
. probe = cell_edac_probe ,
2012-12-22 01:23:51 +04:00
. remove = cell_edac_remove ,
2008-02-07 11:14:53 +03:00
} ;
static int __init cell_edac_init ( void )
{
/* Sanity check registers data structure */
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_df_ecc_address_0 ) ! = 0xf8 ) ;
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_df_ecc_address_1 ) ! = 0x1b8 ) ;
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_df_config ) ! = 0x218 ) ;
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_fir ) ! = 0x230 ) ;
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_mnt_cfg ) ! = 0x210 ) ;
BUILD_BUG_ON ( offsetof ( struct cbe_mic_tm_regs ,
mic_exc ) ! = 0x208 ) ;
return platform_driver_register ( & cell_edac_driver ) ;
}
static void __exit cell_edac_exit ( void )
{
platform_driver_unregister ( & cell_edac_driver ) ;
}
module_init ( cell_edac_init ) ;
module_exit ( cell_edac_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Benjamin Herrenschmidt <benh@kernel.crashing.org> " ) ;
MODULE_DESCRIPTION ( " ECC counting for Cell MIC " ) ;