2012-05-10 10:42:32 +03:00
/*
* Tegra30 Memory Controller
*
* Copyright ( c ) 2012 , NVIDIA CORPORATION . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . 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 . ,
* 51 Franklin St - Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
2013-01-21 11:09:08 +01:00
# include <linux/err.h>
2012-05-10 10:42:32 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/ratelimit.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# define DRV_NAME "tegra30-mc"
# define MC_INTSTATUS 0x0
# define MC_INTMASK 0x4
# define MC_INT_ERR_SHIFT 6
# define MC_INT_ERR_MASK (0x1f << MC_INT_ERR_SHIFT)
# define MC_INT_DECERR_EMEM BIT(MC_INT_ERR_SHIFT)
# define MC_INT_SECURITY_VIOLATION BIT(MC_INT_ERR_SHIFT + 2)
# define MC_INT_ARBITRATION_EMEM BIT(MC_INT_ERR_SHIFT + 3)
# define MC_INT_INVALID_SMMU_PAGE BIT(MC_INT_ERR_SHIFT + 4)
# define MC_ERR_STATUS 0x8
# define MC_ERR_ADR 0xc
# define MC_ERR_TYPE_SHIFT 28
# define MC_ERR_TYPE_MASK (7 << MC_ERR_TYPE_SHIFT)
# define MC_ERR_TYPE_DECERR_EMEM 2
# define MC_ERR_TYPE_SECURITY_TRUSTZONE 3
# define MC_ERR_TYPE_SECURITY_CARVEOUT 4
# define MC_ERR_TYPE_INVALID_SMMU_PAGE 6
# define MC_ERR_INVALID_SMMU_PAGE_SHIFT 25
# define MC_ERR_INVALID_SMMU_PAGE_MASK (7 << MC_ERR_INVALID_SMMU_PAGE_SHIFT)
# define MC_ERR_RW_SHIFT 16
# define MC_ERR_RW BIT(MC_ERR_RW_SHIFT)
# define MC_ERR_SECURITY BIT(MC_ERR_RW_SHIFT + 1)
# define SECURITY_VIOLATION_TYPE BIT(30) /* 0=TRUSTZONE, 1=CARVEOUT */
# define MC_EMEM_ARB_CFG 0x90
# define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
# define MC_EMEM_ARB_TIMING_RCD 0x98
# define MC_EMEM_ARB_TIMING_RP 0x9c
# define MC_EMEM_ARB_TIMING_RC 0xa0
# define MC_EMEM_ARB_TIMING_RAS 0xa4
# define MC_EMEM_ARB_TIMING_FAW 0xa8
# define MC_EMEM_ARB_TIMING_RRD 0xac
# define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0
# define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4
# define MC_EMEM_ARB_TIMING_R2R 0xb8
# define MC_EMEM_ARB_TIMING_W2W 0xbc
# define MC_EMEM_ARB_TIMING_R2W 0xc0
# define MC_EMEM_ARB_TIMING_W2R 0xc4
# define MC_EMEM_ARB_DA_TURNS 0xd0
# define MC_EMEM_ARB_DA_COVERS 0xd4
# define MC_EMEM_ARB_MISC0 0xd8
# define MC_EMEM_ARB_MISC1 0xdc
# define MC_EMEM_ARB_RING3_THROTTLE 0xe4
# define MC_EMEM_ARB_OVERRIDE 0xe8
# define MC_TIMING_CONTROL 0xfc
# define MC_CLIENT_ID_MASK 0x7f
# define NUM_MC_REG_BANKS 4
struct tegra30_mc {
void __iomem * regs [ NUM_MC_REG_BANKS ] ;
struct device * dev ;
u32 ctx [ 0 ] ;
} ;
static inline u32 mc_readl ( struct tegra30_mc * mc , u32 offs )
{
2012-05-11 13:04:45 +03:00
u32 val = 0 ;
2012-05-10 10:42:32 +03:00
if ( offs < 0x10 )
2012-05-11 13:04:45 +03:00
val = readl ( mc - > regs [ 0 ] + offs ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x1f0 )
2012-05-11 13:04:45 +03:00
val = readl ( mc - > regs [ 1 ] + offs - 0x3c ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x228 )
2012-05-11 13:04:45 +03:00
val = readl ( mc - > regs [ 2 ] + offs - 0x200 ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x400 )
2012-05-11 13:04:45 +03:00
val = readl ( mc - > regs [ 3 ] + offs - 0x284 ) ;
return val ;
2012-05-10 10:42:32 +03:00
}
static inline void mc_writel ( struct tegra30_mc * mc , u32 val , u32 offs )
{
2012-09-20 09:32:53 +08:00
if ( offs < 0x10 )
2012-05-10 10:42:32 +03:00
writel ( val , mc - > regs [ 0 ] + offs ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x1f0 )
2012-05-10 10:42:32 +03:00
writel ( val , mc - > regs [ 1 ] + offs - 0x3c ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x228 )
2012-05-10 10:42:32 +03:00
writel ( val , mc - > regs [ 2 ] + offs - 0x200 ) ;
2012-09-20 09:32:53 +08:00
else if ( offs < 0x400 )
2012-05-10 10:42:32 +03:00
writel ( val , mc - > regs [ 3 ] + offs - 0x284 ) ;
}
static const char * const tegra30_mc_client [ ] = {
" csr_ptcr " ,
" cbr_display0a " ,
" cbr_display0ab " ,
" cbr_display0b " ,
" cbr_display0bb " ,
" cbr_display0c " ,
" cbr_display0cb " ,
" cbr_display1b " ,
" cbr_display1bb " ,
" cbr_eppup " ,
" cbr_g2pr " ,
" cbr_g2sr " ,
" cbr_mpeunifbr " ,
" cbr_viruv " ,
" csr_afir " ,
" csr_avpcarm7r " ,
" csr_displayhc " ,
" csr_displayhcb " ,
" csr_fdcdrd " ,
" csr_fdcdrd2 " ,
" csr_g2dr " ,
" csr_hdar " ,
" csr_host1xdmar " ,
" csr_host1xr " ,
" csr_idxsrd " ,
" csr_idxsrd2 " ,
" csr_mpe_ipred " ,
" csr_mpeamemrd " ,
" csr_mpecsrd " ,
" csr_ppcsahbdmar " ,
" csr_ppcsahbslvr " ,
" csr_satar " ,
" csr_texsrd " ,
" csr_texsrd2 " ,
" csr_vdebsevr " ,
" csr_vdember " ,
" csr_vdemcer " ,
" csr_vdetper " ,
" csr_mpcorelpr " ,
" csr_mpcorer " ,
" cbw_eppu " ,
" cbw_eppv " ,
" cbw_eppy " ,
" cbw_mpeunifbw " ,
" cbw_viwsb " ,
" cbw_viwu " ,
" cbw_viwv " ,
" cbw_viwy " ,
" ccw_g2dw " ,
" csw_afiw " ,
" csw_avpcarm7w " ,
" csw_fdcdwr " ,
" csw_fdcdwr2 " ,
" csw_hdaw " ,
" csw_host1xw " ,
" csw_ispw " ,
" csw_mpcorelpw " ,
" csw_mpcorew " ,
" csw_mpecswr " ,
" csw_ppcsahbdmaw " ,
" csw_ppcsahbslvw " ,
" csw_sataw " ,
" csw_vdebsevw " ,
" csw_vdedbgw " ,
" csw_vdembew " ,
" csw_vdetpmw " ,
} ;
static void tegra30_mc_decode ( struct tegra30_mc * mc , int n )
{
u32 err , addr ;
const char * const mc_int_err [ ] = {
" MC_DECERR " ,
" Unknown " ,
" MC_SECURITY_ERR " ,
" MC_ARBITRATION_EMEM " ,
" MC_SMMU_ERR " ,
} ;
const char * const err_type [ ] = {
" Unknown " ,
" Unknown " ,
" DECERR_EMEM " ,
" SECURITY_TRUSTZONE " ,
" SECURITY_CARVEOUT " ,
" Unknown " ,
" INVALID_SMMU_PAGE " ,
" Unknown " ,
} ;
char attr [ 6 ] ;
int cid , perm , type , idx ;
const char * client = " Unknown " ;
idx = n - MC_INT_ERR_SHIFT ;
if ( ( idx < 0 ) | | ( idx > = ARRAY_SIZE ( mc_int_err ) ) | | ( idx = = 1 ) ) {
2012-05-14 13:07:03 +03:00
dev_err_ratelimited ( mc - > dev , " Unknown interrupt status %08lx \n " ,
BIT ( n ) ) ;
2012-05-10 10:42:32 +03:00
return ;
}
2013-06-11 13:11:19 +03:00
err = mc_readl ( mc , MC_ERR_STATUS ) ;
2012-05-10 10:42:32 +03:00
type = ( err & MC_ERR_TYPE_MASK ) > > MC_ERR_TYPE_SHIFT ;
perm = ( err & MC_ERR_INVALID_SMMU_PAGE_MASK ) > >
MC_ERR_INVALID_SMMU_PAGE_SHIFT ;
if ( type = = MC_ERR_TYPE_INVALID_SMMU_PAGE )
sprintf ( attr , " %c-%c-%c " ,
( perm & BIT ( 2 ) ) ? ' R ' : ' - ' ,
( perm & BIT ( 1 ) ) ? ' W ' : ' - ' ,
( perm & BIT ( 0 ) ) ? ' S ' : ' - ' ) ;
else
attr [ 0 ] = ' \0 ' ;
cid = err & MC_CLIENT_ID_MASK ;
if ( cid < ARRAY_SIZE ( tegra30_mc_client ) )
client = tegra30_mc_client [ cid ] ;
2013-06-11 13:11:19 +03:00
addr = mc_readl ( mc , MC_ERR_ADR ) ;
2012-05-10 10:42:32 +03:00
2012-05-14 13:07:03 +03:00
dev_err_ratelimited ( mc - > dev , " %s (0x%08x): 0x%08x %s (%s %s %s %s) \n " ,
2012-05-10 10:42:32 +03:00
mc_int_err [ idx ] , err , addr , client ,
( err & MC_ERR_SECURITY ) ? " secure " : " non-secure " ,
( err & MC_ERR_RW ) ? " write " : " read " ,
err_type [ type ] , attr ) ;
}
static const u32 tegra30_mc_ctx [ ] = {
MC_EMEM_ARB_CFG ,
MC_EMEM_ARB_OUTSTANDING_REQ ,
MC_EMEM_ARB_TIMING_RCD ,
MC_EMEM_ARB_TIMING_RP ,
MC_EMEM_ARB_TIMING_RC ,
MC_EMEM_ARB_TIMING_RAS ,
MC_EMEM_ARB_TIMING_FAW ,
MC_EMEM_ARB_TIMING_RRD ,
MC_EMEM_ARB_TIMING_RAP2PRE ,
MC_EMEM_ARB_TIMING_WAP2PRE ,
MC_EMEM_ARB_TIMING_R2R ,
MC_EMEM_ARB_TIMING_W2W ,
MC_EMEM_ARB_TIMING_R2W ,
MC_EMEM_ARB_TIMING_W2R ,
MC_EMEM_ARB_DA_TURNS ,
MC_EMEM_ARB_DA_COVERS ,
MC_EMEM_ARB_MISC0 ,
MC_EMEM_ARB_MISC1 ,
MC_EMEM_ARB_RING3_THROTTLE ,
MC_EMEM_ARB_OVERRIDE ,
MC_INTMASK ,
} ;
2013-03-14 11:12:10 +02:00
# ifdef CONFIG_PM
2012-05-10 10:42:32 +03:00
static int tegra30_mc_suspend ( struct device * dev )
{
int i ;
struct tegra30_mc * mc = dev_get_drvdata ( dev ) ;
for ( i = 0 ; i < ARRAY_SIZE ( tegra30_mc_ctx ) ; i + + )
mc - > ctx [ i ] = mc_readl ( mc , tegra30_mc_ctx [ i ] ) ;
return 0 ;
}
static int tegra30_mc_resume ( struct device * dev )
{
int i ;
struct tegra30_mc * mc = dev_get_drvdata ( dev ) ;
for ( i = 0 ; i < ARRAY_SIZE ( tegra30_mc_ctx ) ; i + + )
mc_writel ( mc , mc - > ctx [ i ] , tegra30_mc_ctx [ i ] ) ;
mc_writel ( mc , 1 , MC_TIMING_CONTROL ) ;
/* Read-back to ensure that write reached */
mc_readl ( mc , MC_TIMING_CONTROL ) ;
return 0 ;
}
2013-03-14 11:12:10 +02:00
# endif
2012-05-10 10:42:32 +03:00
static UNIVERSAL_DEV_PM_OPS ( tegra30_mc_pm ,
tegra30_mc_suspend ,
tegra30_mc_resume , NULL ) ;
2012-12-21 15:06:41 -08:00
static const struct of_device_id tegra30_mc_of_match [ ] = {
2012-05-10 10:42:32 +03:00
{ . compatible = " nvidia,tegra30-mc " , } ,
{ } ,
} ;
static irqreturn_t tegra30_mc_isr ( int irq , void * data )
{
u32 stat , mask , bit ;
struct tegra30_mc * mc = data ;
stat = mc_readl ( mc , MC_INTSTATUS ) ;
mask = mc_readl ( mc , MC_INTMASK ) ;
mask & = stat ;
if ( ! mask )
return IRQ_NONE ;
2013-06-11 13:11:19 +03:00
while ( ( bit = ffs ( mask ) ) ! = 0 ) {
2012-05-10 10:42:32 +03:00
tegra30_mc_decode ( mc , bit - 1 ) ;
2013-06-11 13:11:19 +03:00
mask & = ~ BIT ( bit - 1 ) ;
}
2012-05-10 10:42:32 +03:00
mc_writel ( mc , stat , MC_INTSTATUS ) ;
return IRQ_HANDLED ;
}
2012-12-21 15:06:41 -08:00
static int tegra30_mc_probe ( struct platform_device * pdev )
2012-05-10 10:42:32 +03:00
{
struct resource * irq ;
struct tegra30_mc * mc ;
size_t bytes ;
int err , i ;
u32 intmask ;
bytes = sizeof ( * mc ) + sizeof ( u32 ) * ARRAY_SIZE ( tegra30_mc_ctx ) ;
mc = devm_kzalloc ( & pdev - > dev , bytes , GFP_KERNEL ) ;
if ( ! mc )
return - ENOMEM ;
mc - > dev = & pdev - > dev ;
for ( i = 0 ; i < ARRAY_SIZE ( mc - > regs ) ; i + + ) {
struct resource * res ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , i ) ;
2013-01-21 11:09:08 +01:00
mc - > regs [ i ] = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( mc - > regs [ i ] ) )
return PTR_ERR ( mc - > regs [ i ] ) ;
2012-05-10 10:42:32 +03:00
}
irq = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( ! irq )
return - ENODEV ;
err = devm_request_irq ( & pdev - > dev , irq - > start , tegra30_mc_isr ,
IRQF_SHARED , dev_name ( & pdev - > dev ) , mc ) ;
if ( err )
return - ENODEV ;
platform_set_drvdata ( pdev , mc ) ;
intmask = MC_INT_INVALID_SMMU_PAGE |
MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION ;
mc_writel ( mc , intmask , MC_INTMASK ) ;
return 0 ;
}
static struct platform_driver tegra30_mc_driver = {
. probe = tegra30_mc_probe ,
. driver = {
. name = DRV_NAME ,
. owner = THIS_MODULE ,
. of_match_table = tegra30_mc_of_match ,
. pm = & tegra30_mc_pm ,
} ,
} ;
module_platform_driver ( tegra30_mc_driver ) ;
MODULE_AUTHOR ( " Hiroshi DOYU <hdoyu@nvidia.com> " ) ;
MODULE_DESCRIPTION ( " Tegra30 MC driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;