// SPDX-License-Identifier: GPL-2.0-only /* * (c) 2003-2012 Advanced Micro Devices, Inc. * * Maintainer: * Andreas Herrmann <herrmann.der.user@googlemail.com> * * Based on the powernow-k7.c module written by Dave Jones. * (C) 2003 Dave Jones on behalf of SuSE Labs * (C) 2004 Dominik Brodowski <linux@brodo.de> * (C) 2004 Pavel Machek <pavel@ucw.cz> * Based upon datasheets & sample CPUs kindly provided by AMD. * * Valuable input gratefully received from Dave Jones, Pavel Machek, * Dominik Brodowski, Jacob Shin, and others. * Originally developed by Paul Devriendt. * * Processor information obtained from Chapter 9 (Power and Thermal * Management) of the "BIOS and Kernel Developer's Guide (BKDG) for * the AMD Athlon 64 and AMD Opteron Processors" and section "2.x * Power Management" in BKDGs for newer AMD CPU families. * * Tables for specific CPUs can be inferred from AMD's processor * power and thermal data sheets, (e.g. 30417.pdf, 30430.pdf, 43375.pdf)
*/
/* Return a frequency in MHz, given an input fid */ static u32 find_freq_from_fid(u32 fid)
{ return 800 + (fid * 100);
}
/* Return a frequency in KHz, given an input fid */ static u32 find_khz_freq_from_fid(u32 fid)
{ return 1000 * find_freq_from_fid(fid);
}
/* Return the vco fid for an input fid * * Each "low" fid has corresponding "high" fid, and you can get to "low" fids * only from corresponding high fids. This returns "high" fid corresponding to * "low" one.
*/ static u32 convert_fid_to_vco_fid(u32 fid)
{ if (fid < HI_FID_TABLE_BOTTOM) return 8 + (2 * fid); else return fid;
}
/* * Return 1 if the pending bit is set. Unless we just instructed the processor * to transition to a new state, seeing this bit set is really bad news.
*/ staticint pending_bit_stuck(void)
{
u32 lo, hi __always_unused;
/* * Update the global current fid / vid values from the status msr. * Returns 1 on error.
*/ staticint query_current_values_with_pending_wait(struct powernow_k8_data *data)
{
u32 lo, hi;
u32 i = 0;
do { if (i++ > 10000) {
pr_debug("detected change pending stuck\n"); return 1;
}
rdmsr(MSR_FIDVID_STATUS, lo, hi);
} while (lo & MSR_S_LO_CHANGE_PENDING);
data->currvid = hi & MSR_S_HI_CURRENT_VID;
data->currfid = lo & MSR_S_LO_CURRENT_FID;
return 0;
}
/* the isochronous relief time */ staticvoid count_off_irt(struct powernow_k8_data *data)
{
udelay((1 << data->irt) * 10);
}
/* the voltage stabilization time */ staticvoid count_off_vst(struct powernow_k8_data *data)
{
udelay(data->vstable * VST_UNITS_20US);
}
/* need to init the control msr to a safe value (for each cpu) */ staticvoid fidvid_msr_init(void)
{
u32 lo, hi;
u8 fid, vid;
rdmsr(MSR_FIDVID_STATUS, lo, hi);
vid = hi & MSR_S_HI_CURRENT_VID;
fid = lo & MSR_S_LO_CURRENT_FID;
lo = fid | (vid << MSR_C_LO_VID_SHIFT);
hi = MSR_C_HI_STP_GNT_BENIGN;
pr_debug("cpu%d, init lo 0x%x, hi 0x%x\n", smp_processor_id(), lo, hi);
wrmsr(MSR_FIDVID_CTL, lo, hi);
}
/* write the new fid value along with the other control fields to the msr */ staticint write_new_fid(struct powernow_k8_data *data, u32 fid)
{
u32 lo;
u32 savevid = data->currvid;
u32 i = 0;
if ((fid & INVALID_FID_MASK) || (data->currvid & INVALID_VID_MASK)) {
pr_err("internal error - overflow on fid write\n"); return 1;
}
lo = fid;
lo |= (data->currvid << MSR_C_LO_VID_SHIFT);
lo |= MSR_C_LO_INIT_FID_VID;
pr_debug("writing fid 0x%x, lo 0x%x, hi 0x%x\n",
fid, lo, data->plllock * PLL_LOCK_CONVERSION);
do {
wrmsr(MSR_FIDVID_CTL, lo, data->plllock * PLL_LOCK_CONVERSION); if (i++ > 100) {
pr_err("Hardware error - pending bit very stuck - no further pstate changes possible\n"); return 1;
}
} while (query_current_values_with_pending_wait(data));
count_off_irt(data);
if (savevid != data->currvid) {
pr_err("vid change on fid trans, old 0x%x, new 0x%x\n",
savevid, data->currvid); return 1;
}
if (fid != data->currfid) {
pr_err("fid trans failed, fid 0x%x, curr 0x%x\n", fid,
data->currfid); return 1;
}
return 0;
}
/* Write a new vid to the hardware */ staticint write_new_vid(struct powernow_k8_data *data, u32 vid)
{
u32 lo;
u32 savefid = data->currfid; int i = 0;
if ((data->currfid & INVALID_FID_MASK) || (vid & INVALID_VID_MASK)) {
pr_err("internal error - overflow on vid write\n"); return 1;
}
lo = data->currfid;
lo |= (vid << MSR_C_LO_VID_SHIFT);
lo |= MSR_C_LO_INIT_FID_VID;
pr_debug("writing vid 0x%x, lo 0x%x, hi 0x%x\n",
vid, lo, STOP_GRANT_5NS);
do {
wrmsr(MSR_FIDVID_CTL, lo, STOP_GRANT_5NS); if (i++ > 100) {
pr_err("internal error - pending bit very stuck - no further pstate changes possible\n"); return 1;
}
} while (query_current_values_with_pending_wait(data));
if (savefid != data->currfid) {
pr_err("fid changed on vid trans, old 0x%x new 0x%x\n",
savefid, data->currfid); return 1;
}
if (vid != data->currvid) {
pr_err("vid trans failed, vid 0x%x, curr 0x%x\n",
vid, data->currvid); return 1;
}
return 0;
}
/* * Reduce the vid by the max of step or reqvid. * Decreasing vid codes represent increasing voltages: * vid of 0 is 1.550V, vid of 0x1e is 0.800V, vid of VID_OFF is off.
*/ staticint decrease_vid_code_by_step(struct powernow_k8_data *data,
u32 reqvid, u32 step)
{ if ((data->currvid - reqvid) > step)
reqvid = data->currvid - step;
if (write_new_vid(data, reqvid)) return 1;
count_off_vst(data);
return 0;
}
/* Change Opteron/Athlon64 fid and vid, by the 3 phases. */ staticint transition_fid_vid(struct powernow_k8_data *data,
u32 reqfid, u32 reqvid)
{ if (core_voltage_pre_transition(data, reqvid, reqfid)) return 1;
if (core_frequency_transition(data, reqfid)) return 1;
if (core_voltage_post_transition(data, reqvid)) return 1;
if (query_current_values_with_pending_wait(data)) return 1;
if (data->batps) { /* use ACPI support to get full speed on mains power */
pr_warn("Only %d pstates usable (use ACPI driver for full range\n",
data->batps);
data->numps = data->batps;
}
for (j = 1; j < data->numps; j++) { if (pst[j-1].fid >= pst[j].fid) {
pr_err("PST out of sequence\n"); return -EINVAL;
}
}
if (data->numps < 2) {
pr_err("no p states to transition\n"); return -ENODEV;
}
if (check_pst_table(data, pst, maxvid)) return -EINVAL;
data->numps = psb->numps;
pr_debug("numpstates: 0x%x\n", data->numps); return fill_powernow_table(data,
(struct pst_s *)(psb+1), maxvid);
} /* * If you see this message, complain to BIOS manufacturer. If * he tells you "we do not support Linux" or some similar * nonsense, remember that Windows 2000 uses the same legacy * mechanism that the old Linux PSB driver uses. Tell them it * is broken with Windows 2000. * * The reference to the AMD documentation is chapter 9 in the * BIOS and Kernel Developer's Guide, which is available on * www.amd.com
*/
pr_err(FW_BUG "No PSB or ACPI _PSS objects\n");
pr_err("Make sure that your BIOS is up to date and Cool'N'Quiet support is enabled in BIOS setup\n"); return -ENODEV;
}
/* verify frequency is OK */ if ((freq > (MAX_FREQ * 1000)) || (freq < (MIN_FREQ * 1000))) {
pr_debug("invalid freq %u kHz, ignoring\n", freq);
invalidate_entry(powernow_table, i); continue;
}
/* verify voltage is OK -
* BIOSs are using "off" to indicate invalid */ if (vid == VID_OFF) {
pr_debug("invalid vid %u, ignoring\n", vid);
invalidate_entry(powernow_table, i); continue;
}
staticvoid powernow_k8_cpu_exit_acpi(struct powernow_k8_data *data)
{ if (data->acpi_data.state_count)
acpi_processor_unregister_performance(data->cpu);
free_cpumask_var(data->acpi_data.shared_cpu_map);
}
staticint get_transition_latency(struct powernow_k8_data *data)
{ int max_latency = 0; int i; for (i = 0; i < data->acpi_data.state_count; i++) { int cur_latency = data->acpi_data.states[i].transition_latency
+ data->acpi_data.states[i].bus_master_latency; if (cur_latency > max_latency)
max_latency = cur_latency;
} if (max_latency == 0) {
pr_err(FW_WARN "Invalid zero transition latency\n");
max_latency = 1;
} /* value in usecs, needs to be in nanoseconds */ return 1000 * max_latency;
}
/* Take a frequency, and issue the fid/vid transition command */ staticint transition_frequency_fidvid(struct powernow_k8_data *data, unsignedint index, struct cpufreq_policy *policy)
{
u32 fid = 0;
u32 vid = 0; int res; struct cpufreq_freqs freqs;
pr_debug("cpu %d transition to index %u\n", smp_processor_id(), index);
/* fid/vid correctness check for k8 */ /* fid are the lower 8 bits of the index we stored into * the cpufreq frequency table in find_psb_table, vid * are the upper 8 bits.
*/
fid = data->powernow_table[index].driver_data & 0xFF;
vid = (data->powernow_table[index].driver_data & 0xFF00) >> 8;
pr_debug("table matched fid 0x%x, giving vid 0x%x\n", fid, vid);
if (query_current_values_with_pending_wait(data)) return 1;
if ((data->currvid == vid) && (data->currfid == fid)) {
pr_debug("target matches current values (fid 0x%x, vid 0x%x)\n",
fid, vid); return 0;
}
pr_debug("cpu %d, changing to fid 0x%x, vid 0x%x\n",
smp_processor_id(), fid, vid);
freqs.old = find_khz_freq_from_fid(data->currfid);
freqs.new = find_khz_freq_from_fid(fid);
cpufreq_freq_transition_begin(policy, &freqs);
res = transition_fid_vid(data, fid, vid);
cpufreq_freq_transition_end(policy, &freqs, res);
if (pending_bit_stuck()) {
pr_err("failing targ, change pending bit set\n"); return -EIO;
}
pr_debug("targ: cpu %d, %d kHz, min %d, max %d\n",
pol->cpu, data->powernow_table[newstate].frequency, pol->min,
pol->max);
if (query_current_values_with_pending_wait(data)) return -EIO;
pr_debug("targ: curr fid 0x%x, vid 0x%x\n",
data->currfid, data->currvid);
if ((checkvid != data->currvid) ||
(checkfid != data->currfid)) {
pr_info("error - out of sync, fix 0x%x 0x%x, vid 0x%x 0x%x\n",
checkfid, data->currfid,
checkvid, data->currvid);
}
mutex_lock(&fidvid_mutex);
powernow_k8_acpi_pst_values(data, newstate);
ret = transition_frequency_fidvid(data, newstate, pol);
if (ret) {
pr_err("transition frequency failed\n");
mutex_unlock(&fidvid_mutex); return 1;
}
mutex_unlock(&fidvid_mutex);
pol->cur = find_khz_freq_from_fid(data->currfid);
return 0;
}
/* Driver entry point to switch to the target frequency */ staticint powernowk8_target(struct cpufreq_policy *pol, unsigned index)
{ struct powernowk8_target_arg pta = { .pol = pol, .newstate = index };
if (pending_bit_stuck()) {
pr_err("failing init, change pending bit set\n");
init_on_cpu->rc = -ENODEV; return;
}
if (query_current_values_with_pending_wait(init_on_cpu->data)) {
init_on_cpu->rc = -ENODEV; return;
}
fidvid_msr_init();
init_on_cpu->rc = 0;
}
#define MISSING_PSS_MSG \
FW_BUG "No compatible ACPI _PSS objects found.\n" \
FW_BUG "First, make sure Cool'N'Quiet is enabled in the BIOS.\n" \
FW_BUG "If that doesn't help, try upgrading your BIOS.\n"
/* per CPU init entry point to the driver */ staticint powernowk8_cpu_init(struct cpufreq_policy *pol)
{ struct powernow_k8_data *data; struct init_on_cpu init_on_cpu; int rc, cpu;
smp_call_function_single(pol->cpu, check_supported_cpu, &rc, 1); if (rc) return -ENODEV;
data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM;
data->cpu = pol->cpu;
if (powernow_k8_cpu_init_acpi(data)) { /* * Use the PSB BIOS structure. This is only available on * an UP version, and is deprecated by AMD.
*/ if (num_online_cpus() != 1) {
pr_err_once(MISSING_PSS_MSG); goto err_out;
} if (pol->cpu != 0) {
pr_err(FW_BUG "No ACPI _PSS objects for CPU other than CPU0. Complain to your BIOS vendor.\n"); goto err_out;
}
rc = find_psb_table(data); if (rc) goto err_out;
/* Take a crude guess here.
* That guess was in microseconds, so multiply with 1000 */
pol->cpuinfo.transition_latency = (
((data->rvo + 8) * data->vstable * VST_UNITS_20US) +
((1 << data->irt) * 30)) * 1000;
} else/* ACPI _PSS objects available */
pol->cpuinfo.transition_latency = get_transition_latency(data);
/* only run on specific CPU from here on */
init_on_cpu.data = data;
smp_call_function_single(data->cpu, powernowk8_cpu_init_on_cpu,
&init_on_cpu, 1);
rc = init_on_cpu.rc; if (rc != 0) goto err_out_exit_acpi;
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.