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