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 */
2013-03-30 14:55:15 +04:00
return clock_ratio [ ( invalue > > 5 ) & 7 ] . driver_data ;
2005-04-17 02:20:36 +04:00
}
/**
2013-10-25 18:15:48 +04:00
* powernow_k6_target - set the PowerNow ! multiplier
2005-04-17 02:20:36 +04:00
* @ best_i : clock_ratio [ best_i ] is the target multiplier
*
* Tries to change the PowerNow ! multiplier
*/
2013-10-25 18:15:48 +04:00
static int powernow_k6_target ( struct cpufreq_policy * policy ,
2013-03-24 10:26:43 +04:00
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
2013-03-30 14:55:15 +04:00
if ( clock_ratio [ best_i ] . driver_data > max_multiplier ) {
2009-01-18 08:00:04 +03:00
printk ( KERN_ERR PFX " invalid target frequency \n " ) ;
2013-10-25 18:15:48 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
freqs . old = busfreq * powernow_k6_get_cpu_multiplier ( ) ;
2013-03-30 14:55:15 +04:00
freqs . new = busfreq * clock_ratio [ best_i ] . driver_data ;
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 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
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 + + ) {
2013-03-30 14:55:15 +04:00
f = clock_ratio [ i ] . driver_data ;
2009-01-18 08:00:04 +03:00
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
2013-09-16 17:26:27 +04:00
return cpufreq_table_validate_and_show ( policy , clock_ratio ) ;
2005-04-17 02:20:36 +04:00
}
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-10-25 18:15:48 +04:00
powernow_k6_target ( 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
}
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver powernow_k6_driver = {
2013-10-03 18:58:17 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = 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 " ,
2013-10-03 18:58:17 +04:00
. attr = cpufreq_generic_attr ,
2005-04-17 02:20:36 +04:00
} ;
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 ) ;