2010-04-22 20:30:13 -07:00
/*
* 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-05 17:40:39 -07:00
# include <linux/suspend.h>
2010-04-22 20:30:13 -07:00
static struct cpufreq_frequency_table freq_table [ ] = {
2013-05-14 19:08:50 +05:30
{ . frequency = 216000 } ,
{ . frequency = 312000 } ,
{ . frequency = 456000 } ,
{ . frequency = 608000 } ,
{ . frequency = 760000 } ,
{ . frequency = 816000 } ,
{ . frequency = 912000 } ,
{ . frequency = 1000000 } ,
{ . frequency = CPUFREQ_TABLE_END } ,
2010-04-22 20:30:13 -07:00
} ;
# define NUM_CPUS 2
static struct clk * cpu_clk ;
2012-09-10 17:05:01 -06:00
static struct clk * pll_x_clk ;
static struct clk * pll_p_clk ;
2010-11-22 18:54:36 -08:00
static struct clk * emc_clk ;
2010-04-22 20:30:13 -07:00
2010-08-05 17:40:39 -07:00
static DEFINE_MUTEX ( tegra_cpu_lock ) ;
static bool is_suspended ;
2010-04-22 20:30:13 -07:00
2012-09-10 17:05:01 -06:00
static int tegra_cpu_clk_set_rate ( unsigned long rate )
{
int ret ;
/*
* Take an extra reference to the main pll so it doesn ' t turn
* off when we move the cpu off of it
*/
clk_prepare_enable ( pll_x_clk ) ;
ret = clk_set_parent ( cpu_clk , pll_p_clk ) ;
if ( ret ) {
pr_err ( " Failed to switch cpu to clock pll_p \n " ) ;
goto out ;
}
if ( rate = = clk_get_rate ( pll_p_clk ) )
goto out ;
ret = clk_set_rate ( pll_x_clk , rate ) ;
if ( ret ) {
pr_err ( " Failed to change pll_x to %lu \n " , rate ) ;
goto out ;
}
ret = clk_set_parent ( cpu_clk , pll_x_clk ) ;
if ( ret ) {
pr_err ( " Failed to switch cpu to clock pll_x \n " ) ;
goto out ;
}
out :
clk_disable_unprepare ( pll_x_clk ) ;
return ret ;
}
2013-03-24 11:56:43 +05:30
static int tegra_update_cpu_speed ( struct cpufreq_policy * policy ,
unsigned long rate )
2010-04-22 20:30:13 -07:00
{
int ret = 0 ;
2010-11-22 18:54:36 -08: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 */
2013-08-14 19:38:24 +05:30
ret = tegra_cpu_clk_set_rate ( rate * 1000 ) ;
if ( ret )
pr_err ( " cpu-tegra: Failed to set cpu frequency to %lu kHz \n " ,
rate ) ;
2010-04-22 20:30:13 -07:00
2013-06-19 11:18:20 +05:30
return ret ;
2010-04-22 20:30:13 -07:00
}
2013-10-25 19:45:48 +05:30
static int tegra_target ( struct cpufreq_policy * policy , unsigned int index )
2010-04-22 20:30:13 -07:00
{
2014-01-09 20:38:43 +05:30
int ret = - EBUSY ;
2010-08-05 17:40:39 -07:00
mutex_lock ( & tegra_cpu_lock ) ;
2014-01-09 20:38:43 +05:30
if ( ! is_suspended )
ret = tegra_update_cpu_speed ( policy ,
freq_table [ index ] . frequency ) ;
2010-04-22 20:30:13 -07:00
2010-08-05 17:40:39 -07:00
mutex_unlock ( & tegra_cpu_lock ) ;
return ret ;
2010-04-22 20:30:13 -07:00
}
2010-08-05 17:40:39 -07: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 ) {
2013-03-24 11:56:43 +05:30
struct cpufreq_policy * policy = cpufreq_cpu_get ( 0 ) ;
2010-08-05 17:40:39 -07:00
is_suspended = true ;
pr_info ( " Tegra cpufreq suspend: setting frequency to %d kHz \n " ,
freq_table [ 0 ] . frequency ) ;
2014-01-09 20:38:43 +05:30
if ( clk_get_rate ( cpu_clk ) / 1000 ! = freq_table [ 0 ] . frequency )
tegra_update_cpu_speed ( policy , freq_table [ 0 ] . frequency ) ;
2013-03-24 11:56:43 +05:30
cpufreq_cpu_put ( policy ) ;
2010-08-05 17:40:39 -07:00
} 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-22 20:30:13 -07:00
static int tegra_cpu_init ( struct cpufreq_policy * policy )
{
2013-10-03 20:42:11 +05:30
int ret ;
2010-04-22 20:30:13 -07:00
if ( policy - > cpu > = NUM_CPUS )
return - EINVAL ;
2012-06-05 09:59:35 +05:30
clk_prepare_enable ( emc_clk ) ;
clk_prepare_enable ( cpu_clk ) ;
2010-10-20 17:47:59 -07:00
2010-04-22 20:30:13 -07:00
/* FIXME: what's the actual transition time? */
2013-10-03 20:42:11 +05:30
ret = cpufreq_generic_init ( policy , freq_table , 300 * 1000 ) ;
if ( ret ) {
clk_disable_unprepare ( cpu_clk ) ;
clk_disable_unprepare ( emc_clk ) ;
return ret ;
}
2010-04-22 20:30:13 -07:00
2010-08-05 17:40:39 -07:00
if ( policy - > cpu = = 0 )
register_pm_notifier ( & tegra_cpu_pm_notifier ) ;
2014-01-09 20:38:43 +05:30
policy - > clk = cpu_clk ;
2010-04-22 20:30:13 -07:00
return 0 ;
}
static int tegra_cpu_exit ( struct cpufreq_policy * policy )
{
2013-09-16 18:56:40 +05:30
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
2013-10-03 20:42:11 +05:30
clk_disable_unprepare ( cpu_clk ) ;
2012-06-05 09:59:35 +05:30
clk_disable_unprepare ( emc_clk ) ;
2010-04-22 20:30:13 -07:00
return 0 ;
}
static struct cpufreq_driver tegra_cpufreq_driver = {
2013-12-03 11:20:45 +05:30
. flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK ,
2013-10-03 20:28:29 +05:30
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 19:45:48 +05:30
. target_index = tegra_target ,
2014-01-09 20:38:43 +05:30
. get = cpufreq_generic_get ,
2010-04-22 20:30:13 -07:00
. init = tegra_cpu_init ,
. exit = tegra_cpu_exit ,
. name = " tegra " ,
2013-10-03 20:28:29 +05:30
. attr = cpufreq_generic_attr ,
2010-04-22 20:30:13 -07:00
} ;
static int __init tegra_cpufreq_init ( void )
{
2013-08-23 09:43:58 +08:00
cpu_clk = clk_get_sys ( NULL , " cclk " ) ;
2012-12-21 00:09:55 +00:00
if ( IS_ERR ( cpu_clk ) )
return PTR_ERR ( cpu_clk ) ;
pll_x_clk = clk_get_sys ( NULL , " pll_x " ) ;
if ( IS_ERR ( pll_x_clk ) )
return PTR_ERR ( pll_x_clk ) ;
2013-08-23 09:43:58 +08:00
pll_p_clk = clk_get_sys ( NULL , " pll_p " ) ;
2012-12-21 00:09:55 +00:00
if ( IS_ERR ( pll_p_clk ) )
return PTR_ERR ( pll_p_clk ) ;
emc_clk = clk_get_sys ( " cpu " , " emc " ) ;
if ( IS_ERR ( emc_clk ) ) {
clk_put ( cpu_clk ) ;
return PTR_ERR ( emc_clk ) ;
}
2010-04-22 20:30:13 -07:00
return cpufreq_register_driver ( & tegra_cpufreq_driver ) ;
}
static void __exit tegra_cpufreq_exit ( void )
{
cpufreq_unregister_driver ( & tegra_cpufreq_driver ) ;
2012-12-21 00:09:55 +00:00
clk_put ( emc_clk ) ;
clk_put ( cpu_clk ) ;
2010-04-22 20:30:13 -07:00
}
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 ) ;