2019-07-25 18:18:32 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Voltage regulators coupler for NVIDIA Tegra30
* Copyright ( C ) 2019 GRATE - DRIVER project
*
* Voltage constraints borrowed from downstream kernel sources
* Copyright ( C ) 2010 - 2011 NVIDIA Corporation
*/
# define pr_fmt(fmt) "tegra voltage-coupler: " fmt
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/of.h>
# include <linux/regulator/coupler.h>
# include <linux/regulator/driver.h>
# include <linux/regulator/machine.h>
# include <soc/tegra/fuse.h>
struct tegra_regulator_coupler {
struct regulator_coupler coupler ;
struct regulator_dev * core_rdev ;
struct regulator_dev * cpu_rdev ;
int core_min_uV ;
} ;
static inline struct tegra_regulator_coupler *
to_tegra_coupler ( struct regulator_coupler * coupler )
{
return container_of ( coupler , struct tegra_regulator_coupler , coupler ) ;
}
static int tegra30_core_limit ( struct tegra_regulator_coupler * tegra ,
struct regulator_dev * core_rdev )
{
int core_min_uV = 0 ;
int core_max_uV ;
int core_cur_uV ;
int err ;
if ( tegra - > core_min_uV > 0 )
return tegra - > core_min_uV ;
core_cur_uV = regulator_get_voltage_rdev ( core_rdev ) ;
if ( core_cur_uV < 0 )
return core_cur_uV ;
core_max_uV = max ( core_cur_uV , 1200000 ) ;
err = regulator_check_voltage ( core_rdev , & core_min_uV , & core_max_uV ) ;
if ( err )
return err ;
/*
* Limit minimum CORE voltage to a value left from bootloader or ,
* if it ' s unreasonably low value , to the most common 1.2 v or to
* whatever maximum value defined via board ' s device - tree .
*/
tegra - > core_min_uV = core_max_uV ;
pr_info ( " core minimum voltage limited to %duV \n " , tegra - > core_min_uV ) ;
return tegra - > core_min_uV ;
}
static int tegra30_core_cpu_limit ( int cpu_uV )
{
if ( cpu_uV < 800000 )
return 950000 ;
if ( cpu_uV < 900000 )
return 1000000 ;
if ( cpu_uV < 1000000 )
return 1100000 ;
if ( cpu_uV < 1100000 )
return 1200000 ;
if ( cpu_uV < 1250000 ) {
switch ( tegra_sku_info . cpu_speedo_id ) {
case 0 . . . 1 :
case 4 :
case 7 . . . 8 :
return 1200000 ;
default :
return 1300000 ;
}
}
return - EINVAL ;
}
static int tegra30_voltage_update ( struct tegra_regulator_coupler * tegra ,
struct regulator_dev * cpu_rdev ,
struct regulator_dev * core_rdev )
{
int core_min_uV , core_max_uV = INT_MAX ;
int cpu_min_uV , cpu_max_uV = INT_MAX ;
int cpu_min_uV_consumers = 0 ;
int core_min_limited_uV ;
int core_target_uV ;
int cpu_target_uV ;
int core_max_step ;
int cpu_max_step ;
int max_spread ;
int core_uV ;
int cpu_uV ;
int err ;
/*
* CPU voltage should not got lower than 300 mV from the CORE .
* CPU voltage should stay below the CORE by 100 mV + , depending
* by the CORE voltage . This applies to all Tegra30 SoC ' s .
*/
max_spread = cpu_rdev - > constraints - > max_spread [ 0 ] ;
cpu_max_step = cpu_rdev - > constraints - > max_uV_step ;
core_max_step = core_rdev - > constraints - > max_uV_step ;
if ( ! max_spread ) {
pr_err_once ( " cpu-core max-spread is undefined in device-tree \n " ) ;
max_spread = 300000 ;
}
if ( ! cpu_max_step ) {
pr_err_once ( " cpu max-step is undefined in device-tree \n " ) ;
cpu_max_step = 150000 ;
}
if ( ! core_max_step ) {
pr_err_once ( " core max-step is undefined in device-tree \n " ) ;
core_max_step = 150000 ;
}
/*
* The CORE voltage scaling is currently not hooked up in drivers ,
* hence we will limit the minimum CORE voltage to a reasonable value .
* This should be good enough for the time being .
*/
core_min_uV = tegra30_core_limit ( tegra , core_rdev ) ;
if ( core_min_uV < 0 )
return core_min_uV ;
err = regulator_check_consumers ( core_rdev , & core_min_uV , & core_max_uV ,
PM_SUSPEND_ON ) ;
if ( err )
return err ;
core_uV = regulator_get_voltage_rdev ( core_rdev ) ;
if ( core_uV < 0 )
return core_uV ;
cpu_min_uV = core_min_uV - max_spread ;
err = regulator_check_consumers ( cpu_rdev , & cpu_min_uV , & cpu_max_uV ,
PM_SUSPEND_ON ) ;
if ( err )
return err ;
err = regulator_check_consumers ( cpu_rdev , & cpu_min_uV_consumers ,
& cpu_max_uV , PM_SUSPEND_ON ) ;
if ( err )
return err ;
err = regulator_check_voltage ( cpu_rdev , & cpu_min_uV , & cpu_max_uV ) ;
if ( err )
return err ;
cpu_uV = regulator_get_voltage_rdev ( cpu_rdev ) ;
if ( cpu_uV < 0 )
return cpu_uV ;
/*
* CPU ' s regulator may not have any consumers , hence the voltage
* must not be changed in that case because CPU simply won ' t
* survive the voltage drop if it ' s running on a higher frequency .
*/
if ( ! cpu_min_uV_consumers )
cpu_min_uV = cpu_uV ;
/*
* Bootloader shall set up voltages correctly , but if it
* happens that there is a violation , then try to fix it
* at first .
*/
core_min_limited_uV = tegra30_core_cpu_limit ( cpu_uV ) ;
if ( core_min_limited_uV < 0 )
return core_min_limited_uV ;
core_min_uV = max ( core_min_uV , tegra30_core_cpu_limit ( cpu_min_uV ) ) ;
err = regulator_check_voltage ( core_rdev , & core_min_uV , & core_max_uV ) ;
if ( err )
return err ;
if ( core_min_limited_uV > core_uV ) {
pr_err ( " core voltage constraint violated: %d %d %d \n " ,
core_uV , core_min_limited_uV , cpu_uV ) ;
goto update_core ;
}
while ( cpu_uV ! = cpu_min_uV | | core_uV ! = core_min_uV ) {
if ( cpu_uV < cpu_min_uV ) {
cpu_target_uV = min ( cpu_uV + cpu_max_step , cpu_min_uV ) ;
} else {
cpu_target_uV = max ( cpu_uV - cpu_max_step , cpu_min_uV ) ;
cpu_target_uV = max ( core_uV - max_spread , cpu_target_uV ) ;
}
2019-12-18 21:55:35 +03:00
if ( cpu_uV = = cpu_target_uV )
goto update_core ;
2019-07-25 18:18:32 +03:00
err = regulator_set_voltage_rdev ( cpu_rdev ,
cpu_target_uV ,
cpu_max_uV ,
PM_SUSPEND_ON ) ;
if ( err )
return err ;
cpu_uV = cpu_target_uV ;
update_core :
core_min_limited_uV = tegra30_core_cpu_limit ( cpu_uV ) ;
if ( core_min_limited_uV < 0 )
return core_min_limited_uV ;
core_target_uV = max ( core_min_limited_uV , core_min_uV ) ;
if ( core_uV < core_target_uV ) {
core_target_uV = min ( core_target_uV , core_uV + core_max_step ) ;
core_target_uV = min ( core_target_uV , cpu_uV + max_spread ) ;
} else {
core_target_uV = max ( core_target_uV , core_uV - core_max_step ) ;
}
2019-12-18 21:55:35 +03:00
if ( core_uV = = core_target_uV )
continue ;
2019-07-25 18:18:32 +03:00
err = regulator_set_voltage_rdev ( core_rdev ,
core_target_uV ,
core_max_uV ,
PM_SUSPEND_ON ) ;
if ( err )
return err ;
core_uV = core_target_uV ;
}
return 0 ;
}
static int tegra30_regulator_balance_voltage ( struct regulator_coupler * coupler ,
struct regulator_dev * rdev ,
suspend_state_t state )
{
struct tegra_regulator_coupler * tegra = to_tegra_coupler ( coupler ) ;
struct regulator_dev * core_rdev = tegra - > core_rdev ;
struct regulator_dev * cpu_rdev = tegra - > cpu_rdev ;
if ( ( core_rdev ! = rdev & & cpu_rdev ! = rdev ) | | state ! = PM_SUSPEND_ON ) {
pr_err ( " regulators are not coupled properly \n " ) ;
return - EINVAL ;
}
return tegra30_voltage_update ( tegra , cpu_rdev , core_rdev ) ;
}
static int tegra30_regulator_attach ( struct regulator_coupler * coupler ,
struct regulator_dev * rdev )
{
struct tegra_regulator_coupler * tegra = to_tegra_coupler ( coupler ) ;
struct device_node * np = rdev - > dev . of_node ;
if ( of_property_read_bool ( np , " nvidia,tegra-core-regulator " ) & &
! tegra - > core_rdev ) {
tegra - > core_rdev = rdev ;
return 0 ;
}
if ( of_property_read_bool ( np , " nvidia,tegra-cpu-regulator " ) & &
! tegra - > cpu_rdev ) {
tegra - > cpu_rdev = rdev ;
return 0 ;
}
return - EINVAL ;
}
static int tegra30_regulator_detach ( struct regulator_coupler * coupler ,
struct regulator_dev * rdev )
{
struct tegra_regulator_coupler * tegra = to_tegra_coupler ( coupler ) ;
if ( tegra - > core_rdev = = rdev ) {
tegra - > core_rdev = NULL ;
return 0 ;
}
if ( tegra - > cpu_rdev = = rdev ) {
tegra - > cpu_rdev = NULL ;
return 0 ;
}
return - EINVAL ;
}
static struct tegra_regulator_coupler tegra30_coupler = {
. coupler = {
. attach_regulator = tegra30_regulator_attach ,
. detach_regulator = tegra30_regulator_detach ,
. balance_voltage = tegra30_regulator_balance_voltage ,
} ,
} ;
static int __init tegra_regulator_coupler_init ( void )
{
if ( ! of_machine_is_compatible ( " nvidia,tegra30 " ) )
return 0 ;
return regulator_coupler_register ( & tegra30_coupler . coupler ) ;
}
arch_initcall ( tegra_regulator_coupler_init ) ;