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
*
*/
2016-04-05 23:28:25 +03:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/cpufreq.h>
2012-01-26 03:09:12 +04:00
# include <asm/cpu_device_id.h>
2005-04-17 02:20:36 +04:00
# 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 [ ] = {
2014-03-28 17:41:47 +04:00
{ 0 , 0 , 1000 } ,
{ 0 , 1 , 2000 } ,
{ 0 , 2 , 4000 } ,
{ 0 , 3 , 8000 } ,
{ 0 , 4 , 16000 } ,
{ 0 , 5 , 33000 } ,
{ 0 , 6 , 66000 } ,
{ 0 , 7 , 99000 } ,
{ 0 , 0 , CPUFREQ_TABLE_END } ,
2005-04-17 02:20:36 +04:00
} ;
/**
* 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
}
2013-10-25 18:15:48 +04:00
static int elanfreq_target ( struct cpufreq_policy * policy ,
unsigned int state )
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 ( ) ;
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 ) ;
2014-04-26 00:15:38 +04:00
struct cpufreq_frequency_table * pos ;
2005-04-17 02:20:36 +04:00
/* 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 */
2014-04-26 00:15:38 +04:00
cpufreq_for_each_entry ( pos , elanfreq_table )
if ( pos - > frequency > max_freq )
pos - > frequency = CPUFREQ_ENTRY_INVALID ;
2005-04-17 02:20:36 +04:00
2013-09-16 17:26:15 +04:00
return cpufreq_table_validate_and_show ( policy , elanfreq_table ) ;
2005-04-17 02:20:36 +04:00
}
# 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 ) ;
2016-04-05 23:28:24 +03:00
pr_warn ( " You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please! \n " ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
__setup ( " elanfreq= " , elanfreq_setup ) ;
# endif
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 ,
2017-07-19 13:12:48 +03:00
. flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING ,
2013-10-03 18:58:05 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = elanfreq_target ,
2005-04-17 02:20:36 +04:00
. init = elanfreq_cpu_init ,
. name = " elanfreq " ,
2013-10-03 18:58:05 +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 elan_id [ ] = {
{ X86_VENDOR_AMD , 4 , 10 , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , elan_id ) ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
static int __init elanfreq_init ( void )
{
2012-01-26 03:09:12 +04:00
if ( ! x86_match_cpu ( elan_id ) )
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 " ) ;
2009-01-18 06:50:33 +03:00
MODULE_AUTHOR ( " Robert Schwebel <r.schwebel@pengutronix.de>, "
" Sven Geggus <sven@geggus.net> " ) ;
2005-04-17 02:20:36 +04:00
MODULE_DESCRIPTION ( " cpufreq driver for AMD's Elan CPUs " ) ;
module_init ( elanfreq_init ) ;
module_exit ( elanfreq_exit ) ;