2005-04-17 02:20:36 +04:00
/*
* ( C ) 2001 - 2004 Dave Jones . < davej @ codemonkey . org . uk >
* ( C ) 2002 Padraig Brady . < padraig @ antefacto . com >
*
* Licensed under the terms of the GNU GPL License version 2.
* Based upon datasheets & sample CPUs kindly provided by VIA .
*
* VIA have currently 3 different versions of Longhaul .
* Version 1 ( Longhaul ) uses the BCR2 MSR at 0x1147 .
* It is present only in Samuel 1 ( C5A ) , Samuel 2 ( C5B ) stepping 0.
2007-02-15 00:00:37 +03:00
* Version 2 of longhaul is backward compatible with v1 , but adds
* LONGHAUL MSR for purpose of both frequency and voltage scaling .
* Present in Samuel 2 ( steppings 1 - 7 only ) ( C5B ) , and Ezra ( C5C ) .
2005-04-17 02:20:36 +04:00
* Version 3 of longhaul got renamed to Powersaver and redesigned
2007-02-15 00:00:37 +03:00
* to use only the POWERSAVER MSR at 0x110a .
2005-04-17 02:20:36 +04:00
* It is present in Ezra - T ( C5M ) , Nehemiah ( C5X ) and above .
* It ' s pretty much the same feature wise to longhaul v2 , though
* there is provision for scaling FSB too , but this doesn ' t work
* too well in practice so we don ' t even try to use this .
*
* BIG FAT DISCLAIMER : Work in progress code . Possibly * dangerous *
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
2006-08-08 21:12:20 +04:00
# include <linux/pci.h>
2005-04-17 02:20:36 +04:00
# include <linux/slab.h>
# include <linux/string.h>
2007-05-28 23:56:19 +04:00
# include <linux/delay.h>
2005-04-17 02:20:36 +04:00
# include <asm/msr.h>
# include <asm/timex.h>
# include <asm/io.h>
2006-07-03 09:19:05 +04:00
# include <asm/acpi.h>
# include <linux/acpi.h>
# include <acpi/processor.h>
2005-04-17 02:20:36 +04:00
# include "longhaul.h"
# define PFX "longhaul: "
# define TYPE_LONGHAUL_V1 1
# define TYPE_LONGHAUL_V2 2
# define TYPE_POWERSAVER 3
# define CPU_SAMUEL 1
# define CPU_SAMUEL2 2
# define CPU_EZRA 3
# define CPU_EZRA_T 4
# define CPU_NEHEMIAH 5
2007-02-01 01:42:47 +03:00
# define CPU_NEHEMIAH_C 6
2005-04-17 02:20:36 +04:00
2006-12-24 16:04:23 +03:00
/* Flags */
# define USE_ACPI_C3 (1 << 1)
# define USE_NORTHBRIDGE (1 << 2)
2005-04-17 02:20:36 +04:00
static int cpu_model ;
2006-08-16 03:07:33 +04:00
static unsigned int numscales = 16 ;
2005-04-17 02:20:36 +04:00
static unsigned int fsb ;
2006-08-16 03:07:33 +04:00
2007-02-23 03:11:16 +03:00
static const struct mV_pos * vrm_mV_table ;
static const unsigned char * mV_vrm_table ;
2006-08-16 03:07:33 +04:00
static unsigned int highest_speed , lowest_speed ; /* kHz */
2005-04-17 02:20:36 +04:00
static unsigned int minmult , maxmult ;
static int can_scale_voltage ;
2006-07-03 09:19:05 +04:00
static struct acpi_processor * pr = NULL ;
static struct acpi_processor_cx * cx = NULL ;
2007-06-06 00:08:50 +04:00
static u32 acpi_regs_addr ;
2006-12-24 16:04:23 +03:00
static u8 longhaul_flags ;
2007-05-28 23:56:19 +04:00
static unsigned int longhaul_index ;
2005-04-17 02:20:36 +04:00
/* Module parameters */
2006-08-16 03:07:33 +04:00
static int scale_voltage ;
2007-07-08 23:51:26 +04:00
static int disable_acpi_c3 ;
2007-10-07 11:24:32 +04:00
static int revid_errata ;
2005-04-17 02:20:36 +04:00
# define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, "longhaul", msg)
/* Clock ratios multiplied by 10 */
static int clock_ratio [ 32 ] ;
static int eblcr_table [ 32 ] ;
static int longhaul_version ;
static struct cpufreq_frequency_table * longhaul_table ;
# ifdef CONFIG_CPU_FREQ_DEBUG
static char speedbuffer [ 8 ] ;
static char * print_speed ( int speed )
{
2006-05-31 01:37:15 +04:00
if ( speed < 1000 ) {
snprintf ( speedbuffer , sizeof ( speedbuffer ) , " %dMHz " , speed ) ;
return speedbuffer ;
}
if ( speed % 1000 = = 0 )
snprintf ( speedbuffer , sizeof ( speedbuffer ) ,
" %dGHz " , speed / 1000 ) ;
else
snprintf ( speedbuffer , sizeof ( speedbuffer ) ,
" %d.%dGHz " , speed / 1000 , ( speed % 1000 ) / 100 ) ;
2005-04-17 02:20:36 +04:00
return speedbuffer ;
}
# endif
static unsigned int calc_speed ( int mult )
{
int khz ;
khz = ( mult / 10 ) * fsb ;
if ( mult % 10 )
khz + = fsb / 2 ;
khz * = 1000 ;
return khz ;
}
static int longhaul_get_cpu_mult ( void )
{
unsigned long invalue = 0 , lo , hi ;
rdmsr ( MSR_IA32_EBL_CR_POWERON , lo , hi ) ;
invalue = ( lo & ( 1 < < 22 | 1 < < 23 | 1 < < 24 | 1 < < 25 ) ) > > 22 ;
if ( longhaul_version = = TYPE_LONGHAUL_V2 | | longhaul_version = = TYPE_POWERSAVER ) {
if ( lo & ( 1 < < 27 ) )
invalue + = 16 ;
}
return eblcr_table [ invalue ] ;
}
2006-07-03 09:19:05 +04:00
/* For processor with BCR2 MSR */
2005-04-17 02:20:36 +04:00
2006-08-08 21:12:20 +04:00
static void do_longhaul1 ( unsigned int clock_ratio_index )
2005-04-17 02:20:36 +04:00
{
2006-07-03 09:19:05 +04:00
union msr_bcr2 bcr2 ;
2005-04-17 02:20:36 +04:00
2006-07-03 09:19:05 +04:00
rdmsrl ( MSR_VIA_BCR2 , bcr2 . val ) ;
/* Enable software clock multiplier */
bcr2 . bits . ESOFTBF = 1 ;
2007-05-28 23:56:19 +04:00
bcr2 . bits . CLOCKMUL = clock_ratio_index & 0xff ;
2005-04-17 02:20:36 +04:00
2006-07-03 09:19:05 +04:00
/* Sync to timer tick */
safe_halt ( ) ;
/* Change frequency on next halt or sleep */
wrmsrl ( MSR_VIA_BCR2 , bcr2 . val ) ;
2006-08-08 21:12:20 +04:00
/* Invoke transition */
ACPI_FLUSH_CPU_CACHE ( ) ;
halt ( ) ;
2006-07-03 09:19:05 +04:00
/* Disable software clock multiplier */
local_irq_disable ( ) ;
rdmsrl ( MSR_VIA_BCR2 , bcr2 . val ) ;
bcr2 . bits . ESOFTBF = 0 ;
wrmsrl ( MSR_VIA_BCR2 , bcr2 . val ) ;
}
2005-06-01 06:03:51 +04:00
2006-07-03 09:19:05 +04:00
/* For processor with Longhaul MSR */
2005-06-01 06:03:51 +04:00
2007-05-28 23:56:19 +04:00
static void do_powersaver ( int cx_address , unsigned int clock_ratio_index ,
unsigned int dir )
2006-07-03 09:19:05 +04:00
{
union msr_longhaul longhaul ;
u32 t ;
2005-06-01 06:03:51 +04:00
2006-07-03 09:19:05 +04:00
rdmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
2007-02-08 20:56:04 +03:00
/* Setup new frequency */
2007-10-07 11:24:32 +04:00
if ( ! revid_errata )
longhaul . bits . RevisionKey = longhaul . bits . RevisionID ;
else
longhaul . bits . RevisionKey = 0 ;
2006-07-03 09:19:05 +04:00
longhaul . bits . SoftBusRatio = clock_ratio_index & 0xf ;
longhaul . bits . SoftBusRatio4 = ( clock_ratio_index & 0x10 ) > > 4 ;
2007-02-08 20:56:04 +03:00
/* Setup new voltage */
if ( can_scale_voltage )
2007-05-28 23:56:19 +04:00
longhaul . bits . SoftVID = ( clock_ratio_index > > 8 ) & 0x1f ;
2007-02-08 20:56:04 +03:00
/* Sync to timer tick */
safe_halt ( ) ;
/* Raise voltage if necessary */
2007-05-28 23:56:19 +04:00
if ( can_scale_voltage & & dir ) {
2006-08-16 03:07:33 +04:00
longhaul . bits . EnableSoftVID = 1 ;
2007-02-08 20:56:04 +03:00
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
/* Change voltage */
if ( ! cx_address ) {
ACPI_FLUSH_CPU_CACHE ( ) ;
halt ( ) ;
} else {
ACPI_FLUSH_CPU_CACHE ( ) ;
/* Invoke C3 */
inb ( cx_address ) ;
/* Dummy op - must do something useless after P_LVL3
* read */
2007-02-11 04:36:29 +03:00
t = inl ( acpi_gbl_FADT . xpm_timer_block . address ) ;
2007-02-08 20:56:04 +03:00
}
longhaul . bits . EnableSoftVID = 0 ;
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
2006-08-16 03:07:33 +04:00
}
2006-07-03 09:19:05 +04:00
/* Change frequency on next halt or sleep */
2007-02-08 20:56:04 +03:00
longhaul . bits . EnableSoftBusRatio = 1 ;
2006-07-03 09:19:05 +04:00
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
2006-12-24 16:04:23 +03:00
if ( ! cx_address ) {
2006-09-24 22:28:13 +04:00
ACPI_FLUSH_CPU_CACHE ( ) ;
halt ( ) ;
} else {
ACPI_FLUSH_CPU_CACHE ( ) ;
/* Invoke C3 */
inb ( cx_address ) ;
/* Dummy op - must do something useless after P_LVL3 read */
2007-02-02 19:48:22 +03:00
t = inl ( acpi_gbl_FADT . xpm_timer_block . address ) ;
2006-09-24 22:28:13 +04:00
}
2006-07-03 09:19:05 +04:00
/* Disable bus ratio bit */
longhaul . bits . EnableSoftBusRatio = 0 ;
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
2007-02-08 20:56:04 +03:00
/* Reduce voltage if necessary */
2007-05-28 23:56:19 +04:00
if ( can_scale_voltage & & ! dir ) {
2007-02-08 20:56:04 +03:00
longhaul . bits . EnableSoftVID = 1 ;
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
/* Change voltage */
if ( ! cx_address ) {
ACPI_FLUSH_CPU_CACHE ( ) ;
halt ( ) ;
} else {
ACPI_FLUSH_CPU_CACHE ( ) ;
/* Invoke C3 */
inb ( cx_address ) ;
/* Dummy op - must do something useless after P_LVL3
* read */
2007-02-11 04:36:29 +03:00
t = inl ( acpi_gbl_FADT . xpm_timer_block . address ) ;
2007-02-08 20:56:04 +03:00
}
longhaul . bits . EnableSoftVID = 0 ;
wrmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
}
2005-04-17 02:20:36 +04:00
}
/**
* longhaul_set_cpu_frequency ( )
* @ clock_ratio_index : bitpattern of the new multiplier .
*
* Sets a new clock ratio .
*/
2007-05-28 23:56:19 +04:00
static void longhaul_setstate ( unsigned int table_index )
2005-04-17 02:20:36 +04:00
{
2007-05-28 23:56:19 +04:00
unsigned int clock_ratio_index ;
2005-04-17 02:20:36 +04:00
int speed , mult ;
struct cpufreq_freqs freqs ;
2006-07-03 09:19:05 +04:00
unsigned long flags ;
unsigned int pic1_mask , pic2_mask ;
2007-06-08 00:31:24 +04:00
u16 bm_status = 0 ;
2007-06-06 00:08:50 +04:00
u32 bm_timeout = 1000 ;
2007-05-28 23:56:19 +04:00
unsigned int dir = 0 ;
2005-04-17 02:20:36 +04:00
2007-05-28 23:56:19 +04:00
clock_ratio_index = longhaul_table [ table_index ] . index ;
/* Safety precautions */
mult = clock_ratio [ clock_ratio_index & 0x1f ] ;
2005-04-17 02:20:36 +04:00
if ( mult = = - 1 )
return ;
speed = calc_speed ( mult ) ;
if ( ( speed > highest_speed ) | | ( speed < lowest_speed ) )
return ;
2007-05-28 23:56:19 +04:00
/* Voltage transition before frequency transition? */
if ( can_scale_voltage & & longhaul_index < table_index )
dir = 1 ;
2005-04-17 02:20:36 +04:00
freqs . old = calc_speed ( longhaul_get_cpu_mult ( ) ) ;
freqs . new = speed ;
freqs . cpu = 0 ; /* longhaul.c is UP only driver */
cpufreq_notify_transition ( & freqs , CPUFREQ_PRECHANGE ) ;
dprintk ( " Setting to FSB:%dMHz Mult:%d.%dx (%s) \n " ,
fsb , mult / 10 , mult % 10 , print_speed ( speed / 1000 ) ) ;
2007-10-07 11:24:32 +04:00
retry_loop :
2006-07-03 09:19:05 +04:00
preempt_disable ( ) ;
local_irq_save ( flags ) ;
pic2_mask = inb ( 0xA1 ) ;
pic1_mask = inb ( 0x21 ) ; /* works on C3. save mask. */
outb ( 0xFF , 0xA1 ) ; /* Overkill */
outb ( 0xFE , 0x21 ) ; /* TMR0 only */
2007-05-18 00:39:02 +04:00
/* Wait while PCI bus is busy. */
2007-06-08 00:31:24 +04:00
if ( acpi_regs_addr & & ( longhaul_flags & USE_NORTHBRIDGE
| | ( ( pr ! = NULL ) & & pr - > flags . bm_control ) ) ) {
bm_status = inw ( acpi_regs_addr ) ;
2007-06-06 00:08:50 +04:00
bm_status & = 1 < < 4 ;
2007-05-18 00:39:02 +04:00
while ( bm_status & & bm_timeout ) {
2007-06-08 00:31:24 +04:00
outw ( 1 < < 4 , acpi_regs_addr ) ;
2007-05-18 00:39:02 +04:00
bm_timeout - - ;
2007-06-08 00:31:24 +04:00
bm_status = inw ( acpi_regs_addr ) ;
2007-06-06 00:08:50 +04:00
bm_status & = 1 < < 4 ;
2007-05-18 00:39:02 +04:00
}
}
2006-12-24 16:04:23 +03:00
if ( longhaul_flags & USE_NORTHBRIDGE ) {
/* Disable AGP and PCI arbiters */
outb ( 3 , 0x22 ) ;
} else if ( ( pr ! = NULL ) & & pr - > flags . bm_control ) {
2007-05-28 23:56:19 +04:00
/* Disable bus master arbitration */
2007-02-02 19:48:22 +03:00
acpi_set_register ( ACPI_BITREG_ARB_DISABLE , 1 ) ;
2006-07-03 09:19:05 +04:00
}
2005-04-17 02:20:36 +04:00
switch ( longhaul_version ) {
/*
* Longhaul v1 . ( Samuel [ C5A ] and Samuel2 stepping 0 [ C5B ] )
* Software controlled multipliers only .
*/
case TYPE_LONGHAUL_V1 :
2006-08-08 21:12:20 +04:00
do_longhaul1 ( clock_ratio_index ) ;
2005-04-17 02:20:36 +04:00
break ;
/*
2007-02-15 00:00:37 +03:00
* Longhaul v2 appears in Samuel2 Steppings 1 - > 7 [ C5B ] and Ezra [ C5C ]
*
2005-04-17 02:20:36 +04:00
* Longhaul v3 ( aka Powersaver ) . ( Ezra - T [ C5M ] & Nehemiah [ C5N ] )
* Nehemiah can do FSB scaling too , but this has never been proven
* to work in practice .
*/
2007-02-15 00:00:37 +03:00
case TYPE_LONGHAUL_V2 :
2005-04-17 02:20:36 +04:00
case TYPE_POWERSAVER :
2006-12-24 16:04:23 +03:00
if ( longhaul_flags & USE_ACPI_C3 ) {
/* Don't allow wakeup */
2007-02-02 19:48:22 +03:00
acpi_set_register ( ACPI_BITREG_BUS_MASTER_RLD , 0 ) ;
2007-05-28 23:56:19 +04:00
do_powersaver ( cx - > address , clock_ratio_index , dir ) ;
2006-12-24 16:04:23 +03:00
} else {
2007-05-28 23:56:19 +04:00
do_powersaver ( 0 , clock_ratio_index , dir ) ;
2006-12-24 16:04:23 +03:00
}
2005-04-17 02:20:36 +04:00
break ;
}
2006-12-24 16:04:23 +03:00
if ( longhaul_flags & USE_NORTHBRIDGE ) {
/* Enable arbiters */
outb ( 0 , 0x22 ) ;
} else if ( ( pr ! = NULL ) & & pr - > flags . bm_control ) {
2006-08-08 21:12:20 +04:00
/* Enable bus master arbitration */
2007-02-02 19:48:22 +03:00
acpi_set_register ( ACPI_BITREG_ARB_DISABLE , 0 ) ;
2006-07-03 09:19:05 +04:00
}
outb ( pic2_mask , 0xA1 ) ; /* restore mask */
outb ( pic1_mask , 0x21 ) ;
local_irq_restore ( flags ) ;
preempt_enable ( ) ;
2007-02-15 00:00:37 +03:00
freqs . new = calc_speed ( longhaul_get_cpu_mult ( ) ) ;
2007-10-07 11:24:32 +04:00
/* Check if requested frequency is set. */
if ( unlikely ( freqs . new ! = speed ) ) {
printk ( KERN_INFO PFX " Failed to set requested frequency! \n " ) ;
/* Revision ID = 1 but processor is expecting revision key
* equal to 0. Jumpers at the bottom of processor will change
* multiplier and FSB , but will not change bits in Longhaul
* MSR nor enable voltage scaling . */
if ( ! revid_errata ) {
printk ( KERN_INFO PFX " Enabling \" Ignore Revision ID \" "
" option. \n " ) ;
revid_errata = 1 ;
msleep ( 200 ) ;
goto retry_loop ;
}
/* Why ACPI C3 sometimes doesn't work is a mystery for me.
* But it does happen . Processor is entering ACPI C3 state ,
* but it doesn ' t change frequency . I tried poking various
* bits in northbridge registers , but without success . */
if ( longhaul_flags & USE_ACPI_C3 ) {
printk ( KERN_INFO PFX " Disabling ACPI C3 support. \n " ) ;
longhaul_flags & = ~ USE_ACPI_C3 ;
if ( revid_errata ) {
printk ( KERN_INFO PFX " Disabling \" Ignore "
" Revision ID \" option. \n " ) ;
revid_errata = 0 ;
}
msleep ( 200 ) ;
goto retry_loop ;
}
/* This shouldn't happen. Longhaul ver. 2 was reported not
* working on processors without voltage scaling , but with
* RevID = 1. RevID errata will make things right . Just
* to be 100 % sure . */
if ( longhaul_version = = TYPE_LONGHAUL_V2 ) {
printk ( KERN_INFO PFX " Switching to Longhaul ver. 1 \n " ) ;
longhaul_version = TYPE_LONGHAUL_V1 ;
msleep ( 200 ) ;
goto retry_loop ;
}
}
/* Report true CPU frequency */
2005-04-17 02:20:36 +04:00
cpufreq_notify_transition ( & freqs , CPUFREQ_POSTCHANGE ) ;
2007-05-18 00:39:02 +04:00
if ( ! bm_timeout )
2007-06-06 00:08:50 +04:00
printk ( KERN_INFO PFX " Warning: Timeout while waiting for idle PCI bus. \n " ) ;
2005-04-17 02:20:36 +04:00
}
/*
* Centaur decided to make life a little more tricky .
* Only longhaul v1 is allowed to read EBLCR BSEL [ 0 : 1 ] .
* Samuel2 and above have to try and guess what the FSB is .
* We do this by assuming we booted at maximum multiplier , and interpolate
* between that value multiplied by possible FSBs and cpu_mhz which
* was calculated at boot time . Really ugly , but no other way to do this .
*/
# define ROUNDING 0xf
2007-01-02 01:49:34 +03:00
static int guess_fsb ( int mult )
2005-04-17 02:20:36 +04:00
{
2007-02-04 17:58:46 +03:00
int speed = cpu_khz / 1000 ;
2005-04-17 02:20:36 +04:00
int i ;
2007-02-04 17:58:46 +03:00
int speeds [ ] = { 666 , 1000 , 1333 , 2000 } ;
int f_max , f_min ;
for ( i = 0 ; i < 4 ; i + + ) {
f_max = ( ( speeds [ i ] * mult ) + 50 ) / 100 ;
f_max + = ( ROUNDING / 2 ) ;
f_min = f_max - ROUNDING ;
if ( ( speed < = f_max ) & & ( speed > = f_min ) )
return speeds [ i ] / 10 ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
static int __init longhaul_get_ranges ( void )
{
2007-05-28 23:56:19 +04:00
unsigned int i , j , k = 0 ;
unsigned int ratio ;
2007-02-01 01:42:47 +03:00
int mult ;
2005-04-17 02:20:36 +04:00
2007-02-01 01:42:47 +03:00
/* Get current frequency */
mult = longhaul_get_cpu_mult ( ) ;
if ( mult = = - 1 ) {
printk ( KERN_INFO PFX " Invalid (reserved) multiplier! \n " ) ;
return - EINVAL ;
}
fsb = guess_fsb ( mult ) ;
if ( fsb = = 0 ) {
printk ( KERN_INFO PFX " Invalid (reserved) FSB! \n " ) ;
return - EINVAL ;
}
/* Get max multiplier - as we always did.
* Longhaul MSR is usefull only when voltage scaling is enabled .
* C3 is booting at max anyway . */
maxmult = mult ;
/* Get min multiplier */
2007-02-08 00:53:29 +03:00
switch ( cpu_model ) {
case CPU_NEHEMIAH :
minmult = 50 ;
2005-04-17 02:20:36 +04:00
break ;
2007-02-08 00:53:29 +03:00
case CPU_NEHEMIAH_C :
minmult = 40 ;
break ;
default :
minmult = 30 ;
2007-02-01 01:42:47 +03:00
break ;
2005-04-17 02:20:36 +04:00
}
dprintk ( " MinMult:%d.%dx MaxMult:%d.%dx \n " ,
minmult / 10 , minmult % 10 , maxmult / 10 , maxmult % 10 ) ;
highest_speed = calc_speed ( maxmult ) ;
lowest_speed = calc_speed ( minmult ) ;
dprintk ( " FSB:%dMHz Lowest speed: %s Highest speed:%s \n " , fsb ,
2007-02-02 19:48:22 +03:00
print_speed ( lowest_speed / 1000 ) ,
2005-04-17 02:20:36 +04:00
print_speed ( highest_speed / 1000 ) ) ;
if ( lowest_speed = = highest_speed ) {
printk ( KERN_INFO PFX " highestspeed == lowest, aborting. \n " ) ;
return - EINVAL ;
}
if ( lowest_speed > highest_speed ) {
printk ( KERN_INFO PFX " nonsense! lowest (%d > %d) ! \n " ,
lowest_speed , highest_speed ) ;
return - EINVAL ;
}
longhaul_table = kmalloc ( ( numscales + 1 ) * sizeof ( struct cpufreq_frequency_table ) , GFP_KERNEL ) ;
if ( ! longhaul_table )
return - ENOMEM ;
2007-05-28 23:56:19 +04:00
for ( j = 0 ; j < numscales ; j + + ) {
2005-04-17 02:20:36 +04:00
ratio = clock_ratio [ j ] ;
if ( ratio = = - 1 )
continue ;
if ( ratio > maxmult | | ratio < minmult )
continue ;
longhaul_table [ k ] . frequency = calc_speed ( ratio ) ;
longhaul_table [ k ] . index = j ;
k + + ;
}
2007-05-28 23:56:19 +04:00
if ( k < = 1 ) {
kfree ( longhaul_table ) ;
return - ENODEV ;
}
/* Sort */
for ( j = 0 ; j < k - 1 ; j + + ) {
unsigned int min_f , min_i ;
min_f = longhaul_table [ j ] . frequency ;
min_i = j ;
for ( i = j + 1 ; i < k ; i + + ) {
if ( longhaul_table [ i ] . frequency < min_f ) {
min_f = longhaul_table [ i ] . frequency ;
min_i = i ;
}
}
if ( min_i ! = j ) {
unsigned int temp ;
temp = longhaul_table [ j ] . frequency ;
longhaul_table [ j ] . frequency = longhaul_table [ min_i ] . frequency ;
longhaul_table [ min_i ] . frequency = temp ;
temp = longhaul_table [ j ] . index ;
longhaul_table [ j ] . index = longhaul_table [ min_i ] . index ;
longhaul_table [ min_i ] . index = temp ;
}
}
2005-04-17 02:20:36 +04:00
longhaul_table [ k ] . frequency = CPUFREQ_TABLE_END ;
2007-05-28 23:56:19 +04:00
/* Find index we are running on */
for ( j = 0 ; j < k ; j + + ) {
if ( clock_ratio [ longhaul_table [ j ] . index & 0x1f ] = = mult ) {
longhaul_index = j ;
break ;
}
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void __init longhaul_setup_voltagescaling ( void )
{
union msr_longhaul longhaul ;
2007-05-28 23:56:19 +04:00
struct mV_pos minvid , maxvid , vid ;
2006-08-16 03:07:33 +04:00
unsigned int j , speed , pos , kHz_step , numvscales ;
2007-02-08 20:56:04 +03:00
int min_vid_speed ;
2005-04-17 02:20:36 +04:00
2006-08-16 03:07:33 +04:00
rdmsrl ( MSR_VIA_LONGHAUL , longhaul . val ) ;
if ( ! ( longhaul . bits . RevisionID & 1 ) ) {
printk ( KERN_INFO PFX " Voltage scaling not supported by CPU. \n " ) ;
2005-04-17 02:20:36 +04:00
return ;
2006-08-16 03:07:33 +04:00
}
if ( ! longhaul . bits . VRMRev ) {
2007-05-28 23:56:19 +04:00
printk ( KERN_INFO PFX " VRM 8.5 \n " ) ;
2006-08-16 03:07:33 +04:00
vrm_mV_table = & vrm85_mV [ 0 ] ;
mV_vrm_table = & mV_vrm85 [ 0 ] ;
} else {
2007-05-28 23:56:19 +04:00
printk ( KERN_INFO PFX " Mobile VRM \n " ) ;
2007-02-15 00:00:37 +03:00
if ( cpu_model < CPU_NEHEMIAH )
return ;
2006-08-16 03:07:33 +04:00
vrm_mV_table = & mobilevrm_mV [ 0 ] ;
mV_vrm_table = & mV_mobilevrm [ 0 ] ;
}
2005-04-17 02:20:36 +04:00
2006-08-16 03:07:33 +04:00
minvid = vrm_mV_table [ longhaul . bits . MinimumVID ] ;
maxvid = vrm_mV_table [ longhaul . bits . MaximumVID ] ;
2005-04-17 02:20:36 +04:00
2006-08-16 03:07:33 +04:00
if ( minvid . mV = = 0 | | maxvid . mV = = 0 | | minvid . mV > maxvid . mV ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PFX " Bogus values Min:%d.%03d Max:%d.%03d. "
" Voltage scaling disabled. \n " ,
2006-08-16 03:07:33 +04:00
minvid . mV / 1000 , minvid . mV % 1000 , maxvid . mV / 1000 , maxvid . mV % 1000 ) ;
2005-04-17 02:20:36 +04:00
return ;
}
2006-08-16 03:07:33 +04:00
if ( minvid . mV = = maxvid . mV ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PFX " Claims to support voltage scaling but min & max are "
" both %d.%03d. Voltage scaling disabled \n " ,
2006-08-16 03:07:33 +04:00
maxvid . mV / 1000 , maxvid . mV % 1000 ) ;
2005-04-17 02:20:36 +04:00
return ;
}
2007-02-08 20:56:04 +03:00
/* How many voltage steps */
numvscales = maxvid . pos - minvid . pos + 1 ;
printk ( KERN_INFO PFX
" Max VID=%d.%03d "
" Min VID=%d.%03d, "
" %d possible voltage scales \n " ,
2006-08-16 03:07:33 +04:00
maxvid . mV / 1000 , maxvid . mV % 1000 ,
minvid . mV / 1000 , minvid . mV % 1000 ,
numvscales ) ;
2007-02-08 20:56:04 +03:00
/* Calculate max frequency at min voltage */
j = longhaul . bits . MinMHzBR ;
if ( longhaul . bits . MinMHzBR4 )
j + = 16 ;
min_vid_speed = eblcr_table [ j ] ;
if ( min_vid_speed = = - 1 )
return ;
switch ( longhaul . bits . MinMHzFSB ) {
case 0 :
min_vid_speed * = 13333 ;
break ;
case 1 :
min_vid_speed * = 10000 ;
break ;
case 3 :
min_vid_speed * = 6666 ;
break ;
default :
return ;
break ;
}
if ( min_vid_speed > = highest_speed )
return ;
/* Calculate kHz for one voltage step */
kHz_step = ( highest_speed - min_vid_speed ) / numvscales ;
2006-08-16 03:07:33 +04:00
j = 0 ;
while ( longhaul_table [ j ] . frequency ! = CPUFREQ_TABLE_END ) {
speed = longhaul_table [ j ] . frequency ;
2007-02-08 20:56:04 +03:00
if ( speed > min_vid_speed )
pos = ( speed - min_vid_speed ) / kHz_step + minvid . pos ;
else
pos = minvid . pos ;
2007-05-28 23:56:19 +04:00
longhaul_table [ j ] . index | = mV_vrm_table [ pos ] < < 8 ;
vid = vrm_mV_table [ mV_vrm_table [ pos ] ] ;
printk ( KERN_INFO PFX " f: %d kHz, index: %d, vid: %d mV \n " , speed , j , vid . mV ) ;
2006-08-16 03:07:33 +04:00
j + + ;
2005-04-17 02:20:36 +04:00
}
can_scale_voltage = 1 ;
2007-05-28 23:56:19 +04:00
printk ( KERN_INFO PFX " Voltage scaling enabled. \n " ) ;
2005-04-17 02:20:36 +04:00
}
static int longhaul_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , longhaul_table ) ;
}
static int longhaul_target ( struct cpufreq_policy * policy ,
unsigned int target_freq , unsigned int relation )
{
unsigned int table_index = 0 ;
2007-05-28 23:56:19 +04:00
unsigned int i ;
unsigned int dir = 0 ;
u8 vid , current_vid ;
2005-04-17 02:20:36 +04:00
if ( cpufreq_frequency_table_target ( policy , longhaul_table , target_freq , relation , & table_index ) )
return - EINVAL ;
2007-05-28 23:56:19 +04:00
/* Don't set same frequency again */
if ( longhaul_index = = table_index )
return 0 ;
2005-04-17 02:20:36 +04:00
2007-05-28 23:56:19 +04:00
if ( ! can_scale_voltage )
longhaul_setstate ( table_index ) ;
else {
/* On test system voltage transitions exceeding single
* step up or down were turning motherboard off . Both
* " ondemand " and " userspace " are unsafe . C7 is doing
* this in hardware , C3 is old and we need to do this
* in software . */
i = longhaul_index ;
current_vid = ( longhaul_table [ longhaul_index ] . index > > 8 ) & 0x1f ;
if ( table_index > longhaul_index )
dir = 1 ;
while ( i ! = table_index ) {
vid = ( longhaul_table [ i ] . index > > 8 ) & 0x1f ;
if ( vid ! = current_vid ) {
longhaul_setstate ( i ) ;
current_vid = vid ;
msleep ( 200 ) ;
}
if ( dir )
i + + ;
else
i - - ;
}
longhaul_setstate ( table_index ) ;
}
longhaul_index = table_index ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static unsigned int longhaul_get ( unsigned int cpu )
{
if ( cpu )
return 0 ;
return calc_speed ( longhaul_get_cpu_mult ( ) ) ;
}
2006-07-09 21:53:08 +04:00
static acpi_status longhaul_walk_callback ( acpi_handle obj_handle ,
u32 nesting_level ,
void * context , void * * return_value )
2006-07-03 09:19:05 +04:00
{
struct acpi_device * d ;
if ( acpi_bus_get_device ( obj_handle , & d ) ) {
return 0 ;
}
* return_value = ( void * ) acpi_driver_data ( d ) ;
return 1 ;
}
2005-04-17 02:20:36 +04:00
2006-08-08 21:12:20 +04:00
/* VIA don't support PM2 reg, but have something similar */
static int enable_arbiter_disable ( void )
{
struct pci_dev * dev ;
2007-05-28 23:56:19 +04:00
int status = 1 ;
2006-09-24 22:28:13 +04:00
int reg ;
2006-08-08 21:12:20 +04:00
u8 pci_cmd ;
/* Find PLE133 host bridge */
2006-09-24 22:28:13 +04:00
reg = 0x78 ;
2007-03-02 22:12:27 +03:00
dev = pci_get_device ( PCI_VENDOR_ID_VIA , PCI_DEVICE_ID_VIA_8601_0 ,
NULL ) ;
2007-09-26 19:08:14 +04:00
/* Find PM133/VT8605 host bridge */
if ( dev = = NULL )
dev = pci_get_device ( PCI_VENDOR_ID_VIA ,
PCI_DEVICE_ID_VIA_8605_0 , NULL ) ;
2006-09-24 22:28:13 +04:00
/* Find CLE266 host bridge */
if ( dev = = NULL ) {
reg = 0x76 ;
2007-03-02 22:12:27 +03:00
dev = pci_get_device ( PCI_VENDOR_ID_VIA ,
PCI_DEVICE_ID_VIA_862X_0 , NULL ) ;
2006-11-30 05:47:41 +03:00
/* Find CN400 V-Link host bridge */
if ( dev = = NULL )
2007-03-02 22:12:27 +03:00
dev = pci_get_device ( PCI_VENDOR_ID_VIA , 0x7259 , NULL ) ;
2006-09-24 22:28:13 +04:00
}
2006-08-08 21:12:20 +04:00
if ( dev ! = NULL ) {
/* Enable access to port 0x22 */
2006-09-24 22:28:13 +04:00
pci_read_config_byte ( dev , reg , & pci_cmd ) ;
2007-02-04 20:43:12 +03:00
if ( ! ( pci_cmd & 1 < < 7 ) ) {
2006-08-08 21:12:20 +04:00
pci_cmd | = 1 < < 7 ;
2006-09-24 22:28:13 +04:00
pci_write_config_byte ( dev , reg , pci_cmd ) ;
2007-02-04 20:43:12 +03:00
pci_read_config_byte ( dev , reg , & pci_cmd ) ;
if ( ! ( pci_cmd & 1 < < 7 ) ) {
printk ( KERN_ERR PFX
" Can't enable access to port 0x22. \n " ) ;
2007-03-02 22:12:27 +03:00
status = 0 ;
2007-02-04 20:43:12 +03:00
}
2006-08-08 21:12:20 +04:00
}
2007-03-02 22:12:27 +03:00
pci_dev_put ( dev ) ;
return status ;
2006-08-08 21:12:20 +04:00
}
return 0 ;
}
2007-05-18 00:33:46 +04:00
static int longhaul_setup_southbridge ( void )
2007-02-04 20:43:12 +03:00
{
struct pci_dev * dev ;
u8 pci_cmd ;
/* Find VT8235 southbridge */
2007-03-02 22:12:27 +03:00
dev = pci_get_device ( PCI_VENDOR_ID_VIA , PCI_DEVICE_ID_VIA_8235 , NULL ) ;
2007-05-18 00:35:29 +04:00
if ( dev = = NULL )
/* Find VT8237 southbridge */
dev = pci_get_device ( PCI_VENDOR_ID_VIA ,
PCI_DEVICE_ID_VIA_8237 , NULL ) ;
2007-02-04 20:43:12 +03:00
if ( dev ! = NULL ) {
/* Set transition time to max */
pci_read_config_byte ( dev , 0xec , & pci_cmd ) ;
pci_cmd & = ~ ( 1 < < 2 ) ;
pci_write_config_byte ( dev , 0xec , pci_cmd ) ;
pci_read_config_byte ( dev , 0xe4 , & pci_cmd ) ;
pci_cmd & = ~ ( 1 < < 7 ) ;
pci_write_config_byte ( dev , 0xe4 , pci_cmd ) ;
pci_read_config_byte ( dev , 0xe5 , & pci_cmd ) ;
pci_cmd | = 1 < < 7 ;
pci_write_config_byte ( dev , 0xe5 , pci_cmd ) ;
2007-06-06 00:08:50 +04:00
/* Get address of ACPI registers block*/
pci_read_config_byte ( dev , 0x81 , & pci_cmd ) ;
if ( pci_cmd & 1 < < 7 ) {
pci_read_config_dword ( dev , 0x88 , & acpi_regs_addr ) ;
acpi_regs_addr & = 0xff00 ;
printk ( KERN_INFO PFX " ACPI I/O at 0x%x \n " , acpi_regs_addr ) ;
}
2007-03-02 22:12:27 +03:00
pci_dev_put ( dev ) ;
2007-02-04 20:43:12 +03:00
return 1 ;
}
return 0 ;
}
2005-04-17 02:20:36 +04:00
static int __init longhaul_cpu_init ( struct cpufreq_policy * policy )
{
struct cpuinfo_x86 * c = cpu_data ;
char * cpuname = NULL ;
int ret ;
2007-02-15 00:00:37 +03:00
u32 lo , hi ;
2005-04-17 02:20:36 +04:00
2006-08-08 21:12:20 +04:00
/* Check what we have on this motherboard */
2005-04-17 02:20:36 +04:00
switch ( c - > x86_model ) {
case 6 :
cpu_model = CPU_SAMUEL ;
cpuname = " C3 'Samuel' [C5A] " ;
longhaul_version = TYPE_LONGHAUL_V1 ;
memcpy ( clock_ratio , samuel1_clock_ratio , sizeof ( samuel1_clock_ratio ) ) ;
memcpy ( eblcr_table , samuel1_eblcr , sizeof ( samuel1_eblcr ) ) ;
break ;
case 7 :
switch ( c - > x86_mask ) {
case 0 :
2007-02-15 00:00:37 +03:00
longhaul_version = TYPE_LONGHAUL_V1 ;
2005-04-17 02:20:36 +04:00
cpu_model = CPU_SAMUEL2 ;
cpuname = " C3 'Samuel 2' [C5B] " ;
2007-02-15 00:00:37 +03:00
/* Note, this is not a typo, early Samuel2's had
* Samuel1 ratios . */
memcpy ( clock_ratio , samuel1_clock_ratio ,
sizeof ( samuel1_clock_ratio ) ) ;
memcpy ( eblcr_table , samuel2_eblcr ,
sizeof ( samuel2_eblcr ) ) ;
2005-04-17 02:20:36 +04:00
break ;
case 1 . . . 15 :
2007-04-22 14:26:04 +04:00
longhaul_version = TYPE_LONGHAUL_V1 ;
2005-04-17 02:20:36 +04:00
if ( c - > x86_mask < 8 ) {
cpu_model = CPU_SAMUEL2 ;
cpuname = " C3 'Samuel 2' [C5B] " ;
} else {
cpu_model = CPU_EZRA ;
cpuname = " C3 'Ezra' [C5C] " ;
}
2007-02-15 00:00:37 +03:00
memcpy ( clock_ratio , ezra_clock_ratio ,
sizeof ( ezra_clock_ratio ) ) ;
memcpy ( eblcr_table , ezra_eblcr ,
sizeof ( ezra_eblcr ) ) ;
2005-04-17 02:20:36 +04:00
break ;
}
break ;
case 8 :
cpu_model = CPU_EZRA_T ;
cpuname = " C3 'Ezra-T' [C5M] " ;
longhaul_version = TYPE_POWERSAVER ;
numscales = 32 ;
memcpy ( clock_ratio , ezrat_clock_ratio , sizeof ( ezrat_clock_ratio ) ) ;
memcpy ( eblcr_table , ezrat_eblcr , sizeof ( ezrat_eblcr ) ) ;
break ;
case 9 :
longhaul_version = TYPE_POWERSAVER ;
2007-02-01 01:50:49 +03:00
numscales = 32 ;
memcpy ( clock_ratio ,
nehemiah_clock_ratio ,
sizeof ( nehemiah_clock_ratio ) ) ;
memcpy ( eblcr_table , nehemiah_eblcr , sizeof ( nehemiah_eblcr ) ) ;
2005-04-17 02:20:36 +04:00
switch ( c - > x86_mask ) {
case 0 . . . 1 :
2007-02-01 01:42:47 +03:00
cpu_model = CPU_NEHEMIAH ;
2007-02-09 01:12:02 +03:00
cpuname = " C3 'Nehemiah A' [C5XLOE] " ;
2005-04-17 02:20:36 +04:00
break ;
case 2 . . . 4 :
2007-02-01 01:42:47 +03:00
cpu_model = CPU_NEHEMIAH ;
2007-02-09 01:12:02 +03:00
cpuname = " C3 'Nehemiah B' [C5XLOH] " ;
2005-04-17 02:20:36 +04:00
break ;
case 5 . . . 15 :
2007-02-01 01:42:47 +03:00
cpu_model = CPU_NEHEMIAH_C ;
2007-02-09 01:12:02 +03:00
cpuname = " C3 'Nehemiah C' [C5P] " ;
2005-04-17 02:20:36 +04:00
break ;
}
break ;
default :
cpuname = " Unknown " ;
break ;
}
2007-02-15 00:00:37 +03:00
/* Check Longhaul ver. 2 */
if ( longhaul_version = = TYPE_LONGHAUL_V2 ) {
rdmsr ( MSR_VIA_LONGHAUL , lo , hi ) ;
if ( lo = = 0 & & hi = = 0 )
/* Looks like MSR isn't present */
longhaul_version = TYPE_LONGHAUL_V1 ;
}
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PFX " VIA %s CPU detected. " , cpuname ) ;
switch ( longhaul_version ) {
case TYPE_LONGHAUL_V1 :
case TYPE_LONGHAUL_V2 :
printk ( " Longhaul v%d supported. \n " , longhaul_version ) ;
break ;
case TYPE_POWERSAVER :
printk ( " Powersaver supported. \n " ) ;
break ;
} ;
2007-02-04 20:43:12 +03:00
/* Doesn't hurt */
2007-05-18 00:33:46 +04:00
longhaul_setup_southbridge ( ) ;
2007-02-04 20:43:12 +03:00
2006-08-08 21:12:20 +04:00
/* Find ACPI data for processor */
2007-02-04 20:43:12 +03:00
acpi_walk_namespace ( ACPI_TYPE_PROCESSOR , ACPI_ROOT_OBJECT ,
ACPI_UINT32_MAX , & longhaul_walk_callback ,
NULL , ( void * ) & pr ) ;
2006-08-08 21:12:20 +04:00
2006-12-24 16:04:23 +03:00
/* Check ACPI support for C3 state */
2007-04-20 23:58:00 +04:00
if ( pr ! = NULL & & longhaul_version = = TYPE_POWERSAVER ) {
2006-08-08 21:12:20 +04:00
cx = & pr - > power . states [ ACPI_STATE_C3 ] ;
2007-05-18 00:33:46 +04:00
if ( cx - > address > 0 & & cx - > latency < = 1000 )
2006-12-24 16:04:23 +03:00
longhaul_flags | = USE_ACPI_C3 ;
2006-09-27 10:25:27 +04:00
}
2007-07-08 23:51:26 +04:00
/* Disable if it isn't working */
if ( disable_acpi_c3 )
longhaul_flags & = ~ USE_ACPI_C3 ;
2006-12-24 16:04:23 +03:00
/* Check if northbridge is friendly */
2007-05-18 00:33:46 +04:00
if ( enable_arbiter_disable ( ) )
2006-12-24 16:04:23 +03:00
longhaul_flags | = USE_NORTHBRIDGE ;
2007-05-18 00:33:46 +04:00
2006-09-27 10:25:27 +04:00
/* Check ACPI support for bus master arbiter disable */
2007-05-18 00:33:46 +04:00
if ( ! ( longhaul_flags & USE_ACPI_C3
| | longhaul_flags & USE_NORTHBRIDGE )
& & ( ( pr = = NULL ) | | ! ( pr - > flags . bm_control ) ) ) {
2006-12-24 16:04:23 +03:00
printk ( KERN_ERR PFX
" No ACPI support. Unsupported northbridge. \n " ) ;
return - ENODEV ;
2006-08-08 21:12:20 +04:00
}
2006-12-24 16:04:23 +03:00
2007-02-04 20:43:12 +03:00
if ( longhaul_flags & USE_NORTHBRIDGE )
2007-05-18 00:33:46 +04:00
printk ( KERN_INFO PFX " Using northbridge support. \n " ) ;
if ( longhaul_flags & USE_ACPI_C3 )
printk ( KERN_INFO PFX " Using ACPI support. \n " ) ;
2006-08-08 21:12:20 +04:00
2005-04-17 02:20:36 +04:00
ret = longhaul_get_ranges ( ) ;
if ( ret ! = 0 )
return ret ;
2007-02-04 20:43:12 +03:00
if ( ( longhaul_version ! = TYPE_LONGHAUL_V1 ) & & ( scale_voltage ! = 0 ) )
2005-04-17 02:20:36 +04:00
longhaul_setup_voltagescaling ( ) ;
2005-06-01 06:03:51 +04:00
policy - > cpuinfo . transition_latency = 200000 ; /* nsec */
2005-04-17 02:20:36 +04:00
policy - > cur = calc_speed ( longhaul_get_cpu_mult ( ) ) ;
ret = cpufreq_frequency_table_cpuinfo ( policy , longhaul_table ) ;
if ( ret )
return ret ;
cpufreq_frequency_table_get_attr ( longhaul_table , policy - > cpu ) ;
return 0 ;
}
static int __devexit longhaul_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
return 0 ;
}
static struct freq_attr * longhaul_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver longhaul_driver = {
2005-04-17 02:20:36 +04:00
. verify = longhaul_verify ,
. target = longhaul_target ,
. get = longhaul_get ,
. init = longhaul_cpu_init ,
. exit = __devexit_p ( longhaul_cpu_exit ) ,
. name = " longhaul " ,
. owner = THIS_MODULE ,
. attr = longhaul_attr ,
} ;
static int __init longhaul_init ( void )
{
struct cpuinfo_x86 * c = cpu_data ;
if ( c - > x86_vendor ! = X86_VENDOR_CENTAUR | | c - > x86 ! = 6 )
return - ENODEV ;
2006-07-04 19:50:57 +04:00
# ifdef CONFIG_SMP
if ( num_online_cpus ( ) > 1 ) {
printk ( KERN_ERR PFX " More than 1 CPU detected, longhaul disabled. \n " ) ;
2006-12-29 06:30:16 +03:00
return - ENODEV ;
2006-07-04 19:50:57 +04:00
}
# endif
# ifdef CONFIG_X86_IO_APIC
if ( cpu_has_apic ) {
printk ( KERN_ERR PFX " APIC detected. Longhaul is currently broken in this configuration. \n " ) ;
return - ENODEV ;
}
# endif
2005-04-17 02:20:36 +04:00
switch ( c - > x86_model ) {
case 6 . . . 9 :
return cpufreq_register_driver ( & longhaul_driver ) ;
2006-12-18 03:07:35 +03:00
case 10 :
printk ( KERN_ERR PFX " Use acpi-cpufreq driver for VIA C7 \n " ) ;
2005-04-17 02:20:36 +04:00
default :
2006-12-18 03:09:59 +03:00
; ;
2005-04-17 02:20:36 +04:00
}
return - ENODEV ;
}
static void __exit longhaul_exit ( void )
{
2006-05-31 01:40:16 +04:00
int i ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < numscales ; i + + ) {
if ( clock_ratio [ i ] = = maxmult ) {
longhaul_setstate ( i ) ;
break ;
}
}
cpufreq_unregister_driver ( & longhaul_driver ) ;
kfree ( longhaul_table ) ;
}
2007-10-07 11:24:32 +04:00
/* Even if BIOS is exporting ACPI C3 state, and it is used
* with success when CPU is idle , this state doesn ' t
* trigger frequency transition in some cases . */
2007-07-08 23:51:26 +04:00
module_param ( disable_acpi_c3 , int , 0644 ) ;
MODULE_PARM_DESC ( disable_acpi_c3 , " Don't use ACPI C3 support " ) ;
2007-10-07 11:24:32 +04:00
/* Change CPU voltage with frequency. Very usefull to save
* power , but most VIA C3 processors aren ' t supporting it . */
2006-08-16 03:07:33 +04:00
module_param ( scale_voltage , int , 0644 ) ;
MODULE_PARM_DESC ( scale_voltage , " Scale voltage of processor " ) ;
2007-10-07 11:24:32 +04:00
/* Force revision key to 0 for processors which doesn't
* support voltage scaling , but are introducing itself as
* such . */
module_param ( revid_errata , int , 0644 ) ;
MODULE_PARM_DESC ( revid_errata , " Ignore CPU Revision ID " ) ;
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Dave Jones <davej@codemonkey.org.uk> " ) ;
MODULE_DESCRIPTION ( " Longhaul driver for VIA Cyrix processors. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-07-07 10:48:26 +04:00
late_initcall ( longhaul_init ) ;
2005-04-17 02:20:36 +04:00
module_exit ( longhaul_exit ) ;