2017-02-03 20:29:28 +03:00
/*
* TI CPUFreq / OPP hw - supported driver
*
* Copyright ( C ) 2016 - 2017 Texas Instruments , Inc .
* Dave Gerlach < d - gerlach @ ti . com >
*
* 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 .
*
* 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/cpu.h>
# include <linux/io.h>
# include <linux/mfd/syscon.h>
2017-02-14 03:45:02 +03:00
# include <linux/init.h>
2017-02-03 20:29:28 +03:00
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/pm_opp.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# define REVISION_MASK 0xF
# define REVISION_SHIFT 28
# define AM33XX_800M_ARM_MPU_MAX_FREQ 0x1E2F
# define AM43XX_600M_ARM_MPU_MAX_FREQ 0xFFA
# define DRA7_EFUSE_HAS_OD_MPU_OPP 11
# define DRA7_EFUSE_HAS_HIGH_MPU_OPP 15
# define DRA7_EFUSE_HAS_ALL_MPU_OPP 23
# define DRA7_EFUSE_NOM_MPU_OPP BIT(0)
# define DRA7_EFUSE_OD_MPU_OPP BIT(1)
# define DRA7_EFUSE_HIGH_MPU_OPP BIT(2)
# define VERSION_COUNT 2
struct ti_cpufreq_data ;
struct ti_cpufreq_soc_data {
unsigned long ( * efuse_xlate ) ( struct ti_cpufreq_data * opp_data ,
unsigned long efuse ) ;
unsigned long efuse_fallback ;
unsigned long efuse_offset ;
unsigned long efuse_mask ;
unsigned long efuse_shift ;
unsigned long rev_offset ;
} ;
struct ti_cpufreq_data {
struct device * cpu_dev ;
struct device_node * opp_node ;
struct regmap * syscon ;
const struct ti_cpufreq_soc_data * soc_data ;
} ;
static unsigned long amx3_efuse_xlate ( struct ti_cpufreq_data * opp_data ,
unsigned long efuse )
{
if ( ! efuse )
efuse = opp_data - > soc_data - > efuse_fallback ;
/* AM335x and AM437x use "OPP disable" bits, so invert */
return ~ efuse ;
}
static unsigned long dra7_efuse_xlate ( struct ti_cpufreq_data * opp_data ,
unsigned long efuse )
{
unsigned long calculated_efuse = DRA7_EFUSE_NOM_MPU_OPP ;
/*
* The efuse on dra7 and am57 parts contains a specific
* value indicating the highest available OPP .
*/
switch ( efuse ) {
case DRA7_EFUSE_HAS_ALL_MPU_OPP :
case DRA7_EFUSE_HAS_HIGH_MPU_OPP :
calculated_efuse | = DRA7_EFUSE_HIGH_MPU_OPP ;
case DRA7_EFUSE_HAS_OD_MPU_OPP :
calculated_efuse | = DRA7_EFUSE_OD_MPU_OPP ;
}
return calculated_efuse ;
}
static struct ti_cpufreq_soc_data am3x_soc_data = {
. efuse_xlate = amx3_efuse_xlate ,
. efuse_fallback = AM33XX_800M_ARM_MPU_MAX_FREQ ,
. efuse_offset = 0x07fc ,
. efuse_mask = 0x1fff ,
. rev_offset = 0x600 ,
} ;
static struct ti_cpufreq_soc_data am4x_soc_data = {
. efuse_xlate = amx3_efuse_xlate ,
. efuse_fallback = AM43XX_600M_ARM_MPU_MAX_FREQ ,
. efuse_offset = 0x0610 ,
. efuse_mask = 0x3f ,
. rev_offset = 0x600 ,
} ;
static struct ti_cpufreq_soc_data dra7_soc_data = {
. efuse_xlate = dra7_efuse_xlate ,
. efuse_offset = 0x020c ,
. efuse_mask = 0xf80000 ,
. efuse_shift = 19 ,
. rev_offset = 0x204 ,
} ;
/**
* ti_cpufreq_get_efuse ( ) - Parse and return efuse value present on SoC
* @ opp_data : pointer to ti_cpufreq_data context
* @ efuse_value : Set to the value parsed from efuse
*
* Returns error code if efuse not read properly .
*/
static int ti_cpufreq_get_efuse ( struct ti_cpufreq_data * opp_data ,
u32 * efuse_value )
{
struct device * dev = opp_data - > cpu_dev ;
u32 efuse ;
int ret ;
ret = regmap_read ( opp_data - > syscon , opp_data - > soc_data - > efuse_offset ,
& efuse ) ;
if ( ret ) {
dev_err ( dev ,
" Failed to read the efuse value from syscon: %d \n " ,
ret ) ;
return ret ;
}
efuse = ( efuse & opp_data - > soc_data - > efuse_mask ) ;
efuse > > = opp_data - > soc_data - > efuse_shift ;
* efuse_value = opp_data - > soc_data - > efuse_xlate ( opp_data , efuse ) ;
return 0 ;
}
/**
* ti_cpufreq_get_rev ( ) - Parse and return rev value present on SoC
* @ opp_data : pointer to ti_cpufreq_data context
* @ revision_value : Set to the value parsed from revision register
*
* Returns error code if revision not read properly .
*/
static int ti_cpufreq_get_rev ( struct ti_cpufreq_data * opp_data ,
u32 * revision_value )
{
struct device * dev = opp_data - > cpu_dev ;
u32 revision ;
int ret ;
ret = regmap_read ( opp_data - > syscon , opp_data - > soc_data - > rev_offset ,
& revision ) ;
if ( ret ) {
dev_err ( dev ,
" Failed to read the revision number from syscon: %d \n " ,
ret ) ;
return ret ;
}
* revision_value = BIT ( ( revision > > REVISION_SHIFT ) & REVISION_MASK ) ;
return 0 ;
}
static int ti_cpufreq_setup_syscon_register ( struct ti_cpufreq_data * opp_data )
{
struct device * dev = opp_data - > cpu_dev ;
struct device_node * np = opp_data - > opp_node ;
opp_data - > syscon = syscon_regmap_lookup_by_phandle ( np ,
" syscon " ) ;
if ( IS_ERR ( opp_data - > syscon ) ) {
dev_err ( dev ,
" \" syscon \" is missing, cannot use OPPv2 table. \n " ) ;
return PTR_ERR ( opp_data - > syscon ) ;
}
return 0 ;
}
static const struct of_device_id ti_cpufreq_of_match [ ] = {
{ . compatible = " ti,am33xx " , . data = & am3x_soc_data , } ,
2017-09-19 23:06:13 +03:00
{ . compatible = " ti,am43 " , . data = & am4x_soc_data , } ,
2017-02-03 20:29:28 +03:00
{ . compatible = " ti,dra7 " , . data = & dra7_soc_data } ,
{ } ,
} ;
static int ti_cpufreq_init ( void )
{
u32 version [ VERSION_COUNT ] ;
struct device_node * np ;
const struct of_device_id * match ;
struct ti_cpufreq_data * opp_data ;
int ret ;
np = of_find_node_by_path ( " / " ) ;
match = of_match_node ( ti_cpufreq_of_match , np ) ;
if ( ! match )
return - ENODEV ;
opp_data = kzalloc ( sizeof ( * opp_data ) , GFP_KERNEL ) ;
if ( ! opp_data )
return - ENOMEM ;
opp_data - > soc_data = match - > data ;
opp_data - > cpu_dev = get_cpu_device ( 0 ) ;
if ( ! opp_data - > cpu_dev ) {
pr_err ( " %s: Failed to get device for CPU0 \n " , __func__ ) ;
return - ENODEV ;
}
opp_data - > opp_node = dev_pm_opp_of_get_opp_desc_node ( opp_data - > cpu_dev ) ;
if ( ! opp_data - > opp_node ) {
dev_info ( opp_data - > cpu_dev ,
" OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables. \n " ) ;
goto register_cpufreq_dt ;
}
ret = ti_cpufreq_setup_syscon_register ( opp_data ) ;
if ( ret )
goto fail_put_node ;
/*
* OPPs determine whether or not they are supported based on
* two metrics :
* 0 - SoC Revision
* 1 - eFuse value
*/
ret = ti_cpufreq_get_rev ( opp_data , & version [ 0 ] ) ;
if ( ret )
goto fail_put_node ;
ret = ti_cpufreq_get_efuse ( opp_data , & version [ 1 ] ) ;
if ( ret )
goto fail_put_node ;
ret = PTR_ERR_OR_ZERO ( dev_pm_opp_set_supported_hw ( opp_data - > cpu_dev ,
version , VERSION_COUNT ) ) ;
if ( ret ) {
dev_err ( opp_data - > cpu_dev ,
" Failed to set supported hardware \n " ) ;
goto fail_put_node ;
}
2017-08-19 23:22:46 +03:00
of_node_put ( opp_data - > opp_node ) ;
2017-02-03 20:29:28 +03:00
register_cpufreq_dt :
platform_device_register_simple ( " cpufreq-dt " , - 1 , NULL , 0 ) ;
return 0 ;
fail_put_node :
of_node_put ( opp_data - > opp_node ) ;
return ret ;
}
2017-02-14 03:45:02 +03:00
device_initcall ( ti_cpufreq_init ) ;