// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2002 - 2005 Benjamin Herrenschmidt <benh@kernel.crashing.org> * and Markus Demleitner <msdemlei@cl.uni-heidelberg.de> * * This driver adds basic cpufreq support for SMU & 970FX based G5 Macs, * that is iMac G5 and latest single CPU desktop.
*/
/* Power mode data is an array of the 32 bits PCR values to use for * the various frequencies, retrieved from the device-tree
*/ staticint g5_pmode_cur;
staticstruct smu_sdbp_fvt *g5_fvt_table; /* table of op. points */ staticint g5_fvt_count; /* number of op. points */ staticint g5_fvt_cur; /* current op. point */
/* * SMU based voltage switching for Neo2 platforms
*/
/* 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;
usleep_range(1000, 1000);
} if (done == 0)
pr_warn("Timeout in clock slewing !\n");
}
/* * SCOM based frequency switching for 970FX rev3
*/ staticint g5_scom_switch_freq(int speed_mode)
{ unsignedlong flags; int to;
/* If frequency is going up, first ramp up the voltage */ if (speed_mode < g5_pmode_cur)
g5_switch_volt(speed_mode);
staticvoid 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);
}
usleep_range(10000, 10000); /* should be faster , to fix */
}
/* * Platform function based frequency switching for PowerMac7,2 & 7,3
*/
/* 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)
rc = pmf_call_one(pfunc_cpu_setfreq_high, NULL); else
rc = pmf_call_one(pfunc_cpu_setfreq_low, NULL);
if (rc)
pr_warn("pfunc switch error %d\n", rc);
/* 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;
usleep_range(500, 500);
} if (done == 0)
pr_warn("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);
/* Check 970FX for now */
valp = of_get_property(cpunode, "cpu-version", NULL); if (!valp) {
DBG("No cpu-version property !\n"); goto bail_noprops;
}
pvr_hi = (*valp) >> 16; if (pvr_hi != 0x3c && pvr_hi != 0x44) {
pr_err("Unsupported CPU version\n"); goto bail_noprops;
}
/* Look for the powertune data in the device-tree */
g5_pmode_data = of_get_property(cpunode, "power-mode-data",&psize); if (!g5_pmode_data) {
DBG("No power-mode-data !\n"); goto bail_noprops;
}
g5_pmode_max = psize / sizeof(u32) - 1;
if (use_volts_smu) { conststruct smu_sdbp_header *shdr;
/* * 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.
*/
valp = of_get_property(cpunode, "clock-frequency", NULL); if (!valp) return -ENODEV;
max_freq = (*valp)/1000;
g5_cpu_freqs[0].frequency = max_freq;
g5_cpu_freqs[1].frequency = max_freq/2;
/* 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());
pr_info("Registering G5 CPU frequency driver\n");
pr_info("Frequency method: %s, Voltage method: %s\n",
freq_method, volt_method);
pr_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;
/* 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 */
valp = of_get_property(cpunode, "clock-frequency", NULL); if (!valp) {
pr_err("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));
/* Check for machines with no useful settings */ if (il == ih) {
pr_warn("No low frequency mode available on this model !\n");
rc = -ENODEV; goto bail;
}
min_freq = 0; if (ih != 0 && il != 0)
min_freq = (max_freq * il) / ih;
/* 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());
pr_info("Registering G5 CPU frequency driver\n");
pr_info("Frequency method: i2c/pfunc, Voltage method: %s\n",
has_volt ? "i2c/pfunc" : "none");
pr_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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.