1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29c5320c8SJacob Shin /*
39c5320c8SJacob Shin  * amd_freq_sensitivity.c: AMD frequency sensitivity feedback powersave bias
49c5320c8SJacob Shin  *                         for the ondemand governor.
59c5320c8SJacob Shin  *
69c5320c8SJacob Shin  * Copyright (C) 2013 Advanced Micro Devices, Inc.
79c5320c8SJacob Shin  *
89c5320c8SJacob Shin  * Author: Jacob Shin <jacob.shin@amd.com>
99c5320c8SJacob Shin  */
109c5320c8SJacob Shin 
119c5320c8SJacob Shin #include <linux/kernel.h>
129c5320c8SJacob Shin #include <linux/module.h>
139c5320c8SJacob Shin #include <linux/types.h>
1459a3b3a8SAkshu Agrawal #include <linux/pci.h>
159c5320c8SJacob Shin #include <linux/percpu-defs.h>
169c5320c8SJacob Shin #include <linux/init.h>
179c5320c8SJacob Shin #include <linux/mod_devicetable.h>
189c5320c8SJacob Shin 
199c5320c8SJacob Shin #include <asm/msr.h>
209c5320c8SJacob Shin #include <asm/cpufeature.h>
21ba5bade4SThomas Gleixner #include <asm/cpu_device_id.h>
229c5320c8SJacob Shin 
237d5a9956SRafael J. Wysocki #include "cpufreq_ondemand.h"
249c5320c8SJacob Shin 
259c5320c8SJacob Shin #define MSR_AMD64_FREQ_SENSITIVITY_ACTUAL	0xc0010080
269c5320c8SJacob Shin #define MSR_AMD64_FREQ_SENSITIVITY_REFERENCE	0xc0010081
279c5320c8SJacob Shin #define CLASS_CODE_SHIFT			56
289c5320c8SJacob Shin #define POWERSAVE_BIAS_MAX			1000
299c5320c8SJacob Shin #define POWERSAVE_BIAS_DEF			400
309c5320c8SJacob Shin 
319c5320c8SJacob Shin struct cpu_data_t {
329c5320c8SJacob Shin 	u64 actual;
339c5320c8SJacob Shin 	u64 reference;
349c5320c8SJacob Shin 	unsigned int freq_prev;
359c5320c8SJacob Shin };
369c5320c8SJacob Shin 
379c5320c8SJacob Shin static DEFINE_PER_CPU(struct cpu_data_t, cpu_data);
389c5320c8SJacob Shin 
amd_powersave_bias_target(struct cpufreq_policy * policy,unsigned int freq_next,unsigned int relation)399c5320c8SJacob Shin static unsigned int amd_powersave_bias_target(struct cpufreq_policy *policy,
409c5320c8SJacob Shin 					      unsigned int freq_next,
419c5320c8SJacob Shin 					      unsigned int relation)
429c5320c8SJacob Shin {
439c5320c8SJacob Shin 	int sensitivity;
449c5320c8SJacob Shin 	long d_actual, d_reference;
459c5320c8SJacob Shin 	struct msr actual, reference;
469c5320c8SJacob Shin 	struct cpu_data_t *data = &per_cpu(cpu_data, policy->cpu);
47bc505475SRafael J. Wysocki 	struct policy_dbs_info *policy_dbs = policy->governor_data;
48bc505475SRafael J. Wysocki 	struct dbs_data *od_data = policy_dbs->dbs_data;
499c5320c8SJacob Shin 	struct od_dbs_tuners *od_tuners = od_data->tuners;
509c5320c8SJacob Shin 
5134ac5d7aSViresh Kumar 	if (!policy->freq_table)
529c5320c8SJacob Shin 		return freq_next;
539c5320c8SJacob Shin 
549c5320c8SJacob Shin 	rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_ACTUAL,
559c5320c8SJacob Shin 		&actual.l, &actual.h);
569c5320c8SJacob Shin 	rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_REFERENCE,
579c5320c8SJacob Shin 		&reference.l, &reference.h);
589c5320c8SJacob Shin 	actual.h &= 0x00ffffff;
599c5320c8SJacob Shin 	reference.h &= 0x00ffffff;
609c5320c8SJacob Shin 
619c5320c8SJacob Shin 	/* counter wrapped around, so stay on current frequency */
629c5320c8SJacob Shin 	if (actual.q < data->actual || reference.q < data->reference) {
639c5320c8SJacob Shin 		freq_next = policy->cur;
649c5320c8SJacob Shin 		goto out;
659c5320c8SJacob Shin 	}
669c5320c8SJacob Shin 
679c5320c8SJacob Shin 	d_actual = actual.q - data->actual;
689c5320c8SJacob Shin 	d_reference = reference.q - data->reference;
699c5320c8SJacob Shin 
709c5320c8SJacob Shin 	/* divide by 0, so stay on current frequency as well */
719c5320c8SJacob Shin 	if (d_reference == 0) {
729c5320c8SJacob Shin 		freq_next = policy->cur;
739c5320c8SJacob Shin 		goto out;
749c5320c8SJacob Shin 	}
759c5320c8SJacob Shin 
769c5320c8SJacob Shin 	sensitivity = POWERSAVE_BIAS_MAX -
779c5320c8SJacob Shin 		(POWERSAVE_BIAS_MAX * (d_reference - d_actual) / d_reference);
789c5320c8SJacob Shin 
799c5320c8SJacob Shin 	clamp(sensitivity, 0, POWERSAVE_BIAS_MAX);
809c5320c8SJacob Shin 
819c5320c8SJacob Shin 	/* this workload is not CPU bound, so choose a lower freq */
829c5320c8SJacob Shin 	if (sensitivity < od_tuners->powersave_bias) {
839c5320c8SJacob Shin 		if (data->freq_prev == policy->cur)
849c5320c8SJacob Shin 			freq_next = policy->cur;
859c5320c8SJacob Shin 
869c5320c8SJacob Shin 		if (freq_next > policy->cur)
879c5320c8SJacob Shin 			freq_next = policy->cur;
889c5320c8SJacob Shin 		else if (freq_next < policy->cur)
899c5320c8SJacob Shin 			freq_next = policy->min;
909c5320c8SJacob Shin 		else {
919c5320c8SJacob Shin 			unsigned int index;
929c5320c8SJacob Shin 
9382577360SViresh Kumar 			index = cpufreq_table_find_index_h(policy,
941f39fa0dSVincent Donnefort 							   policy->cur - 1,
951f39fa0dSVincent Donnefort 							   relation & CPUFREQ_RELATION_E);
9634ac5d7aSViresh Kumar 			freq_next = policy->freq_table[index].frequency;
979c5320c8SJacob Shin 		}
989c5320c8SJacob Shin 
999c5320c8SJacob Shin 		data->freq_prev = freq_next;
1009c5320c8SJacob Shin 	} else
1019c5320c8SJacob Shin 		data->freq_prev = 0;
1029c5320c8SJacob Shin 
1039c5320c8SJacob Shin out:
1049c5320c8SJacob Shin 	data->actual = actual.q;
1059c5320c8SJacob Shin 	data->reference = reference.q;
1069c5320c8SJacob Shin 	return freq_next;
1079c5320c8SJacob Shin }
1089c5320c8SJacob Shin 
amd_freq_sensitivity_init(void)1099c5320c8SJacob Shin static int __init amd_freq_sensitivity_init(void)
1109c5320c8SJacob Shin {
1119c5320c8SJacob Shin 	u64 val;
11259a3b3a8SAkshu Agrawal 	struct pci_dev *pcidev;
113cc9690cfSPu Wen 	unsigned int pci_vendor;
1149c5320c8SJacob Shin 
115cc9690cfSPu Wen 	if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD)
116cc9690cfSPu Wen 		pci_vendor = PCI_VENDOR_ID_AMD;
117cc9690cfSPu Wen 	else if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON)
118cc9690cfSPu Wen 		pci_vendor = PCI_VENDOR_ID_HYGON;
119cc9690cfSPu Wen 	else
1209c5320c8SJacob Shin 		return -ENODEV;
1219c5320c8SJacob Shin 
122cc9690cfSPu Wen 	pcidev = pci_get_device(pci_vendor,
12359a3b3a8SAkshu Agrawal 			PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, NULL);
12459a3b3a8SAkshu Agrawal 
12559a3b3a8SAkshu Agrawal 	if (!pcidev) {
126108ec36bSBorislav Petkov 		if (!boot_cpu_has(X86_FEATURE_PROC_FEEDBACK))
1279c5320c8SJacob Shin 			return -ENODEV;
128*91fda1f8SXiongfeng Wang 	} else {
129*91fda1f8SXiongfeng Wang 		pci_dev_put(pcidev);
13059a3b3a8SAkshu Agrawal 	}
1319c5320c8SJacob Shin 
1329c5320c8SJacob Shin 	if (rdmsrl_safe(MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, &val))
1339c5320c8SJacob Shin 		return -ENODEV;
1349c5320c8SJacob Shin 
1359c5320c8SJacob Shin 	if (!(val >> CLASS_CODE_SHIFT))
1369c5320c8SJacob Shin 		return -ENODEV;
1379c5320c8SJacob Shin 
1389c5320c8SJacob Shin 	od_register_powersave_bias_handler(amd_powersave_bias_target,
1399c5320c8SJacob Shin 			POWERSAVE_BIAS_DEF);
1409c5320c8SJacob Shin 	return 0;
1419c5320c8SJacob Shin }
1429c5320c8SJacob Shin late_initcall(amd_freq_sensitivity_init);
1439c5320c8SJacob Shin 
amd_freq_sensitivity_exit(void)1449c5320c8SJacob Shin static void __exit amd_freq_sensitivity_exit(void)
1459c5320c8SJacob Shin {
1469c5320c8SJacob Shin 	od_unregister_powersave_bias_handler();
1479c5320c8SJacob Shin }
1489c5320c8SJacob Shin module_exit(amd_freq_sensitivity_exit);
1499c5320c8SJacob Shin 
15052fe0b16SLee Jones static const struct x86_cpu_id __maybe_unused amd_freq_sensitivity_ids[] = {
151b11d77faSThomas Gleixner 	X86_MATCH_FEATURE(X86_FEATURE_PROC_FEEDBACK, NULL),
1529c5320c8SJacob Shin 	{}
1539c5320c8SJacob Shin };
1549c5320c8SJacob Shin MODULE_DEVICE_TABLE(x86cpu, amd_freq_sensitivity_ids);
1559c5320c8SJacob Shin 
1569c5320c8SJacob Shin MODULE_AUTHOR("Jacob Shin <jacob.shin@amd.com>");
1579c5320c8SJacob Shin MODULE_DESCRIPTION("AMD frequency sensitivity feedback powersave bias for "
1589c5320c8SJacob Shin 		"the ondemand governor.");
1599c5320c8SJacob Shin MODULE_LICENSE("GPL");
160