2005-06-01 06:03:45 +04:00
/*
* sc520_freq . c : cpufreq driver for the AMD Elan sc520
*
* Copyright ( C ) 2005 Sean Young < sean @ mess . org >
*
* 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
* 2 of the License , or ( at your option ) any later version .
*
* Based on elanfreq . c
*
* 2005 - 03 - 30 : - initial revision
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/cpufreq.h>
2009-01-18 09:27:35 +03:00
# include <linux/timex.h>
# include <linux/io.h>
2005-06-01 06:03:45 +04:00
2012-01-26 03:09:12 +04:00
# include <asm/cpu_device_id.h>
2005-06-01 06:03:45 +04:00
# include <asm/msr.h>
# define MMCR_BASE 0xfffef000 /* The default base address */
# define OFFS_CPUCTL 0x2 /* CPU Control Register */
static __u8 __iomem * cpuctl ;
2009-01-18 09:27:35 +03:00
# define PFX "sc520_freq: "
2005-06-01 06:03:45 +04:00
static struct cpufreq_frequency_table sc520_freq_table [ ] = {
{ 0x01 , 100000 } ,
{ 0x02 , 133000 } ,
{ 0 , CPUFREQ_TABLE_END } ,
} ;
static unsigned int sc520_freq_get_cpu_frequency ( unsigned int cpu )
{
u8 clockspeed_reg = * cpuctl ;
switch ( clockspeed_reg & 0x03 ) {
default :
2009-01-18 09:27:35 +03:00
printk ( KERN_ERR PFX " error: cpuctl register has unexpected "
" value %02x \n " , clockspeed_reg ) ;
2005-06-01 06:03:45 +04:00
case 0x01 :
return 100000 ;
case 0x02 :
return 133000 ;
}
}
2009-01-18 09:27:35 +03:00
static void sc520_freq_set_cpu_state ( unsigned int state )
2005-06-01 06:03:45 +04:00
{
struct cpufreq_freqs freqs ;
u8 clockspeed_reg ;
freqs . old = sc520_freq_get_cpu_frequency ( 0 ) ;
freqs . new = sc520_freq_table [ state ] . frequency ;
freqs . cpu = 0 ; /* AMD Elan is UP */
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " attempting to set frequency to %i kHz \n " ,
2005-06-01 06:03:45 +04:00
sc520_freq_table [ state ] . frequency ) ;
local_irq_disable ( ) ;
clockspeed_reg = * cpuctl & ~ 0x03 ;
* cpuctl = clockspeed_reg | sc520_freq_table [ state ] . index ;
local_irq_enable ( ) ;
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
} ;
2009-01-18 09:27:35 +03:00
static int sc520_freq_verify ( struct cpufreq_policy * policy )
2005-06-01 06:03:45 +04:00
{
return cpufreq_frequency_table_verify ( policy , & sc520_freq_table [ 0 ] ) ;
}
2009-01-18 09:27:35 +03:00
static int sc520_freq_target ( struct cpufreq_policy * policy ,
2005-06-01 06:03:45 +04:00
unsigned int target_freq ,
unsigned int relation )
{
unsigned int newstate = 0 ;
2009-01-18 09:27:35 +03:00
if ( cpufreq_frequency_table_target ( policy , sc520_freq_table ,
target_freq , relation , & newstate ) )
2005-06-01 06:03:45 +04:00
return - EINVAL ;
sc520_freq_set_cpu_state ( newstate ) ;
return 0 ;
}
/*
* Module init and exit code
*/
static int sc520_freq_cpu_init ( struct cpufreq_policy * policy )
{
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( 0 ) ;
2005-06-01 06:03:45 +04:00
int result ;
/* capability check */
if ( c - > x86_vendor ! = X86_VENDOR_AMD | |
c - > x86 ! = 4 | | c - > x86_model ! = 9 )
return - ENODEV ;
/* cpuinfo and default policy values */
policy - > cpuinfo . transition_latency = 1000000 ; /* 1ms */
policy - > cur = sc520_freq_get_cpu_frequency ( 0 ) ;
result = cpufreq_frequency_table_cpuinfo ( policy , sc520_freq_table ) ;
if ( result )
2009-01-18 09:27:35 +03:00
return result ;
2005-06-01 06:03:45 +04:00
cpufreq_frequency_table_get_attr ( sc520_freq_table , policy - > cpu ) ;
return 0 ;
}
static int sc520_freq_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
return 0 ;
}
2009-01-18 09:27:35 +03:00
static struct freq_attr * sc520_freq_attr [ ] = {
2005-06-01 06:03:45 +04:00
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver sc520_freq_driver = {
2005-06-01 06:03:45 +04:00
. get = sc520_freq_get_cpu_frequency ,
. verify = sc520_freq_verify ,
. target = sc520_freq_target ,
. init = sc520_freq_cpu_init ,
. exit = sc520_freq_cpu_exit ,
. name = " sc520_freq " ,
. owner = THIS_MODULE ,
. attr = sc520_freq_attr ,
} ;
2012-01-26 03:09:12 +04:00
static const struct x86_cpu_id sc520_ids [ ] = {
{ X86_VENDOR_AMD , 4 , 9 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , sc520_ids ) ;
2005-06-01 06:03:45 +04:00
static int __init sc520_freq_init ( void )
{
2006-10-17 08:32:55 +04:00
int err ;
2005-06-01 06:03:45 +04:00
2012-01-26 03:09:12 +04:00
if ( ! x86_match_cpu ( sc520_ids ) )
2005-06-01 06:03:45 +04:00
return - ENODEV ;
2012-01-26 03:09:12 +04:00
2005-06-01 06:03:45 +04:00
cpuctl = ioremap ( ( unsigned long ) ( MMCR_BASE + OFFS_CPUCTL ) , 1 ) ;
2009-01-18 09:27:35 +03:00
if ( ! cpuctl ) {
2005-06-01 06:03:45 +04:00
printk ( KERN_ERR " sc520_freq: error: failed to remap memory \n " ) ;
return - ENOMEM ;
}
2006-10-17 08:32:55 +04:00
err = cpufreq_register_driver ( & sc520_freq_driver ) ;
if ( err )
iounmap ( cpuctl ) ;
return err ;
2005-06-01 06:03:45 +04:00
}
static void __exit sc520_freq_exit ( void )
{
cpufreq_unregister_driver ( & sc520_freq_driver ) ;
iounmap ( cpuctl ) ;
}
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Sean Young <sean@mess.org> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for AMD's Elan sc520 CPU " ) ;
module_init ( sc520_freq_init ) ;
module_exit ( sc520_freq_exit ) ;