162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* us2e_cpufreq.c: UltraSPARC-IIe cpu frequency support 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2003 David S. Miller (davem@redhat.com) 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Many thanks to Dominik Brodowski for fixing up the cpufreq 762306a36Sopenharmony_ci * infrastructure in order to make this driver easier to implement. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/sched.h> 1362306a36Sopenharmony_ci#include <linux/smp.h> 1462306a36Sopenharmony_ci#include <linux/cpufreq.h> 1562306a36Sopenharmony_ci#include <linux/threads.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci#include <linux/init.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <asm/asi.h> 2162306a36Sopenharmony_ci#include <asm/timer.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct us2e_freq_percpu_info { 2462306a36Sopenharmony_ci struct cpufreq_frequency_table table[6]; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* Indexed by cpu number. */ 2862306a36Sopenharmony_cistatic struct us2e_freq_percpu_info *us2e_freq_table; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define HBIRD_MEM_CNTL0_ADDR 0x1fe0000f010UL 3162306a36Sopenharmony_ci#define HBIRD_ESTAR_MODE_ADDR 0x1fe0000f080UL 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* UltraSPARC-IIe has five dividers: 1, 2, 4, 6, and 8. These are controlled 3462306a36Sopenharmony_ci * in the ESTAR mode control register. 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ci#define ESTAR_MODE_DIV_1 0x0000000000000000UL 3762306a36Sopenharmony_ci#define ESTAR_MODE_DIV_2 0x0000000000000001UL 3862306a36Sopenharmony_ci#define ESTAR_MODE_DIV_4 0x0000000000000003UL 3962306a36Sopenharmony_ci#define ESTAR_MODE_DIV_6 0x0000000000000002UL 4062306a36Sopenharmony_ci#define ESTAR_MODE_DIV_8 0x0000000000000004UL 4162306a36Sopenharmony_ci#define ESTAR_MODE_DIV_MASK 0x0000000000000007UL 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define MCTRL0_SREFRESH_ENAB 0x0000000000010000UL 4462306a36Sopenharmony_ci#define MCTRL0_REFR_COUNT_MASK 0x0000000000007f00UL 4562306a36Sopenharmony_ci#define MCTRL0_REFR_COUNT_SHIFT 8 4662306a36Sopenharmony_ci#define MCTRL0_REFR_INTERVAL 7800 4762306a36Sopenharmony_ci#define MCTRL0_REFR_CLKS_P_CNT 64 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic unsigned long read_hbreg(unsigned long addr) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci unsigned long ret; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci __asm__ __volatile__("ldxa [%1] %2, %0" 5462306a36Sopenharmony_ci : "=&r" (ret) 5562306a36Sopenharmony_ci : "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E)); 5662306a36Sopenharmony_ci return ret; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void write_hbreg(unsigned long addr, unsigned long val) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci __asm__ __volatile__("stxa %0, [%1] %2\n\t" 6262306a36Sopenharmony_ci "membar #Sync" 6362306a36Sopenharmony_ci : /* no outputs */ 6462306a36Sopenharmony_ci : "r" (val), "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E) 6562306a36Sopenharmony_ci : "memory"); 6662306a36Sopenharmony_ci if (addr == HBIRD_ESTAR_MODE_ADDR) { 6762306a36Sopenharmony_ci /* Need to wait 16 clock cycles for the PLL to lock. */ 6862306a36Sopenharmony_ci udelay(1); 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void self_refresh_ctl(int enable) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci unsigned long mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (enable) 7762306a36Sopenharmony_ci mctrl |= MCTRL0_SREFRESH_ENAB; 7862306a36Sopenharmony_ci else 7962306a36Sopenharmony_ci mctrl &= ~MCTRL0_SREFRESH_ENAB; 8062306a36Sopenharmony_ci write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl); 8162306a36Sopenharmony_ci (void) read_hbreg(HBIRD_MEM_CNTL0_ADDR); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void frob_mem_refresh(int cpu_slowing_down, 8562306a36Sopenharmony_ci unsigned long clock_tick, 8662306a36Sopenharmony_ci unsigned long old_divisor, unsigned long divisor) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci unsigned long old_refr_count, refr_count, mctrl; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci refr_count = (clock_tick * MCTRL0_REFR_INTERVAL); 9162306a36Sopenharmony_ci refr_count /= (MCTRL0_REFR_CLKS_P_CNT * divisor * 1000000000UL); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); 9462306a36Sopenharmony_ci old_refr_count = (mctrl & MCTRL0_REFR_COUNT_MASK) 9562306a36Sopenharmony_ci >> MCTRL0_REFR_COUNT_SHIFT; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci mctrl &= ~MCTRL0_REFR_COUNT_MASK; 9862306a36Sopenharmony_ci mctrl |= refr_count << MCTRL0_REFR_COUNT_SHIFT; 9962306a36Sopenharmony_ci write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl); 10062306a36Sopenharmony_ci mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (cpu_slowing_down && !(mctrl & MCTRL0_SREFRESH_ENAB)) { 10362306a36Sopenharmony_ci unsigned long usecs; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* We have to wait for both refresh counts (old 10662306a36Sopenharmony_ci * and new) to go to zero. 10762306a36Sopenharmony_ci */ 10862306a36Sopenharmony_ci usecs = (MCTRL0_REFR_CLKS_P_CNT * 10962306a36Sopenharmony_ci (refr_count + old_refr_count) * 11062306a36Sopenharmony_ci 1000000UL * 11162306a36Sopenharmony_ci old_divisor) / clock_tick; 11262306a36Sopenharmony_ci udelay(usecs + 1UL); 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic void us2e_transition(unsigned long estar, unsigned long new_bits, 11762306a36Sopenharmony_ci unsigned long clock_tick, 11862306a36Sopenharmony_ci unsigned long old_divisor, unsigned long divisor) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci estar &= ~ESTAR_MODE_DIV_MASK; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* This is based upon the state transition diagram in the IIe manual. */ 12362306a36Sopenharmony_ci if (old_divisor == 2 && divisor == 1) { 12462306a36Sopenharmony_ci self_refresh_ctl(0); 12562306a36Sopenharmony_ci write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); 12662306a36Sopenharmony_ci frob_mem_refresh(0, clock_tick, old_divisor, divisor); 12762306a36Sopenharmony_ci } else if (old_divisor == 1 && divisor == 2) { 12862306a36Sopenharmony_ci frob_mem_refresh(1, clock_tick, old_divisor, divisor); 12962306a36Sopenharmony_ci write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); 13062306a36Sopenharmony_ci self_refresh_ctl(1); 13162306a36Sopenharmony_ci } else if (old_divisor == 1 && divisor > 2) { 13262306a36Sopenharmony_ci us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick, 13362306a36Sopenharmony_ci 1, 2); 13462306a36Sopenharmony_ci us2e_transition(estar, new_bits, clock_tick, 13562306a36Sopenharmony_ci 2, divisor); 13662306a36Sopenharmony_ci } else if (old_divisor > 2 && divisor == 1) { 13762306a36Sopenharmony_ci us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick, 13862306a36Sopenharmony_ci old_divisor, 2); 13962306a36Sopenharmony_ci us2e_transition(estar, new_bits, clock_tick, 14062306a36Sopenharmony_ci 2, divisor); 14162306a36Sopenharmony_ci } else if (old_divisor < divisor) { 14262306a36Sopenharmony_ci frob_mem_refresh(0, clock_tick, old_divisor, divisor); 14362306a36Sopenharmony_ci write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); 14462306a36Sopenharmony_ci } else if (old_divisor > divisor) { 14562306a36Sopenharmony_ci write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); 14662306a36Sopenharmony_ci frob_mem_refresh(1, clock_tick, old_divisor, divisor); 14762306a36Sopenharmony_ci } else { 14862306a36Sopenharmony_ci BUG(); 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic unsigned long index_to_estar_mode(unsigned int index) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci switch (index) { 15562306a36Sopenharmony_ci case 0: 15662306a36Sopenharmony_ci return ESTAR_MODE_DIV_1; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci case 1: 15962306a36Sopenharmony_ci return ESTAR_MODE_DIV_2; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci case 2: 16262306a36Sopenharmony_ci return ESTAR_MODE_DIV_4; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci case 3: 16562306a36Sopenharmony_ci return ESTAR_MODE_DIV_6; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci case 4: 16862306a36Sopenharmony_ci return ESTAR_MODE_DIV_8; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci default: 17162306a36Sopenharmony_ci BUG(); 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic unsigned long index_to_divisor(unsigned int index) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci switch (index) { 17862306a36Sopenharmony_ci case 0: 17962306a36Sopenharmony_ci return 1; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci case 1: 18262306a36Sopenharmony_ci return 2; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci case 2: 18562306a36Sopenharmony_ci return 4; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci case 3: 18862306a36Sopenharmony_ci return 6; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci case 4: 19162306a36Sopenharmony_ci return 8; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci default: 19462306a36Sopenharmony_ci BUG(); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic unsigned long estar_to_divisor(unsigned long estar) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci unsigned long ret; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci switch (estar & ESTAR_MODE_DIV_MASK) { 20362306a36Sopenharmony_ci case ESTAR_MODE_DIV_1: 20462306a36Sopenharmony_ci ret = 1; 20562306a36Sopenharmony_ci break; 20662306a36Sopenharmony_ci case ESTAR_MODE_DIV_2: 20762306a36Sopenharmony_ci ret = 2; 20862306a36Sopenharmony_ci break; 20962306a36Sopenharmony_ci case ESTAR_MODE_DIV_4: 21062306a36Sopenharmony_ci ret = 4; 21162306a36Sopenharmony_ci break; 21262306a36Sopenharmony_ci case ESTAR_MODE_DIV_6: 21362306a36Sopenharmony_ci ret = 6; 21462306a36Sopenharmony_ci break; 21562306a36Sopenharmony_ci case ESTAR_MODE_DIV_8: 21662306a36Sopenharmony_ci ret = 8; 21762306a36Sopenharmony_ci break; 21862306a36Sopenharmony_ci default: 21962306a36Sopenharmony_ci BUG(); 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return ret; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic void __us2e_freq_get(void *arg) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci unsigned long *estar = arg; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci *estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic unsigned int us2e_freq_get(unsigned int cpu) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci unsigned long clock_tick, estar; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci clock_tick = sparc64_get_clock_tick(cpu) / 1000; 23762306a36Sopenharmony_ci if (smp_call_function_single(cpu, __us2e_freq_get, &estar, 1)) 23862306a36Sopenharmony_ci return 0; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci return clock_tick / estar_to_divisor(estar); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic void __us2e_freq_target(void *arg) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci unsigned int cpu = smp_processor_id(); 24662306a36Sopenharmony_ci unsigned int *index = arg; 24762306a36Sopenharmony_ci unsigned long new_bits, new_freq; 24862306a36Sopenharmony_ci unsigned long clock_tick, divisor, old_divisor, estar; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci new_freq = clock_tick = sparc64_get_clock_tick(cpu) / 1000; 25162306a36Sopenharmony_ci new_bits = index_to_estar_mode(*index); 25262306a36Sopenharmony_ci divisor = index_to_divisor(*index); 25362306a36Sopenharmony_ci new_freq /= divisor; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci old_divisor = estar_to_divisor(estar); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci if (old_divisor != divisor) { 26062306a36Sopenharmony_ci us2e_transition(estar, new_bits, clock_tick * 1000, 26162306a36Sopenharmony_ci old_divisor, divisor); 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic int us2e_freq_target(struct cpufreq_policy *policy, unsigned int index) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci unsigned int cpu = policy->cpu; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci return smp_call_function_single(cpu, __us2e_freq_target, &index, 1); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic int us2e_freq_cpu_init(struct cpufreq_policy *policy) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci unsigned int cpu = policy->cpu; 27562306a36Sopenharmony_ci unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000; 27662306a36Sopenharmony_ci struct cpufreq_frequency_table *table = 27762306a36Sopenharmony_ci &us2e_freq_table[cpu].table[0]; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci table[0].driver_data = 0; 28062306a36Sopenharmony_ci table[0].frequency = clock_tick / 1; 28162306a36Sopenharmony_ci table[1].driver_data = 1; 28262306a36Sopenharmony_ci table[1].frequency = clock_tick / 2; 28362306a36Sopenharmony_ci table[2].driver_data = 2; 28462306a36Sopenharmony_ci table[2].frequency = clock_tick / 4; 28562306a36Sopenharmony_ci table[2].driver_data = 3; 28662306a36Sopenharmony_ci table[2].frequency = clock_tick / 6; 28762306a36Sopenharmony_ci table[2].driver_data = 4; 28862306a36Sopenharmony_ci table[2].frequency = clock_tick / 8; 28962306a36Sopenharmony_ci table[2].driver_data = 5; 29062306a36Sopenharmony_ci table[3].frequency = CPUFREQ_TABLE_END; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci policy->cpuinfo.transition_latency = 0; 29362306a36Sopenharmony_ci policy->cur = clock_tick; 29462306a36Sopenharmony_ci policy->freq_table = table; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci return 0; 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic int us2e_freq_cpu_exit(struct cpufreq_policy *policy) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci us2e_freq_target(policy, 0); 30262306a36Sopenharmony_ci return 0; 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic struct cpufreq_driver cpufreq_us2e_driver = { 30662306a36Sopenharmony_ci .name = "UltraSPARC-IIe", 30762306a36Sopenharmony_ci .init = us2e_freq_cpu_init, 30862306a36Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 30962306a36Sopenharmony_ci .target_index = us2e_freq_target, 31062306a36Sopenharmony_ci .get = us2e_freq_get, 31162306a36Sopenharmony_ci .exit = us2e_freq_cpu_exit, 31262306a36Sopenharmony_ci}; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic int __init us2e_freq_init(void) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci unsigned long manuf, impl, ver; 31762306a36Sopenharmony_ci int ret; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci if (tlb_type != spitfire) 32062306a36Sopenharmony_ci return -ENODEV; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci __asm__("rdpr %%ver, %0" : "=r" (ver)); 32362306a36Sopenharmony_ci manuf = ((ver >> 48) & 0xffff); 32462306a36Sopenharmony_ci impl = ((ver >> 32) & 0xffff); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci if (manuf == 0x17 && impl == 0x13) { 32762306a36Sopenharmony_ci us2e_freq_table = kzalloc(NR_CPUS * sizeof(*us2e_freq_table), 32862306a36Sopenharmony_ci GFP_KERNEL); 32962306a36Sopenharmony_ci if (!us2e_freq_table) 33062306a36Sopenharmony_ci return -ENOMEM; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci ret = cpufreq_register_driver(&cpufreq_us2e_driver); 33362306a36Sopenharmony_ci if (ret) 33462306a36Sopenharmony_ci kfree(us2e_freq_table); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci return ret; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return -ENODEV; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic void __exit us2e_freq_exit(void) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci cpufreq_unregister_driver(&cpufreq_us2e_driver); 34562306a36Sopenharmony_ci kfree(us2e_freq_table); 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ciMODULE_AUTHOR("David S. Miller <davem@redhat.com>"); 34962306a36Sopenharmony_ciMODULE_DESCRIPTION("cpufreq driver for UltraSPARC-IIe"); 35062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cimodule_init(us2e_freq_init); 35362306a36Sopenharmony_cimodule_exit(us2e_freq_exit); 354