2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/* us2e_cpufreq.c: UltraSPARC-IIe cpu frequency support
*
* Copyright ( C ) 2003 David S . Miller ( davem @ redhat . com )
*
* Many thanks to Dominik Brodowski for fixing up the cpufreq
* infrastructure in order to make this driver easier to implement .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/sched.h>
# include <linux/smp.h>
# include <linux/cpufreq.h>
# include <linux/threads.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/init.h>
# include <asm/asi.h>
# include <asm/timer.h>
static struct cpufreq_driver * cpufreq_us2e_driver ;
struct us2e_freq_percpu_info {
struct cpufreq_frequency_table table [ 6 ] ;
} ;
/* Indexed by cpu number. */
static struct us2e_freq_percpu_info * us2e_freq_table ;
# define HBIRD_MEM_CNTL0_ADDR 0x1fe0000f010UL
# define HBIRD_ESTAR_MODE_ADDR 0x1fe0000f080UL
/* UltraSPARC-IIe has five dividers: 1, 2, 4, 6, and 8. These are controlled
* in the ESTAR mode control register .
*/
# define ESTAR_MODE_DIV_1 0x0000000000000000UL
# define ESTAR_MODE_DIV_2 0x0000000000000001UL
# define ESTAR_MODE_DIV_4 0x0000000000000003UL
# define ESTAR_MODE_DIV_6 0x0000000000000002UL
# define ESTAR_MODE_DIV_8 0x0000000000000004UL
# define ESTAR_MODE_DIV_MASK 0x0000000000000007UL
# define MCTRL0_SREFRESH_ENAB 0x0000000000010000UL
# define MCTRL0_REFR_COUNT_MASK 0x0000000000007f00UL
# define MCTRL0_REFR_COUNT_SHIFT 8
# define MCTRL0_REFR_INTERVAL 7800
# define MCTRL0_REFR_CLKS_P_CNT 64
static unsigned long read_hbreg ( unsigned long addr )
{
unsigned long ret ;
__asm__ __volatile__ ( " ldxa [%1] %2, %0 "
: " =&r " ( ret )
: " r " ( addr ) , " i " ( ASI_PHYS_BYPASS_EC_E ) ) ;
return ret ;
}
static void write_hbreg ( unsigned long addr , unsigned long val )
{
__asm__ __volatile__ ( " stxa %0, [%1] %2 \n \t "
" membar #Sync "
: /* no outputs */
: " r " ( val ) , " r " ( addr ) , " i " ( ASI_PHYS_BYPASS_EC_E )
: " memory " ) ;
if ( addr = = HBIRD_ESTAR_MODE_ADDR ) {
/* Need to wait 16 clock cycles for the PLL to lock. */
udelay ( 1 ) ;
}
}
static void self_refresh_ctl ( int enable )
{
unsigned long mctrl = read_hbreg ( HBIRD_MEM_CNTL0_ADDR ) ;
if ( enable )
mctrl | = MCTRL0_SREFRESH_ENAB ;
else
mctrl & = ~ MCTRL0_SREFRESH_ENAB ;
write_hbreg ( HBIRD_MEM_CNTL0_ADDR , mctrl ) ;
( void ) read_hbreg ( HBIRD_MEM_CNTL0_ADDR ) ;
}
static void frob_mem_refresh ( int cpu_slowing_down ,
unsigned long clock_tick ,
unsigned long old_divisor , unsigned long divisor )
{
unsigned long old_refr_count , refr_count , mctrl ;
refr_count = ( clock_tick * MCTRL0_REFR_INTERVAL ) ;
refr_count / = ( MCTRL0_REFR_CLKS_P_CNT * divisor * 1000000000UL ) ;
mctrl = read_hbreg ( HBIRD_MEM_CNTL0_ADDR ) ;
old_refr_count = ( mctrl & MCTRL0_REFR_COUNT_MASK )
> > MCTRL0_REFR_COUNT_SHIFT ;
mctrl & = ~ MCTRL0_REFR_COUNT_MASK ;
mctrl | = refr_count < < MCTRL0_REFR_COUNT_SHIFT ;
write_hbreg ( HBIRD_MEM_CNTL0_ADDR , mctrl ) ;
mctrl = read_hbreg ( HBIRD_MEM_CNTL0_ADDR ) ;
if ( cpu_slowing_down & & ! ( mctrl & MCTRL0_SREFRESH_ENAB ) ) {
unsigned long usecs ;
/* We have to wait for both refresh counts (old
* and new ) to go to zero .
*/
usecs = ( MCTRL0_REFR_CLKS_P_CNT *
( refr_count + old_refr_count ) *
1000000UL *
old_divisor ) / clock_tick ;
udelay ( usecs + 1UL ) ;
}
}
static void us2e_transition ( unsigned long estar , unsigned long new_bits ,
unsigned long clock_tick ,
unsigned long old_divisor , unsigned long divisor )
{
estar & = ~ ESTAR_MODE_DIV_MASK ;
/* This is based upon the state transition diagram in the IIe manual. */
if ( old_divisor = = 2 & & divisor = = 1 ) {
self_refresh_ctl ( 0 ) ;
write_hbreg ( HBIRD_ESTAR_MODE_ADDR , estar | new_bits ) ;
frob_mem_refresh ( 0 , clock_tick , old_divisor , divisor ) ;
} else if ( old_divisor = = 1 & & divisor = = 2 ) {
frob_mem_refresh ( 1 , clock_tick , old_divisor , divisor ) ;
write_hbreg ( HBIRD_ESTAR_MODE_ADDR , estar | new_bits ) ;
self_refresh_ctl ( 1 ) ;
} else if ( old_divisor = = 1 & & divisor > 2 ) {
us2e_transition ( estar , ESTAR_MODE_DIV_2 , clock_tick ,
1 , 2 ) ;
us2e_transition ( estar , new_bits , clock_tick ,
2 , divisor ) ;
} else if ( old_divisor > 2 & & divisor = = 1 ) {
us2e_transition ( estar , ESTAR_MODE_DIV_2 , clock_tick ,
old_divisor , 2 ) ;
us2e_transition ( estar , new_bits , clock_tick ,
2 , divisor ) ;
} else if ( old_divisor < divisor ) {
frob_mem_refresh ( 0 , clock_tick , old_divisor , divisor ) ;
write_hbreg ( HBIRD_ESTAR_MODE_ADDR , estar | new_bits ) ;
} else if ( old_divisor > divisor ) {
write_hbreg ( HBIRD_ESTAR_MODE_ADDR , estar | new_bits ) ;
frob_mem_refresh ( 1 , clock_tick , old_divisor , divisor ) ;
} else {
BUG ( ) ;
}
}
static unsigned long index_to_estar_mode ( unsigned int index )
{
switch ( index ) {
case 0 :
return ESTAR_MODE_DIV_1 ;
case 1 :
return ESTAR_MODE_DIV_2 ;
case 2 :
return ESTAR_MODE_DIV_4 ;
case 3 :
return ESTAR_MODE_DIV_6 ;
case 4 :
return ESTAR_MODE_DIV_8 ;
default :
BUG ( ) ;
2011-06-03 18:45:23 +04:00
}
2005-04-17 02:20:36 +04:00
}
static unsigned long index_to_divisor ( unsigned int index )
{
switch ( index ) {
case 0 :
return 1 ;
case 1 :
return 2 ;
case 2 :
return 4 ;
case 3 :
return 6 ;
case 4 :
return 8 ;
default :
BUG ( ) ;
2011-06-03 18:45:23 +04:00
}
2005-04-17 02:20:36 +04:00
}
static unsigned long estar_to_divisor ( unsigned long estar )
{
unsigned long ret ;
switch ( estar & ESTAR_MODE_DIV_MASK ) {
case ESTAR_MODE_DIV_1 :
ret = 1 ;
break ;
case ESTAR_MODE_DIV_2 :
ret = 2 ;
break ;
case ESTAR_MODE_DIV_4 :
ret = 4 ;
break ;
case ESTAR_MODE_DIV_6 :
ret = 6 ;
break ;
case ESTAR_MODE_DIV_8 :
ret = 8 ;
break ;
default :
BUG ( ) ;
2011-06-03 18:45:23 +04:00
}
2005-04-17 02:20:36 +04:00
return ret ;
}
2017-04-13 11:22:43 +03:00
static void __us2e_freq_get ( void * arg )
{
unsigned long * estar = arg ;
* estar = read_hbreg ( HBIRD_ESTAR_MODE_ADDR ) ;
}
2005-08-19 01:35:38 +04:00
static unsigned int us2e_freq_get ( unsigned int cpu )
{
unsigned long clock_tick , estar ;
clock_tick = sparc64_get_clock_tick ( cpu ) / 1000 ;
2017-04-13 11:22:43 +03:00
if ( smp_call_function_single ( cpu , __us2e_freq_get , & estar , 1 ) )
return 0 ;
2005-08-19 01:35:38 +04:00
return clock_tick / estar_to_divisor ( estar ) ;
}
2017-04-13 11:22:43 +03:00
static void __us2e_freq_target ( void * arg )
2005-04-17 02:20:36 +04:00
{
2017-04-13 11:22:43 +03:00
unsigned int cpu = smp_processor_id ( ) ;
unsigned int * index = arg ;
2005-04-17 02:20:36 +04:00
unsigned long new_bits , new_freq ;
unsigned long clock_tick , divisor , old_divisor , estar ;
2005-08-19 01:35:38 +04:00
new_freq = clock_tick = sparc64_get_clock_tick ( cpu ) / 1000 ;
2017-04-13 11:22:43 +03:00
new_bits = index_to_estar_mode ( * index ) ;
divisor = index_to_divisor ( * index ) ;
2005-04-17 02:20:36 +04:00
new_freq / = divisor ;
estar = read_hbreg ( HBIRD_ESTAR_MODE_ADDR ) ;
old_divisor = estar_to_divisor ( estar ) ;
2017-04-13 11:22:43 +03:00
if ( old_divisor ! = divisor ) {
2005-08-19 01:35:38 +04:00
us2e_transition ( estar , new_bits , clock_tick * 1000 ,
old_divisor , divisor ) ;
2017-04-13 11:22:43 +03:00
}
}
2005-04-17 02:20:36 +04:00
2017-04-13 11:22:43 +03:00
static int us2e_freq_target ( struct cpufreq_policy * policy , unsigned int index )
{
unsigned int cpu = policy - > cpu ;
2005-04-17 02:20:36 +04:00
2017-04-13 11:22:43 +03:00
return smp_call_function_single ( cpu , __us2e_freq_target , & index , 1 ) ;
2005-04-17 02:20:36 +04:00
}
static int __init us2e_freq_cpu_init ( struct cpufreq_policy * policy )
{
unsigned int cpu = policy - > cpu ;
2005-08-19 01:35:38 +04:00
unsigned long clock_tick = sparc64_get_clock_tick ( cpu ) / 1000 ;
2005-04-17 02:20:36 +04:00
struct cpufreq_frequency_table * table =
& us2e_freq_table [ cpu ] . table [ 0 ] ;
2013-03-30 14:55:15 +04:00
table [ 0 ] . driver_data = 0 ;
2005-04-17 02:20:36 +04:00
table [ 0 ] . frequency = clock_tick / 1 ;
2013-03-30 14:55:15 +04:00
table [ 1 ] . driver_data = 1 ;
2005-04-17 02:20:36 +04:00
table [ 1 ] . frequency = clock_tick / 2 ;
2013-03-30 14:55:15 +04:00
table [ 2 ] . driver_data = 2 ;
2005-04-17 02:20:36 +04:00
table [ 2 ] . frequency = clock_tick / 4 ;
2013-03-30 14:55:15 +04:00
table [ 2 ] . driver_data = 3 ;
2005-04-17 02:20:36 +04:00
table [ 2 ] . frequency = clock_tick / 6 ;
2013-03-30 14:55:15 +04:00
table [ 2 ] . driver_data = 4 ;
2005-04-17 02:20:36 +04:00
table [ 2 ] . frequency = clock_tick / 8 ;
2013-03-30 14:55:15 +04:00
table [ 2 ] . driver_data = 5 ;
2005-04-17 02:20:36 +04:00
table [ 3 ] . frequency = CPUFREQ_TABLE_END ;
policy - > cpuinfo . transition_latency = 0 ;
policy - > cur = clock_tick ;
2018-02-26 08:09:07 +03:00
policy - > freq_table = table ;
2005-04-17 02:20:36 +04:00
2018-02-26 08:09:07 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int us2e_freq_cpu_exit ( struct cpufreq_policy * policy )
{
2014-03-10 13:23:33 +04:00
if ( cpufreq_us2e_driver )
2013-10-25 18:15:48 +04:00
us2e_freq_target ( policy , 0 ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int __init us2e_freq_init ( void )
{
unsigned long manuf , impl , ver ;
int ret ;
2006-02-09 13:52:44 +03:00
if ( tlb_type ! = spitfire )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
__asm__ ( " rdpr %%ver, %0 " : " =r " ( ver ) ) ;
manuf = ( ( ver > > 48 ) & 0xffff ) ;
impl = ( ( ver > > 32 ) & 0xffff ) ;
if ( manuf = = 0x17 & & impl = = 0x13 ) {
struct cpufreq_driver * driver ;
ret = - ENOMEM ;
2013-08-06 21:23:06 +04:00
driver = kzalloc ( sizeof ( * driver ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! driver )
goto err_out ;
2013-08-06 21:23:06 +04:00
us2e_freq_table = kzalloc ( ( NR_CPUS * sizeof ( * us2e_freq_table ) ) ,
2005-04-17 02:20:36 +04:00
GFP_KERNEL ) ;
if ( ! us2e_freq_table )
goto err_out ;
2005-08-19 01:35:38 +04:00
driver - > init = us2e_freq_cpu_init ;
2013-10-03 18:58:26 +04:00
driver - > verify = cpufreq_generic_frequency_table_verify ;
2013-10-25 18:15:48 +04:00
driver - > target_index = us2e_freq_target ;
2005-08-19 01:35:38 +04:00
driver - > get = us2e_freq_get ;
2005-04-17 02:20:36 +04:00
driver - > exit = us2e_freq_cpu_exit ;
strcpy ( driver - > name , " UltraSPARC-IIe " ) ;
cpufreq_us2e_driver = driver ;
ret = cpufreq_register_driver ( driver ) ;
if ( ret )
goto err_out ;
return 0 ;
err_out :
if ( driver ) {
kfree ( driver ) ;
cpufreq_us2e_driver = NULL ;
}
2005-11-07 12:01:35 +03:00
kfree ( us2e_freq_table ) ;
us2e_freq_table = NULL ;
2005-04-17 02:20:36 +04:00
return ret ;
}
return - ENODEV ;
}
static void __exit us2e_freq_exit ( void )
{
if ( cpufreq_us2e_driver ) {
cpufreq_unregister_driver ( cpufreq_us2e_driver ) ;
kfree ( cpufreq_us2e_driver ) ;
cpufreq_us2e_driver = NULL ;
kfree ( us2e_freq_table ) ;
us2e_freq_table = NULL ;
}
}
MODULE_AUTHOR ( " David S. Miller <davem@redhat.com> " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for UltraSPARC-IIe " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( us2e_freq_init ) ;
module_exit ( us2e_freq_exit ) ;