162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (C) 2002 - 2005 Benjamin Herrenschmidt <benh@kernel.crashing.org>
462306a36Sopenharmony_ci *  and                       Markus Demleitner <msdemlei@cl.uni-heidelberg.de>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This driver adds basic cpufreq support for SMU & 970FX based G5 Macs,
762306a36Sopenharmony_ci * that is iMac G5 and latest single CPU desktop.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#undef DEBUG
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/types.h>
1662306a36Sopenharmony_ci#include <linux/errno.h>
1762306a36Sopenharmony_ci#include <linux/kernel.h>
1862306a36Sopenharmony_ci#include <linux/delay.h>
1962306a36Sopenharmony_ci#include <linux/sched.h>
2062306a36Sopenharmony_ci#include <linux/cpufreq.h>
2162306a36Sopenharmony_ci#include <linux/init.h>
2262306a36Sopenharmony_ci#include <linux/completion.h>
2362306a36Sopenharmony_ci#include <linux/mutex.h>
2462306a36Sopenharmony_ci#include <linux/of.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include <asm/machdep.h>
2762306a36Sopenharmony_ci#include <asm/irq.h>
2862306a36Sopenharmony_ci#include <asm/sections.h>
2962306a36Sopenharmony_ci#include <asm/cputable.h>
3062306a36Sopenharmony_ci#include <asm/time.h>
3162306a36Sopenharmony_ci#include <asm/smu.h>
3262306a36Sopenharmony_ci#include <asm/pmac_pfunc.h>
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define DBG(fmt...) pr_debug(fmt)
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* see 970FX user manual */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define SCOM_PCR 0x0aa001			/* PCR scom addr */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define PCR_HILO_SELECT		0x80000000U	/* 1 = PCR, 0 = PCRH */
4162306a36Sopenharmony_ci#define PCR_SPEED_FULL		0x00000000U	/* 1:1 speed value */
4262306a36Sopenharmony_ci#define PCR_SPEED_HALF		0x00020000U	/* 1:2 speed value */
4362306a36Sopenharmony_ci#define PCR_SPEED_QUARTER	0x00040000U	/* 1:4 speed value */
4462306a36Sopenharmony_ci#define PCR_SPEED_MASK		0x000e0000U	/* speed mask */
4562306a36Sopenharmony_ci#define PCR_SPEED_SHIFT		17
4662306a36Sopenharmony_ci#define PCR_FREQ_REQ_VALID	0x00010000U	/* freq request valid */
4762306a36Sopenharmony_ci#define PCR_VOLT_REQ_VALID	0x00008000U	/* volt request valid */
4862306a36Sopenharmony_ci#define PCR_TARGET_TIME_MASK	0x00006000U	/* target time */
4962306a36Sopenharmony_ci#define PCR_STATLAT_MASK	0x00001f00U	/* STATLAT value */
5062306a36Sopenharmony_ci#define PCR_SNOOPLAT_MASK	0x000000f0U	/* SNOOPLAT value */
5162306a36Sopenharmony_ci#define PCR_SNOOPACC_MASK	0x0000000fU	/* SNOOPACC value */
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci#define SCOM_PSR 0x408001			/* PSR scom addr */
5462306a36Sopenharmony_ci/* warning: PSR is a 64 bits register */
5562306a36Sopenharmony_ci#define PSR_CMD_RECEIVED	0x2000000000000000U   /* command received */
5662306a36Sopenharmony_ci#define PSR_CMD_COMPLETED	0x1000000000000000U   /* command completed */
5762306a36Sopenharmony_ci#define PSR_CUR_SPEED_MASK	0x0300000000000000U   /* current speed */
5862306a36Sopenharmony_ci#define PSR_CUR_SPEED_SHIFT	(56)
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/*
6162306a36Sopenharmony_ci * The G5 only supports two frequencies (Quarter speed is not supported)
6262306a36Sopenharmony_ci */
6362306a36Sopenharmony_ci#define CPUFREQ_HIGH                  0
6462306a36Sopenharmony_ci#define CPUFREQ_LOW                   1
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic struct cpufreq_frequency_table g5_cpu_freqs[] = {
6762306a36Sopenharmony_ci	{0, CPUFREQ_HIGH,	0},
6862306a36Sopenharmony_ci	{0, CPUFREQ_LOW,	0},
6962306a36Sopenharmony_ci	{0, 0,			CPUFREQ_TABLE_END},
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci/* Power mode data is an array of the 32 bits PCR values to use for
7362306a36Sopenharmony_ci * the various frequencies, retrieved from the device-tree
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_cistatic int g5_pmode_cur;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic void (*g5_switch_volt)(int speed_mode);
7862306a36Sopenharmony_cistatic int (*g5_switch_freq)(int speed_mode);
7962306a36Sopenharmony_cistatic int (*g5_query_freq)(void);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic unsigned long transition_latency;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci#ifdef CONFIG_PMAC_SMU
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic const u32 *g5_pmode_data;
8662306a36Sopenharmony_cistatic int g5_pmode_max;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic struct smu_sdbp_fvt *g5_fvt_table;	/* table of op. points */
8962306a36Sopenharmony_cistatic int g5_fvt_count;			/* number of op. points */
9062306a36Sopenharmony_cistatic int g5_fvt_cur;				/* current op. point */
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci/*
9362306a36Sopenharmony_ci * SMU based voltage switching for Neo2 platforms
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic void g5_smu_switch_volt(int speed_mode)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct smu_simple_cmd	cmd;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(comp);
10162306a36Sopenharmony_ci	smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, smu_done_complete,
10262306a36Sopenharmony_ci			 &comp, 'V', 'S', 'L', 'E', 'W',
10362306a36Sopenharmony_ci			 0xff, g5_fvt_cur+1, speed_mode);
10462306a36Sopenharmony_ci	wait_for_completion(&comp);
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/*
10862306a36Sopenharmony_ci * Platform function based voltage/vdnap switching for Neo2
10962306a36Sopenharmony_ci */
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic struct pmf_function *pfunc_set_vdnap0;
11262306a36Sopenharmony_cistatic struct pmf_function *pfunc_vdnap0_complete;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic void g5_vdnap_switch_volt(int speed_mode)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct pmf_args args;
11762306a36Sopenharmony_ci	u32 slew, done = 0;
11862306a36Sopenharmony_ci	unsigned long timeout;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	slew = (speed_mode == CPUFREQ_LOW) ? 1 : 0;
12162306a36Sopenharmony_ci	args.count = 1;
12262306a36Sopenharmony_ci	args.u[0].p = &slew;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	pmf_call_one(pfunc_set_vdnap0, &args);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* It's an irq GPIO so we should be able to just block here,
12762306a36Sopenharmony_ci	 * I'll do that later after I've properly tested the IRQ code for
12862306a36Sopenharmony_ci	 * platform functions
12962306a36Sopenharmony_ci	 */
13062306a36Sopenharmony_ci	timeout = jiffies + HZ/10;
13162306a36Sopenharmony_ci	while(!time_after(jiffies, timeout)) {
13262306a36Sopenharmony_ci		args.count = 1;
13362306a36Sopenharmony_ci		args.u[0].p = &done;
13462306a36Sopenharmony_ci		pmf_call_one(pfunc_vdnap0_complete, &args);
13562306a36Sopenharmony_ci		if (done)
13662306a36Sopenharmony_ci			break;
13762306a36Sopenharmony_ci		usleep_range(1000, 1000);
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci	if (done == 0)
14062306a36Sopenharmony_ci		pr_warn("Timeout in clock slewing !\n");
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/*
14562306a36Sopenharmony_ci * SCOM based frequency switching for 970FX rev3
14662306a36Sopenharmony_ci */
14762306a36Sopenharmony_cistatic int g5_scom_switch_freq(int speed_mode)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	unsigned long flags;
15062306a36Sopenharmony_ci	int to;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* If frequency is going up, first ramp up the voltage */
15362306a36Sopenharmony_ci	if (speed_mode < g5_pmode_cur)
15462306a36Sopenharmony_ci		g5_switch_volt(speed_mode);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	local_irq_save(flags);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/* Clear PCR high */
15962306a36Sopenharmony_ci	scom970_write(SCOM_PCR, 0);
16062306a36Sopenharmony_ci	/* Clear PCR low */
16162306a36Sopenharmony_ci       	scom970_write(SCOM_PCR, PCR_HILO_SELECT | 0);
16262306a36Sopenharmony_ci	/* Set PCR low */
16362306a36Sopenharmony_ci	scom970_write(SCOM_PCR, PCR_HILO_SELECT |
16462306a36Sopenharmony_ci		      g5_pmode_data[speed_mode]);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/* Wait for completion */
16762306a36Sopenharmony_ci	for (to = 0; to < 10; to++) {
16862306a36Sopenharmony_ci		unsigned long psr = scom970_read(SCOM_PSR);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		if ((psr & PSR_CMD_RECEIVED) == 0 &&
17162306a36Sopenharmony_ci		    (((psr >> PSR_CUR_SPEED_SHIFT) ^
17262306a36Sopenharmony_ci		      (g5_pmode_data[speed_mode] >> PCR_SPEED_SHIFT)) & 0x3)
17362306a36Sopenharmony_ci		    == 0)
17462306a36Sopenharmony_ci			break;
17562306a36Sopenharmony_ci		if (psr & PSR_CMD_COMPLETED)
17662306a36Sopenharmony_ci			break;
17762306a36Sopenharmony_ci		udelay(100);
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	local_irq_restore(flags);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* If frequency is going down, last ramp the voltage */
18362306a36Sopenharmony_ci	if (speed_mode > g5_pmode_cur)
18462306a36Sopenharmony_ci		g5_switch_volt(speed_mode);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	g5_pmode_cur = speed_mode;
18762306a36Sopenharmony_ci	ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int g5_scom_query_freq(void)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	unsigned long psr = scom970_read(SCOM_PSR);
19562306a36Sopenharmony_ci	int i;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	for (i = 0; i <= g5_pmode_max; i++)
19862306a36Sopenharmony_ci		if ((((psr >> PSR_CUR_SPEED_SHIFT) ^
19962306a36Sopenharmony_ci		      (g5_pmode_data[i] >> PCR_SPEED_SHIFT)) & 0x3) == 0)
20062306a36Sopenharmony_ci			break;
20162306a36Sopenharmony_ci	return i;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci/*
20562306a36Sopenharmony_ci * Fake voltage switching for platforms with missing support
20662306a36Sopenharmony_ci */
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic void g5_dummy_switch_volt(int speed_mode)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci#endif /* CONFIG_PMAC_SMU */
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci/*
21562306a36Sopenharmony_ci * Platform function based voltage switching for PowerMac7,2 & 7,3
21662306a36Sopenharmony_ci */
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu0_volt_high;
21962306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu0_volt_low;
22062306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu1_volt_high;
22162306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu1_volt_low;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic void g5_pfunc_switch_volt(int speed_mode)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	if (speed_mode == CPUFREQ_HIGH) {
22662306a36Sopenharmony_ci		if (pfunc_cpu0_volt_high)
22762306a36Sopenharmony_ci			pmf_call_one(pfunc_cpu0_volt_high, NULL);
22862306a36Sopenharmony_ci		if (pfunc_cpu1_volt_high)
22962306a36Sopenharmony_ci			pmf_call_one(pfunc_cpu1_volt_high, NULL);
23062306a36Sopenharmony_ci	} else {
23162306a36Sopenharmony_ci		if (pfunc_cpu0_volt_low)
23262306a36Sopenharmony_ci			pmf_call_one(pfunc_cpu0_volt_low, NULL);
23362306a36Sopenharmony_ci		if (pfunc_cpu1_volt_low)
23462306a36Sopenharmony_ci			pmf_call_one(pfunc_cpu1_volt_low, NULL);
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci	usleep_range(10000, 10000); /* should be faster , to fix */
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci/*
24062306a36Sopenharmony_ci * Platform function based frequency switching for PowerMac7,2 & 7,3
24162306a36Sopenharmony_ci */
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu_setfreq_high;
24462306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu_setfreq_low;
24562306a36Sopenharmony_cistatic struct pmf_function *pfunc_cpu_getfreq;
24662306a36Sopenharmony_cistatic struct pmf_function *pfunc_slewing_done;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic int g5_pfunc_switch_freq(int speed_mode)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct pmf_args args;
25162306a36Sopenharmony_ci	u32 done = 0;
25262306a36Sopenharmony_ci	unsigned long timeout;
25362306a36Sopenharmony_ci	int rc;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	DBG("g5_pfunc_switch_freq(%d)\n", speed_mode);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	/* If frequency is going up, first ramp up the voltage */
25862306a36Sopenharmony_ci	if (speed_mode < g5_pmode_cur)
25962306a36Sopenharmony_ci		g5_switch_volt(speed_mode);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	/* Do it */
26262306a36Sopenharmony_ci	if (speed_mode == CPUFREQ_HIGH)
26362306a36Sopenharmony_ci		rc = pmf_call_one(pfunc_cpu_setfreq_high, NULL);
26462306a36Sopenharmony_ci	else
26562306a36Sopenharmony_ci		rc = pmf_call_one(pfunc_cpu_setfreq_low, NULL);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	if (rc)
26862306a36Sopenharmony_ci		pr_warn("pfunc switch error %d\n", rc);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/* It's an irq GPIO so we should be able to just block here,
27162306a36Sopenharmony_ci	 * I'll do that later after I've properly tested the IRQ code for
27262306a36Sopenharmony_ci	 * platform functions
27362306a36Sopenharmony_ci	 */
27462306a36Sopenharmony_ci	timeout = jiffies + HZ/10;
27562306a36Sopenharmony_ci	while(!time_after(jiffies, timeout)) {
27662306a36Sopenharmony_ci		args.count = 1;
27762306a36Sopenharmony_ci		args.u[0].p = &done;
27862306a36Sopenharmony_ci		pmf_call_one(pfunc_slewing_done, &args);
27962306a36Sopenharmony_ci		if (done)
28062306a36Sopenharmony_ci			break;
28162306a36Sopenharmony_ci		usleep_range(500, 500);
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci	if (done == 0)
28462306a36Sopenharmony_ci		pr_warn("Timeout in clock slewing !\n");
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	/* If frequency is going down, last ramp the voltage */
28762306a36Sopenharmony_ci	if (speed_mode > g5_pmode_cur)
28862306a36Sopenharmony_ci		g5_switch_volt(speed_mode);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	g5_pmode_cur = speed_mode;
29162306a36Sopenharmony_ci	ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	return 0;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic int g5_pfunc_query_freq(void)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	struct pmf_args args;
29962306a36Sopenharmony_ci	u32 val = 0;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	args.count = 1;
30262306a36Sopenharmony_ci	args.u[0].p = &val;
30362306a36Sopenharmony_ci	pmf_call_one(pfunc_cpu_getfreq, &args);
30462306a36Sopenharmony_ci	return val ? CPUFREQ_HIGH : CPUFREQ_LOW;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci/*
30962306a36Sopenharmony_ci * Common interface to the cpufreq core
31062306a36Sopenharmony_ci */
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic int g5_cpufreq_target(struct cpufreq_policy *policy, unsigned int index)
31362306a36Sopenharmony_ci{
31462306a36Sopenharmony_ci	return g5_switch_freq(index);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic unsigned int g5_cpufreq_get_speed(unsigned int cpu)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	return g5_cpu_freqs[g5_pmode_cur].frequency;
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int g5_cpufreq_cpu_init(struct cpufreq_policy *policy)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	cpufreq_generic_init(policy, g5_cpu_freqs, transition_latency);
32562306a36Sopenharmony_ci	return 0;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic struct cpufreq_driver g5_cpufreq_driver = {
32962306a36Sopenharmony_ci	.name		= "powermac",
33062306a36Sopenharmony_ci	.flags		= CPUFREQ_CONST_LOOPS,
33162306a36Sopenharmony_ci	.init		= g5_cpufreq_cpu_init,
33262306a36Sopenharmony_ci	.verify		= cpufreq_generic_frequency_table_verify,
33362306a36Sopenharmony_ci	.target_index	= g5_cpufreq_target,
33462306a36Sopenharmony_ci	.get		= g5_cpufreq_get_speed,
33562306a36Sopenharmony_ci	.attr 		= cpufreq_generic_attr,
33662306a36Sopenharmony_ci};
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci#ifdef CONFIG_PMAC_SMU
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic int __init g5_neo2_cpufreq_init(struct device_node *cpunode)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	unsigned int psize, ssize;
34462306a36Sopenharmony_ci	unsigned long max_freq;
34562306a36Sopenharmony_ci	char *freq_method, *volt_method;
34662306a36Sopenharmony_ci	const u32 *valp;
34762306a36Sopenharmony_ci	u32 pvr_hi;
34862306a36Sopenharmony_ci	int use_volts_vdnap = 0;
34962306a36Sopenharmony_ci	int use_volts_smu = 0;
35062306a36Sopenharmony_ci	int rc = -ENODEV;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* Check supported platforms */
35362306a36Sopenharmony_ci	if (of_machine_is_compatible("PowerMac8,1") ||
35462306a36Sopenharmony_ci	    of_machine_is_compatible("PowerMac8,2") ||
35562306a36Sopenharmony_ci	    of_machine_is_compatible("PowerMac9,1") ||
35662306a36Sopenharmony_ci	    of_machine_is_compatible("PowerMac12,1"))
35762306a36Sopenharmony_ci		use_volts_smu = 1;
35862306a36Sopenharmony_ci	else if (of_machine_is_compatible("PowerMac11,2"))
35962306a36Sopenharmony_ci		use_volts_vdnap = 1;
36062306a36Sopenharmony_ci	else
36162306a36Sopenharmony_ci		return -ENODEV;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	/* Check 970FX for now */
36462306a36Sopenharmony_ci	valp = of_get_property(cpunode, "cpu-version", NULL);
36562306a36Sopenharmony_ci	if (!valp) {
36662306a36Sopenharmony_ci		DBG("No cpu-version property !\n");
36762306a36Sopenharmony_ci		goto bail_noprops;
36862306a36Sopenharmony_ci	}
36962306a36Sopenharmony_ci	pvr_hi = (*valp) >> 16;
37062306a36Sopenharmony_ci	if (pvr_hi != 0x3c && pvr_hi != 0x44) {
37162306a36Sopenharmony_ci		pr_err("Unsupported CPU version\n");
37262306a36Sopenharmony_ci		goto bail_noprops;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	/* Look for the powertune data in the device-tree */
37662306a36Sopenharmony_ci	g5_pmode_data = of_get_property(cpunode, "power-mode-data",&psize);
37762306a36Sopenharmony_ci	if (!g5_pmode_data) {
37862306a36Sopenharmony_ci		DBG("No power-mode-data !\n");
37962306a36Sopenharmony_ci		goto bail_noprops;
38062306a36Sopenharmony_ci	}
38162306a36Sopenharmony_ci	g5_pmode_max = psize / sizeof(u32) - 1;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	if (use_volts_smu) {
38462306a36Sopenharmony_ci		const struct smu_sdbp_header *shdr;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci		/* Look for the FVT table */
38762306a36Sopenharmony_ci		shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
38862306a36Sopenharmony_ci		if (!shdr)
38962306a36Sopenharmony_ci			goto bail_noprops;
39062306a36Sopenharmony_ci		g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1];
39162306a36Sopenharmony_ci		ssize = (shdr->len * sizeof(u32)) - sizeof(*shdr);
39262306a36Sopenharmony_ci		g5_fvt_count = ssize / sizeof(*g5_fvt_table);
39362306a36Sopenharmony_ci		g5_fvt_cur = 0;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci		/* Sanity checking */
39662306a36Sopenharmony_ci		if (g5_fvt_count < 1 || g5_pmode_max < 1)
39762306a36Sopenharmony_ci			goto bail_noprops;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		g5_switch_volt = g5_smu_switch_volt;
40062306a36Sopenharmony_ci		volt_method = "SMU";
40162306a36Sopenharmony_ci	} else if (use_volts_vdnap) {
40262306a36Sopenharmony_ci		struct device_node *root;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci		root = of_find_node_by_path("/");
40562306a36Sopenharmony_ci		if (root == NULL) {
40662306a36Sopenharmony_ci			pr_err("Can't find root of device tree\n");
40762306a36Sopenharmony_ci			goto bail_noprops;
40862306a36Sopenharmony_ci		}
40962306a36Sopenharmony_ci		pfunc_set_vdnap0 = pmf_find_function(root, "set-vdnap0");
41062306a36Sopenharmony_ci		pfunc_vdnap0_complete =
41162306a36Sopenharmony_ci			pmf_find_function(root, "slewing-done");
41262306a36Sopenharmony_ci		of_node_put(root);
41362306a36Sopenharmony_ci		if (pfunc_set_vdnap0 == NULL ||
41462306a36Sopenharmony_ci		    pfunc_vdnap0_complete == NULL) {
41562306a36Sopenharmony_ci			pr_err("Can't find required platform function\n");
41662306a36Sopenharmony_ci			goto bail_noprops;
41762306a36Sopenharmony_ci		}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci		g5_switch_volt = g5_vdnap_switch_volt;
42062306a36Sopenharmony_ci		volt_method = "GPIO";
42162306a36Sopenharmony_ci	} else {
42262306a36Sopenharmony_ci		g5_switch_volt = g5_dummy_switch_volt;
42362306a36Sopenharmony_ci		volt_method = "none";
42462306a36Sopenharmony_ci	}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	/*
42762306a36Sopenharmony_ci	 * From what I see, clock-frequency is always the maximal frequency.
42862306a36Sopenharmony_ci	 * The current driver can not slew sysclk yet, so we really only deal
42962306a36Sopenharmony_ci	 * with powertune steps for now. We also only implement full freq and
43062306a36Sopenharmony_ci	 * half freq in this version. So far, I haven't yet seen a machine
43162306a36Sopenharmony_ci	 * supporting anything else.
43262306a36Sopenharmony_ci	 */
43362306a36Sopenharmony_ci	valp = of_get_property(cpunode, "clock-frequency", NULL);
43462306a36Sopenharmony_ci	if (!valp)
43562306a36Sopenharmony_ci		return -ENODEV;
43662306a36Sopenharmony_ci	max_freq = (*valp)/1000;
43762306a36Sopenharmony_ci	g5_cpu_freqs[0].frequency = max_freq;
43862306a36Sopenharmony_ci	g5_cpu_freqs[1].frequency = max_freq/2;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	/* Set callbacks */
44162306a36Sopenharmony_ci	transition_latency = 12000;
44262306a36Sopenharmony_ci	g5_switch_freq = g5_scom_switch_freq;
44362306a36Sopenharmony_ci	g5_query_freq = g5_scom_query_freq;
44462306a36Sopenharmony_ci	freq_method = "SCOM";
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	/* Force apply current frequency to make sure everything is in
44762306a36Sopenharmony_ci	 * sync (voltage is right for example). Firmware may leave us with
44862306a36Sopenharmony_ci	 * a strange setting ...
44962306a36Sopenharmony_ci	 */
45062306a36Sopenharmony_ci	g5_switch_volt(CPUFREQ_HIGH);
45162306a36Sopenharmony_ci	msleep(10);
45262306a36Sopenharmony_ci	g5_pmode_cur = -1;
45362306a36Sopenharmony_ci	g5_switch_freq(g5_query_freq());
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	pr_info("Registering G5 CPU frequency driver\n");
45662306a36Sopenharmony_ci	pr_info("Frequency method: %s, Voltage method: %s\n",
45762306a36Sopenharmony_ci		freq_method, volt_method);
45862306a36Sopenharmony_ci	pr_info("Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n",
45962306a36Sopenharmony_ci		g5_cpu_freqs[1].frequency/1000,
46062306a36Sopenharmony_ci		g5_cpu_freqs[0].frequency/1000,
46162306a36Sopenharmony_ci		g5_cpu_freqs[g5_pmode_cur].frequency/1000);
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	rc = cpufreq_register_driver(&g5_cpufreq_driver);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	/* We keep the CPU node on hold... hopefully, Apple G5 don't have
46662306a36Sopenharmony_ci	 * hotplug CPU with a dynamic device-tree ...
46762306a36Sopenharmony_ci	 */
46862306a36Sopenharmony_ci	return rc;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci bail_noprops:
47162306a36Sopenharmony_ci	of_node_put(cpunode);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	return rc;
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci#endif /* CONFIG_PMAC_SMU */
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic int __init g5_pm72_cpufreq_init(struct device_node *cpunode)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	struct device_node *cpuid = NULL, *hwclock = NULL;
48262306a36Sopenharmony_ci	const u8 *eeprom = NULL;
48362306a36Sopenharmony_ci	const u32 *valp;
48462306a36Sopenharmony_ci	u64 max_freq, min_freq, ih, il;
48562306a36Sopenharmony_ci	int has_volt = 1, rc = 0;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	DBG("cpufreq: Initializing for PowerMac7,2, PowerMac7,3 and"
48862306a36Sopenharmony_ci	    " RackMac3,1...\n");
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	/* Lookup the cpuid eeprom node */
49162306a36Sopenharmony_ci        cpuid = of_find_node_by_path("/u3@0,f8000000/i2c@f8001000/cpuid@a0");
49262306a36Sopenharmony_ci	if (cpuid != NULL)
49362306a36Sopenharmony_ci		eeprom = of_get_property(cpuid, "cpuid", NULL);
49462306a36Sopenharmony_ci	if (eeprom == NULL) {
49562306a36Sopenharmony_ci		pr_err("Can't find cpuid EEPROM !\n");
49662306a36Sopenharmony_ci		rc = -ENODEV;
49762306a36Sopenharmony_ci		goto bail;
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	/* Lookup the i2c hwclock */
50162306a36Sopenharmony_ci	for_each_node_by_name(hwclock, "i2c-hwclock") {
50262306a36Sopenharmony_ci		const char *loc = of_get_property(hwclock,
50362306a36Sopenharmony_ci				"hwctrl-location", NULL);
50462306a36Sopenharmony_ci		if (loc == NULL)
50562306a36Sopenharmony_ci			continue;
50662306a36Sopenharmony_ci		if (strcmp(loc, "CPU CLOCK"))
50762306a36Sopenharmony_ci			continue;
50862306a36Sopenharmony_ci		if (!of_get_property(hwclock, "platform-get-frequency", NULL))
50962306a36Sopenharmony_ci			continue;
51062306a36Sopenharmony_ci		break;
51162306a36Sopenharmony_ci	}
51262306a36Sopenharmony_ci	if (hwclock == NULL) {
51362306a36Sopenharmony_ci		pr_err("Can't find i2c clock chip !\n");
51462306a36Sopenharmony_ci		rc = -ENODEV;
51562306a36Sopenharmony_ci		goto bail;
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	DBG("cpufreq: i2c clock chip found: %pOF\n", hwclock);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	/* Now get all the platform functions */
52162306a36Sopenharmony_ci	pfunc_cpu_getfreq =
52262306a36Sopenharmony_ci		pmf_find_function(hwclock, "get-frequency");
52362306a36Sopenharmony_ci	pfunc_cpu_setfreq_high =
52462306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-frequency-high");
52562306a36Sopenharmony_ci	pfunc_cpu_setfreq_low =
52662306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-frequency-low");
52762306a36Sopenharmony_ci	pfunc_slewing_done =
52862306a36Sopenharmony_ci		pmf_find_function(hwclock, "slewing-done");
52962306a36Sopenharmony_ci	pfunc_cpu0_volt_high =
53062306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-voltage-high-0");
53162306a36Sopenharmony_ci	pfunc_cpu0_volt_low =
53262306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-voltage-low-0");
53362306a36Sopenharmony_ci	pfunc_cpu1_volt_high =
53462306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-voltage-high-1");
53562306a36Sopenharmony_ci	pfunc_cpu1_volt_low =
53662306a36Sopenharmony_ci		pmf_find_function(hwclock, "set-voltage-low-1");
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	/* Check we have minimum requirements */
53962306a36Sopenharmony_ci	if (pfunc_cpu_getfreq == NULL || pfunc_cpu_setfreq_high == NULL ||
54062306a36Sopenharmony_ci	    pfunc_cpu_setfreq_low == NULL || pfunc_slewing_done == NULL) {
54162306a36Sopenharmony_ci		pr_err("Can't find platform functions !\n");
54262306a36Sopenharmony_ci		rc = -ENODEV;
54362306a36Sopenharmony_ci		goto bail;
54462306a36Sopenharmony_ci	}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	/* Check that we have complete sets */
54762306a36Sopenharmony_ci	if (pfunc_cpu0_volt_high == NULL || pfunc_cpu0_volt_low == NULL) {
54862306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu0_volt_high);
54962306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu0_volt_low);
55062306a36Sopenharmony_ci		pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL;
55162306a36Sopenharmony_ci		has_volt = 0;
55262306a36Sopenharmony_ci	}
55362306a36Sopenharmony_ci	if (!has_volt ||
55462306a36Sopenharmony_ci	    pfunc_cpu1_volt_high == NULL || pfunc_cpu1_volt_low == NULL) {
55562306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu1_volt_high);
55662306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu1_volt_low);
55762306a36Sopenharmony_ci		pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL;
55862306a36Sopenharmony_ci	}
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	/* Note: The device tree also contains a "platform-set-values"
56162306a36Sopenharmony_ci	 * function for which I haven't quite figured out the usage. It
56262306a36Sopenharmony_ci	 * might have to be called on init and/or wakeup, I'm not too sure
56362306a36Sopenharmony_ci	 * but things seem to work fine without it so far ...
56462306a36Sopenharmony_ci	 */
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	/* Get max frequency from device-tree */
56762306a36Sopenharmony_ci	valp = of_get_property(cpunode, "clock-frequency", NULL);
56862306a36Sopenharmony_ci	if (!valp) {
56962306a36Sopenharmony_ci		pr_err("Can't find CPU frequency !\n");
57062306a36Sopenharmony_ci		rc = -ENODEV;
57162306a36Sopenharmony_ci		goto bail;
57262306a36Sopenharmony_ci	}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	max_freq = (*valp)/1000;
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	/* Now calculate reduced frequency by using the cpuid input freq
57762306a36Sopenharmony_ci	 * ratio. This requires 64 bits math unless we are willing to lose
57862306a36Sopenharmony_ci	 * some precision
57962306a36Sopenharmony_ci	 */
58062306a36Sopenharmony_ci	ih = *((u32 *)(eeprom + 0x10));
58162306a36Sopenharmony_ci	il = *((u32 *)(eeprom + 0x20));
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	/* Check for machines with no useful settings */
58462306a36Sopenharmony_ci	if (il == ih) {
58562306a36Sopenharmony_ci		pr_warn("No low frequency mode available on this model !\n");
58662306a36Sopenharmony_ci		rc = -ENODEV;
58762306a36Sopenharmony_ci		goto bail;
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	min_freq = 0;
59162306a36Sopenharmony_ci	if (ih != 0 && il != 0)
59262306a36Sopenharmony_ci		min_freq = (max_freq * il) / ih;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	/* Sanity check */
59562306a36Sopenharmony_ci	if (min_freq >= max_freq || min_freq < 1000) {
59662306a36Sopenharmony_ci		pr_err("Can't calculate low frequency !\n");
59762306a36Sopenharmony_ci		rc = -ENXIO;
59862306a36Sopenharmony_ci		goto bail;
59962306a36Sopenharmony_ci	}
60062306a36Sopenharmony_ci	g5_cpu_freqs[0].frequency = max_freq;
60162306a36Sopenharmony_ci	g5_cpu_freqs[1].frequency = min_freq;
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	/* Based on a measurement on Xserve G5, rounded up. */
60462306a36Sopenharmony_ci	transition_latency = 10 * NSEC_PER_MSEC;
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	/* Set callbacks */
60762306a36Sopenharmony_ci	g5_switch_volt = g5_pfunc_switch_volt;
60862306a36Sopenharmony_ci	g5_switch_freq = g5_pfunc_switch_freq;
60962306a36Sopenharmony_ci	g5_query_freq = g5_pfunc_query_freq;
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	/* Force apply current frequency to make sure everything is in
61262306a36Sopenharmony_ci	 * sync (voltage is right for example). Firmware may leave us with
61362306a36Sopenharmony_ci	 * a strange setting ...
61462306a36Sopenharmony_ci	 */
61562306a36Sopenharmony_ci	g5_switch_volt(CPUFREQ_HIGH);
61662306a36Sopenharmony_ci	msleep(10);
61762306a36Sopenharmony_ci	g5_pmode_cur = -1;
61862306a36Sopenharmony_ci	g5_switch_freq(g5_query_freq());
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	pr_info("Registering G5 CPU frequency driver\n");
62162306a36Sopenharmony_ci	pr_info("Frequency method: i2c/pfunc, Voltage method: %s\n",
62262306a36Sopenharmony_ci		has_volt ? "i2c/pfunc" : "none");
62362306a36Sopenharmony_ci	pr_info("Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n",
62462306a36Sopenharmony_ci		g5_cpu_freqs[1].frequency/1000,
62562306a36Sopenharmony_ci		g5_cpu_freqs[0].frequency/1000,
62662306a36Sopenharmony_ci		g5_cpu_freqs[g5_pmode_cur].frequency/1000);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	rc = cpufreq_register_driver(&g5_cpufreq_driver);
62962306a36Sopenharmony_ci bail:
63062306a36Sopenharmony_ci	if (rc != 0) {
63162306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu_getfreq);
63262306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu_setfreq_high);
63362306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu_setfreq_low);
63462306a36Sopenharmony_ci		pmf_put_function(pfunc_slewing_done);
63562306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu0_volt_high);
63662306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu0_volt_low);
63762306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu1_volt_high);
63862306a36Sopenharmony_ci		pmf_put_function(pfunc_cpu1_volt_low);
63962306a36Sopenharmony_ci	}
64062306a36Sopenharmony_ci	of_node_put(hwclock);
64162306a36Sopenharmony_ci	of_node_put(cpuid);
64262306a36Sopenharmony_ci	of_node_put(cpunode);
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	return rc;
64562306a36Sopenharmony_ci}
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_cistatic int __init g5_cpufreq_init(void)
64862306a36Sopenharmony_ci{
64962306a36Sopenharmony_ci	struct device_node *cpunode;
65062306a36Sopenharmony_ci	int rc = 0;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	/* Get first CPU node */
65362306a36Sopenharmony_ci	cpunode = of_cpu_device_node_get(0);
65462306a36Sopenharmony_ci	if (cpunode == NULL) {
65562306a36Sopenharmony_ci		pr_err("Can't find any CPU node\n");
65662306a36Sopenharmony_ci		return -ENODEV;
65762306a36Sopenharmony_ci	}
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	if (of_machine_is_compatible("PowerMac7,2") ||
66062306a36Sopenharmony_ci	    of_machine_is_compatible("PowerMac7,3") ||
66162306a36Sopenharmony_ci	    of_machine_is_compatible("RackMac3,1"))
66262306a36Sopenharmony_ci		rc = g5_pm72_cpufreq_init(cpunode);
66362306a36Sopenharmony_ci#ifdef CONFIG_PMAC_SMU
66462306a36Sopenharmony_ci	else
66562306a36Sopenharmony_ci		rc = g5_neo2_cpufreq_init(cpunode);
66662306a36Sopenharmony_ci#endif /* CONFIG_PMAC_SMU */
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	return rc;
66962306a36Sopenharmony_ci}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_cimodule_init(g5_cpufreq_init);
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
675