2014-02-28 17:03:44 +04:00
/*
2015-02-02 18:32:46 +03:00
* ARM / ARM64 generic CPU idle driver .
2014-02-28 17:03:44 +04:00
*
* Copyright ( C ) 2014 ARM Ltd .
* Author : Lorenzo Pieralisi < lorenzo . pieralisi @ arm . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2015-02-02 18:32:46 +03:00
# define pr_fmt(fmt) "CPUidle arm: " fmt
2014-02-28 17:03:44 +04:00
# include <linux/cpuidle.h>
# include <linux/cpumask.h>
# include <linux/cpu_pm.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
2015-03-05 18:44:42 +03:00
# include <linux/slab.h>
2017-06-12 18:55:10 +03:00
# include <linux/topology.h>
2014-02-28 17:03:44 +04:00
# include <asm/cpuidle.h>
# include "dt_idle_states.h"
/*
2015-02-02 18:32:46 +03:00
* arm_enter_idle_state - Programs CPU to enter the specified state
2014-02-28 17:03:44 +04:00
*
* dev : cpuidle device
* drv : cpuidle driver
* idx : state index
*
* Called from the CPUidle framework to program the device to the
* specified target state selected by the governor .
*/
2015-02-02 18:32:46 +03:00
static int arm_enter_idle_state ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv , int idx )
2014-02-28 17:03:44 +04:00
{
2016-07-19 20:52:56 +03:00
/*
* Pass idle state index to arm_cpuidle_suspend which in turn
* will call the CPU ops suspend protocol with idle index as a
* parameter .
*/
return CPU_PM_CPU_IDLE_ENTER ( arm_cpuidle_suspend , idx ) ;
2014-02-28 17:03:44 +04:00
}
2017-06-12 18:55:10 +03:00
static struct cpuidle_driver arm_idle_driver __initdata = {
2015-02-02 18:32:46 +03:00
. name = " arm_idle " ,
2014-02-28 17:03:44 +04:00
. owner = THIS_MODULE ,
/*
* State at index 0 is standby wfi and considered standard
* on all ARM platforms . If in some platforms simple wfi
* can ' t be used as " state 0 " , DT bindings must be implemented
* to work around this issue and allow installing a special
* handler for idle state index 0.
*/
. states [ 0 ] = {
2015-02-02 18:32:46 +03:00
. enter = arm_enter_idle_state ,
2014-02-28 17:03:44 +04:00
. exit_latency = 1 ,
. target_residency = 1 ,
. power_usage = UINT_MAX ,
. name = " WFI " ,
2015-02-02 18:32:46 +03:00
. desc = " ARM WFI " ,
2014-02-28 17:03:44 +04:00
}
} ;
2015-02-02 18:32:46 +03:00
static const struct of_device_id arm_idle_state_match [ ] __initconst = {
2014-02-28 17:03:44 +04:00
{ . compatible = " arm,idle-state " ,
2015-02-02 18:32:46 +03:00
. data = arm_enter_idle_state } ,
2014-02-28 17:03:44 +04:00
{ } ,
} ;
/*
2017-10-10 08:47:56 +03:00
* arm_idle_init_cpu
2014-02-28 17:03:44 +04:00
*
2015-02-02 18:32:46 +03:00
* Registers the arm specific cpuidle driver with the cpuidle
2014-02-28 17:03:44 +04:00
* framework . It relies on core code to parse the idle states
* and initialize them using driver data structures accordingly .
*/
2017-10-10 08:47:56 +03:00
static int __init arm_idle_init_cpu ( int cpu )
2014-02-28 17:03:44 +04:00
{
2017-10-10 08:47:56 +03:00
int ret ;
2017-06-12 18:55:10 +03:00
struct cpuidle_driver * drv ;
2014-02-28 17:03:44 +04:00
2017-10-10 08:47:56 +03:00
drv = kmemdup ( & arm_idle_driver , sizeof ( * drv ) , GFP_KERNEL ) ;
if ( ! drv )
return - ENOMEM ;
2017-06-12 18:55:10 +03:00
2017-10-10 08:47:56 +03:00
drv - > cpumask = ( struct cpumask * ) cpumask_of ( cpu ) ;
/*
* Initialize idle states data , starting at index 1. This
* driver is DT only , if no DT idle states are detected ( ret
* = = 0 ) let the driver initialization fail accordingly since
* there is no reason to initialize the idle driver if only
* wfi is supported .
*/
ret = dt_init_idle_driver ( drv , arm_idle_state_match , 1 ) ;
if ( ret < = 0 ) {
ret = ret ? : - ENODEV ;
goto out_kfree_drv ;
}
/*
* Call arch CPU operations in order to initialize
* idle states suspend back - end specific data
*/
ret = arm_cpuidle_init ( cpu ) ;
/*
2018-11-01 15:22:38 +03:00
* Allow the initialization to continue for other CPUs , if the reported
2017-10-10 08:47:56 +03:00
* failure is a HW misconfiguration / breakage ( - ENXIO ) .
*/
if ( ret ) {
pr_err ( " CPU %d failed to init idle CPU ops \n " , cpu ) ;
2018-11-01 15:22:38 +03:00
ret = ret = = - ENXIO ? 0 : ret ;
goto out_kfree_drv ;
}
2018-11-01 13:15:58 +03:00
ret = cpuidle_register ( drv , NULL ) ;
if ( ret )
2018-11-01 15:22:38 +03:00
goto out_kfree_drv ;
2014-02-28 17:03:44 +04:00
2015-03-05 18:44:42 +03:00
return 0 ;
2017-10-10 08:47:55 +03:00
out_kfree_drv :
2017-08-31 23:24:36 +03:00
kfree ( drv ) ;
2017-10-10 08:47:56 +03:00
return ret ;
}
/*
* arm_idle_init - Initializes arm cpuidle driver
*
* Initializes arm cpuidle driver for all CPUs , if any CPU fails
* to register cpuidle driver then rollback to cancel all CPUs
* registeration .
*/
static int __init arm_idle_init ( void )
{
int cpu , ret ;
struct cpuidle_driver * drv ;
struct cpuidle_device * dev ;
for_each_possible_cpu ( cpu ) {
ret = arm_idle_init_cpu ( cpu ) ;
if ( ret )
goto out_fail ;
}
return 0 ;
2015-03-05 18:44:42 +03:00
out_fail :
while ( - - cpu > = 0 ) {
dev = per_cpu ( cpuidle_devices , cpu ) ;
2017-10-10 08:47:55 +03:00
drv = cpuidle_get_cpu_driver ( dev ) ;
2018-11-01 13:15:58 +03:00
cpuidle_unregister ( drv ) ;
2017-06-12 18:55:10 +03:00
kfree ( drv ) ;
2015-03-05 18:44:42 +03:00
}
return ret ;
2014-02-28 17:03:44 +04:00
}
2015-02-02 18:32:46 +03:00
device_initcall ( arm_idle_init ) ;