2010-04-23 07:30:13 +04:00
/*
* arch / arm / mach - tegra / cpu - tegra . c
*
* Copyright ( C ) 2010 Google , Inc .
*
* Author :
* Colin Cross < ccross @ google . com >
* Based on arch / arm / plat - omap / cpu - omap . c , ( C ) 2005 Nokia Corporation
*
* 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/module.h>
# include <linux/types.h>
# include <linux/sched.h>
# include <linux/cpufreq.h>
# include <linux/delay.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/clk.h>
# include <linux/io.h>
2010-08-06 04:40:39 +04:00
# include <linux/suspend.h>
2010-04-23 07:30:13 +04:00
# include <asm/system.h>
# include <mach/hardware.h>
# include <mach/clk.h>
/* Frequency table index must be sequential starting at 0 */
static struct cpufreq_frequency_table freq_table [ ] = {
2010-08-06 04:40:39 +04:00
{ 0 , 216000 } ,
{ 1 , 312000 } ,
{ 2 , 456000 } ,
{ 3 , 608000 } ,
{ 4 , 760000 } ,
{ 5 , 816000 } ,
{ 6 , 912000 } ,
{ 7 , 1000000 } ,
{ 8 , CPUFREQ_TABLE_END } ,
2010-04-23 07:30:13 +04:00
} ;
# define NUM_CPUS 2
static struct clk * cpu_clk ;
2010-11-23 05:54:36 +03:00
static struct clk * emc_clk ;
2010-04-23 07:30:13 +04:00
static unsigned long target_cpu_speed [ NUM_CPUS ] ;
2010-08-06 04:40:39 +04:00
static DEFINE_MUTEX ( tegra_cpu_lock ) ;
static bool is_suspended ;
2010-04-23 07:30:13 +04:00
int tegra_verify_speed ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , freq_table ) ;
}
unsigned int tegra_getspeed ( unsigned int cpu )
{
unsigned long rate ;
if ( cpu > = NUM_CPUS )
return 0 ;
rate = clk_get_rate ( cpu_clk ) / 1000 ;
return rate ;
}
2010-08-06 04:40:39 +04:00
static int tegra_update_cpu_speed ( unsigned long rate )
2010-04-23 07:30:13 +04:00
{
int ret = 0 ;
struct cpufreq_freqs freqs ;
freqs . old = tegra_getspeed ( 0 ) ;
freqs . new = rate ;
if ( freqs . old = = freqs . new )
return ret ;
2010-11-23 05:54:36 +03:00
/*
* Vote on memory bus frequency based on cpu frequency
* This sets the minimum frequency , display or avp may request higher
*/
if ( rate > = 816000 )
clk_set_rate ( emc_clk , 600000000 ) ; /* cpu 816 MHz, emc max */
else if ( rate > = 456000 )
clk_set_rate ( emc_clk , 300000000 ) ; /* cpu 456 MHz, emc 150Mhz */
else
clk_set_rate ( emc_clk , 100000000 ) ; /* emc 50Mhz */
2010-04-23 07:30:13 +04:00
for_each_online_cpu ( freqs . cpu )
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
# ifdef CONFIG_CPU_FREQ_DEBUG
printk ( KERN_DEBUG " cpufreq-tegra: transition: %u --> %u \n " ,
freqs . old , freqs . new ) ;
# endif
2011-02-13 02:52:04 +03:00
ret = clk_set_rate ( cpu_clk , freqs . new * 1000 ) ;
2010-04-23 07:30:13 +04:00
if ( ret ) {
pr_err ( " cpu-tegra: Failed to set cpu frequency to %d kHz \n " ,
freqs . new ) ;
return ret ;
}
for_each_online_cpu ( freqs . cpu )
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
return 0 ;
}
2010-08-06 04:40:39 +04:00
static unsigned long tegra_cpu_highest_speed ( void )
{
unsigned long rate = 0 ;
int i ;
for_each_online_cpu ( i )
rate = max ( rate , target_cpu_speed [ i ] ) ;
return rate ;
}
2010-04-23 07:30:13 +04:00
static int tegra_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
int idx ;
unsigned int freq ;
2010-08-06 04:40:39 +04:00
int ret = 0 ;
mutex_lock ( & tegra_cpu_lock ) ;
if ( is_suspended ) {
ret = - EBUSY ;
goto out ;
}
2010-04-23 07:30:13 +04:00
cpufreq_frequency_table_target ( policy , freq_table , target_freq ,
relation , & idx ) ;
freq = freq_table [ idx ] . frequency ;
target_cpu_speed [ policy - > cpu ] = freq ;
2010-08-06 04:40:39 +04:00
ret = tegra_update_cpu_speed ( tegra_cpu_highest_speed ( ) ) ;
out :
mutex_unlock ( & tegra_cpu_lock ) ;
return ret ;
2010-04-23 07:30:13 +04:00
}
2010-08-06 04:40:39 +04:00
static int tegra_pm_notify ( struct notifier_block * nb , unsigned long event ,
void * dummy )
{
mutex_lock ( & tegra_cpu_lock ) ;
if ( event = = PM_SUSPEND_PREPARE ) {
is_suspended = true ;
pr_info ( " Tegra cpufreq suspend: setting frequency to %d kHz \n " ,
freq_table [ 0 ] . frequency ) ;
tegra_update_cpu_speed ( freq_table [ 0 ] . frequency ) ;
} else if ( event = = PM_POST_SUSPEND ) {
is_suspended = false ;
}
mutex_unlock ( & tegra_cpu_lock ) ;
return NOTIFY_OK ;
}
static struct notifier_block tegra_cpu_pm_notifier = {
. notifier_call = tegra_pm_notify ,
} ;
2010-04-23 07:30:13 +04:00
static int tegra_cpu_init ( struct cpufreq_policy * policy )
{
if ( policy - > cpu > = NUM_CPUS )
return - EINVAL ;
cpu_clk = clk_get_sys ( NULL , " cpu " ) ;
if ( IS_ERR ( cpu_clk ) )
return PTR_ERR ( cpu_clk ) ;
2010-11-23 05:54:36 +03:00
emc_clk = clk_get_sys ( " cpu " , " emc " ) ;
if ( IS_ERR ( emc_clk ) ) {
clk_put ( cpu_clk ) ;
return PTR_ERR ( emc_clk ) ;
}
clk_enable ( emc_clk ) ;
2010-10-21 04:47:59 +04:00
clk_enable ( cpu_clk ) ;
2010-04-23 07:30:13 +04:00
cpufreq_frequency_table_cpuinfo ( policy , freq_table ) ;
cpufreq_frequency_table_get_attr ( freq_table , policy - > cpu ) ;
policy - > cur = tegra_getspeed ( policy - > cpu ) ;
target_cpu_speed [ policy - > cpu ] = policy - > cur ;
/* FIXME: what's the actual transition time? */
policy - > cpuinfo . transition_latency = 300 * 1000 ;
policy - > shared_type = CPUFREQ_SHARED_TYPE_ALL ;
cpumask_copy ( policy - > related_cpus , cpu_possible_mask ) ;
2010-08-06 04:40:39 +04:00
if ( policy - > cpu = = 0 )
register_pm_notifier ( & tegra_cpu_pm_notifier ) ;
2010-04-23 07:30:13 +04:00
return 0 ;
}
static int tegra_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_cpuinfo ( policy , freq_table ) ;
2010-11-23 05:54:36 +03:00
clk_disable ( emc_clk ) ;
clk_put ( emc_clk ) ;
2010-04-23 07:30:13 +04:00
clk_put ( cpu_clk ) ;
return 0 ;
}
static struct freq_attr * tegra_cpufreq_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
static struct cpufreq_driver tegra_cpufreq_driver = {
. verify = tegra_verify_speed ,
. target = tegra_target ,
. get = tegra_getspeed ,
. init = tegra_cpu_init ,
. exit = tegra_cpu_exit ,
. name = " tegra " ,
. attr = tegra_cpufreq_attr ,
} ;
static int __init tegra_cpufreq_init ( void )
{
return cpufreq_register_driver ( & tegra_cpufreq_driver ) ;
}
static void __exit tegra_cpufreq_exit ( void )
{
cpufreq_unregister_driver ( & tegra_cpufreq_driver ) ;
}
MODULE_AUTHOR ( " Colin Cross <ccross@android.com> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for Nvidia Tegra2 " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( tegra_cpufreq_init ) ;
module_exit ( tegra_cpufreq_exit ) ;