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 <andrew.grover@intel.com>
60131aa3dSAlex Chiang * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
70131aa3dSAlex Chiang * Copyright (C) 2004 Dominik Brodowski <linux@brodo.de>
80131aa3dSAlex Chiang * Copyright (C) 2004 Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
90131aa3dSAlex Chiang * - Added processor hotplug support
10ac212b69SRafael J. Wysocki * Copyright (C) 2013, Intel Corporation
11ac212b69SRafael J. Wysocki * Rafael J. Wysocki <rafael.j.wysocki@intel.com>
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
300131aa3dSAlex Chiang
310131aa3dSAlex Chiang MODULE_AUTHOR("Paul Diefenbaugh");
320131aa3dSAlex Chiang MODULE_DESCRIPTION("ACPI Processor Driver");
330131aa3dSAlex Chiang MODULE_LICENSE("GPL");
340131aa3dSAlex Chiang
35ac212b69SRafael J. Wysocki static int acpi_processor_start(struct device *dev);
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 .probe = acpi_processor_start,
50ac212b69SRafael J. Wysocki .remove = acpi_processor_stop,
510131aa3dSAlex Chiang };
520131aa3dSAlex Chiang
acpi_processor_notify(acpi_handle handle,u32 event,void * data)53ac212b69SRafael J. Wysocki static void acpi_processor_notify(acpi_handle handle, u32 event, void *data)
540131aa3dSAlex Chiang {
55ac212b69SRafael J. Wysocki struct acpi_device *device = data;
560131aa3dSAlex Chiang struct acpi_processor *pr;
570131aa3dSAlex Chiang int saved;
580131aa3dSAlex Chiang
59ac212b69SRafael J. Wysocki if (device->handle != handle)
60ac212b69SRafael J. Wysocki return;
61ac212b69SRafael J. Wysocki
62ac212b69SRafael J. Wysocki pr = acpi_driver_data(device);
630131aa3dSAlex Chiang if (!pr)
640131aa3dSAlex Chiang return;
650131aa3dSAlex Chiang
660131aa3dSAlex Chiang switch (event) {
670131aa3dSAlex Chiang case ACPI_PROCESSOR_NOTIFY_PERFORMANCE:
680131aa3dSAlex Chiang saved = pr->performance_platform_limit;
690131aa3dSAlex Chiang acpi_processor_ppc_has_changed(pr, 1);
700131aa3dSAlex Chiang if (saved == pr->performance_platform_limit)
710131aa3dSAlex Chiang break;
720131aa3dSAlex Chiang acpi_bus_generate_netlink_event(device->pnp.device_class,
730131aa3dSAlex Chiang dev_name(&device->dev), event,
740131aa3dSAlex Chiang pr->performance_platform_limit);
750131aa3dSAlex Chiang break;
760131aa3dSAlex Chiang case ACPI_PROCESSOR_NOTIFY_POWER:
77a36a7fecSSudeep Holla acpi_processor_power_state_has_changed(pr);
780131aa3dSAlex Chiang acpi_bus_generate_netlink_event(device->pnp.device_class,
790131aa3dSAlex Chiang dev_name(&device->dev), event, 0);
800131aa3dSAlex Chiang break;
810131aa3dSAlex Chiang case ACPI_PROCESSOR_NOTIFY_THROTTLING:
820131aa3dSAlex Chiang acpi_processor_tstate_has_changed(pr);
830131aa3dSAlex Chiang acpi_bus_generate_netlink_event(device->pnp.device_class,
840131aa3dSAlex Chiang dev_name(&device->dev), event, 0);
85879dca01SAlan Cox break;
860131aa3dSAlex Chiang default:
8752af99c3SRafael J. Wysocki acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event);
880131aa3dSAlex Chiang break;
890131aa3dSAlex Chiang }
900131aa3dSAlex Chiang
910131aa3dSAlex Chiang return;
920131aa3dSAlex Chiang }
930131aa3dSAlex Chiang
94fe7bf106SPaul Gortmaker static int __acpi_processor_start(struct acpi_device *device);
95ac212b69SRafael J. Wysocki
acpi_soft_cpu_online(unsigned int cpu)9664f3bf2fSSebastian Andrzej Siewior static int acpi_soft_cpu_online(unsigned int cpu)
970131aa3dSAlex Chiang {
980131aa3dSAlex Chiang struct acpi_processor *pr = per_cpu(processors, cpu);
99ac212b69SRafael J. Wysocki struct acpi_device *device;
1008da83734SToshi Kani
10199ece713SRafael J. Wysocki if (!pr)
10264f3bf2fSSebastian Andrzej Siewior return 0;
10399ece713SRafael J. Wysocki
10499ece713SRafael J. Wysocki device = acpi_fetch_acpi_dev(pr->handle);
10599ece713SRafael J. Wysocki if (!device)
10699ece713SRafael J. Wysocki return 0;
10799ece713SRafael J. Wysocki
108ac212b69SRafael J. Wysocki /*
109ac212b69SRafael J. Wysocki * CPU got physically hotplugged and onlined for the first time:
110ac212b69SRafael J. Wysocki * Initialize missing things.
11199b72508SThomas Renninger */
11299b72508SThomas Renninger if (pr->flags.need_hotplug_init) {
113ac212b69SRafael J. Wysocki int ret;
114ac212b69SRafael J. Wysocki
11547db4547SToshi Kani pr_info("Will online and init hotplugged CPU: %d\n",
11647db4547SToshi Kani pr->id);
11799b72508SThomas Renninger pr->flags.need_hotplug_init = 0;
118ac212b69SRafael J. Wysocki ret = __acpi_processor_start(device);
119ac212b69SRafael J. Wysocki WARN(ret, "Failed to start CPU: %d\n", pr->id);
12099b72508SThomas Renninger } else {
121ac212b69SRafael J. Wysocki /* Normal CPU soft online event. */
1220131aa3dSAlex Chiang acpi_processor_ppc_has_changed(pr, 0);
123b7db60f4SFeng Tang acpi_processor_hotplug(pr);
12464f3bf2fSSebastian Andrzej Siewior acpi_processor_reevaluate_tstate(pr, false);
1250131aa3dSAlex Chiang acpi_processor_tstate_has_changed(pr);
1260131aa3dSAlex Chiang }
12764f3bf2fSSebastian Andrzej Siewior return 0;
1280131aa3dSAlex Chiang }
1290131aa3dSAlex Chiang
acpi_soft_cpu_dead(unsigned int cpu)13064f3bf2fSSebastian Andrzej Siewior static int acpi_soft_cpu_dead(unsigned int cpu)
13164f3bf2fSSebastian Andrzej Siewior {
13264f3bf2fSSebastian Andrzej Siewior struct acpi_processor *pr = per_cpu(processors, cpu);
13364f3bf2fSSebastian Andrzej Siewior
13499ece713SRafael J. Wysocki if (!pr || !acpi_fetch_acpi_dev(pr->handle))
13564f3bf2fSSebastian Andrzej Siewior return 0;
13664f3bf2fSSebastian Andrzej Siewior
13764f3bf2fSSebastian Andrzej Siewior acpi_processor_reevaluate_tstate(pr, true);
13864f3bf2fSSebastian Andrzej Siewior return 0;
13964f3bf2fSSebastian Andrzej Siewior }
1400131aa3dSAlex Chiang
141239708a3SAshwin Chaugule #ifdef CONFIG_ACPI_CPU_FREQ_PSS
acpi_pss_perf_init(struct acpi_processor * pr)1427fdc74daSRiwen Lu static void acpi_pss_perf_init(struct acpi_processor *pr)
14354d5dcc4SThomas Renninger {
14454d5dcc4SThomas Renninger acpi_processor_ppc_has_changed(pr, 0);
145239708a3SAshwin Chaugule
14654d5dcc4SThomas Renninger acpi_processor_get_throttling_info(pr);
14722e7551eSLan Tianyu
14822e7551eSLan Tianyu if (pr->flags.throttling)
14922e7551eSLan Tianyu pr->flags.limit = 1;
150239708a3SAshwin Chaugule }
151239708a3SAshwin Chaugule #else
acpi_pss_perf_init(struct acpi_processor * pr)1527fdc74daSRiwen Lu static inline void acpi_pss_perf_init(struct acpi_processor *pr) {}
153239708a3SAshwin Chaugule #endif /* CONFIG_ACPI_CPU_FREQ_PSS */
154239708a3SAshwin Chaugule
__acpi_processor_start(struct acpi_device * device)155239708a3SAshwin Chaugule static int __acpi_processor_start(struct acpi_device *device)
156239708a3SAshwin Chaugule {
157239708a3SAshwin Chaugule struct acpi_processor *pr = acpi_driver_data(device);
158239708a3SAshwin Chaugule acpi_status status;
159239708a3SAshwin Chaugule int result = 0;
160239708a3SAshwin Chaugule
161239708a3SAshwin Chaugule if (!pr)
162239708a3SAshwin Chaugule return -ENODEV;
163239708a3SAshwin Chaugule
164239708a3SAshwin Chaugule if (pr->flags.need_hotplug_init)
165239708a3SAshwin Chaugule return 0;
166239708a3SAshwin Chaugule
1674f2f7573SAshwin Chaugule result = acpi_cppc_processor_probe(pr);
16865e95891SSrinivas Pandruvada if (result && !IS_ENABLED(CONFIG_ACPI_CPU_FREQ_PSS))
169512bb03fSHanjun Guo dev_dbg(&device->dev, "CPPC data invalid or not present\n");
1704f2f7573SAshwin Chaugule
171239708a3SAshwin Chaugule if (!cpuidle_get_driver() || cpuidle_get_driver() == &acpi_idle_driver)
172239708a3SAshwin Chaugule acpi_processor_power_init(pr);
173239708a3SAshwin Chaugule
1747fdc74daSRiwen Lu acpi_pss_perf_init(pr);
1757fdc74daSRiwen Lu
1767fdc74daSRiwen Lu result = acpi_processor_thermal_init(pr, device);
177239708a3SAshwin Chaugule if (result)
178239708a3SAshwin Chaugule goto err_power_exit;
179239708a3SAshwin Chaugule
180239708a3SAshwin Chaugule status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
181239708a3SAshwin Chaugule acpi_processor_notify, device);
182239708a3SAshwin Chaugule if (ACPI_SUCCESS(status))
183239708a3SAshwin Chaugule return 0;
184239708a3SAshwin Chaugule
185a5cbdf69SThomas Gleixner result = -ENODEV;
1867fdc74daSRiwen Lu acpi_processor_thermal_exit(pr, device);
187a5cbdf69SThomas Gleixner
18854d5dcc4SThomas Renninger err_power_exit:
18938a991b6SDaniel Lezcano acpi_processor_power_exit(pr);
19054d5dcc4SThomas Renninger return result;
19154d5dcc4SThomas Renninger }
19254d5dcc4SThomas Renninger
acpi_processor_start(struct device * dev)193fe7bf106SPaul Gortmaker static int acpi_processor_start(struct device *dev)
1940131aa3dSAlex Chiang {
195d0a7edbbSLan Tianyu struct acpi_device *device = ACPI_COMPANION(dev);
1968153f9acSThomas Gleixner int ret;
1970131aa3dSAlex Chiang
198d0a7edbbSLan Tianyu if (!device)
199ac212b69SRafael J. Wysocki return -ENODEV;
2000131aa3dSAlex Chiang
2018153f9acSThomas Gleixner /* Protect against concurrent CPU hotplug operations */
202fdaf0a51SThomas Gleixner cpu_hotplug_disable();
2038153f9acSThomas Gleixner ret = __acpi_processor_start(device);
204fdaf0a51SThomas Gleixner cpu_hotplug_enable();
2058153f9acSThomas Gleixner return ret;
2060131aa3dSAlex Chiang }
2070131aa3dSAlex Chiang
acpi_processor_stop(struct device * dev)208ac212b69SRafael J. Wysocki static int acpi_processor_stop(struct device *dev)
2090131aa3dSAlex Chiang {
210d0a7edbbSLan Tianyu struct acpi_device *device = ACPI_COMPANION(dev);
211ac212b69SRafael J. Wysocki struct acpi_processor *pr;
2120131aa3dSAlex Chiang
213d0a7edbbSLan Tianyu if (!device)
214ac212b69SRafael J. Wysocki return 0;
2150131aa3dSAlex Chiang
216ac212b69SRafael J. Wysocki acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
217ac212b69SRafael J. Wysocki acpi_processor_notify);
2180131aa3dSAlex Chiang
2190131aa3dSAlex Chiang pr = acpi_driver_data(device);
220ac212b69SRafael J. Wysocki if (!pr)
221ac212b69SRafael J. Wysocki return 0;
22238a991b6SDaniel Lezcano acpi_processor_power_exit(pr);
2230131aa3dSAlex Chiang
2244f2f7573SAshwin Chaugule acpi_cppc_processor_exit(pr);
2254f2f7573SAshwin Chaugule
2267fdc74daSRiwen Lu acpi_processor_thermal_exit(pr, device);
2277fdc74daSRiwen Lu
2280131aa3dSAlex Chiang return 0;
2290131aa3dSAlex Chiang }
2300131aa3dSAlex Chiang
231d15ce412SViresh Kumar bool acpi_processor_cpufreq_init;
232d15ce412SViresh Kumar
acpi_processor_notifier(struct notifier_block * nb,unsigned long event,void * data)233d15ce412SViresh Kumar static int acpi_processor_notifier(struct notifier_block *nb,
234d15ce412SViresh Kumar unsigned long event, void *data)
235d15ce412SViresh Kumar {
236d15ce412SViresh Kumar struct cpufreq_policy *policy = data;
237d15ce412SViresh Kumar
238d15ce412SViresh Kumar if (event == CPUFREQ_CREATE_POLICY) {
2393000ce3cSRafael J. Wysocki acpi_thermal_cpufreq_init(policy);
2403000ce3cSRafael J. Wysocki acpi_processor_ppc_init(policy);
241d15ce412SViresh Kumar } else if (event == CPUFREQ_REMOVE_POLICY) {
2423000ce3cSRafael J. Wysocki acpi_processor_ppc_exit(policy);
2433000ce3cSRafael J. Wysocki acpi_thermal_cpufreq_exit(policy);
244d15ce412SViresh Kumar }
245d15ce412SViresh Kumar
246d15ce412SViresh Kumar return 0;
247d15ce412SViresh Kumar }
248d15ce412SViresh Kumar
249d15ce412SViresh Kumar static struct notifier_block acpi_processor_notifier_block = {
250d15ce412SViresh Kumar .notifier_call = acpi_processor_notifier,
251d15ce412SViresh Kumar };
252d15ce412SViresh Kumar
2530131aa3dSAlex Chiang /*
2540131aa3dSAlex Chiang * We keep the driver loaded even when ACPI is not running.
2550131aa3dSAlex Chiang * This is needed for the powernow-k8 driver, that works even without
2560131aa3dSAlex Chiang * ACPI, but needs symbols from this driver
2570131aa3dSAlex Chiang */
25864f3bf2fSSebastian Andrzej Siewior static enum cpuhp_state hp_online;
acpi_processor_driver_init(void)259ac212b69SRafael J. Wysocki static int __init acpi_processor_driver_init(void)
2600131aa3dSAlex Chiang {
2610131aa3dSAlex Chiang int result = 0;
2620131aa3dSAlex Chiang
2630131aa3dSAlex Chiang if (acpi_disabled)
2640131aa3dSAlex Chiang return 0;
2650131aa3dSAlex Chiang
266*c0e0421aSRafael J. Wysocki if (!cpufreq_register_notifier(&acpi_processor_notifier_block,
267*c0e0421aSRafael J. Wysocki CPUFREQ_POLICY_NOTIFIER)) {
268*c0e0421aSRafael J. Wysocki acpi_processor_cpufreq_init = true;
269*c0e0421aSRafael J. Wysocki acpi_processor_ignore_ppc_init();
270*c0e0421aSRafael J. Wysocki }
271*c0e0421aSRafael J. Wysocki
272ac212b69SRafael J. Wysocki result = driver_register(&acpi_processor_driver);
2730131aa3dSAlex Chiang if (result < 0)
27446bcfad7SDeepthi Dharwar return result;
2750131aa3dSAlex Chiang
27664f3bf2fSSebastian Andrzej Siewior result = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
27764f3bf2fSSebastian Andrzej Siewior "acpi/cpu-drv:online",
27864f3bf2fSSebastian Andrzej Siewior acpi_soft_cpu_online, NULL);
27964f3bf2fSSebastian Andrzej Siewior if (result < 0)
28064f3bf2fSSebastian Andrzej Siewior goto err;
28164f3bf2fSSebastian Andrzej Siewior hp_online = result;
28264f3bf2fSSebastian Andrzej Siewior cpuhp_setup_state_nocalls(CPUHP_ACPI_CPUDRV_DEAD, "acpi/cpu-drv:dead",
28364f3bf2fSSebastian Andrzej Siewior NULL, acpi_soft_cpu_dead);
28464f3bf2fSSebastian Andrzej Siewior
2850131aa3dSAlex Chiang acpi_processor_throttling_init();
2860131aa3dSAlex Chiang return 0;
28764f3bf2fSSebastian Andrzej Siewior err:
28864f3bf2fSSebastian Andrzej Siewior driver_unregister(&acpi_processor_driver);
28964f3bf2fSSebastian Andrzej Siewior return result;
2900131aa3dSAlex Chiang }
2910131aa3dSAlex Chiang
acpi_processor_driver_exit(void)292ac212b69SRafael J. Wysocki static void __exit acpi_processor_driver_exit(void)
2930131aa3dSAlex Chiang {
2940131aa3dSAlex Chiang if (acpi_disabled)
2950131aa3dSAlex Chiang return;
2960131aa3dSAlex Chiang
297d15ce412SViresh Kumar if (acpi_processor_cpufreq_init) {
298d15ce412SViresh Kumar cpufreq_unregister_notifier(&acpi_processor_notifier_block,
299d15ce412SViresh Kumar CPUFREQ_POLICY_NOTIFIER);
300d15ce412SViresh Kumar acpi_processor_cpufreq_init = false;
301d15ce412SViresh Kumar }
302d15ce412SViresh Kumar
30364f3bf2fSSebastian Andrzej Siewior cpuhp_remove_state_nocalls(hp_online);
30464f3bf2fSSebastian Andrzej Siewior cpuhp_remove_state_nocalls(CPUHP_ACPI_CPUDRV_DEAD);
305ac212b69SRafael J. Wysocki driver_unregister(&acpi_processor_driver);
3060131aa3dSAlex Chiang }
3070131aa3dSAlex Chiang
308ac212b69SRafael J. Wysocki module_init(acpi_processor_driver_init);
309ac212b69SRafael J. Wysocki module_exit(acpi_processor_driver_exit);
3100131aa3dSAlex Chiang
3110131aa3dSAlex Chiang MODULE_ALIAS("processor");
312