| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * amd_freq_sensitivity.c: AMD frequency sensitivity feedback powersave bias | 
 |  *                         for the ondemand governor. | 
 |  * | 
 |  * Copyright (C) 2013 Advanced Micro Devices, Inc. | 
 |  * | 
 |  * Author: Jacob Shin <jacob.shin@amd.com> | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/types.h> | 
 | #include <linux/pci.h> | 
 | #include <linux/percpu-defs.h> | 
 | #include <linux/init.h> | 
 | #include <linux/mod_devicetable.h> | 
 |  | 
 | #include <asm/msr.h> | 
 | #include <asm/cpufeature.h> | 
 | #include <asm/cpu_device_id.h> | 
 |  | 
 | #include "cpufreq_ondemand.h" | 
 |  | 
 | #define MSR_AMD64_FREQ_SENSITIVITY_ACTUAL	0xc0010080 | 
 | #define MSR_AMD64_FREQ_SENSITIVITY_REFERENCE	0xc0010081 | 
 | #define CLASS_CODE_SHIFT			56 | 
 | #define POWERSAVE_BIAS_MAX			1000 | 
 | #define POWERSAVE_BIAS_DEF			400 | 
 |  | 
 | struct cpu_data_t { | 
 | 	u64 actual; | 
 | 	u64 reference; | 
 | 	unsigned int freq_prev; | 
 | }; | 
 |  | 
 | static DEFINE_PER_CPU(struct cpu_data_t, cpu_data); | 
 |  | 
 | static unsigned int amd_powersave_bias_target(struct cpufreq_policy *policy, | 
 | 					      unsigned int freq_next, | 
 | 					      unsigned int relation) | 
 | { | 
 | 	int sensitivity; | 
 | 	long d_actual, d_reference; | 
 | 	struct msr actual, reference; | 
 | 	struct cpu_data_t *data = &per_cpu(cpu_data, policy->cpu); | 
 | 	struct policy_dbs_info *policy_dbs = policy->governor_data; | 
 | 	struct dbs_data *od_data = policy_dbs->dbs_data; | 
 | 	struct od_dbs_tuners *od_tuners = od_data->tuners; | 
 |  | 
 | 	if (!policy->freq_table) | 
 | 		return freq_next; | 
 |  | 
 | 	rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, | 
 | 		&actual.l, &actual.h); | 
 | 	rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_REFERENCE, | 
 | 		&reference.l, &reference.h); | 
 | 	actual.h &= 0x00ffffff; | 
 | 	reference.h &= 0x00ffffff; | 
 |  | 
 | 	/* counter wrapped around, so stay on current frequency */ | 
 | 	if (actual.q < data->actual || reference.q < data->reference) { | 
 | 		freq_next = policy->cur; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	d_actual = actual.q - data->actual; | 
 | 	d_reference = reference.q - data->reference; | 
 |  | 
 | 	/* divide by 0, so stay on current frequency as well */ | 
 | 	if (d_reference == 0) { | 
 | 		freq_next = policy->cur; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	sensitivity = POWERSAVE_BIAS_MAX - | 
 | 		(POWERSAVE_BIAS_MAX * (d_reference - d_actual) / d_reference); | 
 |  | 
 | 	clamp(sensitivity, 0, POWERSAVE_BIAS_MAX); | 
 |  | 
 | 	/* this workload is not CPU bound, so choose a lower freq */ | 
 | 	if (sensitivity < od_tuners->powersave_bias) { | 
 | 		if (data->freq_prev == policy->cur) | 
 | 			freq_next = policy->cur; | 
 |  | 
 | 		if (freq_next > policy->cur) | 
 | 			freq_next = policy->cur; | 
 | 		else if (freq_next < policy->cur) | 
 | 			freq_next = policy->min; | 
 | 		else { | 
 | 			unsigned int index; | 
 |  | 
 | 			index = cpufreq_table_find_index_h(policy, | 
 | 							   policy->cur - 1, | 
 | 							   relation & CPUFREQ_RELATION_E); | 
 | 			freq_next = policy->freq_table[index].frequency; | 
 | 		} | 
 |  | 
 | 		data->freq_prev = freq_next; | 
 | 	} else | 
 | 		data->freq_prev = 0; | 
 |  | 
 | out: | 
 | 	data->actual = actual.q; | 
 | 	data->reference = reference.q; | 
 | 	return freq_next; | 
 | } | 
 |  | 
 | static int __init amd_freq_sensitivity_init(void) | 
 | { | 
 | 	u64 val; | 
 | 	struct pci_dev *pcidev; | 
 | 	unsigned int pci_vendor; | 
 |  | 
 | 	if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) | 
 | 		pci_vendor = PCI_VENDOR_ID_AMD; | 
 | 	else if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) | 
 | 		pci_vendor = PCI_VENDOR_ID_HYGON; | 
 | 	else | 
 | 		return -ENODEV; | 
 |  | 
 | 	pcidev = pci_get_device(pci_vendor, | 
 | 			PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, NULL); | 
 |  | 
 | 	if (!pcidev) { | 
 | 		if (!boot_cpu_has(X86_FEATURE_PROC_FEEDBACK)) | 
 | 			return -ENODEV; | 
 | 	} | 
 |  | 
 | 	if (rdmsrl_safe(MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, &val)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	if (!(val >> CLASS_CODE_SHIFT)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	od_register_powersave_bias_handler(amd_powersave_bias_target, | 
 | 			POWERSAVE_BIAS_DEF); | 
 | 	return 0; | 
 | } | 
 | late_initcall(amd_freq_sensitivity_init); | 
 |  | 
 | static void __exit amd_freq_sensitivity_exit(void) | 
 | { | 
 | 	od_unregister_powersave_bias_handler(); | 
 | } | 
 | module_exit(amd_freq_sensitivity_exit); | 
 |  | 
 | static const struct x86_cpu_id __maybe_unused amd_freq_sensitivity_ids[] = { | 
 | 	X86_MATCH_FEATURE(X86_FEATURE_PROC_FEEDBACK, NULL), | 
 | 	{} | 
 | }; | 
 | MODULE_DEVICE_TABLE(x86cpu, amd_freq_sensitivity_ids); | 
 |  | 
 | MODULE_AUTHOR("Jacob Shin <jacob.shin@amd.com>"); | 
 | MODULE_DESCRIPTION("AMD frequency sensitivity feedback powersave bias for " | 
 | 		"the ondemand governor."); | 
 | MODULE_LICENSE("GPL"); |