2010-05-25 04:07:46 +04:00
/*
* drivers / powergate / tegra - powergate . c
*
* Copyright ( c ) 2010 Google , Inc
*
* Author :
* Colin Cross < ccross @ google . com >
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that 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 .
*
*/
# include <linux/kernel.h>
# include <linux/clk.h>
# include <linux/debugfs.h>
# include <linux/delay.h>
# include <linux/err.h>
2013-03-29 00:35:03 +04:00
# include <linux/export.h>
2010-05-25 04:07:46 +04:00
# include <linux/init.h>
# include <linux/io.h>
# include <linux/seq_file.h>
# include <linux/spinlock.h>
2013-01-11 11:46:26 +04:00
# include <linux/clk/tegra.h>
2013-03-30 03:38:18 +04:00
# include <linux/tegra-powergate.h>
2010-05-25 04:07:46 +04:00
2012-02-10 03:47:46 +04:00
# include "fuse.h"
2012-10-05 00:24:09 +04:00
# include "iomap.h"
2012-02-10 03:47:46 +04:00
2010-05-25 04:07:46 +04:00
# define PWRGATE_TOGGLE 0x30
# define PWRGATE_TOGGLE_START (1 << 8)
# define REMOVE_CLAMPING 0x34
# define PWRGATE_STATUS 0x38
2012-02-10 03:47:46 +04:00
static int tegra_num_powerdomains ;
2012-02-10 03:47:49 +04:00
static int tegra_num_cpu_domains ;
static u8 * tegra_cpu_domains ;
static u8 tegra30_cpu_domains [ ] = {
TEGRA_POWERGATE_CPU0 ,
TEGRA_POWERGATE_CPU1 ,
TEGRA_POWERGATE_CPU2 ,
TEGRA_POWERGATE_CPU3 ,
} ;
2012-02-10 03:47:46 +04:00
2010-05-25 04:07:46 +04:00
static DEFINE_SPINLOCK ( tegra_powergate_lock ) ;
static void __iomem * pmc = IO_ADDRESS ( TEGRA_PMC_BASE ) ;
static u32 pmc_read ( unsigned long reg )
{
return readl ( pmc + reg ) ;
}
static void pmc_write ( u32 val , unsigned long reg )
{
writel ( val , pmc + reg ) ;
}
static int tegra_powergate_set ( int id , bool new_state )
{
bool status ;
unsigned long flags ;
spin_lock_irqsave ( & tegra_powergate_lock , flags ) ;
status = pmc_read ( PWRGATE_STATUS ) & ( 1 < < id ) ;
if ( status = = new_state ) {
spin_unlock_irqrestore ( & tegra_powergate_lock , flags ) ;
2013-03-29 00:35:04 +04:00
return 0 ;
2010-05-25 04:07:46 +04:00
}
pmc_write ( PWRGATE_TOGGLE_START | id , PWRGATE_TOGGLE ) ;
spin_unlock_irqrestore ( & tegra_powergate_lock , flags ) ;
return 0 ;
}
int tegra_powergate_power_on ( int id )
{
2012-02-10 03:47:46 +04:00
if ( id < 0 | | id > = tegra_num_powerdomains )
2010-05-25 04:07:46 +04:00
return - EINVAL ;
return tegra_powergate_set ( id , true ) ;
}
int tegra_powergate_power_off ( int id )
{
2012-02-10 03:47:46 +04:00
if ( id < 0 | | id > = tegra_num_powerdomains )
2010-05-25 04:07:46 +04:00
return - EINVAL ;
return tegra_powergate_set ( id , false ) ;
}
2012-02-10 03:47:47 +04:00
int tegra_powergate_is_powered ( int id )
2010-05-25 04:07:46 +04:00
{
u32 status ;
2012-02-10 03:47:46 +04:00
if ( id < 0 | | id > = tegra_num_powerdomains )
return - EINVAL ;
2010-05-25 04:07:46 +04:00
status = pmc_read ( PWRGATE_STATUS ) & ( 1 < < id ) ;
return ! ! status ;
}
int tegra_powergate_remove_clamping ( int id )
{
u32 mask ;
2012-02-10 03:47:46 +04:00
if ( id < 0 | | id > = tegra_num_powerdomains )
2010-05-25 04:07:46 +04:00
return - EINVAL ;
/*
* Tegra 2 has a bug where PCIE and VDE clamping masks are
* swapped relatively to the partition ids
*/
if ( id = = TEGRA_POWERGATE_VDEC )
mask = ( 1 < < TEGRA_POWERGATE_PCIE ) ;
else if ( id = = TEGRA_POWERGATE_PCIE )
mask = ( 1 < < TEGRA_POWERGATE_VDEC ) ;
else
mask = ( 1 < < id ) ;
pmc_write ( mask , REMOVE_CLAMPING ) ;
return 0 ;
}
/* Must be called with clk disabled, and returns with clk enabled */
int tegra_powergate_sequence_power_up ( int id , struct clk * clk )
{
int ret ;
tegra_periph_reset_assert ( clk ) ;
ret = tegra_powergate_power_on ( id ) ;
if ( ret )
goto err_power ;
2012-06-05 08:29:35 +04:00
ret = clk_prepare_enable ( clk ) ;
2010-05-25 04:07:46 +04:00
if ( ret )
goto err_clk ;
udelay ( 10 ) ;
ret = tegra_powergate_remove_clamping ( id ) ;
if ( ret )
goto err_clamp ;
udelay ( 10 ) ;
tegra_periph_reset_deassert ( clk ) ;
return 0 ;
err_clamp :
2012-06-05 08:29:35 +04:00
clk_disable_unprepare ( clk ) ;
2010-05-25 04:07:46 +04:00
err_clk :
tegra_powergate_power_off ( id ) ;
err_power :
return ret ;
}
2013-03-29 00:35:03 +04:00
EXPORT_SYMBOL ( tegra_powergate_sequence_power_up ) ;
2010-05-25 04:07:46 +04:00
2012-02-10 03:47:49 +04:00
int tegra_cpu_powergate_id ( int cpuid )
{
if ( cpuid > 0 & & cpuid < tegra_num_cpu_domains )
return tegra_cpu_domains [ cpuid ] ;
return - EINVAL ;
}
2012-02-10 03:47:46 +04:00
int __init tegra_powergate_init ( void )
{
switch ( tegra_chip_id ) {
case TEGRA20 :
tegra_num_powerdomains = 7 ;
break ;
2012-02-10 03:47:48 +04:00
case TEGRA30 :
tegra_num_powerdomains = 14 ;
2012-02-10 03:47:49 +04:00
tegra_num_cpu_domains = 4 ;
tegra_cpu_domains = tegra30_cpu_domains ;
2012-02-10 03:47:48 +04:00
break ;
2012-02-10 03:47:46 +04:00
default :
/* Unknown Tegra variant. Disable powergating */
tegra_num_powerdomains = 0 ;
break ;
}
return 0 ;
}
2010-05-25 04:07:46 +04:00
# ifdef CONFIG_DEBUG_FS
2012-09-06 18:55:29 +04:00
static const char * const * powergate_name ;
static const char * const powergate_name_t20 [ ] = {
2010-05-25 04:07:46 +04:00
[ TEGRA_POWERGATE_CPU ] = " cpu " ,
[ TEGRA_POWERGATE_3D ] = " 3d " ,
[ TEGRA_POWERGATE_VENC ] = " venc " ,
[ TEGRA_POWERGATE_VDEC ] = " vdec " ,
[ TEGRA_POWERGATE_PCIE ] = " pcie " ,
[ TEGRA_POWERGATE_L2 ] = " l2 " ,
[ TEGRA_POWERGATE_MPE ] = " mpe " ,
} ;
2012-09-06 18:55:29 +04:00
static const char * const powergate_name_t30 [ ] = {
[ TEGRA_POWERGATE_CPU ] = " cpu0 " ,
[ TEGRA_POWERGATE_3D ] = " 3d0 " ,
[ TEGRA_POWERGATE_VENC ] = " venc " ,
[ TEGRA_POWERGATE_VDEC ] = " vdec " ,
[ TEGRA_POWERGATE_PCIE ] = " pcie " ,
[ TEGRA_POWERGATE_L2 ] = " l2 " ,
[ TEGRA_POWERGATE_MPE ] = " mpe " ,
[ TEGRA_POWERGATE_HEG ] = " heg " ,
[ TEGRA_POWERGATE_SATA ] = " sata " ,
[ TEGRA_POWERGATE_CPU1 ] = " cpu1 " ,
[ TEGRA_POWERGATE_CPU2 ] = " cpu2 " ,
[ TEGRA_POWERGATE_CPU3 ] = " cpu3 " ,
[ TEGRA_POWERGATE_CELP ] = " celp " ,
[ TEGRA_POWERGATE_3D1 ] = " 3d1 " ,
} ;
2010-05-25 04:07:46 +04:00
static int powergate_show ( struct seq_file * s , void * data )
{
int i ;
seq_printf ( s , " powergate powered \n " ) ;
seq_printf ( s , " ------------------ \n " ) ;
2012-02-10 03:47:46 +04:00
for ( i = 0 ; i < tegra_num_powerdomains ; i + + )
2010-05-25 04:07:46 +04:00
seq_printf ( s , " %9s %7s \n " , powergate_name [ i ] ,
tegra_powergate_is_powered ( i ) ? " yes " : " no " ) ;
return 0 ;
}
static int powergate_open ( struct inode * inode , struct file * file )
{
return single_open ( file , powergate_show , inode - > i_private ) ;
}
static const struct file_operations powergate_fops = {
. open = powergate_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
2012-05-02 13:08:06 +04:00
int __init tegra_powergate_debugfs_init ( void )
2010-05-25 04:07:46 +04:00
{
struct dentry * d ;
2012-09-06 18:55:29 +04:00
switch ( tegra_chip_id ) {
case TEGRA20 :
powergate_name = powergate_name_t20 ;
break ;
case TEGRA30 :
powergate_name = powergate_name_t30 ;
break ;
}
if ( powergate_name ) {
d = debugfs_create_file ( " powergate " , S_IRUGO , NULL , NULL ,
& powergate_fops ) ;
if ( ! d )
return - ENOMEM ;
}
2010-05-25 04:07:46 +04:00
2012-09-06 18:55:28 +04:00
return 0 ;
2010-05-25 04:07:46 +04:00
}
# endif