2013-06-05 13:30:48 +04:00
/*
* Copyright 2013 Freescale Semiconductor , Inc .
*
2015-03-13 07:39:01 +03:00
* CPU Frequency Scaling driver for Freescale QorIQ SoCs .
2013-06-05 13:30:48 +04:00
*
* 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 .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/clk.h>
2017-02-09 05:33:02 +03:00
# include <linux/clk-provider.h>
2013-06-05 13:30:48 +04:00
# include <linux/cpufreq.h>
2015-11-26 12:21:11 +03:00
# include <linux/cpu_cooling.h>
2013-06-05 13:30:48 +04:00
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of.h>
# include <linux/slab.h>
# include <linux/smp.h>
2015-04-11 01:17:11 +03:00
# if !defined(CONFIG_ARM)
2015-03-04 14:56:20 +03:00
# include <asm/smp.h> /* for get_hard_smp_processor_id() in UP configs */
2015-04-11 01:17:11 +03:00
# endif
2015-03-04 14:56:20 +03:00
2013-06-05 13:30:48 +04:00
/**
2015-03-13 07:39:01 +03:00
* struct cpu_data
2015-06-04 09:25:42 +03:00
* @ pclk : the parent clock of cpu
2013-06-05 13:30:48 +04:00
* @ table : frequency table
*/
struct cpu_data {
2015-06-04 09:25:42 +03:00
struct clk * * pclk ;
2013-06-05 13:30:48 +04:00
struct cpufreq_frequency_table * table ;
2015-11-26 12:21:11 +03:00
struct thermal_cooling_device * cdev ;
2013-06-05 13:30:48 +04:00
} ;
2017-02-09 05:33:02 +03:00
/*
* Don ' t use cpufreq on this SoC - - used when the SoC would have otherwise
* matched a more generic compatible .
*/
# define SOC_BLACKLIST 1
2013-06-05 13:30:48 +04:00
/**
* struct soc_data - SoC specific data
2017-02-09 05:33:02 +03:00
* @ flags : SOC_xxx
2013-06-05 13:30:48 +04:00
*/
struct soc_data {
2017-02-09 05:33:02 +03:00
u32 flags ;
2013-06-05 13:30:48 +04:00
} ;
2015-03-13 07:39:01 +03:00
static u32 get_bus_freq ( void )
{
struct device_node * soc ;
u32 sysfreq ;
soc = of_find_node_by_type ( NULL , " soc " ) ;
if ( ! soc )
return 0 ;
if ( of_property_read_u32 ( soc , " bus-frequency " , & sysfreq ) )
sysfreq = 0 ;
of_node_put ( soc ) ;
2013-06-05 13:30:48 +04:00
2015-03-13 07:39:01 +03:00
return sysfreq ;
}
2013-06-05 13:30:48 +04:00
2017-02-09 05:33:02 +03:00
static struct clk * cpu_to_clk ( int cpu )
2013-06-05 13:30:48 +04:00
{
2017-02-09 05:33:02 +03:00
struct device_node * np ;
struct clk * clk ;
2015-03-13 07:39:01 +03:00
if ( ! cpu_present ( cpu ) )
return NULL ;
np = of_get_cpu_node ( cpu , NULL ) ;
if ( ! np )
return NULL ;
2017-02-09 05:33:02 +03:00
clk = of_clk_get ( np , 0 ) ;
2015-03-13 07:39:01 +03:00
of_node_put ( np ) ;
2017-02-09 05:33:02 +03:00
return clk ;
2015-03-13 07:39:01 +03:00
}
/* traverse cpu nodes to get cpu mask of sharing clock wire */
static void set_affected_cpus ( struct cpufreq_policy * policy )
{
struct cpumask * dstp = policy - > cpus ;
2017-02-09 05:33:02 +03:00
struct clk * clk ;
2015-03-13 07:39:01 +03:00
int i ;
for_each_present_cpu ( i ) {
2017-02-09 05:33:02 +03:00
clk = cpu_to_clk ( i ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " %s: no clock for cpu %d \n " , __func__ , i ) ;
2015-03-13 07:39:01 +03:00
continue ;
2017-02-09 05:33:02 +03:00
}
2015-03-13 07:39:01 +03:00
2017-02-09 05:33:02 +03:00
if ( clk_is_match ( policy - > clk , clk ) )
2015-03-13 07:39:01 +03:00
cpumask_set_cpu ( i , dstp ) ;
}
2013-06-05 13:30:48 +04:00
}
/* reduce the duplicated frequencies in frequency table */
static void freq_table_redup ( struct cpufreq_frequency_table * freq_table ,
int count )
{
int i , j ;
for ( i = 1 ; i < count ; i + + ) {
for ( j = 0 ; j < i ; j + + ) {
if ( freq_table [ j ] . frequency = = CPUFREQ_ENTRY_INVALID | |
freq_table [ j ] . frequency ! =
freq_table [ i ] . frequency )
continue ;
freq_table [ i ] . frequency = CPUFREQ_ENTRY_INVALID ;
break ;
}
}
}
/* sort the frequencies in frequency table in descenting order */
static void freq_table_sort ( struct cpufreq_frequency_table * freq_table ,
int count )
{
int i , j , ind ;
unsigned int freq , max_freq ;
struct cpufreq_frequency_table table ;
2015-03-13 07:39:01 +03:00
2013-06-05 13:30:48 +04:00
for ( i = 0 ; i < count - 1 ; i + + ) {
max_freq = freq_table [ i ] . frequency ;
ind = i ;
for ( j = i + 1 ; j < count ; j + + ) {
freq = freq_table [ j ] . frequency ;
if ( freq = = CPUFREQ_ENTRY_INVALID | |
freq < = max_freq )
continue ;
ind = j ;
max_freq = freq ;
}
if ( ind ! = i ) {
/* exchange the frequencies */
table . driver_data = freq_table [ i ] . driver_data ;
table . frequency = freq_table [ i ] . frequency ;
freq_table [ i ] . driver_data = freq_table [ ind ] . driver_data ;
freq_table [ i ] . frequency = freq_table [ ind ] . frequency ;
freq_table [ ind ] . driver_data = table . driver_data ;
freq_table [ ind ] . frequency = table . frequency ;
}
}
}
2015-03-13 07:39:01 +03:00
static int qoriq_cpufreq_cpu_init ( struct cpufreq_policy * policy )
2013-06-05 13:30:48 +04:00
{
2017-02-09 05:33:02 +03:00
struct device_node * np ;
2013-06-05 13:30:48 +04:00
int i , count , ret ;
2017-02-09 05:33:02 +03:00
u32 freq ;
2013-06-05 13:30:48 +04:00
struct clk * clk ;
2017-02-09 05:33:02 +03:00
const struct clk_hw * hwclk ;
2013-06-05 13:30:48 +04:00
struct cpufreq_frequency_table * table ;
struct cpu_data * data ;
unsigned int cpu = policy - > cpu ;
2014-06-06 03:56:17 +04:00
u64 u64temp ;
2013-06-05 13:30:48 +04:00
np = of_get_cpu_node ( cpu , NULL ) ;
if ( ! np )
return - ENODEV ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
2015-03-13 07:39:01 +03:00
if ( ! data )
2013-06-05 13:30:48 +04:00
goto err_np ;
2014-01-09 19:08:43 +04:00
policy - > clk = of_clk_get ( np , 0 ) ;
if ( IS_ERR ( policy - > clk ) ) {
2013-06-05 13:30:48 +04:00
pr_err ( " %s: no clock information \n " , __func__ ) ;
goto err_nomem2 ;
}
2017-02-09 05:33:02 +03:00
hwclk = __clk_get_hw ( policy - > clk ) ;
count = clk_hw_get_num_parents ( hwclk ) ;
2013-06-05 13:30:48 +04:00
2015-06-04 09:25:42 +03:00
data - > pclk = kcalloc ( count , sizeof ( struct clk * ) , GFP_KERNEL ) ;
if ( ! data - > pclk ) {
pr_err ( " %s: no memory \n " , __func__ ) ;
2017-02-09 05:33:02 +03:00
goto err_nomem2 ;
2015-06-04 09:25:42 +03:00
}
2013-06-05 13:30:48 +04:00
table = kcalloc ( count + 1 , sizeof ( * table ) , GFP_KERNEL ) ;
if ( ! table ) {
pr_err ( " %s: no memory \n " , __func__ ) ;
2015-06-04 09:25:42 +03:00
goto err_pclk ;
2013-06-05 13:30:48 +04:00
}
for ( i = 0 ; i < count ; i + + ) {
2017-02-09 05:33:02 +03:00
clk = clk_hw_get_parent_by_index ( hwclk , i ) - > clk ;
2015-06-04 09:25:42 +03:00
data - > pclk [ i ] = clk ;
2013-06-05 13:30:48 +04:00
freq = clk_get_rate ( clk ) ;
2017-02-09 05:33:02 +03:00
table [ i ] . frequency = freq / 1000 ;
2013-06-05 13:30:48 +04:00
table [ i ] . driver_data = i ;
}
freq_table_redup ( table , count ) ;
freq_table_sort ( table , count ) ;
table [ i ] . frequency = CPUFREQ_TABLE_END ;
/* set the min and max frequency properly */
2013-09-16 17:26:28 +04:00
ret = cpufreq_table_validate_and_show ( policy , table ) ;
2013-06-05 13:30:48 +04:00
if ( ret ) {
pr_err ( " invalid frequency table: %d \n " , ret ) ;
goto err_nomem1 ;
}
data - > table = table ;
/* update ->cpus if we have cluster, no harm if not */
2015-03-13 07:39:01 +03:00
set_affected_cpus ( policy ) ;
policy - > driver_data = data ;
2013-06-05 13:30:48 +04:00
2014-06-06 03:56:17 +04:00
/* Minimum transition latency is 12 platform clocks */
u64temp = 12ULL * NSEC_PER_SEC ;
2015-03-13 07:39:01 +03:00
do_div ( u64temp , get_bus_freq ( ) ) ;
2014-06-06 03:56:17 +04:00
policy - > cpuinfo . transition_latency = u64temp + 1 ;
2014-04-28 20:18:18 +04:00
2013-06-05 13:30:48 +04:00
of_node_put ( np ) ;
return 0 ;
err_nomem1 :
kfree ( table ) ;
2015-06-04 09:25:42 +03:00
err_pclk :
kfree ( data - > pclk ) ;
2013-06-05 13:30:48 +04:00
err_nomem2 :
kfree ( data ) ;
err_np :
of_node_put ( np ) ;
return - ENODEV ;
}
2016-04-19 12:00:06 +03:00
static int qoriq_cpufreq_cpu_exit ( struct cpufreq_policy * policy )
2013-06-05 13:30:48 +04:00
{
2015-03-13 07:39:01 +03:00
struct cpu_data * data = policy - > driver_data ;
2013-06-05 13:30:48 +04:00
2016-04-19 12:00:07 +03:00
cpufreq_cooling_unregister ( data - > cdev ) ;
2015-06-04 09:25:42 +03:00
kfree ( data - > pclk ) ;
2013-06-05 13:30:48 +04:00
kfree ( data - > table ) ;
kfree ( data ) ;
2015-03-13 07:39:01 +03:00
policy - > driver_data = NULL ;
2013-06-05 13:30:48 +04:00
return 0 ;
}
2015-03-13 07:39:01 +03:00
static int qoriq_cpufreq_target ( struct cpufreq_policy * policy ,
2013-10-25 18:15:48 +04:00
unsigned int index )
2013-06-05 13:30:48 +04:00
{
struct clk * parent ;
2015-03-13 07:39:01 +03:00
struct cpu_data * data = policy - > driver_data ;
2013-06-05 13:30:48 +04:00
2015-06-04 09:25:42 +03:00
parent = data - > pclk [ data - > table [ index ] . driver_data ] ;
2014-01-09 19:08:43 +04:00
return clk_set_parent ( policy - > clk , parent ) ;
2013-06-05 13:30:48 +04:00
}
2015-11-26 12:21:11 +03:00
static void qoriq_cpufreq_ready ( struct cpufreq_policy * policy )
{
struct cpu_data * cpud = policy - > driver_data ;
struct device_node * np = of_get_cpu_node ( policy - > cpu , NULL ) ;
if ( of_find_property ( np , " #cooling-cells " , NULL ) ) {
cpud - > cdev = of_cpufreq_cooling_register ( np ,
policy - > related_cpus ) ;
2016-04-18 10:59:32 +03:00
if ( IS_ERR ( cpud - > cdev ) & & PTR_ERR ( cpud - > cdev ) ! = - ENOSYS ) {
pr_err ( " cpu%d is not running as cooling device: %ld \n " ,
2015-11-26 12:21:11 +03:00
policy - > cpu , PTR_ERR ( cpud - > cdev ) ) ;
cpud - > cdev = NULL ;
}
}
of_node_put ( np ) ;
}
2015-03-13 07:39:01 +03:00
static struct cpufreq_driver qoriq_cpufreq_driver = {
. name = " qoriq_cpufreq " ,
2013-06-05 13:30:48 +04:00
. flags = CPUFREQ_CONST_LOOPS ,
2015-03-13 07:39:01 +03:00
. init = qoriq_cpufreq_cpu_init ,
2016-04-19 12:00:06 +03:00
. exit = qoriq_cpufreq_cpu_exit ,
2013-10-03 18:58:18 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2015-03-13 07:39:01 +03:00
. target_index = qoriq_cpufreq_target ,
2014-01-09 19:08:43 +04:00
. get = cpufreq_generic_get ,
2015-11-26 12:21:11 +03:00
. ready = qoriq_cpufreq_ready ,
2013-10-03 18:58:18 +04:00
. attr = cpufreq_generic_attr ,
2013-06-05 13:30:48 +04:00
} ;
2017-02-09 05:33:02 +03:00
static const struct soc_data blacklist = {
. flags = SOC_BLACKLIST ,
} ;
2015-03-13 07:39:01 +03:00
static const struct of_device_id node_matches [ ] __initconst = {
2017-02-09 05:33:02 +03:00
/* e6500 cannot use cpufreq due to erratum A-008083 */
{ . compatible = " fsl,b4420-clockgen " , & blacklist } ,
{ . compatible = " fsl,b4860-clockgen " , & blacklist } ,
{ . compatible = " fsl,t2080-clockgen " , & blacklist } ,
{ . compatible = " fsl,t4240-clockgen " , & blacklist } ,
{ . compatible = " fsl,ls1012a-clockgen " , } ,
{ . compatible = " fsl,ls1021a-clockgen " , } ,
{ . compatible = " fsl,ls1043a-clockgen " , } ,
{ . compatible = " fsl,ls1046a-clockgen " , } ,
{ . compatible = " fsl,ls1088a-clockgen " , } ,
{ . compatible = " fsl,ls2080a-clockgen " , } ,
{ . compatible = " fsl,p4080-clockgen " , } ,
{ . compatible = " fsl,qoriq-clockgen-1.0 " , } ,
2013-06-05 13:30:48 +04:00
{ . compatible = " fsl,qoriq-clockgen-2.0 " , } ,
{ }
} ;
2015-03-13 07:39:01 +03:00
static int __init qoriq_cpufreq_init ( void )
2013-06-05 13:30:48 +04:00
{
int ret ;
struct device_node * np ;
const struct of_device_id * match ;
const struct soc_data * data ;
np = of_find_matching_node ( NULL , node_matches ) ;
if ( ! np )
return - ENODEV ;
match = of_match_node ( node_matches , np ) ;
data = match - > data ;
of_node_put ( np ) ;
2017-02-09 05:33:02 +03:00
if ( data & & data - > flags & SOC_BLACKLIST )
return - ENODEV ;
2015-03-13 07:39:01 +03:00
ret = cpufreq_register_driver ( & qoriq_cpufreq_driver ) ;
2013-06-05 13:30:48 +04:00
if ( ! ret )
2015-03-13 07:39:01 +03:00
pr_info ( " Freescale QorIQ CPU frequency scaling driver \n " ) ;
2013-06-05 13:30:48 +04:00
return ret ;
}
2015-03-13 07:39:01 +03:00
module_init ( qoriq_cpufreq_init ) ;
2013-06-05 13:30:48 +04:00
2015-03-13 07:39:01 +03:00
static void __exit qoriq_cpufreq_exit ( void )
2013-06-05 13:30:48 +04:00
{
2015-03-13 07:39:01 +03:00
cpufreq_unregister_driver ( & qoriq_cpufreq_driver ) ;
2013-06-05 13:30:48 +04:00
}
2015-03-13 07:39:01 +03:00
module_exit ( qoriq_cpufreq_exit ) ;
2013-06-05 13:30:48 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Tang Yuantian <Yuantian.Tang@freescale.com> " ) ;
2015-03-13 07:39:01 +03:00
MODULE_DESCRIPTION ( " cpufreq driver for Freescale QorIQ series SoCs " ) ;