162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * amd_freq_sensitivity.c: AMD frequency sensitivity feedback powersave bias 462306a36Sopenharmony_ci * for the ondemand governor. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2013 Advanced Micro Devices, Inc. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Author: Jacob Shin <jacob.shin@amd.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/types.h> 1462306a36Sopenharmony_ci#include <linux/pci.h> 1562306a36Sopenharmony_ci#include <linux/percpu-defs.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <asm/msr.h> 2062306a36Sopenharmony_ci#include <asm/cpufeature.h> 2162306a36Sopenharmony_ci#include <asm/cpu_device_id.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "cpufreq_ondemand.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define MSR_AMD64_FREQ_SENSITIVITY_ACTUAL 0xc0010080 2662306a36Sopenharmony_ci#define MSR_AMD64_FREQ_SENSITIVITY_REFERENCE 0xc0010081 2762306a36Sopenharmony_ci#define CLASS_CODE_SHIFT 56 2862306a36Sopenharmony_ci#define POWERSAVE_BIAS_MAX 1000 2962306a36Sopenharmony_ci#define POWERSAVE_BIAS_DEF 400 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct cpu_data_t { 3262306a36Sopenharmony_ci u64 actual; 3362306a36Sopenharmony_ci u64 reference; 3462306a36Sopenharmony_ci unsigned int freq_prev; 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct cpu_data_t, cpu_data); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic unsigned int amd_powersave_bias_target(struct cpufreq_policy *policy, 4062306a36Sopenharmony_ci unsigned int freq_next, 4162306a36Sopenharmony_ci unsigned int relation) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci int sensitivity; 4462306a36Sopenharmony_ci long d_actual, d_reference; 4562306a36Sopenharmony_ci struct msr actual, reference; 4662306a36Sopenharmony_ci struct cpu_data_t *data = &per_cpu(cpu_data, policy->cpu); 4762306a36Sopenharmony_ci struct policy_dbs_info *policy_dbs = policy->governor_data; 4862306a36Sopenharmony_ci struct dbs_data *od_data = policy_dbs->dbs_data; 4962306a36Sopenharmony_ci struct od_dbs_tuners *od_tuners = od_data->tuners; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (!policy->freq_table) 5262306a36Sopenharmony_ci return freq_next; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, 5562306a36Sopenharmony_ci &actual.l, &actual.h); 5662306a36Sopenharmony_ci rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_REFERENCE, 5762306a36Sopenharmony_ci &reference.l, &reference.h); 5862306a36Sopenharmony_ci actual.h &= 0x00ffffff; 5962306a36Sopenharmony_ci reference.h &= 0x00ffffff; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci /* counter wrapped around, so stay on current frequency */ 6262306a36Sopenharmony_ci if (actual.q < data->actual || reference.q < data->reference) { 6362306a36Sopenharmony_ci freq_next = policy->cur; 6462306a36Sopenharmony_ci goto out; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci d_actual = actual.q - data->actual; 6862306a36Sopenharmony_ci d_reference = reference.q - data->reference; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* divide by 0, so stay on current frequency as well */ 7162306a36Sopenharmony_ci if (d_reference == 0) { 7262306a36Sopenharmony_ci freq_next = policy->cur; 7362306a36Sopenharmony_ci goto out; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci sensitivity = POWERSAVE_BIAS_MAX - 7762306a36Sopenharmony_ci (POWERSAVE_BIAS_MAX * (d_reference - d_actual) / d_reference); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci clamp(sensitivity, 0, POWERSAVE_BIAS_MAX); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci /* this workload is not CPU bound, so choose a lower freq */ 8262306a36Sopenharmony_ci if (sensitivity < od_tuners->powersave_bias) { 8362306a36Sopenharmony_ci if (data->freq_prev == policy->cur) 8462306a36Sopenharmony_ci freq_next = policy->cur; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (freq_next > policy->cur) 8762306a36Sopenharmony_ci freq_next = policy->cur; 8862306a36Sopenharmony_ci else if (freq_next < policy->cur) 8962306a36Sopenharmony_ci freq_next = policy->min; 9062306a36Sopenharmony_ci else { 9162306a36Sopenharmony_ci unsigned int index; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci index = cpufreq_table_find_index_h(policy, 9462306a36Sopenharmony_ci policy->cur - 1, 9562306a36Sopenharmony_ci relation & CPUFREQ_RELATION_E); 9662306a36Sopenharmony_ci freq_next = policy->freq_table[index].frequency; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci data->freq_prev = freq_next; 10062306a36Sopenharmony_ci } else 10162306a36Sopenharmony_ci data->freq_prev = 0; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ciout: 10462306a36Sopenharmony_ci data->actual = actual.q; 10562306a36Sopenharmony_ci data->reference = reference.q; 10662306a36Sopenharmony_ci return freq_next; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int __init amd_freq_sensitivity_init(void) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci u64 val; 11262306a36Sopenharmony_ci struct pci_dev *pcidev; 11362306a36Sopenharmony_ci unsigned int pci_vendor; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) 11662306a36Sopenharmony_ci pci_vendor = PCI_VENDOR_ID_AMD; 11762306a36Sopenharmony_ci else if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) 11862306a36Sopenharmony_ci pci_vendor = PCI_VENDOR_ID_HYGON; 11962306a36Sopenharmony_ci else 12062306a36Sopenharmony_ci return -ENODEV; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci pcidev = pci_get_device(pci_vendor, 12362306a36Sopenharmony_ci PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, NULL); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (!pcidev) { 12662306a36Sopenharmony_ci if (!boot_cpu_has(X86_FEATURE_PROC_FEEDBACK)) 12762306a36Sopenharmony_ci return -ENODEV; 12862306a36Sopenharmony_ci } else { 12962306a36Sopenharmony_ci pci_dev_put(pcidev); 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci if (rdmsrl_safe(MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, &val)) 13362306a36Sopenharmony_ci return -ENODEV; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (!(val >> CLASS_CODE_SHIFT)) 13662306a36Sopenharmony_ci return -ENODEV; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci od_register_powersave_bias_handler(amd_powersave_bias_target, 13962306a36Sopenharmony_ci POWERSAVE_BIAS_DEF); 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_cilate_initcall(amd_freq_sensitivity_init); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic void __exit amd_freq_sensitivity_exit(void) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci od_unregister_powersave_bias_handler(); 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_cimodule_exit(amd_freq_sensitivity_exit); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic const struct x86_cpu_id __maybe_unused amd_freq_sensitivity_ids[] = { 15162306a36Sopenharmony_ci X86_MATCH_FEATURE(X86_FEATURE_PROC_FEEDBACK, NULL), 15262306a36Sopenharmony_ci {} 15362306a36Sopenharmony_ci}; 15462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, amd_freq_sensitivity_ids); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ciMODULE_AUTHOR("Jacob Shin <jacob.shin@amd.com>"); 15762306a36Sopenharmony_ciMODULE_DESCRIPTION("AMD frequency sensitivity feedback powersave bias for " 15862306a36Sopenharmony_ci "the ondemand governor."); 15962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 160