2005-04-17 02:20:36 +04:00
/*
2006-02-28 08:43:23 +03:00
* elanfreq : cpufreq driver for the AMD ELAN family
2005-04-17 02:20:36 +04:00
*
* ( c ) Copyright 2002 Robert Schwebel < r . schwebel @ pengutronix . de >
*
2006-02-28 08:43:23 +03:00
* Parts of this code are ( c ) Sven Geggus < sven @ geggus . net >
2005-04-17 02:20:36 +04:00
*
2006-02-28 08:43:23 +03:00
* All Rights Reserved .
2005-04-17 02:20:36 +04:00
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
2006-02-28 08:43:23 +03:00
* 2 of the License , or ( at your option ) any later version .
2005-04-17 02:20:36 +04:00
*
* 2002 - 02 - 13 : - initial revision for 2.4 .18 - pre9 by Robert Schwebel
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/cpufreq.h>
# include <asm/msr.h>
2008-07-28 15:00:49 +04:00
# include <linux/timex.h>
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
# define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */
2005-04-17 02:20:36 +04:00
# define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */
/* Module parameter */
static int max_freq ;
struct s_elan_multiplier {
int clock ; /* frequency in kHz */
int val40h ; /* PMU Force Mode register */
int val80h ; /* CPU Clock Speed Register */
} ;
/*
2006-02-28 08:43:23 +03:00
* It is important that the frequencies
2005-04-17 02:20:36 +04:00
* are listed in ascending order here !
*/
2008-07-30 21:01:42 +04:00
static struct s_elan_multiplier elan_multiplier [ ] = {
2005-04-17 02:20:36 +04:00
{ 1000 , 0x02 , 0x18 } ,
{ 2000 , 0x02 , 0x10 } ,
{ 4000 , 0x02 , 0x08 } ,
{ 8000 , 0x00 , 0x00 } ,
{ 16000 , 0x00 , 0x02 } ,
{ 33000 , 0x00 , 0x04 } ,
{ 66000 , 0x01 , 0x04 } ,
{ 99000 , 0x01 , 0x05 }
} ;
static struct cpufreq_frequency_table elanfreq_table [ ] = {
{ 0 , 1000 } ,
{ 1 , 2000 } ,
{ 2 , 4000 } ,
{ 3 , 8000 } ,
{ 4 , 16000 } ,
{ 5 , 33000 } ,
{ 6 , 66000 } ,
{ 7 , 99000 } ,
{ 0 , CPUFREQ_TABLE_END } ,
} ;
/**
* elanfreq_get_cpu_frequency : determine current cpu speed
*
* Finds out at which frequency the CPU of the Elan SOC runs
2006-02-28 08:43:23 +03:00
* at the moment . Frequencies from 1 to 33 MHz are generated
2005-04-17 02:20:36 +04:00
* the normal way , 66 and 99 MHz are called " Hyperspeed Mode "
2006-02-28 08:43:23 +03:00
* and have the rest of the chip running with 33 MHz .
2005-04-17 02:20:36 +04:00
*/
static unsigned int elanfreq_get_cpu_frequency ( unsigned int cpu )
{
2006-02-28 08:43:23 +03:00
u8 clockspeed_reg ; /* Clock Speed Register */
2005-04-17 02:20:36 +04:00
local_irq_disable ( ) ;
2008-07-28 15:00:49 +04:00
outb_p ( 0x80 , REG_CSCIR ) ;
2006-02-28 08:43:23 +03:00
clockspeed_reg = inb_p ( REG_CSCDR ) ;
2005-04-17 02:20:36 +04:00
local_irq_enable ( ) ;
2006-02-28 08:43:23 +03:00
if ( ( clockspeed_reg & 0xE0 ) = = 0xE0 )
return 0 ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
/* Are we in CPU clock multiplied mode (66/99 MHz)? */
if ( ( clockspeed_reg & 0xE0 ) = = 0xC0 ) {
if ( ( clockspeed_reg & 0x01 ) = = 0 )
2005-04-17 02:20:36 +04:00
return 66000 ;
2006-02-28 08:43:23 +03:00
else
return 99000 ;
}
2005-04-17 02:20:36 +04:00
/* 33 MHz is not 32 MHz... */
2008-07-28 15:00:49 +04:00
if ( ( clockspeed_reg & 0xE0 ) = = 0xA0 )
2005-04-17 02:20:36 +04:00
return 33000 ;
2008-07-28 15:00:49 +04:00
return ( 1 < < ( ( clockspeed_reg & 0xE0 ) > > 5 ) ) * 1000 ;
2005-04-17 02:20:36 +04:00
}
/**
2006-02-28 08:43:23 +03:00
* elanfreq_set_cpu_frequency : Change the CPU core frequency
* @ cpu : cpu number
2005-04-17 02:20:36 +04:00
* @ freq : frequency in kHz
*
2006-02-28 08:43:23 +03:00
* This function takes a frequency value and changes the CPU frequency
2005-04-17 02:20:36 +04:00
* according to this . Note that the frequency has to be checked by
* elanfreq_validatespeed ( ) for correctness !
2006-02-28 08:43:23 +03:00
*
* There is no return value .
2005-04-17 02:20:36 +04:00
*/
2008-07-28 15:00:49 +04:00
static void elanfreq_set_cpu_state ( unsigned int state )
2006-02-28 08:43:23 +03:00
{
2005-04-17 02:20:36 +04:00
struct cpufreq_freqs freqs ;
freqs . old = elanfreq_get_cpu_frequency ( 0 ) ;
freqs . new = elan_multiplier [ state ] . clock ;
freqs . cpu = 0 ; /* elanfreq.c is UP only driver */
2006-02-28 08:43:23 +03:00
2005-04-17 02:20:36 +04:00
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
2006-02-28 08:43:23 +03:00
printk ( KERN_INFO " elanfreq: attempting to set frequency to %i kHz \n " ,
elan_multiplier [ state ] . clock ) ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
/*
* Access to the Elan ' s internal registers is indexed via
* 0x22 : Chip Setup & Control Register Index Register ( CSCI )
* 0x23 : Chip Setup & Control Register Data Register ( CSCD )
2005-04-17 02:20:36 +04:00
*
*/
2006-02-28 08:43:23 +03:00
/*
* 0x40 is the Power Management Unit ' s Force Mode Register .
2005-04-17 02:20:36 +04:00
* Bit 6 enables Hyperspeed Mode ( 66 / 100 MHz core frequency )
*/
local_irq_disable ( ) ;
2008-07-28 15:00:49 +04:00
outb_p ( 0x40 , REG_CSCIR ) ; /* Disable hyperspeed mode */
outb_p ( 0x00 , REG_CSCDR ) ;
2005-04-17 02:20:36 +04:00
local_irq_enable ( ) ; /* wait till internal pipelines and */
udelay ( 1000 ) ; /* buffers have cleaned up */
local_irq_disable ( ) ;
/* now, set the CPU clock speed register (0x80) */
2008-07-28 15:00:49 +04:00
outb_p ( 0x80 , REG_CSCIR ) ;
outb_p ( elan_multiplier [ state ] . val80h , REG_CSCDR ) ;
2005-04-17 02:20:36 +04:00
/* now, the hyperspeed bit in PMU Force Mode Register (0x40) */
2008-07-28 15:00:49 +04:00
outb_p ( 0x40 , REG_CSCIR ) ;
outb_p ( elan_multiplier [ state ] . val40h , REG_CSCDR ) ;
2005-04-17 02:20:36 +04:00
udelay ( 10000 ) ;
local_irq_enable ( ) ;
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
} ;
/**
* elanfreq_validatespeed : test if frequency range is valid
2006-02-28 08:43:23 +03:00
* @ policy : the policy to validate
2005-04-17 02:20:36 +04:00
*
2006-02-28 08:43:23 +03:00
* This function checks if a given frequency range in kHz is valid
* for the hardware supported by the driver .
2005-04-17 02:20:36 +04:00
*/
2008-07-28 15:00:49 +04:00
static int elanfreq_verify ( struct cpufreq_policy * policy )
2005-04-17 02:20:36 +04:00
{
return cpufreq_frequency_table_verify ( policy , & elanfreq_table [ 0 ] ) ;
}
2008-07-28 15:00:49 +04:00
static int elanfreq_target ( struct cpufreq_policy * policy ,
2006-02-28 08:43:23 +03:00
unsigned int target_freq ,
2005-04-17 02:20:36 +04:00
unsigned int relation )
{
2006-02-28 08:43:23 +03:00
unsigned int newstate = 0 ;
2005-04-17 02:20:36 +04:00
if ( cpufreq_frequency_table_target ( policy , & elanfreq_table [ 0 ] , target_freq , relation , & newstate ) )
return - EINVAL ;
elanfreq_set_cpu_state ( newstate ) ;
return 0 ;
}
/*
* Module init and exit code
*/
static int elanfreq_cpu_init ( struct cpufreq_policy * policy )
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2005-04-17 02:20:36 +04:00
unsigned int i ;
int result ;
/* capability check */
if ( ( c - > x86_vendor ! = X86_VENDOR_AMD ) | |
2008-07-28 15:00:49 +04:00
( c - > x86 ! = 4 ) | | ( c - > x86_model ! = 10 ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
/* max freq */
if ( ! max_freq )
max_freq = elanfreq_get_cpu_frequency ( 0 ) ;
/* table init */
2008-07-28 15:00:49 +04:00
for ( i = 0 ; ( elanfreq_table [ i ] . frequency ! = CPUFREQ_TABLE_END ) ; i + + ) {
2005-04-17 02:20:36 +04:00
if ( elanfreq_table [ i ] . frequency > max_freq )
elanfreq_table [ i ] . frequency = CPUFREQ_ENTRY_INVALID ;
}
/* cpuinfo and default policy values */
policy - > cpuinfo . transition_latency = CPUFREQ_ETERNAL ;
policy - > cur = elanfreq_get_cpu_frequency ( 0 ) ;
result = cpufreq_frequency_table_cpuinfo ( policy , elanfreq_table ) ;
if ( result )
2008-07-28 15:00:49 +04:00
return result ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
cpufreq_frequency_table_get_attr ( elanfreq_table , policy - > cpu ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int elanfreq_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
return 0 ;
}
# ifndef MODULE
/**
* elanfreq_setup - elanfreq command line parameter parsing
*
* elanfreq command line parameter . Use :
* elanfreq = 66000
* to set the maximum CPU frequency to 66 MHz . Note that in
* case you do not give this boot parameter , the maximum
* frequency will fall back to _current_ CPU frequency which
* might be lower . If you build this as a module , use the
* max_freq module parameter instead .
*/
static int __init elanfreq_setup ( char * str )
{
max_freq = simple_strtoul ( str , & str , 0 ) ;
printk ( KERN_WARNING " You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please! \n " ) ;
return 1 ;
}
__setup ( " elanfreq= " , elanfreq_setup ) ;
# endif
2008-07-28 15:00:49 +04:00
static struct freq_attr * elanfreq_attr [ ] = {
2005-04-17 02:20:36 +04:00
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver elanfreq_driver = {
2006-02-28 08:43:23 +03:00
. get = elanfreq_get_cpu_frequency ,
. verify = elanfreq_verify ,
. target = elanfreq_target ,
2005-04-17 02:20:36 +04:00
. init = elanfreq_cpu_init ,
. exit = elanfreq_cpu_exit ,
. name = " elanfreq " ,
. owner = THIS_MODULE ,
. attr = elanfreq_attr ,
} ;
2006-02-28 08:43:23 +03:00
static int __init elanfreq_init ( void )
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2005-04-17 02:20:36 +04:00
/* Test if we have the right hardware */
if ( ( c - > x86_vendor ! = X86_VENDOR_AMD ) | |
2008-07-28 15:00:49 +04:00
( c - > x86 ! = 4 ) | | ( c - > x86_model ! = 10 ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO " elanfreq: error: no Elan processor found! \n " ) ;
2008-07-28 15:00:49 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
return cpufreq_register_driver ( & elanfreq_driver ) ;
}
2006-02-28 08:43:23 +03:00
static void __exit elanfreq_exit ( void )
2005-04-17 02:20:36 +04:00
{
cpufreq_unregister_driver ( & elanfreq_driver ) ;
}
2008-07-28 15:00:49 +04:00
module_param ( max_freq , int , 0444 ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Robert Schwebel <r.schwebel@pengutronix.de>, Sven Geggus <sven@geggus.net> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for AMD's Elan CPUs " ) ;
module_init ( elanfreq_init ) ;
module_exit ( elanfreq_exit ) ;