2010-04-23 07:30:13 +04: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 .
*
*/
2018-05-18 23:06:34 +03:00
# include <linux/clk.h>
2010-04-23 07:30:13 +04:00
# include <linux/cpufreq.h>
# include <linux/err.h>
2018-05-18 23:06:34 +03:00
# include <linux/init.h>
# include <linux/module.h>
2018-05-18 23:06:42 +03:00
# include <linux/platform_device.h>
2018-05-18 23:06:34 +03:00
# include <linux/types.h>
2010-04-23 07:30:13 +04:00
static struct cpufreq_frequency_table freq_table [ ] = {
2013-05-14 17:38:50 +04:00
{ . frequency = 216000 } ,
{ . frequency = 312000 } ,
{ . frequency = 456000 } ,
{ . frequency = 608000 } ,
{ . frequency = 760000 } ,
{ . frequency = 816000 } ,
{ . frequency = 912000 } ,
{ . frequency = 1000000 } ,
{ . frequency = CPUFREQ_TABLE_END } ,
2010-04-23 07:30:13 +04:00
} ;
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq {
struct device * dev ;
struct cpufreq_driver driver ;
struct clk * cpu_clk ;
struct clk * pll_x_clk ;
struct clk * pll_p_clk ;
bool pll_x_prepared ;
} ;
2010-04-23 07:30:13 +04:00
2014-06-02 21:19:29 +04:00
static unsigned int tegra_get_intermediate ( struct cpufreq_policy * policy ,
unsigned int index )
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = cpufreq_get_driver_data ( ) ;
unsigned int ifreq = clk_get_rate ( cpufreq - > pll_p_clk ) / 1000 ;
2014-06-02 21:19:29 +04:00
/*
* Don ' t switch to intermediate freq if :
* - we are already at it , i . e . policy - > cur = = ifreq
* - index corresponds to ifreq
*/
2018-05-18 23:06:38 +03:00
if ( freq_table [ index ] . frequency = = ifreq | | policy - > cur = = ifreq )
2014-06-02 21:19:29 +04:00
return 0 ;
return ifreq ;
}
static int tegra_target_intermediate ( struct cpufreq_policy * policy ,
unsigned int index )
2012-09-11 03:05:01 +04:00
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = cpufreq_get_driver_data ( ) ;
2012-09-11 03:05:01 +04:00
int ret ;
/*
* Take an extra reference to the main pll so it doesn ' t turn
2014-06-02 21:19:29 +04:00
* off when we move the cpu off of it as enabling it again while we
2014-06-10 08:57:12 +04:00
* switch to it from tegra_target ( ) would take additional time .
*
* When target - freq is equal to intermediate freq we don ' t need to
* switch to an intermediate freq and so this routine isn ' t called .
* Also , we wouldn ' t be using pll_x anymore and must not take extra
* reference to it , as it can be disabled now to save some power .
2012-09-11 03:05:01 +04:00
*/
2018-05-18 23:06:42 +03:00
clk_prepare_enable ( cpufreq - > pll_x_clk ) ;
2012-09-11 03:05:01 +04:00
2018-05-18 23:06:42 +03:00
ret = clk_set_parent ( cpufreq - > cpu_clk , cpufreq - > pll_p_clk ) ;
2014-06-02 21:19:29 +04:00
if ( ret )
2018-05-18 23:06:42 +03:00
clk_disable_unprepare ( cpufreq - > pll_x_clk ) ;
2014-06-02 21:19:29 +04:00
else
2018-05-18 23:06:42 +03:00
cpufreq - > pll_x_prepared = true ;
2012-09-11 03:05:01 +04:00
return ret ;
}
2014-05-15 09:51:19 +04:00
static int tegra_target ( struct cpufreq_policy * policy , unsigned int index )
2010-04-23 07:30:13 +04:00
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = cpufreq_get_driver_data ( ) ;
2014-05-15 09:51:19 +04:00
unsigned long rate = freq_table [ index ] . frequency ;
2018-05-18 23:06:42 +03:00
unsigned int ifreq = clk_get_rate ( cpufreq - > pll_p_clk ) / 1000 ;
2018-05-18 23:06:39 +03:00
int ret ;
2010-04-23 07:30:13 +04:00
2014-06-02 21:19:29 +04:00
/*
* target freq = = pll_p , don ' t need to take extra reference to pll_x_clk
* as it isn ' t used anymore .
*/
if ( rate = = ifreq )
2018-05-18 23:06:42 +03:00
return clk_set_parent ( cpufreq - > cpu_clk , cpufreq - > pll_p_clk ) ;
2014-06-02 21:19:29 +04:00
2018-05-18 23:06:42 +03:00
ret = clk_set_rate ( cpufreq - > pll_x_clk , rate * 1000 ) ;
2014-06-02 21:19:29 +04:00
/* Restore to earlier frequency on error, i.e. pll_x */
2013-08-14 18:08:24 +04:00
if ( ret )
2018-05-18 23:06:42 +03:00
dev_err ( cpufreq - > dev , " Failed to change pll_x to %lu \n " , rate ) ;
2014-06-02 21:19:29 +04:00
2018-05-18 23:06:42 +03:00
ret = clk_set_parent ( cpufreq - > cpu_clk , cpufreq - > pll_x_clk ) ;
2014-06-02 21:19:29 +04:00
/* This shouldn't fail while changing or restoring */
WARN_ON ( ret ) ;
/*
* Drop count to pll_x clock only if we switched to intermediate freq
* earlier while transitioning to a target frequency .
*/
2018-05-18 23:06:42 +03:00
if ( cpufreq - > pll_x_prepared ) {
clk_disable_unprepare ( cpufreq - > pll_x_clk ) ;
cpufreq - > pll_x_prepared = false ;
2014-06-02 21:19:29 +04:00
}
2010-04-23 07:30:13 +04:00
2013-06-19 09:48:20 +04:00
return ret ;
2010-04-23 07:30:13 +04:00
}
static int tegra_cpu_init ( struct cpufreq_policy * policy )
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = cpufreq_get_driver_data ( ) ;
2013-10-03 19:12:11 +04:00
int ret ;
2018-05-18 23:06:42 +03:00
clk_prepare_enable ( cpufreq - > cpu_clk ) ;
2010-10-21 04:47:59 +04:00
2010-04-23 07:30:13 +04:00
/* FIXME: what's the actual transition time? */
2013-10-03 19:12:11 +04:00
ret = cpufreq_generic_init ( policy , freq_table , 300 * 1000 ) ;
if ( ret ) {
2018-05-18 23:06:42 +03:00
clk_disable_unprepare ( cpufreq - > cpu_clk ) ;
2013-10-03 19:12:11 +04:00
return ret ;
}
2010-04-23 07:30:13 +04:00
2018-05-18 23:06:42 +03:00
policy - > clk = cpufreq - > cpu_clk ;
2014-03-04 07:00:30 +04:00
policy - > suspend_freq = freq_table [ 0 ] . frequency ;
2010-04-23 07:30:13 +04:00
return 0 ;
}
static int tegra_cpu_exit ( struct cpufreq_policy * policy )
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = cpufreq_get_driver_data ( ) ;
clk_disable_unprepare ( cpufreq - > cpu_clk ) ;
2010-04-23 07:30:13 +04:00
return 0 ;
}
2018-05-18 23:06:42 +03:00
static int tegra20_cpufreq_probe ( struct platform_device * pdev )
2010-04-23 07:30:13 +04:00
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq ;
2018-05-18 23:06:36 +03:00
int err ;
2018-05-18 23:06:42 +03:00
cpufreq = devm_kzalloc ( & pdev - > dev , sizeof ( * cpufreq ) , GFP_KERNEL ) ;
if ( ! cpufreq )
return - ENOMEM ;
2018-05-18 23:06:40 +03:00
2018-05-18 23:06:42 +03:00
cpufreq - > cpu_clk = clk_get_sys ( NULL , " cclk " ) ;
if ( IS_ERR ( cpufreq - > cpu_clk ) )
return PTR_ERR ( cpufreq - > cpu_clk ) ;
2012-12-21 04:09:55 +04:00
2018-05-18 23:06:42 +03:00
cpufreq - > pll_x_clk = clk_get_sys ( NULL , " pll_x " ) ;
if ( IS_ERR ( cpufreq - > pll_x_clk ) ) {
err = PTR_ERR ( cpufreq - > pll_x_clk ) ;
2018-05-18 23:06:36 +03:00
goto put_cpu ;
}
2012-12-21 04:09:55 +04:00
2018-05-18 23:06:42 +03:00
cpufreq - > pll_p_clk = clk_get_sys ( NULL , " pll_p " ) ;
if ( IS_ERR ( cpufreq - > pll_p_clk ) ) {
err = PTR_ERR ( cpufreq - > pll_p_clk ) ;
2018-05-18 23:06:36 +03:00
goto put_pll_x ;
}
2018-05-18 23:06:42 +03:00
cpufreq - > dev = & pdev - > dev ;
cpufreq - > driver . get = cpufreq_generic_get ;
cpufreq - > driver . attr = cpufreq_generic_attr ;
cpufreq - > driver . init = tegra_cpu_init ;
cpufreq - > driver . exit = tegra_cpu_exit ;
cpufreq - > driver . flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK ;
cpufreq - > driver . verify = cpufreq_generic_frequency_table_verify ;
cpufreq - > driver . suspend = cpufreq_generic_suspend ;
cpufreq - > driver . driver_data = cpufreq ;
cpufreq - > driver . target_index = tegra_target ;
cpufreq - > driver . get_intermediate = tegra_get_intermediate ;
cpufreq - > driver . target_intermediate = tegra_target_intermediate ;
snprintf ( cpufreq - > driver . name , CPUFREQ_NAME_LEN , " tegra " ) ;
err = cpufreq_register_driver ( & cpufreq - > driver ) ;
2018-05-18 23:06:36 +03:00
if ( err )
goto put_pll_p ;
2018-05-18 23:06:42 +03:00
platform_set_drvdata ( pdev , cpufreq ) ;
2018-05-18 23:06:36 +03:00
return 0 ;
put_pll_p :
2018-05-18 23:06:42 +03:00
clk_put ( cpufreq - > pll_p_clk ) ;
2018-05-18 23:06:36 +03:00
put_pll_x :
2018-05-18 23:06:42 +03:00
clk_put ( cpufreq - > pll_x_clk ) ;
2018-05-18 23:06:36 +03:00
put_cpu :
2018-05-18 23:06:42 +03:00
clk_put ( cpufreq - > cpu_clk ) ;
2012-12-21 04:09:55 +04:00
2018-05-18 23:06:36 +03:00
return err ;
2010-04-23 07:30:13 +04:00
}
2018-05-18 23:06:42 +03:00
static int tegra20_cpufreq_remove ( struct platform_device * pdev )
2010-04-23 07:30:13 +04:00
{
2018-05-18 23:06:42 +03:00
struct tegra20_cpufreq * cpufreq = platform_get_drvdata ( pdev ) ;
cpufreq_unregister_driver ( & cpufreq - > driver ) ;
clk_put ( cpufreq - > pll_p_clk ) ;
clk_put ( cpufreq - > pll_x_clk ) ;
clk_put ( cpufreq - > cpu_clk ) ;
return 0 ;
2010-04-23 07:30:13 +04:00
}
2018-05-18 23:06:42 +03:00
static struct platform_driver tegra20_cpufreq_driver = {
. probe = tegra20_cpufreq_probe ,
. remove = tegra20_cpufreq_remove ,
. driver = {
. name = " tegra20-cpufreq " ,
} ,
} ;
module_platform_driver ( tegra20_cpufreq_driver ) ;
MODULE_ALIAS ( " platform:tegra20-cpufreq " ) ;
2010-04-23 07:30:13 +04:00
MODULE_AUTHOR ( " Colin Cross <ccross@android.com> " ) ;
2018-05-18 23:06:32 +03:00
MODULE_DESCRIPTION ( " NVIDIA Tegra20 cpufreq driver " ) ;
2010-04-23 07:30:13 +04:00
MODULE_LICENSE ( " GPL " ) ;