2021-09-03 11:39:24 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( c ) 2020 MediaTek Inc .
*/
# include <linux/bitfield.h>
# include <linux/cpufreq.h>
# include <linux/energy_model.h>
# include <linux/init.h>
# include <linux/iopoll.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
# include <linux/slab.h>
# define LUT_MAX_ENTRIES 32U
# define LUT_FREQ GENMASK(11, 0)
# define LUT_ROW_SIZE 0x4
# define CPUFREQ_HW_STATUS BIT(0)
# define SVS_HW_STATUS BIT(1)
# define POLL_USEC 1000
# define TIMEOUT_USEC 300000
enum {
REG_FREQ_LUT_TABLE ,
REG_FREQ_ENABLE ,
REG_FREQ_PERF_STATE ,
REG_FREQ_HW_STATE ,
REG_EM_POWER_TBL ,
REG_FREQ_LATENCY ,
REG_ARRAY_SIZE ,
} ;
struct mtk_cpufreq_data {
struct cpufreq_frequency_table * table ;
void __iomem * reg_bases [ REG_ARRAY_SIZE ] ;
int nr_opp ;
} ;
static const u16 cpufreq_mtk_offsets [ REG_ARRAY_SIZE ] = {
[ REG_FREQ_LUT_TABLE ] = 0x0 ,
[ REG_FREQ_ENABLE ] = 0x84 ,
[ REG_FREQ_PERF_STATE ] = 0x88 ,
[ REG_FREQ_HW_STATE ] = 0x8c ,
[ REG_EM_POWER_TBL ] = 0x90 ,
[ REG_FREQ_LATENCY ] = 0x110 ,
} ;
static int __maybe_unused
mtk_cpufreq_get_cpu_power ( unsigned long * mW ,
unsigned long * KHz , struct device * cpu_dev )
{
struct mtk_cpufreq_data * data ;
struct cpufreq_policy * policy ;
int i ;
policy = cpufreq_cpu_get_raw ( cpu_dev - > id ) ;
if ( ! policy )
return 0 ;
data = policy - > driver_data ;
for ( i = 0 ; i < data - > nr_opp ; i + + ) {
if ( data - > table [ i ] . frequency < * KHz )
break ;
}
i - - ;
* KHz = data - > table [ i ] . frequency ;
* mW = readl_relaxed ( data - > reg_bases [ REG_EM_POWER_TBL ] +
i * LUT_ROW_SIZE ) / 1000 ;
return 0 ;
}
static int mtk_cpufreq_hw_target_index ( struct cpufreq_policy * policy ,
unsigned int index )
{
struct mtk_cpufreq_data * data = policy - > driver_data ;
writel_relaxed ( index , data - > reg_bases [ REG_FREQ_PERF_STATE ] ) ;
return 0 ;
}
static unsigned int mtk_cpufreq_hw_get ( unsigned int cpu )
{
struct mtk_cpufreq_data * data ;
struct cpufreq_policy * policy ;
unsigned int index ;
policy = cpufreq_cpu_get_raw ( cpu ) ;
if ( ! policy )
return 0 ;
data = policy - > driver_data ;
index = readl_relaxed ( data - > reg_bases [ REG_FREQ_PERF_STATE ] ) ;
index = min ( index , LUT_MAX_ENTRIES - 1 ) ;
return data - > table [ index ] . frequency ;
}
static unsigned int mtk_cpufreq_hw_fast_switch ( struct cpufreq_policy * policy ,
unsigned int target_freq )
{
struct mtk_cpufreq_data * data = policy - > driver_data ;
unsigned int index ;
2021-10-07 19:42:18 +03:00
index = cpufreq_table_find_index_dl ( policy , target_freq , false ) ;
2021-09-03 11:39:24 +03:00
writel_relaxed ( index , data - > reg_bases [ REG_FREQ_PERF_STATE ] ) ;
return policy - > freq_table [ index ] . frequency ;
}
static int mtk_cpu_create_freq_table ( struct platform_device * pdev ,
struct mtk_cpufreq_data * data )
{
struct device * dev = & pdev - > dev ;
u32 temp , i , freq , prev_freq = 0 ;
void __iomem * base_table ;
data - > table = devm_kcalloc ( dev , LUT_MAX_ENTRIES + 1 ,
sizeof ( * data - > table ) , GFP_KERNEL ) ;
if ( ! data - > table )
return - ENOMEM ;
base_table = data - > reg_bases [ REG_FREQ_LUT_TABLE ] ;
for ( i = 0 ; i < LUT_MAX_ENTRIES ; i + + ) {
temp = readl_relaxed ( base_table + ( i * LUT_ROW_SIZE ) ) ;
freq = FIELD_GET ( LUT_FREQ , temp ) * 1000 ;
if ( freq = = prev_freq )
break ;
data - > table [ i ] . frequency = freq ;
dev_dbg ( dev , " index=%d freq=%d \n " , i , data - > table [ i ] . frequency ) ;
prev_freq = freq ;
}
data - > table [ i ] . frequency = CPUFREQ_TABLE_END ;
data - > nr_opp = i ;
return 0 ;
}
static int mtk_cpu_resources_init ( struct platform_device * pdev ,
struct cpufreq_policy * policy ,
const u16 * offsets )
{
struct mtk_cpufreq_data * data ;
struct device * dev = & pdev - > dev ;
void __iomem * base ;
int ret , i ;
int index ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
index = of_perf_domain_get_sharing_cpumask ( policy - > cpu , " performance-domains " ,
" #performance-domain-cells " ,
policy - > cpus ) ;
if ( index < 0 )
return index ;
base = devm_platform_ioremap_resource ( pdev , index ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
for ( i = REG_FREQ_LUT_TABLE ; i < REG_ARRAY_SIZE ; i + + )
data - > reg_bases [ i ] = base + offsets [ i ] ;
ret = mtk_cpu_create_freq_table ( pdev , data ) ;
if ( ret ) {
dev_info ( dev , " Domain-%d failed to create freq table \n " , index ) ;
return ret ;
}
policy - > freq_table = data - > table ;
policy - > driver_data = data ;
return 0 ;
}
static int mtk_cpufreq_hw_cpu_init ( struct cpufreq_policy * policy )
{
struct platform_device * pdev = cpufreq_get_driver_data ( ) ;
int sig , pwr_hw = CPUFREQ_HW_STATUS | SVS_HW_STATUS ;
struct mtk_cpufreq_data * data ;
unsigned int latency ;
int ret ;
/* Get the bases of cpufreq for domains */
ret = mtk_cpu_resources_init ( pdev , policy , platform_get_drvdata ( pdev ) ) ;
if ( ret ) {
dev_info ( & pdev - > dev , " CPUFreq resource init failed \n " ) ;
return ret ;
}
data = policy - > driver_data ;
latency = readl_relaxed ( data - > reg_bases [ REG_FREQ_LATENCY ] ) * 1000 ;
if ( ! latency )
latency = CPUFREQ_ETERNAL ;
policy - > cpuinfo . transition_latency = latency ;
policy - > fast_switch_possible = true ;
/* HW should be in enabled state to proceed now */
writel_relaxed ( 0x1 , data - > reg_bases [ REG_FREQ_ENABLE ] ) ;
if ( readl_poll_timeout ( data - > reg_bases [ REG_FREQ_HW_STATE ] , sig ,
( sig & pwr_hw ) = = pwr_hw , POLL_USEC ,
TIMEOUT_USEC ) ) {
if ( ! ( sig & CPUFREQ_HW_STATUS ) ) {
pr_info ( " cpufreq hardware of CPU%d is not enabled \n " ,
policy - > cpu ) ;
return - ENODEV ;
}
pr_info ( " SVS of CPU%d is not enabled \n " , policy - > cpu ) ;
}
return 0 ;
}
static int mtk_cpufreq_hw_cpu_exit ( struct cpufreq_policy * policy )
{
struct mtk_cpufreq_data * data = policy - > driver_data ;
/* HW should be in paused state now */
writel_relaxed ( 0x0 , data - > reg_bases [ REG_FREQ_ENABLE ] ) ;
return 0 ;
}
static void mtk_cpufreq_register_em ( struct cpufreq_policy * policy )
{
struct em_data_callback em_cb = EM_DATA_CB ( mtk_cpufreq_get_cpu_power ) ;
struct mtk_cpufreq_data * data = policy - > driver_data ;
em_dev_register_perf_domain ( get_cpu_device ( policy - > cpu ) , data - > nr_opp ,
& em_cb , policy - > cpus , true ) ;
}
static struct cpufreq_driver cpufreq_mtk_hw_driver = {
. flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
CPUFREQ_IS_COOLING_DEV ,
. verify = cpufreq_generic_frequency_table_verify ,
. target_index = mtk_cpufreq_hw_target_index ,
. get = mtk_cpufreq_hw_get ,
. init = mtk_cpufreq_hw_cpu_init ,
. exit = mtk_cpufreq_hw_cpu_exit ,
. register_em = mtk_cpufreq_register_em ,
. fast_switch = mtk_cpufreq_hw_fast_switch ,
. name = " mtk-cpufreq-hw " ,
. attr = cpufreq_generic_attr ,
} ;
static int mtk_cpufreq_hw_driver_probe ( struct platform_device * pdev )
{
const void * data ;
int ret ;
data = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! data )
return - EINVAL ;
platform_set_drvdata ( pdev , ( void * ) data ) ;
cpufreq_mtk_hw_driver . driver_data = pdev ;
ret = cpufreq_register_driver ( & cpufreq_mtk_hw_driver ) ;
if ( ret )
dev_err ( & pdev - > dev , " CPUFreq HW driver failed to register \n " ) ;
return ret ;
}
static int mtk_cpufreq_hw_driver_remove ( struct platform_device * pdev )
{
return cpufreq_unregister_driver ( & cpufreq_mtk_hw_driver ) ;
}
static const struct of_device_id mtk_cpufreq_hw_match [ ] = {
{ . compatible = " mediatek,cpufreq-hw " , . data = & cpufreq_mtk_offsets } ,
{ }
} ;
static struct platform_driver mtk_cpufreq_hw_driver = {
. probe = mtk_cpufreq_hw_driver_probe ,
. remove = mtk_cpufreq_hw_driver_remove ,
. driver = {
. name = " mtk-cpufreq-hw " ,
. of_match_table = mtk_cpufreq_hw_match ,
} ,
} ;
module_platform_driver ( mtk_cpufreq_hw_driver ) ;
MODULE_AUTHOR ( " Hector Yuan <hector.yuan@mediatek.com> " ) ;
MODULE_DESCRIPTION ( " Mediatek cpufreq-hw driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;