2017-06-18 11:38:11 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Power Interface ( SCMI ) based CPUFreq Interface driver
*
* Copyright ( C ) 2018 ARM Ltd .
* Sudeep Holla < sudeep . holla @ arm . com >
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/cpu.h>
# include <linux/cpufreq.h>
# include <linux/cpumask.h>
2019-02-04 14:09:52 +03:00
# include <linux/energy_model.h>
2017-06-18 11:38:11 +03:00
# include <linux/export.h>
# include <linux/module.h>
# include <linux/pm_opp.h>
# include <linux/slab.h>
# include <linux/scmi_protocol.h>
# include <linux/types.h>
struct scmi_data {
int domain_id ;
struct device * cpu_dev ;
} ;
static const struct scmi_handle * handle ;
static unsigned int scmi_cpufreq_get_rate ( unsigned int cpu )
{
struct cpufreq_policy * policy = cpufreq_cpu_get_raw ( cpu ) ;
struct scmi_perf_ops * perf_ops = handle - > perf_ops ;
struct scmi_data * priv = policy - > driver_data ;
unsigned long rate ;
int ret ;
ret = perf_ops - > freq_get ( handle , priv - > domain_id , & rate , false ) ;
if ( ret )
return 0 ;
return rate / 1000 ;
}
/*
* perf_ops - > freq_set is not a synchronous , the actual OPP change will
* happen asynchronously and can get notified if the events are
* subscribed for by the SCMI firmware
*/
static int
scmi_cpufreq_set_target ( struct cpufreq_policy * policy , unsigned int index )
{
int ret ;
struct scmi_data * priv = policy - > driver_data ;
struct scmi_perf_ops * perf_ops = handle - > perf_ops ;
2019-01-09 13:42:36 +03:00
u64 freq = policy - > freq_table [ index ] . frequency ;
2017-06-18 11:38:11 +03:00
2019-01-09 13:42:36 +03:00
ret = perf_ops - > freq_set ( handle , priv - > domain_id , freq * 1000 , false ) ;
2017-06-18 11:38:11 +03:00
if ( ! ret )
arch_set_freq_scale ( policy - > related_cpus , freq ,
policy - > cpuinfo . max_freq ) ;
return ret ;
}
2017-07-20 16:43:07 +03:00
static unsigned int scmi_cpufreq_fast_switch ( struct cpufreq_policy * policy ,
unsigned int target_freq )
{
struct scmi_data * priv = policy - > driver_data ;
struct scmi_perf_ops * perf_ops = handle - > perf_ops ;
if ( ! perf_ops - > freq_set ( handle , priv - > domain_id ,
target_freq * 1000 , true ) ) {
arch_set_freq_scale ( policy - > related_cpus , target_freq ,
policy - > cpuinfo . max_freq ) ;
return target_freq ;
}
return 0 ;
}
2017-06-18 11:38:11 +03:00
static int
scmi_get_sharing_cpus ( struct device * cpu_dev , struct cpumask * cpumask )
{
int cpu , domain , tdomain ;
struct device * tcpu_dev ;
domain = handle - > perf_ops - > device_domain_id ( cpu_dev ) ;
if ( domain < 0 )
return domain ;
for_each_possible_cpu ( cpu ) {
if ( cpu = = cpu_dev - > id )
continue ;
tcpu_dev = get_cpu_device ( cpu ) ;
if ( ! tcpu_dev )
continue ;
tdomain = handle - > perf_ops - > device_domain_id ( tcpu_dev ) ;
if ( tdomain = = domain )
cpumask_set_cpu ( cpu , cpumask ) ;
}
return 0 ;
}
2019-02-04 14:09:52 +03:00
static int __maybe_unused
scmi_get_cpu_power ( unsigned long * power , unsigned long * KHz , int cpu )
{
struct device * cpu_dev = get_cpu_device ( cpu ) ;
unsigned long Hz ;
int ret , domain ;
if ( ! cpu_dev ) {
pr_err ( " failed to get cpu%d device \n " , cpu ) ;
return - ENODEV ;
}
domain = handle - > perf_ops - > device_domain_id ( cpu_dev ) ;
if ( domain < 0 )
return domain ;
/* Get the power cost of the performance domain. */
Hz = * KHz * 1000 ;
ret = handle - > perf_ops - > est_power_get ( handle , domain , & Hz , power ) ;
if ( ret )
return ret ;
/* The EM framework specifies the frequency in KHz. */
* KHz = Hz / 1000 ;
return 0 ;
}
2017-06-18 11:38:11 +03:00
static int scmi_cpufreq_init ( struct cpufreq_policy * policy )
{
2019-02-04 14:09:52 +03:00
int ret , nr_opp ;
2017-06-18 11:38:11 +03:00
unsigned int latency ;
struct device * cpu_dev ;
struct scmi_data * priv ;
struct cpufreq_frequency_table * freq_table ;
2019-02-04 14:09:52 +03:00
struct em_data_callback em_cb = EM_DATA_CB ( scmi_get_cpu_power ) ;
2017-06-18 11:38:11 +03:00
cpu_dev = get_cpu_device ( policy - > cpu ) ;
if ( ! cpu_dev ) {
pr_err ( " failed to get cpu%d device \n " , policy - > cpu ) ;
return - ENODEV ;
}
2018-05-09 19:52:06 +03:00
ret = handle - > perf_ops - > device_opps_add ( handle , cpu_dev ) ;
2017-06-18 11:38:11 +03:00
if ( ret ) {
dev_warn ( cpu_dev , " failed to add opps to the device \n " ) ;
return ret ;
}
ret = scmi_get_sharing_cpus ( cpu_dev , policy - > cpus ) ;
if ( ret ) {
dev_warn ( cpu_dev , " failed to get sharing cpumask \n " ) ;
return ret ;
}
ret = dev_pm_opp_set_sharing_cpus ( cpu_dev , policy - > cpus ) ;
if ( ret ) {
dev_err ( cpu_dev , " %s: failed to mark OPPs as shared: %d \n " ,
__func__ , ret ) ;
return ret ;
}
2019-02-04 14:09:52 +03:00
nr_opp = dev_pm_opp_get_opp_count ( cpu_dev ) ;
if ( nr_opp < = 0 ) {
2017-06-18 11:38:11 +03:00
dev_dbg ( cpu_dev , " OPP table is not ready, deferring probe \n " ) ;
ret = - EPROBE_DEFER ;
goto out_free_opp ;
}
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
ret = - ENOMEM ;
goto out_free_opp ;
}
ret = dev_pm_opp_init_cpufreq_table ( cpu_dev , & freq_table ) ;
if ( ret ) {
dev_err ( cpu_dev , " failed to init cpufreq table: %d \n " , ret ) ;
goto out_free_priv ;
}
priv - > cpu_dev = cpu_dev ;
priv - > domain_id = handle - > perf_ops - > device_domain_id ( cpu_dev ) ;
policy - > driver_data = priv ;
2018-04-03 13:07:38 +03:00
policy - > freq_table = freq_table ;
2017-06-18 11:38:11 +03:00
/* SCMI allows DVFS request for any domain from any CPU */
policy - > dvfs_possible_from_any_cpu = true ;
2018-05-09 19:52:06 +03:00
latency = handle - > perf_ops - > transition_latency_get ( handle , cpu_dev ) ;
2017-06-18 11:38:11 +03:00
if ( ! latency )
latency = CPUFREQ_ETERNAL ;
policy - > cpuinfo . transition_latency = latency ;
2017-07-20 16:43:07 +03:00
policy - > fast_switch_possible = true ;
2019-02-04 14:09:52 +03:00
em_register_perf_domain ( policy - > cpus , nr_opp , & em_cb ) ;
2017-06-18 11:38:11 +03:00
return 0 ;
out_free_priv :
kfree ( priv ) ;
out_free_opp :
2019-01-04 12:44:33 +03:00
dev_pm_opp_remove_all_dynamic ( cpu_dev ) ;
2017-06-18 11:38:11 +03:00
return ret ;
}
static int scmi_cpufreq_exit ( struct cpufreq_policy * policy )
{
struct scmi_data * priv = policy - > driver_data ;
dev_pm_opp_free_cpufreq_table ( priv - > cpu_dev , & policy - > freq_table ) ;
2019-01-04 12:44:33 +03:00
dev_pm_opp_remove_all_dynamic ( priv - > cpu_dev ) ;
2019-02-16 19:31:48 +03:00
kfree ( priv ) ;
2017-06-18 11:38:11 +03:00
return 0 ;
}
static struct cpufreq_driver scmi_cpufreq_driver = {
. name = " scmi " ,
. flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
2019-01-29 07:55:14 +03:00
CPUFREQ_NEED_INITIAL_FREQ_CHECK |
CPUFREQ_IS_COOLING_DEV ,
2017-06-18 11:38:11 +03:00
. verify = cpufreq_generic_frequency_table_verify ,
. attr = cpufreq_generic_attr ,
. target_index = scmi_cpufreq_set_target ,
2017-07-20 16:43:07 +03:00
. fast_switch = scmi_cpufreq_fast_switch ,
2017-06-18 11:38:11 +03:00
. get = scmi_cpufreq_get_rate ,
. init = scmi_cpufreq_init ,
. exit = scmi_cpufreq_exit ,
} ;
static int scmi_cpufreq_probe ( struct scmi_device * sdev )
{
int ret ;
handle = sdev - > handle ;
if ( ! handle | | ! handle - > perf_ops )
return - ENODEV ;
ret = cpufreq_register_driver ( & scmi_cpufreq_driver ) ;
if ( ret ) {
dev_err ( & sdev - > dev , " %s: registering cpufreq failed, err: %d \n " ,
__func__ , ret ) ;
}
return ret ;
}
static void scmi_cpufreq_remove ( struct scmi_device * sdev )
{
cpufreq_unregister_driver ( & scmi_cpufreq_driver ) ;
}
static const struct scmi_device_id scmi_id_table [ ] = {
{ SCMI_PROTOCOL_PERF } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( scmi , scmi_id_table ) ;
static struct scmi_driver scmi_cpufreq_drv = {
. name = " scmi-cpufreq " ,
. probe = scmi_cpufreq_probe ,
. remove = scmi_cpufreq_remove ,
. id_table = scmi_id_table ,
} ;
module_scmi_driver ( scmi_cpufreq_drv ) ;
MODULE_AUTHOR ( " Sudeep Holla <sudeep.holla@arm.com> " ) ;
MODULE_DESCRIPTION ( " ARM SCMI CPUFreq interface driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;