2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-08-19 05:05:06 +03:00
/*
* Copyright ( c ) 2015 Linaro Ltd .
* Author : Pi - Cheng Chen < pi - cheng . chen @ linaro . org >
*/
# include <linux/clk.h>
# include <linux/cpu.h>
# include <linux/cpufreq.h>
# include <linux/cpumask.h>
2022-05-05 14:52:20 +03:00
# include <linux/minmax.h>
2016-02-29 19:04:21 +03:00
# include <linux/module.h>
2015-08-19 05:05:06 +03:00
# include <linux/of.h>
2022-05-05 14:52:19 +03:00
# include <linux/of_platform.h>
2015-08-19 05:05:06 +03:00
# include <linux/platform_device.h>
# include <linux/pm_opp.h>
# include <linux/regulator/consumer.h>
2022-05-05 14:52:19 +03:00
struct mtk_cpufreq_platform_data {
int min_volt_shift ;
int max_volt_shift ;
int proc_max_volt ;
int sram_min_volt ;
int sram_max_volt ;
2022-05-05 14:52:22 +03:00
bool ccifreq_supported ;
2022-05-05 14:52:19 +03:00
} ;
2015-08-19 05:05:06 +03:00
/*
* The struct mtk_cpu_dvfs_info holds necessary information for doing CPU DVFS
* on each CPU power / clock domain of Mediatek SoCs . Each CPU cluster in
* Mediatek SoCs has two voltage inputs , Vproc and Vsram . In some cases the two
* voltage inputs need to be controlled under a hardware limitation :
* 100 mV < Vsram - Vproc < 200 mV
*
* When scaling the clock frequency of a CPU clock domain , the clock source
* needs to be switched to another stable PLL clock temporarily until
* the original PLL becomes stable at target frequency .
*/
struct mtk_cpu_dvfs_info {
2015-12-10 06:48:13 +03:00
struct cpumask cpus ;
2015-08-19 05:05:06 +03:00
struct device * cpu_dev ;
2022-05-05 14:52:22 +03:00
struct device * cci_dev ;
2015-08-19 05:05:06 +03:00
struct regulator * proc_reg ;
struct regulator * sram_reg ;
struct clk * cpu_clk ;
struct clk * inter_clk ;
2015-12-10 06:48:13 +03:00
struct list_head list_head ;
2015-08-19 05:05:06 +03:00
int intermediate_voltage ;
bool need_voltage_tracking ;
2022-05-05 14:52:22 +03:00
int vproc_on_boot ;
2022-04-22 10:52:29 +03:00
int pre_vproc ;
2022-05-05 14:52:21 +03:00
/* Avoid race condition for regulators between notify and policy */
struct mutex reg_lock ;
struct notifier_block opp_nb ;
unsigned int opp_cpu ;
unsigned long current_freq ;
2022-05-05 14:52:19 +03:00
const struct mtk_cpufreq_platform_data * soc_data ;
2022-05-05 14:52:20 +03:00
int vtrack_max ;
2022-05-05 14:52:22 +03:00
bool ccifreq_bound ;
2015-08-19 05:05:06 +03:00
} ;
2022-05-05 14:52:18 +03:00
static struct platform_device * cpufreq_pdev ;
2015-12-10 06:48:13 +03:00
static LIST_HEAD ( dvfs_info_list ) ;
static struct mtk_cpu_dvfs_info * mtk_cpu_dvfs_info_lookup ( int cpu )
{
struct mtk_cpu_dvfs_info * info ;
2016-04-05 05:38:06 +03:00
list_for_each_entry ( info , & dvfs_info_list , list_head ) {
2015-12-10 06:48:13 +03:00
if ( cpumask_test_cpu ( cpu , & info - > cpus ) )
return info ;
}
return NULL ;
}
2015-08-19 05:05:06 +03:00
static int mtk_cpufreq_voltage_tracking ( struct mtk_cpu_dvfs_info * info ,
int new_vproc )
{
2022-05-05 14:52:19 +03:00
const struct mtk_cpufreq_platform_data * soc_data = info - > soc_data ;
2015-08-19 05:05:06 +03:00
struct regulator * proc_reg = info - > proc_reg ;
struct regulator * sram_reg = info - > sram_reg ;
2022-04-22 10:52:28 +03:00
int pre_vproc , pre_vsram , new_vsram , vsram , vproc , ret ;
2022-05-05 14:52:20 +03:00
int retry = info - > vtrack_max ;
2015-08-19 05:05:06 +03:00
2022-04-22 10:52:28 +03:00
pre_vproc = regulator_get_voltage ( proc_reg ) ;
if ( pre_vproc < 0 ) {
2022-04-22 10:52:27 +03:00
dev_err ( info - > cpu_dev ,
2022-04-22 10:52:28 +03:00
" invalid Vproc value: %d \n " , pre_vproc ) ;
return pre_vproc ;
2015-11-29 11:31:37 +03:00
}
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:20 +03:00
pre_vsram = regulator_get_voltage ( sram_reg ) ;
if ( pre_vsram < 0 ) {
dev_err ( info - > cpu_dev , " invalid Vsram value: %d \n " , pre_vsram ) ;
return pre_vsram ;
}
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:20 +03:00
new_vsram = clamp ( new_vproc + soc_data - > min_volt_shift ,
soc_data - > sram_min_volt , soc_data - > sram_max_volt ) ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:20 +03:00
do {
if ( pre_vproc < = new_vproc ) {
vsram = clamp ( pre_vproc + soc_data - > max_volt_shift ,
soc_data - > sram_min_volt , new_vsram ) ;
ret = regulator_set_voltage ( sram_reg , vsram ,
soc_data - > sram_max_volt ) ;
2015-08-19 05:05:06 +03:00
if ( ret )
return ret ;
2022-05-05 14:52:20 +03:00
if ( vsram = = soc_data - > sram_max_volt | |
new_vsram = = soc_data - > sram_min_volt )
vproc = new_vproc ;
else
vproc = vsram - soc_data - > min_volt_shift ;
2015-08-19 05:05:06 +03:00
ret = regulator_set_voltage ( proc_reg , vproc ,
2022-05-05 14:52:20 +03:00
soc_data - > proc_max_volt ) ;
2015-08-19 05:05:06 +03:00
if ( ret ) {
2022-04-22 10:52:28 +03:00
regulator_set_voltage ( sram_reg , pre_vsram ,
2022-05-05 14:52:20 +03:00
soc_data - > sram_max_volt ) ;
2015-08-19 05:05:06 +03:00
return ret ;
}
2022-05-05 14:52:20 +03:00
} else if ( pre_vproc > new_vproc ) {
2022-05-05 14:52:19 +03:00
vproc = max ( new_vproc ,
pre_vsram - soc_data - > max_volt_shift ) ;
2015-08-19 05:05:06 +03:00
ret = regulator_set_voltage ( proc_reg , vproc ,
2022-05-05 14:52:20 +03:00
soc_data - > proc_max_volt ) ;
2015-08-19 05:05:06 +03:00
if ( ret )
return ret ;
if ( vproc = = new_vproc )
vsram = new_vsram ;
else
2022-05-05 14:52:19 +03:00
vsram = max ( new_vsram ,
vproc + soc_data - > min_volt_shift ) ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:20 +03:00
ret = regulator_set_voltage ( sram_reg , vsram ,
soc_data - > sram_max_volt ) ;
2015-08-19 05:05:06 +03:00
if ( ret ) {
2022-04-22 10:52:28 +03:00
regulator_set_voltage ( proc_reg , pre_vproc ,
2022-05-05 14:52:20 +03:00
soc_data - > proc_max_volt ) ;
2015-08-19 05:05:06 +03:00
return ret ;
}
2022-05-05 14:52:20 +03:00
}
pre_vproc = vproc ;
pre_vsram = vsram ;
if ( - - retry < 0 ) {
dev_err ( info - > cpu_dev ,
" over loop count, failed to set voltage \n " ) ;
return - EINVAL ;
}
} while ( vproc ! = new_vproc | | vsram ! = new_vsram ) ;
2015-08-19 05:05:06 +03:00
return 0 ;
}
static int mtk_cpufreq_set_voltage ( struct mtk_cpu_dvfs_info * info , int vproc )
{
2022-05-05 14:52:19 +03:00
const struct mtk_cpufreq_platform_data * soc_data = info - > soc_data ;
2022-04-22 10:52:29 +03:00
int ret ;
2015-08-19 05:05:06 +03:00
if ( info - > need_voltage_tracking )
2022-04-22 10:52:29 +03:00
ret = mtk_cpufreq_voltage_tracking ( info , vproc ) ;
2015-08-19 05:05:06 +03:00
else
2022-04-22 10:52:29 +03:00
ret = regulator_set_voltage ( info - > proc_reg , vproc ,
2022-05-05 14:52:19 +03:00
soc_data - > proc_max_volt ) ;
2022-04-22 10:52:29 +03:00
if ( ! ret )
info - > pre_vproc = vproc ;
return ret ;
2015-08-19 05:05:06 +03:00
}
2022-05-05 14:52:22 +03:00
static bool is_ccifreq_ready ( struct mtk_cpu_dvfs_info * info )
{
struct device_link * sup_link ;
if ( info - > ccifreq_bound )
return true ;
sup_link = device_link_add ( info - > cpu_dev , info - > cci_dev ,
DL_FLAG_AUTOREMOVE_CONSUMER ) ;
if ( ! sup_link ) {
dev_err ( info - > cpu_dev , " cpu%d: sup_link is NULL \n " , info - > opp_cpu ) ;
return false ;
}
if ( sup_link - > supplier - > links . status ! = DL_DEV_DRIVER_BOUND )
return false ;
info - > ccifreq_bound = true ;
return true ;
}
2015-08-19 05:05:06 +03:00
static int mtk_cpufreq_set_target ( struct cpufreq_policy * policy ,
unsigned int index )
{
struct cpufreq_frequency_table * freq_table = policy - > freq_table ;
struct clk * cpu_clk = policy - > clk ;
struct clk * armpll = clk_get_parent ( cpu_clk ) ;
struct mtk_cpu_dvfs_info * info = policy - > driver_data ;
struct device * cpu_dev = info - > cpu_dev ;
struct dev_pm_opp * opp ;
2022-04-22 10:52:28 +03:00
long freq_hz , pre_freq_hz ;
int vproc , pre_vproc , inter_vproc , target_vproc , ret ;
2015-08-19 05:05:06 +03:00
inter_vproc = info - > intermediate_voltage ;
2022-04-22 10:52:28 +03:00
pre_freq_hz = clk_get_rate ( cpu_clk ) ;
2022-04-22 10:52:29 +03:00
2022-05-05 14:52:21 +03:00
mutex_lock ( & info - > reg_lock ) ;
2022-04-22 10:52:29 +03:00
if ( unlikely ( info - > pre_vproc < = 0 ) )
pre_vproc = regulator_get_voltage ( info - > proc_reg ) ;
else
pre_vproc = info - > pre_vproc ;
2022-04-22 10:52:28 +03:00
if ( pre_vproc < 0 ) {
dev_err ( cpu_dev , " invalid Vproc value: %d \n " , pre_vproc ) ;
2022-05-10 12:05:31 +03:00
ret = pre_vproc ;
goto out ;
2015-11-29 11:31:37 +03:00
}
2015-08-19 05:05:06 +03:00
freq_hz = freq_table [ index ] . frequency * 1000 ;
opp = dev_pm_opp_find_freq_ceil ( cpu_dev , & freq_hz ) ;
if ( IS_ERR ( opp ) ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev , " cpu%d: failed to find OPP for %ld \n " ,
policy - > cpu , freq_hz ) ;
2022-05-10 12:05:31 +03:00
ret = PTR_ERR ( opp ) ;
goto out ;
2015-08-19 05:05:06 +03:00
}
vproc = dev_pm_opp_get_voltage ( opp ) ;
2017-01-23 07:41:47 +03:00
dev_pm_opp_put ( opp ) ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:22 +03:00
/*
* If MediaTek cci is supported but is not ready , we will use the value
* of max ( target cpu voltage , booting voltage ) to prevent high freqeuncy
* low voltage crash .
*/
if ( info - > soc_data - > ccifreq_supported & & ! is_ccifreq_ready ( info ) )
vproc = max ( vproc , info - > vproc_on_boot ) ;
2015-08-19 05:05:06 +03:00
/*
* If the new voltage or the intermediate voltage is higher than the
* current voltage , scale up voltage first .
*/
2022-05-05 14:52:20 +03:00
target_vproc = max ( inter_vproc , vproc ) ;
if ( pre_vproc < = target_vproc ) {
2015-08-19 05:05:06 +03:00
ret = mtk_cpufreq_set_voltage ( info , target_vproc ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to scale up voltage! \n " , policy - > cpu ) ;
2022-04-22 10:52:28 +03:00
mtk_cpufreq_set_voltage ( info , pre_vproc ) ;
2022-05-05 14:52:21 +03:00
goto out ;
2015-08-19 05:05:06 +03:00
}
}
/* Reparent the CPU clock to intermediate clock. */
ret = clk_set_parent ( cpu_clk , info - > inter_clk ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to re-parent cpu clock! \n " , policy - > cpu ) ;
2022-04-22 10:52:28 +03:00
mtk_cpufreq_set_voltage ( info , pre_vproc ) ;
2022-05-05 14:52:21 +03:00
goto out ;
2015-08-19 05:05:06 +03:00
}
/* Set the original PLL to target rate. */
ret = clk_set_rate ( armpll , freq_hz ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to scale cpu clock rate! \n " , policy - > cpu ) ;
2015-08-19 05:05:06 +03:00
clk_set_parent ( cpu_clk , armpll ) ;
2022-04-22 10:52:28 +03:00
mtk_cpufreq_set_voltage ( info , pre_vproc ) ;
2022-05-05 14:52:21 +03:00
goto out ;
2015-08-19 05:05:06 +03:00
}
/* Set parent of CPU clock back to the original PLL. */
ret = clk_set_parent ( cpu_clk , armpll ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to re-parent cpu clock! \n " , policy - > cpu ) ;
2015-08-19 05:05:06 +03:00
mtk_cpufreq_set_voltage ( info , inter_vproc ) ;
2022-05-05 14:52:21 +03:00
goto out ;
2015-08-19 05:05:06 +03:00
}
/*
* If the new voltage is lower than the intermediate voltage or the
* original voltage , scale down to the new voltage .
*/
2022-04-22 10:52:28 +03:00
if ( vproc < inter_vproc | | vproc < pre_vproc ) {
2015-08-19 05:05:06 +03:00
ret = mtk_cpufreq_set_voltage ( info , vproc ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to scale down voltage! \n " , policy - > cpu ) ;
2015-08-19 05:05:06 +03:00
clk_set_parent ( cpu_clk , info - > inter_clk ) ;
2022-04-22 10:52:28 +03:00
clk_set_rate ( armpll , pre_freq_hz ) ;
2015-08-19 05:05:06 +03:00
clk_set_parent ( cpu_clk , armpll ) ;
2022-05-05 14:52:21 +03:00
goto out ;
2015-08-19 05:05:06 +03:00
}
}
2022-05-05 14:52:21 +03:00
info - > current_freq = freq_hz ;
out :
mutex_unlock ( & info - > reg_lock ) ;
return ret ;
2015-08-19 05:05:06 +03:00
}
2015-12-16 16:29:14 +03:00
# define DYNAMIC_POWER "dynamic-power-coefficient"
2022-05-05 14:52:21 +03:00
static int mtk_cpufreq_opp_notifier ( struct notifier_block * nb ,
unsigned long event , void * data )
{
struct dev_pm_opp * opp = data ;
struct dev_pm_opp * new_opp ;
struct mtk_cpu_dvfs_info * info ;
unsigned long freq , volt ;
struct cpufreq_policy * policy ;
int ret = 0 ;
info = container_of ( nb , struct mtk_cpu_dvfs_info , opp_nb ) ;
if ( event = = OPP_EVENT_ADJUST_VOLTAGE ) {
freq = dev_pm_opp_get_freq ( opp ) ;
mutex_lock ( & info - > reg_lock ) ;
if ( info - > current_freq = = freq ) {
volt = dev_pm_opp_get_voltage ( opp ) ;
ret = mtk_cpufreq_set_voltage ( info , volt ) ;
if ( ret )
dev_err ( info - > cpu_dev ,
" failed to scale voltage: %d \n " , ret ) ;
}
mutex_unlock ( & info - > reg_lock ) ;
} else if ( event = = OPP_EVENT_DISABLE ) {
freq = dev_pm_opp_get_freq ( opp ) ;
/* case of current opp item is disabled */
if ( info - > current_freq = = freq ) {
freq = 1 ;
new_opp = dev_pm_opp_find_freq_ceil ( info - > cpu_dev ,
& freq ) ;
if ( IS_ERR ( new_opp ) ) {
dev_err ( info - > cpu_dev ,
" all opp items are disabled \n " ) ;
ret = PTR_ERR ( new_opp ) ;
return notifier_from_errno ( ret ) ;
}
dev_pm_opp_put ( new_opp ) ;
policy = cpufreq_cpu_get ( info - > opp_cpu ) ;
if ( policy ) {
cpufreq_driver_target ( policy , freq / 1000 ,
CPUFREQ_RELATION_L ) ;
cpufreq_cpu_put ( policy ) ;
}
}
}
return notifier_from_errno ( ret ) ;
}
2022-05-05 14:52:22 +03:00
static struct device * of_get_cci ( struct device * cpu_dev )
{
struct device_node * np ;
struct platform_device * pdev ;
np = of_parse_phandle ( cpu_dev - > of_node , " mediatek,cci " , 0 ) ;
if ( IS_ERR_OR_NULL ( np ) )
return NULL ;
pdev = of_find_device_by_node ( np ) ;
of_node_put ( np ) ;
if ( IS_ERR_OR_NULL ( pdev ) )
return NULL ;
return & pdev - > dev ;
}
2015-08-19 05:05:06 +03:00
static int mtk_cpu_dvfs_info_init ( struct mtk_cpu_dvfs_info * info , int cpu )
{
struct device * cpu_dev ;
struct dev_pm_opp * opp ;
unsigned long rate ;
int ret ;
cpu_dev = get_cpu_device ( cpu ) ;
if ( ! cpu_dev ) {
2022-04-08 07:58:56 +03:00
dev_err ( cpu_dev , " failed to get cpu%d device \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
return - ENODEV ;
}
2022-04-08 07:58:56 +03:00
info - > cpu_dev = cpu_dev ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:22 +03:00
info - > ccifreq_bound = false ;
if ( info - > soc_data - > ccifreq_supported ) {
info - > cci_dev = of_get_cci ( info - > cpu_dev ) ;
if ( IS_ERR_OR_NULL ( info - > cci_dev ) ) {
ret = PTR_ERR ( info - > cci_dev ) ;
dev_err ( cpu_dev , " cpu%d: failed to get cci device \n " , cpu ) ;
return - ENODEV ;
}
}
2022-04-08 07:58:56 +03:00
info - > cpu_clk = clk_get ( cpu_dev , " cpu " ) ;
if ( IS_ERR ( info - > cpu_clk ) ) {
ret = PTR_ERR ( info - > cpu_clk ) ;
return dev_err_probe ( cpu_dev , ret ,
" cpu%d: failed to get cpu clk \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
}
2022-04-08 07:58:56 +03:00
info - > inter_clk = clk_get ( cpu_dev , " intermediate " ) ;
if ( IS_ERR ( info - > inter_clk ) ) {
ret = PTR_ERR ( info - > inter_clk ) ;
dev_err_probe ( cpu_dev , ret ,
" cpu%d: failed to get intermediate clk \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
goto out_free_resources ;
}
2022-04-08 07:58:56 +03:00
info - > proc_reg = regulator_get_optional ( cpu_dev , " proc " ) ;
if ( IS_ERR ( info - > proc_reg ) ) {
ret = PTR_ERR ( info - > proc_reg ) ;
dev_err_probe ( cpu_dev , ret ,
" cpu%d: failed to get proc regulator \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
goto out_free_resources ;
}
2022-04-08 07:58:58 +03:00
ret = regulator_enable ( info - > proc_reg ) ;
if ( ret ) {
dev_warn ( cpu_dev , " cpu%d: failed to enable vproc \n " , cpu ) ;
goto out_free_resources ;
}
2015-08-19 05:05:06 +03:00
/* Both presence and absence of sram regulator are valid cases. */
2022-04-22 10:52:33 +03:00
info - > sram_reg = regulator_get_optional ( cpu_dev , " sram " ) ;
2022-07-13 14:15:36 +03:00
if ( IS_ERR ( info - > sram_reg ) ) {
ret = PTR_ERR ( info - > sram_reg ) ;
if ( ret = = - EPROBE_DEFER )
goto out_free_resources ;
2022-04-08 07:58:56 +03:00
info - > sram_reg = NULL ;
2022-07-13 14:15:36 +03:00
} else {
2022-04-08 07:58:58 +03:00
ret = regulator_enable ( info - > sram_reg ) ;
if ( ret ) {
dev_warn ( cpu_dev , " cpu%d: failed to enable vsram \n " , cpu ) ;
goto out_free_resources ;
}
}
2015-08-19 05:05:06 +03:00
2015-12-27 09:21:57 +03:00
/* Get OPP-sharing information from "operating-points-v2" bindings */
ret = dev_pm_opp_of_get_sharing_cpus ( cpu_dev , & info - > cpus ) ;
if ( ret ) {
2022-04-08 07:58:56 +03:00
dev_err ( cpu_dev ,
" cpu%d: failed to get OPP-sharing information \n " , cpu ) ;
2015-12-27 09:21:57 +03:00
goto out_free_resources ;
}
ret = dev_pm_opp_of_cpumask_add_table ( & info - > cpus ) ;
2015-08-19 05:05:06 +03:00
if ( ret ) {
2022-04-08 07:58:56 +03:00
dev_warn ( cpu_dev , " cpu%d: no OPP table \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
goto out_free_resources ;
}
2022-04-08 07:58:58 +03:00
ret = clk_prepare_enable ( info - > cpu_clk ) ;
if ( ret )
goto out_free_opp_table ;
ret = clk_prepare_enable ( info - > inter_clk ) ;
if ( ret )
goto out_disable_mux_clock ;
2022-05-05 14:52:22 +03:00
if ( info - > soc_data - > ccifreq_supported ) {
info - > vproc_on_boot = regulator_get_voltage ( info - > proc_reg ) ;
if ( info - > vproc_on_boot < 0 ) {
2022-05-17 16:34:50 +03:00
ret = info - > vproc_on_boot ;
2022-05-05 14:52:22 +03:00
dev_err ( info - > cpu_dev ,
" invalid Vproc value: %d \n " , info - > vproc_on_boot ) ;
goto out_disable_inter_clock ;
}
}
2015-08-19 05:05:06 +03:00
/* Search a safe voltage for intermediate frequency. */
2022-04-08 07:58:56 +03:00
rate = clk_get_rate ( info - > inter_clk ) ;
2015-08-19 05:05:06 +03:00
opp = dev_pm_opp_find_freq_ceil ( cpu_dev , & rate ) ;
if ( IS_ERR ( opp ) ) {
2022-04-08 07:58:56 +03:00
dev_err ( cpu_dev , " cpu%d: failed to get intermediate opp \n " , cpu ) ;
2015-08-19 05:05:06 +03:00
ret = PTR_ERR ( opp ) ;
2022-04-08 07:58:58 +03:00
goto out_disable_inter_clock ;
2015-08-19 05:05:06 +03:00
}
info - > intermediate_voltage = dev_pm_opp_get_voltage ( opp ) ;
2017-01-23 07:41:47 +03:00
dev_pm_opp_put ( opp ) ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:21 +03:00
mutex_init ( & info - > reg_lock ) ;
info - > current_freq = clk_get_rate ( info - > cpu_clk ) ;
info - > opp_cpu = cpu ;
info - > opp_nb . notifier_call = mtk_cpufreq_opp_notifier ;
ret = dev_pm_opp_register_notifier ( cpu_dev , & info - > opp_nb ) ;
if ( ret ) {
dev_err ( cpu_dev , " cpu%d: failed to register opp notifier \n " , cpu ) ;
goto out_disable_inter_clock ;
}
2015-08-19 05:05:06 +03:00
/*
* If SRAM regulator is present , software " voltage tracking " is needed
* for this CPU power domain .
*/
2022-04-08 07:58:56 +03:00
info - > need_voltage_tracking = ( info - > sram_reg ! = NULL ) ;
2015-08-19 05:05:06 +03:00
2022-05-05 14:52:20 +03:00
/*
* We assume min voltage is 0 and tracking target voltage using
* min_volt_shift for each iteration .
* The vtrack_max is 3 times of expeted iteration count .
*/
info - > vtrack_max = 3 * DIV_ROUND_UP ( max ( info - > soc_data - > sram_max_volt ,
info - > soc_data - > proc_max_volt ) ,
info - > soc_data - > min_volt_shift ) ;
2015-08-19 05:05:06 +03:00
return 0 ;
2022-04-08 07:58:58 +03:00
out_disable_inter_clock :
clk_disable_unprepare ( info - > inter_clk ) ;
out_disable_mux_clock :
clk_disable_unprepare ( info - > cpu_clk ) ;
2015-08-19 05:05:06 +03:00
out_free_opp_table :
2015-12-27 09:21:57 +03:00
dev_pm_opp_of_cpumask_remove_table ( & info - > cpus ) ;
2015-08-19 05:05:06 +03:00
out_free_resources :
2022-04-08 07:58:58 +03:00
if ( regulator_is_enabled ( info - > proc_reg ) )
regulator_disable ( info - > proc_reg ) ;
if ( info - > sram_reg & & regulator_is_enabled ( info - > sram_reg ) )
regulator_disable ( info - > sram_reg ) ;
2022-04-08 07:58:56 +03:00
if ( ! IS_ERR ( info - > proc_reg ) )
regulator_put ( info - > proc_reg ) ;
if ( ! IS_ERR ( info - > sram_reg ) )
regulator_put ( info - > sram_reg ) ;
if ( ! IS_ERR ( info - > cpu_clk ) )
clk_put ( info - > cpu_clk ) ;
if ( ! IS_ERR ( info - > inter_clk ) )
clk_put ( info - > inter_clk ) ;
2015-08-19 05:05:06 +03:00
return ret ;
}
static void mtk_cpu_dvfs_info_release ( struct mtk_cpu_dvfs_info * info )
{
2022-04-08 07:58:58 +03:00
if ( ! IS_ERR ( info - > proc_reg ) ) {
regulator_disable ( info - > proc_reg ) ;
2015-08-19 05:05:06 +03:00
regulator_put ( info - > proc_reg ) ;
2022-04-08 07:58:58 +03:00
}
if ( ! IS_ERR ( info - > sram_reg ) ) {
regulator_disable ( info - > sram_reg ) ;
2015-08-19 05:05:06 +03:00
regulator_put ( info - > sram_reg ) ;
2022-04-08 07:58:58 +03:00
}
if ( ! IS_ERR ( info - > cpu_clk ) ) {
clk_disable_unprepare ( info - > cpu_clk ) ;
2015-08-19 05:05:06 +03:00
clk_put ( info - > cpu_clk ) ;
2022-04-08 07:58:58 +03:00
}
if ( ! IS_ERR ( info - > inter_clk ) ) {
clk_disable_unprepare ( info - > inter_clk ) ;
2015-08-19 05:05:06 +03:00
clk_put ( info - > inter_clk ) ;
2022-04-08 07:58:58 +03:00
}
2015-08-19 05:05:06 +03:00
2015-12-27 09:21:57 +03:00
dev_pm_opp_of_cpumask_remove_table ( & info - > cpus ) ;
2022-05-05 14:52:21 +03:00
dev_pm_opp_unregister_notifier ( info - > cpu_dev , & info - > opp_nb ) ;
2015-08-19 05:05:06 +03:00
}
static int mtk_cpufreq_init ( struct cpufreq_policy * policy )
{
struct mtk_cpu_dvfs_info * info ;
struct cpufreq_frequency_table * freq_table ;
int ret ;
2015-12-10 06:48:13 +03:00
info = mtk_cpu_dvfs_info_lookup ( policy - > cpu ) ;
if ( ! info ) {
2022-04-26 14:17:14 +03:00
pr_err ( " dvfs info for cpu%d is not initialized. \n " ,
policy - > cpu ) ;
2015-12-10 06:48:13 +03:00
return - EINVAL ;
2015-08-19 05:05:06 +03:00
}
ret = dev_pm_opp_init_cpufreq_table ( info - > cpu_dev , & freq_table ) ;
if ( ret ) {
2022-04-22 10:52:27 +03:00
dev_err ( info - > cpu_dev ,
" failed to init cpufreq table for cpu%d: %d \n " ,
policy - > cpu , ret ) ;
2015-12-10 06:48:13 +03:00
return ret ;
2015-08-19 05:05:06 +03:00
}
2015-12-10 06:48:13 +03:00
cpumask_copy ( policy - > cpus , & info - > cpus ) ;
2018-02-26 08:08:55 +03:00
policy - > freq_table = freq_table ;
2015-08-19 05:05:06 +03:00
policy - > driver_data = info ;
policy - > clk = info - > cpu_clk ;
return 0 ;
}
static int mtk_cpufreq_exit ( struct cpufreq_policy * policy )
{
struct mtk_cpu_dvfs_info * info = policy - > driver_data ;
dev_pm_opp_free_cpufreq_table ( info - > cpu_dev , & policy - > freq_table ) ;
return 0 ;
}
2017-08-09 13:12:38 +03:00
static struct cpufreq_driver mtk_cpufreq_driver = {
2021-02-02 07:55:11 +03:00
. flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
2019-01-29 07:55:12 +03:00
CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
CPUFREQ_IS_COOLING_DEV ,
2015-08-19 05:05:06 +03:00
. verify = cpufreq_generic_frequency_table_verify ,
. target_index = mtk_cpufreq_set_target ,
. get = cpufreq_generic_get ,
. init = mtk_cpufreq_init ,
. exit = mtk_cpufreq_exit ,
2021-08-10 09:54:36 +03:00
. register_em = cpufreq_register_em_with_opp ,
2015-08-19 05:05:06 +03:00
. name = " mtk-cpufreq " ,
. attr = cpufreq_generic_attr ,
} ;
2017-08-09 13:12:38 +03:00
static int mtk_cpufreq_probe ( struct platform_device * pdev )
2015-08-19 05:05:06 +03:00
{
2022-05-05 14:52:19 +03:00
const struct mtk_cpufreq_platform_data * data ;
2016-04-05 05:38:06 +03:00
struct mtk_cpu_dvfs_info * info , * tmp ;
2015-12-10 06:48:13 +03:00
int cpu , ret ;
2022-05-05 14:52:19 +03:00
data = dev_get_platdata ( & pdev - > dev ) ;
if ( ! data ) {
dev_err ( & pdev - > dev ,
" failed to get mtk cpufreq platform data \n " ) ;
return - ENODEV ;
}
2015-12-10 06:48:13 +03:00
for_each_possible_cpu ( cpu ) {
info = mtk_cpu_dvfs_info_lookup ( cpu ) ;
if ( info )
continue ;
info = devm_kzalloc ( & pdev - > dev , sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info ) {
ret = - ENOMEM ;
goto release_dvfs_info_list ;
}
2022-05-05 14:52:19 +03:00
info - > soc_data = data ;
2015-12-10 06:48:13 +03:00
ret = mtk_cpu_dvfs_info_init ( info , cpu ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" failed to initialize dvfs info for cpu%d \n " ,
cpu ) ;
goto release_dvfs_info_list ;
}
list_add ( & info - > list_head , & dvfs_info_list ) ;
}
2015-08-19 05:05:06 +03:00
2017-08-09 13:12:38 +03:00
ret = cpufreq_register_driver ( & mtk_cpufreq_driver ) ;
2015-12-10 06:48:13 +03:00
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register mtk cpufreq driver \n " ) ;
goto release_dvfs_info_list ;
}
return 0 ;
release_dvfs_info_list :
2016-04-05 05:38:06 +03:00
list_for_each_entry_safe ( info , tmp , & dvfs_info_list , list_head ) {
2015-12-10 06:48:13 +03:00
mtk_cpu_dvfs_info_release ( info ) ;
2016-04-05 05:38:06 +03:00
list_del ( & info - > list_head ) ;
2015-12-10 06:48:13 +03:00
}
2015-08-19 05:05:06 +03:00
return ret ;
}
2017-08-09 13:12:38 +03:00
static struct platform_driver mtk_cpufreq_platdrv = {
2015-08-19 05:05:06 +03:00
. driver = {
2017-08-09 13:12:38 +03:00
. name = " mtk-cpufreq " ,
2015-08-19 05:05:06 +03:00
} ,
2017-08-09 13:12:38 +03:00
. probe = mtk_cpufreq_probe ,
2015-08-19 05:05:06 +03:00
} ;
2022-05-05 14:52:19 +03:00
static const struct mtk_cpufreq_platform_data mt2701_platform_data = {
. min_volt_shift = 100000 ,
. max_volt_shift = 200000 ,
. proc_max_volt = 1150000 ,
. sram_min_volt = 0 ,
. sram_max_volt = 1150000 ,
2022-05-05 14:52:22 +03:00
. ccifreq_supported = false ,
} ;
static const struct mtk_cpufreq_platform_data mt8183_platform_data = {
. min_volt_shift = 100000 ,
. max_volt_shift = 200000 ,
. proc_max_volt = 1150000 ,
. sram_min_volt = 0 ,
. sram_max_volt = 1150000 ,
. ccifreq_supported = true ,
2022-05-05 14:52:19 +03:00
} ;
2022-05-05 14:52:23 +03:00
static const struct mtk_cpufreq_platform_data mt8186_platform_data = {
. min_volt_shift = 100000 ,
. max_volt_shift = 250000 ,
. proc_max_volt = 1118750 ,
. sram_min_volt = 850000 ,
. sram_max_volt = 1118750 ,
. ccifreq_supported = true ,
} ;
2017-03-02 14:03:45 +03:00
/* List of machines supported by this driver */
2017-08-09 13:12:38 +03:00
static const struct of_device_id mtk_cpufreq_machines [ ] __initconst = {
2022-05-05 14:52:19 +03:00
{ . compatible = " mediatek,mt2701 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt2712 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt7622 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt7623 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt8167 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt817x " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt8173 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt8176 " , . data = & mt2701_platform_data } ,
2022-05-05 14:52:22 +03:00
{ . compatible = " mediatek,mt8183 " , . data = & mt8183_platform_data } ,
2022-05-05 14:52:23 +03:00
{ . compatible = " mediatek,mt8186 " , . data = & mt8186_platform_data } ,
2022-05-05 14:52:19 +03:00
{ . compatible = " mediatek,mt8365 " , . data = & mt2701_platform_data } ,
{ . compatible = " mediatek,mt8516 " , . data = & mt2701_platform_data } ,
2017-03-02 14:03:45 +03:00
{ }
} ;
2020-11-03 18:11:33 +03:00
MODULE_DEVICE_TABLE ( of , mtk_cpufreq_machines ) ;
2017-03-02 14:03:45 +03:00
2017-08-09 13:12:38 +03:00
static int __init mtk_cpufreq_driver_init ( void )
2015-08-19 05:05:06 +03:00
{
2017-03-02 14:03:45 +03:00
struct device_node * np ;
const struct of_device_id * match ;
2022-05-05 14:52:19 +03:00
const struct mtk_cpufreq_platform_data * data ;
2015-08-19 05:05:06 +03:00
int err ;
2017-03-02 14:03:45 +03:00
np = of_find_node_by_path ( " / " ) ;
if ( ! np )
2015-08-19 05:05:06 +03:00
return - ENODEV ;
2017-08-09 13:12:38 +03:00
match = of_match_node ( mtk_cpufreq_machines , np ) ;
2017-03-02 14:03:45 +03:00
of_node_put ( np ) ;
if ( ! match ) {
2018-02-22 08:56:31 +03:00
pr_debug ( " Machine is not compatible with mtk-cpufreq \n " ) ;
2017-03-02 14:03:45 +03:00
return - ENODEV ;
}
2022-05-05 14:52:19 +03:00
data = match - > data ;
2017-03-02 14:03:45 +03:00
2017-08-09 13:12:38 +03:00
err = platform_driver_register ( & mtk_cpufreq_platdrv ) ;
2015-08-19 05:05:06 +03:00
if ( err )
return err ;
/*
* Since there ' s no place to hold device registration code and no
* device tree based way to match cpufreq driver yet , both the driver
* and the device registration codes are put here to handle defer
* probing .
*/
2022-05-05 14:52:19 +03:00
cpufreq_pdev = platform_device_register_data ( NULL , " mtk-cpufreq " , - 1 ,
data , sizeof ( * data ) ) ;
2022-05-05 14:52:18 +03:00
if ( IS_ERR ( cpufreq_pdev ) ) {
2015-08-19 05:05:06 +03:00
pr_err ( " failed to register mtk-cpufreq platform device \n " ) ;
2020-10-31 04:18:54 +03:00
platform_driver_unregister ( & mtk_cpufreq_platdrv ) ;
2022-05-05 14:52:18 +03:00
return PTR_ERR ( cpufreq_pdev ) ;
2015-08-19 05:05:06 +03:00
}
return 0 ;
}
2022-04-08 07:58:55 +03:00
module_init ( mtk_cpufreq_driver_init )
static void __exit mtk_cpufreq_driver_exit ( void )
{
2022-05-05 14:52:18 +03:00
platform_device_unregister ( cpufreq_pdev ) ;
2022-04-08 07:58:55 +03:00
platform_driver_unregister ( & mtk_cpufreq_platdrv ) ;
}
module_exit ( mtk_cpufreq_driver_exit )
2017-11-21 00:32:01 +03:00
MODULE_DESCRIPTION ( " MediaTek CPUFreq driver " ) ;
MODULE_AUTHOR ( " Pi-Cheng Chen <pi-cheng.chen@linaro.org> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;