2019-05-29 17:17:58 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-04-09 22:20:41 +03:00
/*
* Copyright ( c ) 2011 - 2014 , The Linux Foundation . All rights reserved .
* Copyright ( c ) 2014 , 2015 , Linaro Ltd .
*
2016-01-04 20:58:28 +03:00
* SAW power controller driver
2015-04-09 22:20:41 +03:00
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_device.h>
# include <linux/err.h>
# include <linux/platform_device.h>
# include <linux/cpuidle.h>
# include <linux/cpu_pm.h>
# include <linux/qcom_scm.h>
2021-07-29 18:56:05 +03:00
# include <soc/qcom/spm.h>
2015-04-09 22:20:41 +03:00
# include <asm/proc-fns.h>
# include <asm/suspend.h>
2020-04-16 11:58:21 +03:00
# include "dt_idle_states.h"
2021-07-29 18:56:05 +03:00
struct cpuidle_qcom_spm_data {
2020-04-16 11:58:21 +03:00
struct cpuidle_driver cpuidle_driver ;
2021-07-29 18:56:05 +03:00
struct spm_driver_data * spm ;
2015-04-09 22:20:41 +03:00
} ;
static int qcom_pm_collapse ( unsigned long int unused )
{
qcom_scm_cpu_power_down ( QCOM_SCM_CPU_PWR_DOWN_L2_ON ) ;
/*
* Returns here only if there was a pending interrupt and we did not
* power down as a result .
*/
return - 1 ;
}
2020-04-16 11:58:21 +03:00
static int qcom_cpu_spc ( struct spm_driver_data * drv )
2015-04-09 22:20:41 +03:00
{
int ret ;
spm_set_low_power_mode ( drv , PM_SLEEP_MODE_SPC ) ;
ret = cpu_suspend ( 0 , qcom_pm_collapse ) ;
/*
* ARM common code executes WFI without calling into our driver and
* if the SPM mode is not reset , then we may accidently power down the
* cpu when we intended only to gate the cpu clock .
* Ensure the state is set to standby before returning .
*/
spm_set_low_power_mode ( drv , PM_SLEEP_MODE_STBY ) ;
return ret ;
}
2020-04-16 11:58:21 +03:00
static int spm_enter_idle_state ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv , int idx )
2015-04-09 22:20:41 +03:00
{
2021-07-29 18:56:05 +03:00
struct cpuidle_qcom_spm_data * data = container_of ( drv , struct cpuidle_qcom_spm_data ,
cpuidle_driver ) ;
2020-04-16 11:58:21 +03:00
2021-07-29 18:56:05 +03:00
return CPU_PM_CPU_IDLE_ENTER_PARAM ( qcom_cpu_spc , idx , data - > spm ) ;
2015-04-09 22:20:41 +03:00
}
2020-04-16 11:58:21 +03:00
static struct cpuidle_driver qcom_spm_idle_driver = {
. name = " qcom_spm " ,
. owner = THIS_MODULE ,
. states [ 0 ] = {
. enter = spm_enter_idle_state ,
. exit_latency = 1 ,
. target_residency = 1 ,
. power_usage = UINT_MAX ,
. name = " WFI " ,
. desc = " ARM WFI " ,
}
} ;
static const struct of_device_id qcom_idle_state_match [ ] = {
{ . compatible = " qcom,idle-state-spc " , . data = spm_enter_idle_state } ,
2015-04-09 22:20:41 +03:00
{ } ,
} ;
2021-07-29 18:56:05 +03:00
static int spm_cpuidle_register ( struct device * cpuidle_dev , int cpu )
2015-04-09 22:20:41 +03:00
{
2021-07-29 18:56:05 +03:00
struct platform_device * pdev = NULL ;
struct device_node * cpu_node , * saw_node ;
struct cpuidle_qcom_spm_data * data = NULL ;
2020-04-16 11:58:21 +03:00
int ret ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
cpu_node = of_cpu_device_node_get ( cpu ) ;
if ( ! cpu_node )
return - ENODEV ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
saw_node = of_parse_phandle ( cpu_node , " qcom,saw " , 0 ) ;
if ( ! saw_node )
return - ENODEV ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
pdev = of_find_device_by_node ( saw_node ) ;
of_node_put ( saw_node ) ;
of_node_put ( cpu_node ) ;
if ( ! pdev )
return - ENODEV ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
data = devm_kzalloc ( cpuidle_dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
data - > spm = dev_get_drvdata ( & pdev - > dev ) ;
if ( ! data - > spm )
return - EINVAL ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
data - > cpuidle_driver = qcom_spm_idle_driver ;
data - > cpuidle_driver . cpumask = ( struct cpumask * ) cpumask_of ( cpu ) ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
ret = dt_init_idle_driver ( & data - > cpuidle_driver ,
qcom_idle_state_match , 1 ) ;
if ( ret < = 0 )
return ret ? : - ENODEV ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
return cpuidle_register ( & data - > cpuidle_driver , NULL ) ;
}
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
static int spm_cpuidle_drv_probe ( struct platform_device * pdev )
2015-04-09 22:20:41 +03:00
{
2020-04-16 11:58:21 +03:00
int cpu , ret ;
if ( ! qcom_scm_is_available ( ) )
return - EPROBE_DEFER ;
2015-04-09 22:20:41 +03:00
2021-12-01 16:05:04 +03:00
ret = qcom_scm_set_warm_boot_addr ( cpu_resume_arm ) ;
if ( ret )
return dev_err_probe ( & pdev - > dev , ret , " set warm boot addr failed " ) ;
2021-07-29 18:56:05 +03:00
for_each_possible_cpu ( cpu ) {
ret = spm_cpuidle_register ( & pdev - > dev , cpu ) ;
if ( ret & & ret ! = - ENODEV ) {
dev_err ( & pdev - > dev ,
" Cannot register for CPU%d: %d \n " , cpu , ret ) ;
}
}
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
return 0 ;
}
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
static struct platform_driver spm_cpuidle_driver = {
. probe = spm_cpuidle_drv_probe ,
. driver = {
. name = " qcom-spm-cpuidle " ,
. suppress_bind_attrs = true ,
} ,
} ;
2015-04-09 22:20:41 +03:00
2021-12-01 16:05:02 +03:00
static bool __init qcom_spm_find_any_cpu ( void )
{
struct device_node * cpu_node , * saw_node ;
for_each_of_cpu_node ( cpu_node ) {
saw_node = of_parse_phandle ( cpu_node , " qcom,saw " , 0 ) ;
if ( of_device_is_available ( saw_node ) ) {
of_node_put ( saw_node ) ;
of_node_put ( cpu_node ) ;
return true ;
}
of_node_put ( saw_node ) ;
}
return false ;
}
2021-07-29 18:56:05 +03:00
static int __init qcom_spm_cpuidle_init ( void )
{
struct platform_device * pdev ;
int ret ;
2015-04-09 22:20:41 +03:00
2021-07-29 18:56:05 +03:00
ret = platform_driver_register ( & spm_cpuidle_driver ) ;
2020-04-16 11:58:21 +03:00
if ( ret )
return ret ;
2021-12-01 16:05:02 +03:00
/* Make sure there is actually any CPU managed by the SPM */
if ( ! qcom_spm_find_any_cpu ( ) )
return 0 ;
2021-07-29 18:56:05 +03:00
pdev = platform_device_register_simple ( " qcom-spm-cpuidle " ,
- 1 , NULL , 0 ) ;
if ( IS_ERR ( pdev ) ) {
platform_driver_unregister ( & spm_cpuidle_driver ) ;
return PTR_ERR ( pdev ) ;
}
2015-04-09 22:20:41 +03:00
return 0 ;
}
2021-07-29 18:56:05 +03:00
device_initcall ( qcom_spm_cpuidle_init ) ;