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