2005-11-07 06:27:33 +03:00
/*
* Copyright ( C ) 2002 - 2005 Benjamin Herrenschmidt < benh @ kernel . crashing . org >
* and Markus Demleitner < msdemlei @ cl . uni - heidelberg . de >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This driver adds basic cpufreq support for SMU & 970F X based G5 Macs ,
* that is iMac G5 and latest single CPU desktop .
*/
2006-07-06 09:09:19 +04:00
# undef DEBUG
2005-11-07 06:27:33 +03:00
# include <linux/module.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/sched.h>
# include <linux/cpufreq.h>
# include <linux/init.h>
# include <linux/completion.h>
2006-03-26 13:37:14 +04:00
# include <linux/mutex.h>
2005-11-07 06:27:33 +03:00
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/irq.h>
# include <asm/sections.h>
# include <asm/cputable.h>
# include <asm/time.h>
# include <asm/smu.h>
2006-01-07 03:45:28 +03:00
# include <asm/pmac_pfunc.h>
2005-11-07 06:27:33 +03:00
2006-07-06 09:09:19 +04:00
# define DBG(fmt...) pr_debug(fmt)
2005-11-07 06:27:33 +03:00
/* see 970FX user manual */
# define SCOM_PCR 0x0aa001 /* PCR scom addr */
# define PCR_HILO_SELECT 0x80000000U /* 1 = PCR, 0 = PCRH */
# define PCR_SPEED_FULL 0x00000000U /* 1:1 speed value */
# define PCR_SPEED_HALF 0x00020000U /* 1:2 speed value */
# define PCR_SPEED_QUARTER 0x00040000U /* 1:4 speed value */
# define PCR_SPEED_MASK 0x000e0000U /* speed mask */
# define PCR_SPEED_SHIFT 17
# define PCR_FREQ_REQ_VALID 0x00010000U /* freq request valid */
# define PCR_VOLT_REQ_VALID 0x00008000U /* volt request valid */
# define PCR_TARGET_TIME_MASK 0x00006000U /* target time */
# define PCR_STATLAT_MASK 0x00001f00U /* STATLAT value */
# define PCR_SNOOPLAT_MASK 0x000000f0U /* SNOOPLAT value */
# define PCR_SNOOPACC_MASK 0x0000000fU /* SNOOPACC value */
# define SCOM_PSR 0x408001 /* PSR scom addr */
/* warning: PSR is a 64 bits register */
# define PSR_CMD_RECEIVED 0x2000000000000000U /* command received */
# define PSR_CMD_COMPLETED 0x1000000000000000U /* command completed */
# define PSR_CUR_SPEED_MASK 0x0300000000000000U /* current speed */
# define PSR_CUR_SPEED_SHIFT (56)
/*
* The G5 only supports two frequencies ( Quarter speed is not supported )
*/
# define CPUFREQ_HIGH 0
# define CPUFREQ_LOW 1
static struct cpufreq_frequency_table g5_cpu_freqs [ ] = {
{ CPUFREQ_HIGH , 0 } ,
{ CPUFREQ_LOW , 0 } ,
{ 0 , CPUFREQ_TABLE_END } ,
} ;
static struct freq_attr * g5_cpu_freqs_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
/* Power mode data is an array of the 32 bits PCR values to use for
2006-01-10 02:10:13 +03:00
* the various frequencies , retrieved from the device - tree
2005-11-07 06:27:33 +03:00
*/
static int g5_pmode_cur ;
2006-01-07 03:45:28 +03:00
static void ( * g5_switch_volt ) ( int speed_mode ) ;
static int ( * g5_switch_freq ) ( int speed_mode ) ;
static int ( * g5_query_freq ) ( void ) ;
2006-03-26 13:37:14 +04:00
static DEFINE_MUTEX ( g5_switch_mutex ) ;
2005-11-07 06:27:33 +03:00
2009-02-19 10:07:41 +03:00
static unsigned long transition_latency ;
2005-11-07 06:27:33 +03:00
2006-07-10 10:44:54 +04:00
# ifdef CONFIG_PMAC_SMU
2006-07-06 09:09:19 +04:00
2006-09-14 10:59:31 +04:00
static const u32 * g5_pmode_data ;
2006-07-06 09:09:19 +04:00
static int g5_pmode_max ;
2005-11-07 06:27:33 +03:00
static struct smu_sdbp_fvt * g5_fvt_table ; /* table of op. points */
static int g5_fvt_count ; /* number of op. points */
static int g5_fvt_cur ; /* current op. point */
2006-01-07 03:45:28 +03:00
/*
* SMU based voltage switching for Neo2 platforms
*/
2005-11-07 06:27:33 +03:00
2006-01-07 03:45:28 +03:00
static void g5_smu_switch_volt ( int speed_mode )
2005-11-07 06:27:33 +03:00
{
struct smu_simple_cmd cmd ;
2006-10-01 10:28:10 +04:00
DECLARE_COMPLETION_ONSTACK ( comp ) ;
2005-11-07 06:27:33 +03:00
smu_queue_simple ( & cmd , SMU_CMD_POWER_COMMAND , 8 , smu_done_complete ,
& comp , ' V ' , ' S ' , ' L ' , ' E ' , ' W ' ,
0xff , g5_fvt_cur + 1 , speed_mode ) ;
wait_for_completion ( & comp ) ;
}
2006-01-07 03:45:28 +03:00
/*
* Platform function based voltage / vdnap switching for Neo2
*/
static struct pmf_function * pfunc_set_vdnap0 ;
static struct pmf_function * pfunc_vdnap0_complete ;
static void g5_vdnap_switch_volt ( int speed_mode )
2005-11-07 06:27:33 +03:00
{
2006-01-07 03:45:28 +03:00
struct pmf_args args ;
u32 slew , done = 0 ;
unsigned long timeout ;
2005-11-07 06:27:33 +03:00
2006-01-07 03:45:28 +03:00
slew = ( speed_mode = = CPUFREQ_LOW ) ? 1 : 0 ;
args . count = 1 ;
args . u [ 0 ] . p = & slew ;
2005-11-07 06:27:33 +03:00
2006-01-07 03:45:28 +03:00
pmf_call_one ( pfunc_set_vdnap0 , & args ) ;
2005-11-07 06:27:33 +03:00
2006-01-07 03:45:28 +03:00
/* It's an irq GPIO so we should be able to just block here,
* I ' ll do that later after I ' ve properly tested the IRQ code for
* platform functions
*/
timeout = jiffies + HZ / 10 ;
while ( ! time_after ( jiffies , timeout ) ) {
args . count = 1 ;
args . u [ 0 ] . p = & done ;
pmf_call_one ( pfunc_vdnap0_complete , & args ) ;
if ( done )
break ;
msleep ( 1 ) ;
}
if ( done = = 0 )
printk ( KERN_WARNING " cpufreq: Timeout in clock slewing ! \n " ) ;
}
2005-11-07 06:27:33 +03:00
2006-01-07 03:45:28 +03:00
/*
* SCOM based frequency switching for 970F X rev3
*/
static int g5_scom_switch_freq ( int speed_mode )
{
unsigned long flags ;
int to ;
2005-11-07 06:27:33 +03:00
/* If frequency is going up, first ramp up the voltage */
if ( speed_mode < g5_pmode_cur )
g5_switch_volt ( speed_mode ) ;
2006-01-07 03:45:28 +03:00
local_irq_save ( flags ) ;
2005-11-07 06:27:33 +03:00
/* Clear PCR high */
scom970_write ( SCOM_PCR , 0 ) ;
/* Clear PCR low */
scom970_write ( SCOM_PCR , PCR_HILO_SELECT | 0 ) ;
/* Set PCR low */
scom970_write ( SCOM_PCR , PCR_HILO_SELECT |
g5_pmode_data [ speed_mode ] ) ;
/* Wait for completion */
for ( to = 0 ; to < 10 ; to + + ) {
unsigned long psr = scom970_read ( SCOM_PSR ) ;
if ( ( psr & PSR_CMD_RECEIVED ) = = 0 & &
( ( ( psr > > PSR_CUR_SPEED_SHIFT ) ^
( g5_pmode_data [ speed_mode ] > > PCR_SPEED_SHIFT ) ) & 0x3 )
= = 0 )
break ;
if ( psr & PSR_CMD_COMPLETED )
break ;
udelay ( 100 ) ;
}
2006-01-07 03:45:28 +03:00
local_irq_restore ( flags ) ;
2005-11-07 06:27:33 +03:00
/* If frequency is going down, last ramp the voltage */
if ( speed_mode > g5_pmode_cur )
g5_switch_volt ( speed_mode ) ;
g5_pmode_cur = speed_mode ;
ppc_proc_freq = g5_cpu_freqs [ speed_mode ] . frequency * 1000ul ;
return 0 ;
}
2006-01-07 03:45:28 +03:00
static int g5_scom_query_freq ( void )
2005-11-07 06:27:33 +03:00
{
unsigned long psr = scom970_read ( SCOM_PSR ) ;
int i ;
for ( i = 0 ; i < = g5_pmode_max ; i + + )
if ( ( ( ( psr > > PSR_CUR_SPEED_SHIFT ) ^
( g5_pmode_data [ i ] > > PCR_SPEED_SHIFT ) ) & 0x3 ) = = 0 )
break ;
return i ;
}
2006-07-06 09:09:19 +04:00
/*
* Fake voltage switching for platforms with missing support
*/
static void g5_dummy_switch_volt ( int speed_mode )
{
}
2006-07-10 10:44:54 +04:00
# endif /* CONFIG_PMAC_SMU */
2006-07-06 09:09:19 +04:00
2006-01-07 03:45:28 +03:00
/*
* Platform function based voltage switching for PowerMac7 , 2 & 7 , 3
*/
static struct pmf_function * pfunc_cpu0_volt_high ;
static struct pmf_function * pfunc_cpu0_volt_low ;
static struct pmf_function * pfunc_cpu1_volt_high ;
static struct pmf_function * pfunc_cpu1_volt_low ;
static void g5_pfunc_switch_volt ( int speed_mode )
{
if ( speed_mode = = CPUFREQ_HIGH ) {
if ( pfunc_cpu0_volt_high )
pmf_call_one ( pfunc_cpu0_volt_high , NULL ) ;
if ( pfunc_cpu1_volt_high )
pmf_call_one ( pfunc_cpu1_volt_high , NULL ) ;
} else {
if ( pfunc_cpu0_volt_low )
pmf_call_one ( pfunc_cpu0_volt_low , NULL ) ;
if ( pfunc_cpu1_volt_low )
pmf_call_one ( pfunc_cpu1_volt_low , NULL ) ;
}
msleep ( 10 ) ; /* should be faster , to fix */
}
/*
* Platform function based frequency switching for PowerMac7 , 2 & 7 , 3
*/
static struct pmf_function * pfunc_cpu_setfreq_high ;
static struct pmf_function * pfunc_cpu_setfreq_low ;
static struct pmf_function * pfunc_cpu_getfreq ;
2009-06-28 10:26:10 +04:00
static struct pmf_function * pfunc_slewing_done ;
2006-01-07 03:45:28 +03:00
static int g5_pfunc_switch_freq ( int speed_mode )
{
struct pmf_args args ;
u32 done = 0 ;
unsigned long timeout ;
2006-07-06 09:09:19 +04:00
int rc ;
DBG ( " g5_pfunc_switch_freq(%d) \n " , speed_mode ) ;
2006-01-07 03:45:28 +03:00
/* If frequency is going up, first ramp up the voltage */
if ( speed_mode < g5_pmode_cur )
g5_switch_volt ( speed_mode ) ;
/* Do it */
if ( speed_mode = = CPUFREQ_HIGH )
2006-07-06 09:09:19 +04:00
rc = pmf_call_one ( pfunc_cpu_setfreq_high , NULL ) ;
2006-01-07 03:45:28 +03:00
else
2006-07-06 09:09:19 +04:00
rc = pmf_call_one ( pfunc_cpu_setfreq_low , NULL ) ;
if ( rc )
printk ( KERN_WARNING " cpufreq: pfunc switch error %d \n " , rc ) ;
2006-01-07 03:45:28 +03:00
/* It's an irq GPIO so we should be able to just block here,
* I ' ll do that later after I ' ve properly tested the IRQ code for
* platform functions
*/
timeout = jiffies + HZ / 10 ;
while ( ! time_after ( jiffies , timeout ) ) {
args . count = 1 ;
args . u [ 0 ] . p = & done ;
pmf_call_one ( pfunc_slewing_done , & args ) ;
if ( done )
break ;
msleep ( 1 ) ;
}
if ( done = = 0 )
printk ( KERN_WARNING " cpufreq: Timeout in clock slewing ! \n " ) ;
/* If frequency is going down, last ramp the voltage */
if ( speed_mode > g5_pmode_cur )
g5_switch_volt ( speed_mode ) ;
g5_pmode_cur = speed_mode ;
ppc_proc_freq = g5_cpu_freqs [ speed_mode ] . frequency * 1000ul ;
return 0 ;
}
static int g5_pfunc_query_freq ( void )
{
struct pmf_args args ;
u32 val = 0 ;
args . count = 1 ;
args . u [ 0 ] . p = & val ;
pmf_call_one ( pfunc_cpu_getfreq , & args ) ;
return val ? CPUFREQ_HIGH : CPUFREQ_LOW ;
}
/*
* Common interface to the cpufreq core
*/
2005-11-07 06:27:33 +03:00
static int g5_cpufreq_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , g5_cpu_freqs ) ;
}
static int g5_cpufreq_target ( struct cpufreq_policy * policy ,
unsigned int target_freq , unsigned int relation )
{
2006-01-07 03:45:28 +03:00
unsigned int newstate = 0 ;
struct cpufreq_freqs freqs ;
int rc ;
2005-11-07 06:27:33 +03:00
if ( cpufreq_frequency_table_target ( policy , g5_cpu_freqs ,
target_freq , relation , & newstate ) )
return - EINVAL ;
2006-01-07 03:45:28 +03:00
if ( g5_pmode_cur = = newstate )
return 0 ;
2006-03-26 13:37:14 +04:00
mutex_lock ( & g5_switch_mutex ) ;
2006-01-07 03:45:28 +03:00
freqs . old = g5_cpu_freqs [ g5_pmode_cur ] . frequency ;
freqs . new = g5_cpu_freqs [ newstate ] . frequency ;
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_PRECHANGE ) ;
2006-01-07 03:45:28 +03:00
rc = g5_switch_freq ( newstate ) ;
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_POSTCHANGE ) ;
2006-01-07 03:45:28 +03:00
2006-03-26 13:37:14 +04:00
mutex_unlock ( & g5_switch_mutex ) ;
2006-01-07 03:45:28 +03:00
return rc ;
2005-11-07 06:27:33 +03:00
}
static unsigned int g5_cpufreq_get_speed ( unsigned int cpu )
{
return g5_cpu_freqs [ g5_pmode_cur ] . frequency ;
}
static int g5_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
2009-02-19 10:07:41 +03:00
policy - > cpuinfo . transition_latency = transition_latency ;
2005-11-07 06:27:33 +03:00
policy - > cur = g5_cpu_freqs [ g5_query_freq ( ) ] . frequency ;
2007-03-21 13:40:42 +03:00
/* secondary CPUs are tied to the primary one by the
* cpufreq core if in the secondary policy we tell it that
* it actually must be one policy together with all others . */
2010-04-26 19:32:32 +04:00
cpumask_copy ( policy - > cpus , cpu_online_mask ) ;
2005-11-07 06:27:33 +03:00
cpufreq_frequency_table_get_attr ( g5_cpu_freqs , policy - > cpu ) ;
return cpufreq_frequency_table_cpuinfo ( policy ,
g5_cpu_freqs ) ;
}
static struct cpufreq_driver g5_cpufreq_driver = {
. name = " powermac " ,
. owner = THIS_MODULE ,
. flags = CPUFREQ_CONST_LOOPS ,
. init = g5_cpufreq_cpu_init ,
. verify = g5_cpufreq_verify ,
. target = g5_cpufreq_target ,
. get = g5_cpufreq_get_speed ,
. attr = g5_cpu_freqs_attr ,
} ;
2006-07-10 10:44:54 +04:00
# ifdef CONFIG_PMAC_SMU
2006-07-06 09:09:19 +04:00
2006-01-07 03:45:28 +03:00
static int __init g5_neo2_cpufreq_init ( struct device_node * cpus )
2005-11-07 06:27:33 +03:00
{
struct device_node * cpunode ;
unsigned int psize , ssize ;
unsigned long max_freq ;
2006-01-07 03:45:28 +03:00
char * freq_method , * volt_method ;
2006-07-12 09:40:29 +04:00
const u32 * valp ;
u32 pvr_hi ;
2006-01-07 03:45:28 +03:00
int use_volts_vdnap = 0 ;
int use_volts_smu = 0 ;
2005-11-07 06:27:33 +03:00
int rc = - ENODEV ;
2006-01-07 03:45:28 +03:00
/* Check supported platforms */
2010-02-02 07:34:14 +03:00
if ( of_machine_is_compatible ( " PowerMac8,1 " ) | |
of_machine_is_compatible ( " PowerMac8,2 " ) | |
of_machine_is_compatible ( " PowerMac9,1 " ) )
2006-01-07 03:45:28 +03:00
use_volts_smu = 1 ;
2010-02-02 07:34:14 +03:00
else if ( of_machine_is_compatible ( " PowerMac11,2 " ) )
2006-01-07 03:45:28 +03:00
use_volts_vdnap = 1 ;
else
return - ENODEV ;
/* Get first CPU node */
for ( cpunode = NULL ;
( cpunode = of_get_next_child ( cpus , cpunode ) ) ! = NULL ; ) {
2007-04-03 16:26:41 +04:00
const u32 * reg = of_get_property ( cpunode , " reg " , NULL ) ;
2006-01-07 03:45:28 +03:00
if ( reg = = NULL | | ( * reg ) ! = 0 )
continue ;
if ( ! strcmp ( cpunode - > type , " cpu " ) )
break ;
}
if ( cpunode = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find any CPU 0 node \n " ) ;
2005-11-07 06:27:33 +03:00
return - ENODEV ;
}
/* Check 970FX for now */
2007-04-03 16:26:41 +04:00
valp = of_get_property ( cpunode , " cpu-version " , NULL ) ;
2005-11-07 06:27:33 +03:00
if ( ! valp ) {
DBG ( " No cpu-version property ! \n " ) ;
goto bail_noprops ;
}
2006-01-07 03:45:28 +03:00
pvr_hi = ( * valp ) > > 16 ;
if ( pvr_hi ! = 0x3c & & pvr_hi ! = 0x44 ) {
printk ( KERN_ERR " cpufreq: Unsupported CPU version \n " ) ;
2005-11-07 06:27:33 +03:00
goto bail_noprops ;
}
/* Look for the powertune data in the device-tree */
2007-04-03 16:26:41 +04:00
g5_pmode_data = of_get_property ( cpunode , " power-mode-data " , & psize ) ;
2005-11-07 06:27:33 +03:00
if ( ! g5_pmode_data ) {
DBG ( " No power-mode-data ! \n " ) ;
goto bail_noprops ;
}
g5_pmode_max = psize / sizeof ( u32 ) - 1 ;
2006-01-07 03:45:28 +03:00
if ( use_volts_smu ) {
2006-07-12 09:40:29 +04:00
const struct smu_sdbp_header * shdr ;
2006-01-07 03:45:28 +03:00
/* Look for the FVT table */
shdr = smu_get_sdb_partition ( SMU_SDB_FVT_ID , NULL ) ;
if ( ! shdr )
goto bail_noprops ;
g5_fvt_table = ( struct smu_sdbp_fvt * ) & shdr [ 1 ] ;
ssize = ( shdr - > len * sizeof ( u32 ) ) -
sizeof ( struct smu_sdbp_header ) ;
g5_fvt_count = ssize / sizeof ( struct smu_sdbp_fvt ) ;
g5_fvt_cur = 0 ;
/* Sanity checking */
if ( g5_fvt_count < 1 | | g5_pmode_max < 1 )
goto bail_noprops ;
g5_switch_volt = g5_smu_switch_volt ;
volt_method = " SMU " ;
} else if ( use_volts_vdnap ) {
struct device_node * root ;
root = of_find_node_by_path ( " / " ) ;
if ( root = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find root of "
" device tree \n " ) ;
goto bail_noprops ;
}
pfunc_set_vdnap0 = pmf_find_function ( root , " set-vdnap0 " ) ;
pfunc_vdnap0_complete =
pmf_find_function ( root , " slewing-done " ) ;
if ( pfunc_set_vdnap0 = = NULL | |
pfunc_vdnap0_complete = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find required "
" platform function \n " ) ;
goto bail_noprops ;
}
g5_switch_volt = g5_vdnap_switch_volt ;
volt_method = " GPIO " ;
} else {
g5_switch_volt = g5_dummy_switch_volt ;
volt_method = " none " ;
}
2005-11-07 06:27:33 +03:00
/*
* From what I see , clock - frequency is always the maximal frequency .
* The current driver can not slew sysclk yet , so we really only deal
* with powertune steps for now . We also only implement full freq and
* half freq in this version . So far , I haven ' t yet seen a machine
* supporting anything else .
*/
2007-04-03 16:26:41 +04:00
valp = of_get_property ( cpunode , " clock-frequency " , NULL ) ;
2005-11-07 06:27:33 +03:00
if ( ! valp )
return - ENODEV ;
max_freq = ( * valp ) / 1000 ;
g5_cpu_freqs [ 0 ] . frequency = max_freq ;
g5_cpu_freqs [ 1 ] . frequency = max_freq / 2 ;
2006-01-07 03:45:28 +03:00
/* Set callbacks */
2009-02-19 10:07:41 +03:00
transition_latency = 12000 ;
2006-01-07 03:45:28 +03:00
g5_switch_freq = g5_scom_switch_freq ;
g5_query_freq = g5_scom_query_freq ;
freq_method = " SCOM " ;
2005-11-07 06:27:33 +03:00
/* Force apply current frequency to make sure everything is in
* sync ( voltage is right for example ) . Firmware may leave us with
* a strange setting . . .
*/
2006-01-07 03:45:28 +03:00
g5_switch_volt ( CPUFREQ_HIGH ) ;
msleep ( 10 ) ;
g5_pmode_cur = - 1 ;
g5_switch_freq ( g5_query_freq ( ) ) ;
2005-11-07 06:27:33 +03:00
printk ( KERN_INFO " Registering G5 CPU frequency driver \n " ) ;
2006-01-07 03:45:28 +03:00
printk ( KERN_INFO " Frequency method: %s, Voltage method: %s \n " ,
freq_method , volt_method ) ;
2005-11-07 06:27:33 +03:00
printk ( KERN_INFO " Low: %d Mhz, High: %d Mhz, Cur: %d MHz \n " ,
g5_cpu_freqs [ 1 ] . frequency / 1000 ,
g5_cpu_freqs [ 0 ] . frequency / 1000 ,
g5_cpu_freqs [ g5_pmode_cur ] . frequency / 1000 ) ;
rc = cpufreq_register_driver ( & g5_cpufreq_driver ) ;
/* We keep the CPU node on hold... hopefully, Apple G5 don't have
* hotplug CPU with a dynamic device - tree . . .
*/
return rc ;
bail_noprops :
of_node_put ( cpunode ) ;
return rc ;
}
2006-07-10 10:44:54 +04:00
# endif /* CONFIG_PMAC_SMU */
2006-07-06 09:09:19 +04:00
2006-01-07 03:45:28 +03:00
static int __init g5_pm72_cpufreq_init ( struct device_node * cpus )
{
struct device_node * cpuid = NULL , * hwclock = NULL , * cpunode = NULL ;
2006-07-12 09:40:29 +04:00
const u8 * eeprom = NULL ;
const u32 * valp ;
2006-01-07 03:45:28 +03:00
u64 max_freq , min_freq , ih , il ;
int has_volt = 1 , rc = 0 ;
2006-07-06 09:09:19 +04:00
DBG ( " cpufreq: Initializing for PowerMac7,2, PowerMac7,3 and "
" RackMac3,1... \n " ) ;
2006-01-07 03:45:28 +03:00
/* Get first CPU node */
for ( cpunode = NULL ;
( cpunode = of_get_next_child ( cpus , cpunode ) ) ! = NULL ; ) {
if ( ! strcmp ( cpunode - > type , " cpu " ) )
break ;
}
if ( cpunode = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find any CPU node \n " ) ;
return - ENODEV ;
}
/* Lookup the cpuid eeprom node */
cpuid = of_find_node_by_path ( " /u3@0,f8000000/i2c@f8001000/cpuid@a0 " ) ;
if ( cpuid ! = NULL )
2007-04-03 16:26:41 +04:00
eeprom = of_get_property ( cpuid , " cpuid " , NULL ) ;
2006-01-07 03:45:28 +03:00
if ( eeprom = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find cpuid EEPROM ! \n " ) ;
rc = - ENODEV ;
goto bail ;
}
/* Lookup the i2c hwclock */
for ( hwclock = NULL ;
( hwclock = of_find_node_by_name ( hwclock , " i2c-hwclock " ) ) ! = NULL ; ) {
2007-04-03 16:26:41 +04:00
const char * loc = of_get_property ( hwclock ,
2006-07-12 09:40:29 +04:00
" hwctrl-location " , NULL ) ;
2006-01-07 03:45:28 +03:00
if ( loc = = NULL )
continue ;
if ( strcmp ( loc , " CPU CLOCK " ) )
continue ;
2007-04-03 16:26:41 +04:00
if ( ! of_get_property ( hwclock , " platform-get-frequency " , NULL ) )
2006-01-07 03:45:28 +03:00
continue ;
break ;
}
if ( hwclock = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find i2c clock chip ! \n " ) ;
rc = - ENODEV ;
goto bail ;
}
DBG ( " cpufreq: i2c clock chip found: %s \n " , hwclock - > full_name ) ;
/* Now get all the platform functions */
pfunc_cpu_getfreq =
pmf_find_function ( hwclock , " get-frequency " ) ;
pfunc_cpu_setfreq_high =
pmf_find_function ( hwclock , " set-frequency-high " ) ;
pfunc_cpu_setfreq_low =
pmf_find_function ( hwclock , " set-frequency-low " ) ;
pfunc_slewing_done =
pmf_find_function ( hwclock , " slewing-done " ) ;
pfunc_cpu0_volt_high =
pmf_find_function ( hwclock , " set-voltage-high-0 " ) ;
pfunc_cpu0_volt_low =
pmf_find_function ( hwclock , " set-voltage-low-0 " ) ;
pfunc_cpu1_volt_high =
pmf_find_function ( hwclock , " set-voltage-high-1 " ) ;
pfunc_cpu1_volt_low =
pmf_find_function ( hwclock , " set-voltage-low-1 " ) ;
/* Check we have minimum requirements */
if ( pfunc_cpu_getfreq = = NULL | | pfunc_cpu_setfreq_high = = NULL | |
pfunc_cpu_setfreq_low = = NULL | | pfunc_slewing_done = = NULL ) {
printk ( KERN_ERR " cpufreq: Can't find platform functions ! \n " ) ;
rc = - ENODEV ;
goto bail ;
}
/* Check that we have complete sets */
if ( pfunc_cpu0_volt_high = = NULL | | pfunc_cpu0_volt_low = = NULL ) {
pmf_put_function ( pfunc_cpu0_volt_high ) ;
pmf_put_function ( pfunc_cpu0_volt_low ) ;
pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL ;
has_volt = 0 ;
}
if ( ! has_volt | |
pfunc_cpu1_volt_high = = NULL | | pfunc_cpu1_volt_low = = NULL ) {
pmf_put_function ( pfunc_cpu1_volt_high ) ;
pmf_put_function ( pfunc_cpu1_volt_low ) ;
pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL ;
}
/* Note: The device tree also contains a "platform-set-values"
* function for which I haven ' t quite figured out the usage . It
* might have to be called on init and / or wakeup , I ' m not too sure
* but things seem to work fine without it so far . . .
*/
/* Get max frequency from device-tree */
2007-04-03 16:26:41 +04:00
valp = of_get_property ( cpunode , " clock-frequency " , NULL ) ;
2006-01-07 03:45:28 +03:00
if ( ! valp ) {
printk ( KERN_ERR " cpufreq: Can't find CPU frequency ! \n " ) ;
rc = - ENODEV ;
goto bail ;
}
max_freq = ( * valp ) / 1000 ;
/* Now calculate reduced frequency by using the cpuid input freq
* ratio . This requires 64 bits math unless we are willing to lose
* some precision
*/
ih = * ( ( u32 * ) ( eeprom + 0x10 ) ) ;
il = * ( ( u32 * ) ( eeprom + 0x20 ) ) ;
2006-07-06 09:09:19 +04:00
/* Check for machines with no useful settings */
if ( il = = ih ) {
printk ( KERN_WARNING " cpufreq: No low frequency mode available "
" on this model ! \n " ) ;
rc = - ENODEV ;
goto bail ;
}
2006-01-07 03:45:28 +03:00
min_freq = 0 ;
if ( ih ! = 0 & & il ! = 0 )
min_freq = ( max_freq * il ) / ih ;
/* Sanity check */
if ( min_freq > = max_freq | | min_freq < 1000 ) {
printk ( KERN_ERR " cpufreq: Can't calculate low frequency ! \n " ) ;
2006-07-06 09:09:19 +04:00
rc = - ENXIO ;
2006-01-07 03:45:28 +03:00
goto bail ;
}
g5_cpu_freqs [ 0 ] . frequency = max_freq ;
g5_cpu_freqs [ 1 ] . frequency = min_freq ;
/* Set callbacks */
2009-02-19 10:07:41 +03:00
transition_latency = CPUFREQ_ETERNAL ;
2006-01-07 03:45:28 +03:00
g5_switch_volt = g5_pfunc_switch_volt ;
g5_switch_freq = g5_pfunc_switch_freq ;
g5_query_freq = g5_pfunc_query_freq ;
/* Force apply current frequency to make sure everything is in
* sync ( voltage is right for example ) . Firmware may leave us with
* a strange setting . . .
*/
g5_switch_volt ( CPUFREQ_HIGH ) ;
msleep ( 10 ) ;
g5_pmode_cur = - 1 ;
g5_switch_freq ( g5_query_freq ( ) ) ;
printk ( KERN_INFO " Registering G5 CPU frequency driver \n " ) ;
printk ( KERN_INFO " Frequency method: i2c/pfunc, "
" Voltage method: %s \n " , has_volt ? " i2c/pfunc " : " none " ) ;
printk ( KERN_INFO " Low: %d Mhz, High: %d Mhz, Cur: %d MHz \n " ,
g5_cpu_freqs [ 1 ] . frequency / 1000 ,
g5_cpu_freqs [ 0 ] . frequency / 1000 ,
g5_cpu_freqs [ g5_pmode_cur ] . frequency / 1000 ) ;
rc = cpufreq_register_driver ( & g5_cpufreq_driver ) ;
bail :
if ( rc ! = 0 ) {
pmf_put_function ( pfunc_cpu_getfreq ) ;
pmf_put_function ( pfunc_cpu_setfreq_high ) ;
pmf_put_function ( pfunc_cpu_setfreq_low ) ;
pmf_put_function ( pfunc_slewing_done ) ;
pmf_put_function ( pfunc_cpu0_volt_high ) ;
pmf_put_function ( pfunc_cpu0_volt_low ) ;
pmf_put_function ( pfunc_cpu1_volt_high ) ;
pmf_put_function ( pfunc_cpu1_volt_low ) ;
}
of_node_put ( hwclock ) ;
of_node_put ( cpuid ) ;
of_node_put ( cpunode ) ;
return rc ;
}
static int __init g5_cpufreq_init ( void )
{
struct device_node * cpus ;
2006-07-06 09:09:19 +04:00
int rc = 0 ;
2006-01-07 03:45:28 +03:00
cpus = of_find_node_by_path ( " /cpus " ) ;
if ( cpus = = NULL ) {
DBG ( " No /cpus node ! \n " ) ;
return - ENODEV ;
}
2010-02-02 07:34:14 +03:00
if ( of_machine_is_compatible ( " PowerMac7,2 " ) | |
of_machine_is_compatible ( " PowerMac7,3 " ) | |
of_machine_is_compatible ( " RackMac3,1 " ) )
2006-01-07 03:45:28 +03:00
rc = g5_pm72_cpufreq_init ( cpus ) ;
2006-07-10 10:44:54 +04:00
# ifdef CONFIG_PMAC_SMU
2006-01-07 03:45:28 +03:00
else
rc = g5_neo2_cpufreq_init ( cpus ) ;
2006-07-10 10:44:54 +04:00
# endif /* CONFIG_PMAC_SMU */
2006-01-07 03:45:28 +03:00
of_node_put ( cpus ) ;
return rc ;
}
2005-11-07 06:27:33 +03:00
module_init ( g5_cpufreq_init ) ;
MODULE_LICENSE ( " GPL " ) ;