1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
20131aa3dSAlex Chiang /*
3ac212b69SRafael J. Wysocki  * processor_driver.c - ACPI Processor Driver
40131aa3dSAlex Chiang  *
50131aa3dSAlex Chiang  *  Copyright (C) 2001, 2002 Andy Grover <[email protected]>
60131aa3dSAlex Chiang  *  Copyright (C) 2001, 2002 Paul Diefenbaugh <[email protected]>
70131aa3dSAlex Chiang  *  Copyright (C) 2004       Dominik Brodowski <[email protected]>
80131aa3dSAlex Chiang  *  Copyright (C) 2004  Anil S Keshavamurthy <[email protected]>
90131aa3dSAlex Chiang  *  			- Added processor hotplug support
10ac212b69SRafael J. Wysocki  *  Copyright (C) 2013, Intel Corporation
11ac212b69SRafael J. Wysocki  *                      Rafael J. Wysocki <[email protected]>
120131aa3dSAlex Chiang  */
130131aa3dSAlex Chiang 
140131aa3dSAlex Chiang #include <linux/kernel.h>
150131aa3dSAlex Chiang #include <linux/module.h>
160131aa3dSAlex Chiang #include <linux/init.h>
170131aa3dSAlex Chiang #include <linux/cpufreq.h>
180131aa3dSAlex Chiang #include <linux/cpu.h>
190131aa3dSAlex Chiang #include <linux/cpuidle.h>
205a0e3ad6STejun Heo #include <linux/slab.h>
2147db4547SToshi Kani #include <linux/acpi.h>
220131aa3dSAlex Chiang 
230131aa3dSAlex Chiang #include <acpi/processor.h>
240131aa3dSAlex Chiang 
25ac212b69SRafael J. Wysocki #include "internal.h"
26ac212b69SRafael J. Wysocki 
270131aa3dSAlex Chiang #define ACPI_PROCESSOR_NOTIFY_PERFORMANCE 0x80
280131aa3dSAlex Chiang #define ACPI_PROCESSOR_NOTIFY_POWER	0x81
290131aa3dSAlex Chiang #define ACPI_PROCESSOR_NOTIFY_THROTTLING	0x82
309c4a13a0SMeng Li #define ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED	0x85
310131aa3dSAlex Chiang 
320131aa3dSAlex Chiang MODULE_AUTHOR("Paul Diefenbaugh");
330131aa3dSAlex Chiang MODULE_DESCRIPTION("ACPI Processor Driver");
340131aa3dSAlex Chiang MODULE_LICENSE("GPL");
350131aa3dSAlex Chiang 
36ac212b69SRafael J. Wysocki static int acpi_processor_stop(struct device *dev);
370131aa3dSAlex Chiang 
380131aa3dSAlex Chiang static const struct acpi_device_id processor_device_ids[] = {
390131aa3dSAlex Chiang 	{ACPI_PROCESSOR_OBJECT_HID, 0},
409f324bdaSToshi Kani 	{ACPI_PROCESSOR_DEVICE_HID, 0},
410131aa3dSAlex Chiang 	{"", 0},
420131aa3dSAlex Chiang };
430131aa3dSAlex Chiang MODULE_DEVICE_TABLE(acpi, processor_device_ids);
440131aa3dSAlex Chiang 
45ac212b69SRafael J. Wysocki static struct device_driver acpi_processor_driver = {
460131aa3dSAlex Chiang 	.name = "processor",
47ac212b69SRafael J. Wysocki 	.bus = &cpu_subsys,
48ac212b69SRafael J. Wysocki 	.acpi_match_table = processor_device_ids,
49ac212b69SRafael J. Wysocki 	.remove = acpi_processor_stop,
500131aa3dSAlex Chiang };
510131aa3dSAlex Chiang 
acpi_processor_notify(acpi_handle handle,u32 event,void * data)52ac212b69SRafael J. Wysocki static void acpi_processor_notify(acpi_handle handle, u32 event, void *data)
530131aa3dSAlex Chiang {
54ac212b69SRafael J. Wysocki 	struct acpi_device *device = data;
550131aa3dSAlex Chiang 	struct acpi_processor *pr;
560131aa3dSAlex Chiang 	int saved;
570131aa3dSAlex Chiang 
58ac212b69SRafael J. Wysocki 	if (device->handle != handle)
59ac212b69SRafael J. Wysocki 		return;
60ac212b69SRafael J. Wysocki 
61ac212b69SRafael J. Wysocki 	pr = acpi_driver_data(device);
620131aa3dSAlex Chiang 	if (!pr)
630131aa3dSAlex Chiang 		return;
640131aa3dSAlex Chiang 
650131aa3dSAlex Chiang 	switch (event) {
660131aa3dSAlex Chiang 	case ACPI_PROCESSOR_NOTIFY_PERFORMANCE:
670131aa3dSAlex Chiang 		saved = pr->performance_platform_limit;
680131aa3dSAlex Chiang 		acpi_processor_ppc_has_changed(pr, 1);
690131aa3dSAlex Chiang 		if (saved == pr->performance_platform_limit)
700131aa3dSAlex Chiang 			break;
710131aa3dSAlex Chiang 		acpi_bus_generate_netlink_event(device->pnp.device_class,
720131aa3dSAlex Chiang 						  dev_name(&device->dev), event,
730131aa3dSAlex Chiang 						  pr->performance_platform_limit);
740131aa3dSAlex Chiang 		break;
750131aa3dSAlex Chiang 	case ACPI_PROCESSOR_NOTIFY_POWER:
76a36a7fecSSudeep Holla 		acpi_processor_power_state_has_changed(pr);
770131aa3dSAlex Chiang 		acpi_bus_generate_netlink_event(device->pnp.device_class,
780131aa3dSAlex Chiang 						  dev_name(&device->dev), event, 0);
790131aa3dSAlex Chiang 		break;
800131aa3dSAlex Chiang 	case ACPI_PROCESSOR_NOTIFY_THROTTLING:
810131aa3dSAlex Chiang 		acpi_processor_tstate_has_changed(pr);
820131aa3dSAlex Chiang 		acpi_bus_generate_netlink_event(device->pnp.device_class,
830131aa3dSAlex Chiang 						  dev_name(&device->dev), event, 0);
84879dca01SAlan Cox 		break;
859c4a13a0SMeng Li 	case ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED:
869c4a13a0SMeng Li 		cpufreq_update_limits(pr->id);
879c4a13a0SMeng Li 		acpi_bus_generate_netlink_event(device->pnp.device_class,
889c4a13a0SMeng Li 						  dev_name(&device->dev), event, 0);
899c4a13a0SMeng Li 		break;
900131aa3dSAlex Chiang 	default:
9152af99c3SRafael J. Wysocki 		acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event);
920131aa3dSAlex Chiang 		break;
930131aa3dSAlex Chiang 	}
940131aa3dSAlex Chiang 
950131aa3dSAlex Chiang 	return;
960131aa3dSAlex Chiang }
970131aa3dSAlex Chiang 
98fe7bf106SPaul Gortmaker static int __acpi_processor_start(struct acpi_device *device);
99ac212b69SRafael J. Wysocki 
acpi_soft_cpu_online(unsigned int cpu)10064f3bf2fSSebastian Andrzej Siewior static int acpi_soft_cpu_online(unsigned int cpu)
1010131aa3dSAlex Chiang {
1020131aa3dSAlex Chiang 	struct acpi_processor *pr = per_cpu(processors, cpu);
103ac212b69SRafael J. Wysocki 	struct acpi_device *device;
1048da83734SToshi Kani 
10599ece713SRafael J. Wysocki 	if (!pr)
10664f3bf2fSSebastian Andrzej Siewior 		return 0;
10799ece713SRafael J. Wysocki 
10899ece713SRafael J. Wysocki 	device = acpi_fetch_acpi_dev(pr->handle);
10999ece713SRafael J. Wysocki 	if (!device)
11099ece713SRafael J. Wysocki 		return 0;
11199ece713SRafael J. Wysocki 
112ac212b69SRafael J. Wysocki 	/*
113ac212b69SRafael J. Wysocki 	 * CPU got physically hotplugged and onlined for the first time:
114ac212b69SRafael J. Wysocki 	 * Initialize missing things.
11599b72508SThomas Renninger 	 */
116c1385c1fSJonathan Cameron 	if (!pr->flags.previously_online) {
117ac212b69SRafael J. Wysocki 		int ret;
118ac212b69SRafael J. Wysocki 
119ac212b69SRafael J. Wysocki 		ret = __acpi_processor_start(device);
120ac212b69SRafael J. Wysocki 		WARN(ret, "Failed to start CPU: %d\n", pr->id);
12199b72508SThomas Renninger 	} else {
122ac212b69SRafael J. Wysocki 		/* Normal CPU soft online event. */
1230131aa3dSAlex Chiang 		acpi_processor_ppc_has_changed(pr, 0);
124b7db60f4SFeng Tang 		acpi_processor_hotplug(pr);
12564f3bf2fSSebastian Andrzej Siewior 		acpi_processor_reevaluate_tstate(pr, false);
1260131aa3dSAlex Chiang 		acpi_processor_tstate_has_changed(pr);
1270131aa3dSAlex Chiang 	}
12864f3bf2fSSebastian Andrzej Siewior 	return 0;
1290131aa3dSAlex Chiang }
1300131aa3dSAlex Chiang 
acpi_soft_cpu_dead(unsigned int cpu)13164f3bf2fSSebastian Andrzej Siewior static int acpi_soft_cpu_dead(unsigned int cpu)
13264f3bf2fSSebastian Andrzej Siewior {
13364f3bf2fSSebastian Andrzej Siewior 	struct acpi_processor *pr = per_cpu(processors, cpu);
13464f3bf2fSSebastian Andrzej Siewior 
13599ece713SRafael J. Wysocki 	if (!pr || !acpi_fetch_acpi_dev(pr->handle))
13664f3bf2fSSebastian Andrzej Siewior 		return 0;
13764f3bf2fSSebastian Andrzej Siewior 
13864f3bf2fSSebastian Andrzej Siewior 	acpi_processor_reevaluate_tstate(pr, true);
13964f3bf2fSSebastian Andrzej Siewior 	return 0;
14064f3bf2fSSebastian Andrzej Siewior }
1410131aa3dSAlex Chiang 
142239708a3SAshwin Chaugule #ifdef CONFIG_ACPI_CPU_FREQ_PSS
acpi_pss_perf_init(struct acpi_processor * pr)1437fdc74daSRiwen Lu static void acpi_pss_perf_init(struct acpi_processor *pr)
14454d5dcc4SThomas Renninger {
14554d5dcc4SThomas Renninger 	acpi_processor_ppc_has_changed(pr, 0);
146239708a3SAshwin Chaugule 
14754d5dcc4SThomas Renninger 	acpi_processor_get_throttling_info(pr);
14822e7551eSLan Tianyu 
14922e7551eSLan Tianyu 	if (pr->flags.throttling)
15022e7551eSLan Tianyu 		pr->flags.limit = 1;
151239708a3SAshwin Chaugule }
152239708a3SAshwin Chaugule #else
acpi_pss_perf_init(struct acpi_processor * pr)1537fdc74daSRiwen Lu static inline void acpi_pss_perf_init(struct acpi_processor *pr) {}
154239708a3SAshwin Chaugule #endif /* CONFIG_ACPI_CPU_FREQ_PSS */
155239708a3SAshwin Chaugule 
__acpi_processor_start(struct acpi_device * device)156239708a3SAshwin Chaugule static int __acpi_processor_start(struct acpi_device *device)
157239708a3SAshwin Chaugule {
158239708a3SAshwin Chaugule 	struct acpi_processor *pr = acpi_driver_data(device);
159239708a3SAshwin Chaugule 	acpi_status status;
160239708a3SAshwin Chaugule 	int result = 0;
161239708a3SAshwin Chaugule 
162239708a3SAshwin Chaugule 	if (!pr)
163239708a3SAshwin Chaugule 		return -ENODEV;
164239708a3SAshwin Chaugule 
1654f2f7573SAshwin Chaugule 	result = acpi_cppc_processor_probe(pr);
16665e95891SSrinivas Pandruvada 	if (result && !IS_ENABLED(CONFIG_ACPI_CPU_FREQ_PSS))
167512bb03fSHanjun Guo 		dev_dbg(&device->dev, "CPPC data invalid or not present\n");
1684f2f7573SAshwin Chaugule 
169239708a3SAshwin Chaugule 	if (!cpuidle_get_driver() || cpuidle_get_driver() == &acpi_idle_driver)
170239708a3SAshwin Chaugule 		acpi_processor_power_init(pr);
171239708a3SAshwin Chaugule 
1727fdc74daSRiwen Lu 	acpi_pss_perf_init(pr);
1737fdc74daSRiwen Lu 
1747fdc74daSRiwen Lu 	result = acpi_processor_thermal_init(pr, device);
175239708a3SAshwin Chaugule 	if (result)
176239708a3SAshwin Chaugule 		goto err_power_exit;
177239708a3SAshwin Chaugule 
178239708a3SAshwin Chaugule 	status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
179239708a3SAshwin Chaugule 					     acpi_processor_notify, device);
180c1385c1fSJonathan Cameron 	if (!ACPI_SUCCESS(status)) {
181c1385c1fSJonathan Cameron 		result = -ENODEV;
182c1385c1fSJonathan Cameron 		goto err_thermal_exit;
183c1385c1fSJonathan Cameron 	}
184c1385c1fSJonathan Cameron 	pr->flags.previously_online = 1;
185c1385c1fSJonathan Cameron 
186239708a3SAshwin Chaugule 	return 0;
187239708a3SAshwin Chaugule 
188c1385c1fSJonathan Cameron err_thermal_exit:
1897fdc74daSRiwen Lu 	acpi_processor_thermal_exit(pr, device);
19054d5dcc4SThomas Renninger err_power_exit:
19138a991b6SDaniel Lezcano 	acpi_processor_power_exit(pr);
19254d5dcc4SThomas Renninger 	return result;
19354d5dcc4SThomas Renninger }
19454d5dcc4SThomas Renninger 
acpi_processor_stop(struct device * dev)195ac212b69SRafael J. Wysocki static int acpi_processor_stop(struct device *dev)
1960131aa3dSAlex Chiang {
197d0a7edbbSLan Tianyu 	struct acpi_device *device = ACPI_COMPANION(dev);
198ac212b69SRafael J. Wysocki 	struct acpi_processor *pr;
1990131aa3dSAlex Chiang 
200d0a7edbbSLan Tianyu 	if (!device)
201ac212b69SRafael J. Wysocki 		return 0;
2020131aa3dSAlex Chiang 
203ac212b69SRafael J. Wysocki 	acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
204ac212b69SRafael J. Wysocki 				   acpi_processor_notify);
2050131aa3dSAlex Chiang 
2060131aa3dSAlex Chiang 	pr = acpi_driver_data(device);
207ac212b69SRafael J. Wysocki 	if (!pr)
208ac212b69SRafael J. Wysocki 		return 0;
20938a991b6SDaniel Lezcano 	acpi_processor_power_exit(pr);
2100131aa3dSAlex Chiang 
2114f2f7573SAshwin Chaugule 	acpi_cppc_processor_exit(pr);
2124f2f7573SAshwin Chaugule 
2137fdc74daSRiwen Lu 	acpi_processor_thermal_exit(pr, device);
2147fdc74daSRiwen Lu 
2150131aa3dSAlex Chiang 	return 0;
2160131aa3dSAlex Chiang }
2170131aa3dSAlex Chiang 
218d15ce412SViresh Kumar bool acpi_processor_cpufreq_init;
219d15ce412SViresh Kumar 
acpi_processor_notifier(struct notifier_block * nb,unsigned long event,void * data)220d15ce412SViresh Kumar static int acpi_processor_notifier(struct notifier_block *nb,
221d15ce412SViresh Kumar 				   unsigned long event, void *data)
222d15ce412SViresh Kumar {
223d15ce412SViresh Kumar 	struct cpufreq_policy *policy = data;
224d15ce412SViresh Kumar 
225d15ce412SViresh Kumar 	if (event == CPUFREQ_CREATE_POLICY) {
2263000ce3cSRafael J. Wysocki 		acpi_thermal_cpufreq_init(policy);
2273000ce3cSRafael J. Wysocki 		acpi_processor_ppc_init(policy);
228d15ce412SViresh Kumar 	} else if (event == CPUFREQ_REMOVE_POLICY) {
2293000ce3cSRafael J. Wysocki 		acpi_processor_ppc_exit(policy);
2303000ce3cSRafael J. Wysocki 		acpi_thermal_cpufreq_exit(policy);
231d15ce412SViresh Kumar 	}
232d15ce412SViresh Kumar 
233d15ce412SViresh Kumar 	return 0;
234d15ce412SViresh Kumar }
235d15ce412SViresh Kumar 
236d15ce412SViresh Kumar static struct notifier_block acpi_processor_notifier_block = {
237d15ce412SViresh Kumar 	.notifier_call = acpi_processor_notifier,
238d15ce412SViresh Kumar };
239d15ce412SViresh Kumar 
acpi_processor_init_invariance_cppc(void)240*b79276dcSMario Limonciello void __weak acpi_processor_init_invariance_cppc(void)
241*b79276dcSMario Limonciello { }
242*b79276dcSMario Limonciello 
2430131aa3dSAlex Chiang /*
2440131aa3dSAlex Chiang  * We keep the driver loaded even when ACPI is not running.
2450131aa3dSAlex Chiang  * This is needed for the powernow-k8 driver, that works even without
2460131aa3dSAlex Chiang  * ACPI, but needs symbols from this driver
2470131aa3dSAlex Chiang  */
24864f3bf2fSSebastian Andrzej Siewior static enum cpuhp_state hp_online;
acpi_processor_driver_init(void)249ac212b69SRafael J. Wysocki static int __init acpi_processor_driver_init(void)
2500131aa3dSAlex Chiang {
2510131aa3dSAlex Chiang 	int result = 0;
2520131aa3dSAlex Chiang 
2530131aa3dSAlex Chiang 	if (acpi_disabled)
2540131aa3dSAlex Chiang 		return 0;
2550131aa3dSAlex Chiang 
256c0e0421aSRafael J. Wysocki 	if (!cpufreq_register_notifier(&acpi_processor_notifier_block,
257c0e0421aSRafael J. Wysocki 				       CPUFREQ_POLICY_NOTIFIER)) {
258c0e0421aSRafael J. Wysocki 		acpi_processor_cpufreq_init = true;
259c0e0421aSRafael J. Wysocki 		acpi_processor_ignore_ppc_init();
260c0e0421aSRafael J. Wysocki 	}
261c0e0421aSRafael J. Wysocki 
262ac212b69SRafael J. Wysocki 	result = driver_register(&acpi_processor_driver);
2630131aa3dSAlex Chiang 	if (result < 0)
26446bcfad7SDeepthi Dharwar 		return result;
2650131aa3dSAlex Chiang 
266c1385c1fSJonathan Cameron 	result = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
26764f3bf2fSSebastian Andrzej Siewior 				   "acpi/cpu-drv:online",
26864f3bf2fSSebastian Andrzej Siewior 				   acpi_soft_cpu_online, NULL);
26964f3bf2fSSebastian Andrzej Siewior 	if (result < 0)
27064f3bf2fSSebastian Andrzej Siewior 		goto err;
27164f3bf2fSSebastian Andrzej Siewior 	hp_online = result;
27264f3bf2fSSebastian Andrzej Siewior 	cpuhp_setup_state_nocalls(CPUHP_ACPI_CPUDRV_DEAD, "acpi/cpu-drv:dead",
27364f3bf2fSSebastian Andrzej Siewior 				  NULL, acpi_soft_cpu_dead);
27464f3bf2fSSebastian Andrzej Siewior 
2750131aa3dSAlex Chiang 	acpi_processor_throttling_init();
276*b79276dcSMario Limonciello 
277*b79276dcSMario Limonciello 	/*
278*b79276dcSMario Limonciello 	 * Frequency invariance calculations on AMD platforms can't be run until
279*b79276dcSMario Limonciello 	 * after acpi_cppc_processor_probe() has been called for all online CPUs
280*b79276dcSMario Limonciello 	 */
281*b79276dcSMario Limonciello 	acpi_processor_init_invariance_cppc();
2820131aa3dSAlex Chiang 	return 0;
28364f3bf2fSSebastian Andrzej Siewior err:
28464f3bf2fSSebastian Andrzej Siewior 	driver_unregister(&acpi_processor_driver);
28564f3bf2fSSebastian Andrzej Siewior 	return result;
2860131aa3dSAlex Chiang }
2870131aa3dSAlex Chiang 
acpi_processor_driver_exit(void)288ac212b69SRafael J. Wysocki static void __exit acpi_processor_driver_exit(void)
2890131aa3dSAlex Chiang {
2900131aa3dSAlex Chiang 	if (acpi_disabled)
2910131aa3dSAlex Chiang 		return;
2920131aa3dSAlex Chiang 
293d15ce412SViresh Kumar 	if (acpi_processor_cpufreq_init) {
294d15ce412SViresh Kumar 		cpufreq_unregister_notifier(&acpi_processor_notifier_block,
295d15ce412SViresh Kumar 					    CPUFREQ_POLICY_NOTIFIER);
296d15ce412SViresh Kumar 		acpi_processor_cpufreq_init = false;
297d15ce412SViresh Kumar 	}
298d15ce412SViresh Kumar 
29964f3bf2fSSebastian Andrzej Siewior 	cpuhp_remove_state_nocalls(hp_online);
30064f3bf2fSSebastian Andrzej Siewior 	cpuhp_remove_state_nocalls(CPUHP_ACPI_CPUDRV_DEAD);
301ac212b69SRafael J. Wysocki 	driver_unregister(&acpi_processor_driver);
3020131aa3dSAlex Chiang }
3030131aa3dSAlex Chiang 
304ac212b69SRafael J. Wysocki module_init(acpi_processor_driver_init);
305ac212b69SRafael J. Wysocki module_exit(acpi_processor_driver_exit);
3060131aa3dSAlex Chiang 
3070131aa3dSAlex Chiang MODULE_ALIAS("processor");
308