2005-04-17 02:20:36 +04:00
/*
* This file was based upon code in Powertweak Linux ( http : //powertweak.sf.net)
2009-01-18 08:00:04 +03:00
* ( C ) 2000 - 2003 Dave Jones , Arjan van de Ven , Janne Pänkälä ,
* Dominik Brodowski .
2005-04-17 02:20:36 +04:00
*
* Licensed under the terms of the GNU GPL License version 2.
*
* BIG FAT DISCLAIMER : Work in progress code . Possibly * dangerous *
*/
# include <linux/kernel.h>
2006-02-28 08:43:23 +03:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/ioport.h>
2008-07-28 23:08:16 +04:00
# include <linux/timex.h>
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
2012-01-26 03:09:12 +04:00
# include <asm/cpu_device_id.h>
2009-01-18 08:00:04 +03:00
# include <asm/msr.h>
2008-07-28 23:08:16 +04:00
# define POWERNOW_IOPORT 0xfff0 / * it doesn't matter where, as long
as it is unused */
2005-04-17 02:20:36 +04:00
2009-01-18 08:00:04 +03:00
# define PFX "powernow-k6: "
2005-04-17 02:20:36 +04:00
static unsigned int busfreq ; /* FSB, in 10 kHz */
static unsigned int max_multiplier ;
/* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */
static struct cpufreq_frequency_table clock_ratio [ ] = {
{ 45 , /* 000 -> 4.5x */ 0 } ,
{ 50 , /* 001 -> 5.0x */ 0 } ,
{ 40 , /* 010 -> 4.0x */ 0 } ,
{ 55 , /* 011 -> 5.5x */ 0 } ,
{ 20 , /* 100 -> 2.0x */ 0 } ,
{ 30 , /* 101 -> 3.0x */ 0 } ,
{ 60 , /* 110 -> 6.0x */ 0 } ,
{ 35 , /* 111 -> 3.5x */ 0 } ,
{ 0 , CPUFREQ_TABLE_END }
} ;
/**
* powernow_k6_get_cpu_multiplier - returns the current FSB multiplier
*
* Returns the current setting of the frequency multiplier . Core clock
* speed is frequency of the Front - Side Bus multiplied with this value .
*/
static int powernow_k6_get_cpu_multiplier ( void )
{
2009-01-18 08:00:04 +03:00
u64 invalue = 0 ;
u32 msrval ;
2006-02-28 08:43:23 +03:00
2005-04-17 02:20:36 +04:00
msrval = POWERNOW_IOPORT + 0x1 ;
wrmsr ( MSR_K6_EPMR , msrval , 0 ) ; /* enable the PowerNow port */
2008-07-28 23:08:16 +04:00
invalue = inl ( POWERNOW_IOPORT + 0x8 ) ;
2005-04-17 02:20:36 +04:00
msrval = POWERNOW_IOPORT + 0x0 ;
wrmsr ( MSR_K6_EPMR , msrval , 0 ) ; /* disable it again */
return clock_ratio [ ( invalue > > 5 ) & 7 ] . index ;
}
/**
* powernow_k6_set_state - set the PowerNow ! multiplier
* @ best_i : clock_ratio [ best_i ] is the target multiplier
*
* Tries to change the PowerNow ! multiplier
*/
2013-03-24 10:26:43 +04:00
static void powernow_k6_set_state ( struct cpufreq_policy * policy ,
unsigned int best_i )
2005-04-17 02:20:36 +04:00
{
2009-01-18 08:00:04 +03:00
unsigned long outvalue = 0 , invalue = 0 ;
unsigned long msrval ;
struct cpufreq_freqs freqs ;
2005-04-17 02:20:36 +04:00
if ( clock_ratio [ best_i ] . index > max_multiplier ) {
2009-01-18 08:00:04 +03:00
printk ( KERN_ERR PFX " invalid target frequency \n " ) ;
2005-04-17 02:20:36 +04:00
return ;
}
freqs . old = busfreq * powernow_k6_get_cpu_multiplier ( ) ;
freqs . new = busfreq * clock_ratio [ best_i ] . index ;
2006-02-28 08:43:23 +03:00
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_PRECHANGE ) ;
2005-04-17 02:20:36 +04:00
/* we now need to transform best_i to the BVC format, see AMD#23446 */
outvalue = ( 1 < < 12 ) | ( 1 < < 10 ) | ( 1 < < 9 ) | ( best_i < < 5 ) ;
msrval = POWERNOW_IOPORT + 0x1 ;
wrmsr ( MSR_K6_EPMR , msrval , 0 ) ; /* enable the PowerNow port */
2008-07-28 23:08:16 +04:00
invalue = inl ( POWERNOW_IOPORT + 0x8 ) ;
2005-04-17 02:20:36 +04:00
invalue = invalue & 0xf ;
outvalue = outvalue | invalue ;
2008-07-28 23:08:16 +04:00
outl ( outvalue , ( POWERNOW_IOPORT + 0x8 ) ) ;
2005-04-17 02:20:36 +04:00
msrval = POWERNOW_IOPORT + 0x0 ;
wrmsr ( MSR_K6_EPMR , msrval , 0 ) ; /* disable it again */
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_POSTCHANGE ) ;
2005-04-17 02:20:36 +04:00
return ;
}
/**
* powernow_k6_verify - verifies a new CPUfreq policy
* @ policy : new policy
*
* Policy must be within lowest and highest possible CPU Frequency ,
* and at least one possible state must be within min and max .
*/
static int powernow_k6_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , & clock_ratio [ 0 ] ) ;
}
/**
* powernow_k6_setpolicy - sets a new CPUFreq policy
* @ policy : new policy
* @ target_freq : the target frequency
2009-01-18 08:00:04 +03:00
* @ relation : how that frequency relates to achieved frequency
* ( CPUFREQ_RELATION_L or CPUFREQ_RELATION_H )
2005-04-17 02:20:36 +04:00
*
* sets a new CPUFreq policy
*/
2008-07-28 23:08:16 +04:00
static int powernow_k6_target ( struct cpufreq_policy * policy ,
2005-04-17 02:20:36 +04:00
unsigned int target_freq ,
unsigned int relation )
{
2009-01-18 08:00:04 +03:00
unsigned int newstate = 0 ;
2005-04-17 02:20:36 +04:00
2009-01-18 08:00:04 +03:00
if ( cpufreq_frequency_table_target ( policy , & clock_ratio [ 0 ] ,
target_freq , relation , & newstate ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2013-03-24 10:26:43 +04:00
powernow_k6_set_state ( policy , newstate ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int powernow_k6_cpu_init ( struct cpufreq_policy * policy )
{
2009-01-18 08:00:04 +03:00
unsigned int i , f ;
2005-04-17 02:20:36 +04:00
int result ;
if ( policy - > cpu ! = 0 )
return - ENODEV ;
/* get frequencies */
max_multiplier = powernow_k6_get_cpu_multiplier ( ) ;
busfreq = cpu_khz / max_multiplier ;
/* table init */
2008-07-28 23:08:16 +04:00
for ( i = 0 ; ( clock_ratio [ i ] . frequency ! = CPUFREQ_TABLE_END ) ; i + + ) {
2009-01-18 08:00:04 +03:00
f = clock_ratio [ i ] . index ;
if ( f > max_multiplier )
2005-04-17 02:20:36 +04:00
clock_ratio [ i ] . frequency = CPUFREQ_ENTRY_INVALID ;
else
2009-01-18 08:00:04 +03:00
clock_ratio [ i ] . frequency = busfreq * f ;
2005-04-17 02:20:36 +04:00
}
/* cpuinfo and default policy values */
2009-10-25 21:45:57 +03:00
policy - > cpuinfo . transition_latency = 200000 ;
2005-04-17 02:20:36 +04:00
policy - > cur = busfreq * max_multiplier ;
result = cpufreq_frequency_table_cpuinfo ( policy , clock_ratio ) ;
if ( result )
2008-07-28 23:08:16 +04:00
return result ;
2005-04-17 02:20:36 +04:00
cpufreq_frequency_table_get_attr ( clock_ratio , policy - > cpu ) ;
return 0 ;
}
static int powernow_k6_cpu_exit ( struct cpufreq_policy * policy )
{
unsigned int i ;
2008-07-28 23:08:16 +04:00
for ( i = 0 ; i < 8 ; i + + ) {
if ( i = = max_multiplier )
2013-03-24 10:26:43 +04:00
powernow_k6_set_state ( policy , i ) ;
2005-04-17 02:20:36 +04:00
}
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
2006-02-28 08:43:23 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static unsigned int powernow_k6_get ( unsigned int cpu )
{
2009-01-18 08:00:04 +03:00
unsigned int ret ;
ret = ( busfreq * powernow_k6_get_cpu_multiplier ( ) ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
2008-07-28 23:08:16 +04:00
static struct freq_attr * powernow_k6_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 powernow_k6_driver = {
2006-02-28 08:43:23 +03:00
. verify = powernow_k6_verify ,
. target = powernow_k6_target ,
2005-04-17 02:20:36 +04:00
. init = powernow_k6_cpu_init ,
. exit = powernow_k6_cpu_exit ,
. get = powernow_k6_get ,
. name = " powernow-k6 " ,
. owner = THIS_MODULE ,
. attr = powernow_k6_attr ,
} ;
2012-01-26 03:09:12 +04:00
static const struct x86_cpu_id powernow_k6_ids [ ] = {
{ X86_VENDOR_AMD , 5 , 12 } ,
{ X86_VENDOR_AMD , 5 , 13 } ,
{ }
} ;
2012-02-12 03:04:12 +04:00
MODULE_DEVICE_TABLE ( x86cpu , powernow_k6_ids ) ;
2005-04-17 02:20:36 +04:00
/**
* powernow_k6_init - initializes the k6 PowerNow ! CPUFreq driver
*
* Initializes the K6 PowerNow ! support . Returns - ENODEV on unsupported
* devices , - EINVAL or - ENOMEM on problems during initiatization , and zero
* on success .
*/
static int __init powernow_k6_init ( void )
2006-02-28 08:43:23 +03:00
{
2012-01-26 03:09:12 +04:00
if ( ! x86_match_cpu ( powernow_k6_ids ) )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
if ( ! request_region ( POWERNOW_IOPORT , 16 , " PowerNow! " ) ) {
2009-01-18 08:00:04 +03:00
printk ( KERN_INFO PFX " PowerNow IOPORT region already used. \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
if ( cpufreq_register_driver ( & powernow_k6_driver ) ) {
2008-07-28 23:08:16 +04:00
release_region ( POWERNOW_IOPORT , 16 ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
return 0 ;
}
/**
* powernow_k6_exit - unregisters AMD K6 - 2 + / 3 + PowerNow ! support
*
* Unregisters AMD K6 - 2 + / K6 - 3 + PowerNow ! support .
*/
static void __exit powernow_k6_exit ( void )
{
cpufreq_unregister_driver ( & powernow_k6_driver ) ;
2008-07-28 23:08:16 +04:00
release_region ( POWERNOW_IOPORT , 16 ) ;
2005-04-17 02:20:36 +04:00
}
2009-01-18 08:00:04 +03:00
MODULE_AUTHOR ( " Arjan van de Ven, Dave Jones <davej@redhat.com>, "
" Dominik Brodowski <linux@brodo.de> " ) ;
2008-07-28 23:08:16 +04:00
MODULE_DESCRIPTION ( " PowerNow! driver for AMD K6-2+ / K6-3+ processors. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2005-04-17 02:20:36 +04:00
module_init ( powernow_k6_init ) ;
module_exit ( powernow_k6_exit ) ;