2013-02-04 17:44:48 +05:30
/*
* Copyright ( c ) 2012 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com/
*
* EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
* Based on work done by Jonghwan Choi < jhbird . choi @ samsung . com >
* Support for only EXYNOS5250 is present .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
# include <linux/module.h>
# include <linux/devfreq.h>
# include <linux/io.h>
2013-09-19 16:03:52 -05:00
# include <linux/pm_opp.h>
2013-02-04 17:44:48 +05:30
# include <linux/slab.h>
# include <linux/suspend.h>
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/platform_device.h>
# include <linux/pm_qos.h>
# include <linux/regulator/consumer.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
# include "exynos_ppmu.h"
# define MAX_SAFEVOLT 1100000 /* 1.10V */
/* Assume that the bus is saturated if the utilization is 25% */
# define INT_BUS_SATURATION_RATIO 25
enum int_level_idx {
LV_0 ,
LV_1 ,
LV_2 ,
LV_3 ,
LV_4 ,
_LV_END
} ;
enum exynos_ppmu_list {
PPMU_RIGHT ,
PPMU_END ,
} ;
struct busfreq_data_int {
struct device * dev ;
struct devfreq * devfreq ;
struct regulator * vdd_int ;
2014-03-21 18:31:45 +01:00
struct busfreq_ppmu_data ppmu_data ;
2013-02-04 17:44:48 +05:30
unsigned long curr_freq ;
bool disabled ;
struct notifier_block pm_notifier ;
struct mutex lock ;
struct pm_qos_request int_req ;
struct clk * int_clk ;
} ;
struct int_bus_opp_table {
unsigned int idx ;
unsigned long clk ;
unsigned long volt ;
} ;
static struct int_bus_opp_table exynos5_int_opp_table [ ] = {
{ LV_0 , 266000 , 1025000 } ,
{ LV_1 , 200000 , 1025000 } ,
{ LV_2 , 160000 , 1025000 } ,
{ LV_3 , 133000 , 1025000 } ,
{ LV_4 , 100000 , 1025000 } ,
{ 0 , 0 , 0 } ,
} ;
static int exynos5_int_setvolt ( struct busfreq_data_int * data ,
unsigned long volt )
{
return regulator_set_voltage ( data - > vdd_int , volt , MAX_SAFEVOLT ) ;
}
static int exynos5_busfreq_int_target ( struct device * dev , unsigned long * _freq ,
u32 flags )
{
int err = 0 ;
struct platform_device * pdev = container_of ( dev , struct platform_device ,
dev ) ;
struct busfreq_data_int * data = platform_get_drvdata ( pdev ) ;
2013-09-19 16:03:51 -05:00
struct dev_pm_opp * opp ;
2013-02-04 17:44:48 +05:30
unsigned long old_freq , freq ;
unsigned long volt ;
rcu_read_lock ( ) ;
opp = devfreq_recommended_opp ( dev , _freq , flags ) ;
if ( IS_ERR ( opp ) ) {
rcu_read_unlock ( ) ;
dev_err ( dev , " %s: Invalid OPP. \n " , __func__ ) ;
return PTR_ERR ( opp ) ;
}
2013-09-19 16:03:50 -05:00
freq = dev_pm_opp_get_freq ( opp ) ;
volt = dev_pm_opp_get_voltage ( opp ) ;
2013-02-04 17:44:48 +05:30
rcu_read_unlock ( ) ;
old_freq = data - > curr_freq ;
if ( old_freq = = freq )
return 0 ;
2013-12-09 00:22:53 +09:00
dev_dbg ( dev , " targeting %lukHz %luuV \n " , freq , volt ) ;
2013-02-04 17:44:48 +05:30
mutex_lock ( & data - > lock ) ;
if ( data - > disabled )
goto out ;
if ( freq > exynos5_int_opp_table [ 0 ] . clk )
pm_qos_update_request ( & data - > int_req , freq * 16 / 1000 ) ;
else
pm_qos_update_request ( & data - > int_req , - 1 ) ;
if ( old_freq < freq )
err = exynos5_int_setvolt ( data , volt ) ;
if ( err )
goto out ;
err = clk_set_rate ( data - > int_clk , freq * 1000 ) ;
if ( err )
goto out ;
if ( old_freq > freq )
err = exynos5_int_setvolt ( data , volt ) ;
if ( err )
goto out ;
data - > curr_freq = freq ;
out :
mutex_unlock ( & data - > lock ) ;
return err ;
}
static int exynos5_int_get_dev_status ( struct device * dev ,
struct devfreq_dev_status * stat )
{
struct platform_device * pdev = container_of ( dev , struct platform_device ,
dev ) ;
struct busfreq_data_int * data = platform_get_drvdata ( pdev ) ;
2014-03-21 18:31:45 +01:00
struct busfreq_ppmu_data * ppmu_data = & data - > ppmu_data ;
2013-02-04 17:44:48 +05:30
int busier_dmc ;
2014-03-21 18:31:46 +01:00
exynos_read_ppmu ( ppmu_data ) ;
busier_dmc = exynos_get_busier_ppmu ( ppmu_data ) ;
2013-02-04 17:44:48 +05:30
stat - > current_frequency = data - > curr_freq ;
/* Number of cycles spent on memory access */
2014-03-21 18:31:45 +01:00
stat - > busy_time = ppmu_data - > ppmu [ busier_dmc ] . count [ PPMU_PMNCNT3 ] ;
2013-02-04 17:44:48 +05:30
stat - > busy_time * = 100 / INT_BUS_SATURATION_RATIO ;
2014-03-21 18:31:45 +01:00
stat - > total_time = ppmu_data - > ppmu [ busier_dmc ] . ccnt ;
2013-02-04 17:44:48 +05:30
return 0 ;
}
static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
. initial_freq = 160000 ,
. polling_ms = 100 ,
. target = exynos5_busfreq_int_target ,
. get_dev_status = exynos5_int_get_dev_status ,
} ;
static int exynos5250_init_int_tables ( struct busfreq_data_int * data )
{
int i , err = 0 ;
for ( i = LV_0 ; i < _LV_END ; i + + ) {
2013-09-19 16:03:50 -05:00
err = dev_pm_opp_add ( data - > dev , exynos5_int_opp_table [ i ] . clk ,
2013-02-04 17:44:48 +05:30
exynos5_int_opp_table [ i ] . volt ) ;
if ( err ) {
dev_err ( data - > dev , " Cannot add opp entries. \n " ) ;
return err ;
}
}
return 0 ;
}
static int exynos5_busfreq_int_pm_notifier_event ( struct notifier_block * this ,
unsigned long event , void * ptr )
{
struct busfreq_data_int * data = container_of ( this ,
struct busfreq_data_int , pm_notifier ) ;
2013-09-19 16:03:51 -05:00
struct dev_pm_opp * opp ;
2013-02-04 17:44:48 +05:30
unsigned long maxfreq = ULONG_MAX ;
unsigned long freq ;
unsigned long volt ;
int err = 0 ;
switch ( event ) {
case PM_SUSPEND_PREPARE :
/* Set Fastest and Deactivate DVFS */
mutex_lock ( & data - > lock ) ;
data - > disabled = true ;
rcu_read_lock ( ) ;
2013-09-19 16:03:50 -05:00
opp = dev_pm_opp_find_freq_floor ( data - > dev , & maxfreq ) ;
2013-02-04 17:44:48 +05:30
if ( IS_ERR ( opp ) ) {
rcu_read_unlock ( ) ;
err = PTR_ERR ( opp ) ;
goto unlock ;
}
2013-09-19 16:03:50 -05:00
freq = dev_pm_opp_get_freq ( opp ) ;
volt = dev_pm_opp_get_voltage ( opp ) ;
2013-02-04 17:44:48 +05:30
rcu_read_unlock ( ) ;
err = exynos5_int_setvolt ( data , volt ) ;
if ( err )
goto unlock ;
err = clk_set_rate ( data - > int_clk , freq * 1000 ) ;
if ( err )
goto unlock ;
data - > curr_freq = freq ;
unlock :
mutex_unlock ( & data - > lock ) ;
if ( err )
return NOTIFY_BAD ;
return NOTIFY_OK ;
case PM_POST_RESTORE :
case PM_POST_SUSPEND :
/* Reactivate */
mutex_lock ( & data - > lock ) ;
data - > disabled = false ;
mutex_unlock ( & data - > lock ) ;
return NOTIFY_OK ;
}
return NOTIFY_DONE ;
}
static int exynos5_busfreq_int_probe ( struct platform_device * pdev )
{
struct busfreq_data_int * data ;
2014-03-21 18:31:45 +01:00
struct busfreq_ppmu_data * ppmu_data ;
2013-09-19 16:03:51 -05:00
struct dev_pm_opp * opp ;
2013-02-04 17:44:48 +05:30
struct device * dev = & pdev - > dev ;
struct device_node * np ;
unsigned long initial_freq ;
unsigned long initial_volt ;
int err = 0 ;
int i ;
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct busfreq_data_int ) ,
GFP_KERNEL ) ;
if ( data = = NULL ) {
dev_err ( dev , " Cannot allocate memory. \n " ) ;
return - ENOMEM ;
}
2014-03-21 18:31:45 +01:00
ppmu_data = & data - > ppmu_data ;
ppmu_data - > ppmu_end = PPMU_END ;
ppmu_data - > ppmu = devm_kzalloc ( dev ,
sizeof ( struct exynos_ppmu ) * PPMU_END ,
GFP_KERNEL ) ;
if ( ! ppmu_data - > ppmu ) {
dev_err ( dev , " Failed to allocate memory for exynos_ppmu \n " ) ;
return - ENOMEM ;
}
2013-02-04 17:44:48 +05:30
np = of_find_compatible_node ( NULL , NULL , " samsung,exynos5250-ppmu " ) ;
if ( np = = NULL ) {
pr_err ( " Unable to find PPMU node \n " ) ;
return - ENOENT ;
}
2014-03-21 18:31:45 +01:00
for ( i = 0 ; i < ppmu_data - > ppmu_end ; i + + ) {
2013-02-04 17:44:48 +05:30
/* map PPMU memory region */
2014-03-21 18:31:45 +01:00
ppmu_data - > ppmu [ i ] . hw_base = of_iomap ( np , i ) ;
if ( ppmu_data - > ppmu [ i ] . hw_base = = NULL ) {
2013-02-04 17:44:48 +05:30
dev_err ( & pdev - > dev , " failed to map memory region \n " ) ;
return - ENOMEM ;
}
}
data - > pm_notifier . notifier_call = exynos5_busfreq_int_pm_notifier_event ;
data - > dev = dev ;
mutex_init ( & data - > lock ) ;
err = exynos5250_init_int_tables ( data ) ;
if ( err )
2013-06-05 11:43:01 +05:30
return err ;
2013-02-04 17:44:48 +05:30
2013-06-05 11:43:01 +05:30
data - > vdd_int = devm_regulator_get ( dev , " vdd_int " ) ;
2013-02-04 17:44:48 +05:30
if ( IS_ERR ( data - > vdd_int ) ) {
dev_err ( dev , " Cannot get the regulator \" vdd_int \" \n " ) ;
2013-06-05 11:43:01 +05:30
return PTR_ERR ( data - > vdd_int ) ;
2013-02-04 17:44:48 +05:30
}
2013-06-05 11:43:01 +05:30
data - > int_clk = devm_clk_get ( dev , " int_clk " ) ;
2013-02-04 17:44:48 +05:30
if ( IS_ERR ( data - > int_clk ) ) {
dev_err ( dev , " Cannot get clock \" int_clk \" \n " ) ;
2013-06-05 11:43:01 +05:30
return PTR_ERR ( data - > int_clk ) ;
2013-02-04 17:44:48 +05:30
}
rcu_read_lock ( ) ;
2013-09-19 16:03:50 -05:00
opp = dev_pm_opp_find_freq_floor ( dev ,
2013-02-04 17:44:48 +05:30
& exynos5_devfreq_int_profile . initial_freq ) ;
if ( IS_ERR ( opp ) ) {
rcu_read_unlock ( ) ;
dev_err ( dev , " Invalid initial frequency %lu kHz. \n " ,
exynos5_devfreq_int_profile . initial_freq ) ;
2013-06-05 11:43:01 +05:30
return PTR_ERR ( opp ) ;
2013-02-04 17:44:48 +05:30
}
2013-09-19 16:03:50 -05:00
initial_freq = dev_pm_opp_get_freq ( opp ) ;
initial_volt = dev_pm_opp_get_voltage ( opp ) ;
2013-02-04 17:44:48 +05:30
rcu_read_unlock ( ) ;
data - > curr_freq = initial_freq ;
err = clk_set_rate ( data - > int_clk , initial_freq * 1000 ) ;
if ( err ) {
dev_err ( dev , " Failed to set initial frequency \n " ) ;
2013-06-05 11:43:01 +05:30
return err ;
2013-02-04 17:44:48 +05:30
}
err = exynos5_int_setvolt ( data , initial_volt ) ;
if ( err )
2013-06-05 11:43:01 +05:30
return err ;
2013-02-04 17:44:48 +05:30
platform_set_drvdata ( pdev , data ) ;
2014-03-21 18:31:45 +01:00
busfreq_mon_reset ( ppmu_data ) ;
2013-02-04 17:44:48 +05:30
2014-05-09 16:43:11 +09:00
data - > devfreq = devm_devfreq_add_device ( dev , & exynos5_devfreq_int_profile ,
2013-02-04 17:44:48 +05:30
" simple_ondemand " , NULL ) ;
2014-05-09 16:43:11 +09:00
if ( IS_ERR ( data - > devfreq ) )
return PTR_ERR ( data - > devfreq ) ;
2013-02-04 17:44:48 +05:30
2014-05-09 16:43:11 +09:00
err = devm_devfreq_register_opp_notifier ( dev , data - > devfreq ) ;
if ( err < 0 ) {
dev_err ( dev , " Failed to register opp notifier \n " ) ;
return err ;
2013-02-04 17:44:48 +05:30
}
err = register_pm_notifier ( & data - > pm_notifier ) ;
if ( err ) {
dev_err ( dev , " Failed to setup pm notifier \n " ) ;
2014-05-09 16:43:11 +09:00
return err ;
2013-02-04 17:44:48 +05:30
}
/* TODO: Add a new QOS class for int/mif bus */
pm_qos_add_request ( & data - > int_req , PM_QOS_NETWORK_THROUGHPUT , - 1 ) ;
return 0 ;
}
static int exynos5_busfreq_int_remove ( struct platform_device * pdev )
{
struct busfreq_data_int * data = platform_get_drvdata ( pdev ) ;
pm_qos_remove_request ( & data - > int_req ) ;
unregister_pm_notifier ( & data - > pm_notifier ) ;
return 0 ;
}
2014-03-20 11:59:12 +09:00
# ifdef CONFIG_PM_SLEEP
2013-02-04 17:44:48 +05:30
static int exynos5_busfreq_int_resume ( struct device * dev )
{
struct platform_device * pdev = container_of ( dev , struct platform_device ,
dev ) ;
struct busfreq_data_int * data = platform_get_drvdata ( pdev ) ;
2014-03-21 18:31:45 +01:00
struct busfreq_ppmu_data * ppmu_data = & data - > ppmu_data ;
2013-02-04 17:44:48 +05:30
2014-03-21 18:31:45 +01:00
busfreq_mon_reset ( ppmu_data ) ;
2013-02-04 17:44:48 +05:30
return 0 ;
}
static const struct dev_pm_ops exynos5_busfreq_int_pm = {
. resume = exynos5_busfreq_int_resume ,
} ;
2014-03-20 11:59:12 +09:00
# endif
static SIMPLE_DEV_PM_OPS ( exynos5_busfreq_int_pm_ops , NULL ,
exynos5_busfreq_int_resume ) ;
2013-02-04 17:44:48 +05:30
/* platform device pointer for exynos5 devfreq device. */
static struct platform_device * exynos5_devfreq_pdev ;
static struct platform_driver exynos5_busfreq_int_driver = {
. probe = exynos5_busfreq_int_probe ,
. remove = exynos5_busfreq_int_remove ,
. driver = {
. name = " exynos5-bus-int " ,
. owner = THIS_MODULE ,
2014-03-20 11:59:12 +09:00
. pm = & exynos5_busfreq_int_pm_ops ,
2013-02-04 17:44:48 +05:30
} ,
} ;
static int __init exynos5_busfreq_int_init ( void )
{
int ret ;
ret = platform_driver_register ( & exynos5_busfreq_int_driver ) ;
if ( ret < 0 )
goto out ;
exynos5_devfreq_pdev =
platform_device_register_simple ( " exynos5-bus-int " , - 1 , NULL , 0 ) ;
2013-06-05 11:42:59 +05:30
if ( IS_ERR ( exynos5_devfreq_pdev ) ) {
2013-02-04 17:44:48 +05:30
ret = PTR_ERR ( exynos5_devfreq_pdev ) ;
goto out1 ;
}
return 0 ;
out1 :
platform_driver_unregister ( & exynos5_busfreq_int_driver ) ;
out :
return ret ;
}
late_initcall ( exynos5_busfreq_int_init ) ;
static void __exit exynos5_busfreq_int_exit ( void )
{
platform_device_unregister ( exynos5_devfreq_pdev ) ;
platform_driver_unregister ( & exynos5_busfreq_int_driver ) ;
}
module_exit ( exynos5_busfreq_int_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " EXYNOS5 busfreq driver with devfreq framework " ) ;