/*
* linux / drivers / cpufreq / cpufreq_userspace . c
*
* Copyright ( C ) 2001 Russell King
* ( C ) 2002 - 2004 Dominik Brodowski < linux @ brodo . de >
*
* 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 .
*
*/
# include <linux/config.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/smp.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/interrupt.h>
# include <linux/cpufreq.h>
# include <linux/types.h>
# include <linux/fs.h>
# include <linux/sysfs.h>
# include <linux/mutex.h>
# include <asm/uaccess.h>
/**
* A few values needed by the userspace governor
*/
static unsigned int cpu_max_freq [ NR_CPUS ] ;
static unsigned int cpu_min_freq [ NR_CPUS ] ;
static unsigned int cpu_cur_freq [ NR_CPUS ] ; /* current CPU freq */
static unsigned int cpu_set_freq [ NR_CPUS ] ; /* CPU freq desired by userspace */
static unsigned int cpu_is_managed [ NR_CPUS ] ;
static struct cpufreq_policy current_policy [ NR_CPUS ] ;
static DEFINE_MUTEX ( userspace_mutex ) ;
# define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_GOVERNOR, "userspace", msg)
/* keep track of frequency transitions */
static int
userspace_cpufreq_notifier ( struct notifier_block * nb , unsigned long val ,
void * data )
{
struct cpufreq_freqs * freq = data ;
dprintk ( " saving cpu_cur_freq of cpu %u to be %u kHz \n " , freq - > cpu , freq - > new ) ;
cpu_cur_freq [ freq - > cpu ] = freq - > new ;
return 0 ;
}
static struct notifier_block userspace_cpufreq_notifier_block = {
. notifier_call = userspace_cpufreq_notifier
} ;
/**
* cpufreq_set - set the CPU frequency
* @ freq : target frequency in kHz
* @ cpu : CPU for which the frequency is to be set
*
* Sets the CPU frequency to freq .
*/
static int cpufreq_set ( unsigned int freq , unsigned int cpu )
{
int ret = - EINVAL ;
dprintk ( " cpufreq_set for cpu %u, freq %u kHz \n " , cpu , freq ) ;
mutex_lock ( & userspace_mutex ) ;
if ( ! cpu_is_managed [ cpu ] )
goto err ;
cpu_set_freq [ cpu ] = freq ;
if ( freq < cpu_min_freq [ cpu ] )
freq = cpu_min_freq [ cpu ] ;
if ( freq > cpu_max_freq [ cpu ] )
freq = cpu_max_freq [ cpu ] ;
/*
* We ' re safe from concurrent calls to - > target ( ) here
* as we hold the userspace_mutex lock . If we were calling
* cpufreq_driver_target , a deadlock situation might occur :
* A : cpufreq_set ( lock userspace_mutex ) - > cpufreq_driver_target ( lock policy - > lock )
* B : cpufreq_set_policy ( lock policy - > lock ) - > __cpufreq_governor - > cpufreq_governor_userspace ( lock userspace_mutex )
*/
ret = __cpufreq_driver_target ( & current_policy [ cpu ] , freq ,
CPUFREQ_RELATION_L ) ;
err :
mutex_unlock ( & userspace_mutex ) ;
return ret ;
}
/************************** sysfs interface ************************/
static ssize_t show_speed ( struct cpufreq_policy * policy , char * buf )
{
return sprintf ( buf , " %u \n " , cpu_cur_freq [ policy - > cpu ] ) ;
}
static ssize_t
store_speed ( struct cpufreq_policy * policy , const char * buf , size_t count )
{
unsigned int freq = 0 ;
unsigned int ret ;
ret = sscanf ( buf , " %u " , & freq ) ;
if ( ret ! = 1 )
return - EINVAL ;
cpufreq_set ( freq , policy - > cpu ) ;
return count ;
}
static struct freq_attr freq_attr_scaling_setspeed =
{
. attr = { . name = " scaling_setspeed " , . mode = 0644 , . owner = THIS_MODULE } ,
. show = show_speed ,
. store = store_speed ,
} ;
static int cpufreq_governor_userspace ( struct cpufreq_policy * policy ,
unsigned int event )
{
unsigned int cpu = policy - > cpu ;
switch ( event ) {
case CPUFREQ_GOV_START :
if ( ! cpu_online ( cpu ) )
return - EINVAL ;
BUG_ON ( ! policy - > cur ) ;
mutex_lock ( & userspace_mutex ) ;
cpu_is_managed [ cpu ] = 1 ;
cpu_min_freq [ cpu ] = policy - > min ;
cpu_max_freq [ cpu ] = policy - > max ;
cpu_cur_freq [ cpu ] = policy - > cur ;
cpu_set_freq [ cpu ] = policy - > cur ;
sysfs_create_file ( & policy - > kobj , & freq_attr_scaling_setspeed . attr ) ;
memcpy ( & current_policy [ cpu ] , policy , sizeof ( struct cpufreq_policy ) ) ;
dprintk ( " managing cpu %u started (%u - %u kHz, currently %u kHz) \n " , cpu , cpu_min_freq [ cpu ] , cpu_max_freq [ cpu ] , cpu_cur_freq [ cpu ] ) ;
mutex_unlock ( & userspace_mutex ) ;
break ;
case CPUFREQ_GOV_STOP :
mutex_lock ( & userspace_mutex ) ;
cpu_is_managed [ cpu ] = 0 ;
cpu_min_freq [ cpu ] = 0 ;
cpu_max_freq [ cpu ] = 0 ;
cpu_set_freq [ cpu ] = 0 ;
sysfs_remove_file ( & policy - > kobj , & freq_attr_scaling_setspeed . attr ) ;
dprintk ( " managing cpu %u stopped \n " , cpu ) ;
mutex_unlock ( & userspace_mutex ) ;
break ;
case CPUFREQ_GOV_LIMITS :
mutex_lock ( & userspace_mutex ) ;
cpu_min_freq [ cpu ] = policy - > min ;
cpu_max_freq [ cpu ] = policy - > max ;
dprintk ( " limit event for cpu %u: %u - %u kHz, currently %u kHz, last set to %u kHz \n " , cpu , cpu_min_freq [ cpu ] , cpu_max_freq [ cpu ] , cpu_cur_freq [ cpu ] , cpu_set_freq [ cpu ] ) ;
if ( policy - > max < cpu_set_freq [ cpu ] ) {
__cpufreq_driver_target ( & current_policy [ cpu ] , policy - > max ,
CPUFREQ_RELATION_H ) ;
} else if ( policy - > min > cpu_set_freq [ cpu ] ) {
__cpufreq_driver_target ( & current_policy [ cpu ] , policy - > min ,
CPUFREQ_RELATION_L ) ;
} else {
__cpufreq_driver_target ( & current_policy [ cpu ] , cpu_set_freq [ cpu ] ,
CPUFREQ_RELATION_L ) ;
}
memcpy ( & current_policy [ cpu ] , policy , sizeof ( struct cpufreq_policy ) ) ;
mutex_unlock ( & userspace_mutex ) ;
break ;
}
return 0 ;
}
struct cpufreq_governor cpufreq_gov_userspace = {
. name = " userspace " ,
. governor = cpufreq_governor_userspace ,
. owner = THIS_MODULE ,
} ;
EXPORT_SYMBOL ( cpufreq_gov_userspace ) ;
static int __init cpufreq_gov_userspace_init ( void )
{
cpufreq_register_notifier ( & userspace_cpufreq_notifier_block , CPUFREQ_TRANSITION_NOTIFIER ) ;
return cpufreq_register_governor ( & cpufreq_gov_userspace ) ;
}
static void __exit cpufreq_gov_userspace_exit ( void )
{
cpufreq_unregister_governor ( & cpufreq_gov_userspace ) ;
cpufreq_unregister_notifier ( & userspace_cpufreq_notifier_block , CPUFREQ_TRANSITION_NOTIFIER ) ;
}
MODULE_AUTHOR ( " Dominik Brodowski <linux@brodo.de>, Russell King <rmk@arm.linux.org.uk> " ) ;
MODULE_DESCRIPTION ( " CPUfreq policy governor 'userspace' " ) ;
MODULE_LICENSE ( " GPL " ) ;
fs_initcall ( cpufreq_gov_userspace_init ) ;
module_exit ( cpufreq_gov_userspace_exit ) ;