1f6cc69f1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
23e8c4d31SAmit Kucheria /*
33e8c4d31SAmit Kucheria * x86_pkg_temp_thermal driver
43e8c4d31SAmit Kucheria * Copyright (c) 2013, Intel Corporation.
53e8c4d31SAmit Kucheria */
63e8c4d31SAmit Kucheria #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
73e8c4d31SAmit Kucheria
83e8c4d31SAmit Kucheria #include <linux/module.h>
93e8c4d31SAmit Kucheria #include <linux/init.h>
10983eb370SZhang Rui #include <linux/intel_tcc.h>
113e8c4d31SAmit Kucheria #include <linux/err.h>
123e8c4d31SAmit Kucheria #include <linux/param.h>
133e8c4d31SAmit Kucheria #include <linux/device.h>
143e8c4d31SAmit Kucheria #include <linux/platform_device.h>
153e8c4d31SAmit Kucheria #include <linux/cpu.h>
163e8c4d31SAmit Kucheria #include <linux/smp.h>
173e8c4d31SAmit Kucheria #include <linux/slab.h>
183e8c4d31SAmit Kucheria #include <linux/pm.h>
193e8c4d31SAmit Kucheria #include <linux/thermal.h>
203e8c4d31SAmit Kucheria #include <linux/debugfs.h>
219223d0dcSBorislav Petkov
223e8c4d31SAmit Kucheria #include <asm/cpu_device_id.h>
239223d0dcSBorislav Petkov
249223d0dcSBorislav Petkov #include "thermal_interrupt.h"
253e8c4d31SAmit Kucheria
263e8c4d31SAmit Kucheria /*
273e8c4d31SAmit Kucheria * Rate control delay: Idea is to introduce denounce effect
283e8c4d31SAmit Kucheria * This should be long enough to avoid reduce events, when
293e8c4d31SAmit Kucheria * threshold is set to a temperature, which is constantly
303e8c4d31SAmit Kucheria * violated, but at the short enough to take any action.
313e8c4d31SAmit Kucheria * The action can be remove threshold or change it to next
323e8c4d31SAmit Kucheria * interesting setting. Based on experiments, in around
333e8c4d31SAmit Kucheria * every 5 seconds under load will give us a significant
343e8c4d31SAmit Kucheria * temperature change.
353e8c4d31SAmit Kucheria */
363e8c4d31SAmit Kucheria #define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000
373e8c4d31SAmit Kucheria static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY;
383e8c4d31SAmit Kucheria module_param(notify_delay_ms, int, 0644);
393e8c4d31SAmit Kucheria MODULE_PARM_DESC(notify_delay_ms,
403e8c4d31SAmit Kucheria "User space notification delay in milli seconds.");
413e8c4d31SAmit Kucheria
423e8c4d31SAmit Kucheria /* Number of trip points in thermal zone. Currently it can't
433e8c4d31SAmit Kucheria * be more than 2. MSR can allow setting and getting notifications
443e8c4d31SAmit Kucheria * for only 2 thresholds. This define enforces this, if there
453e8c4d31SAmit Kucheria * is some wrong values returned by cpuid for number of thresholds.
463e8c4d31SAmit Kucheria */
473e8c4d31SAmit Kucheria #define MAX_NUMBER_OF_TRIPS 2
483e8c4d31SAmit Kucheria
49b2ce1c88SLen Brown struct zone_device {
503e8c4d31SAmit Kucheria int cpu;
513e8c4d31SAmit Kucheria bool work_scheduled;
523e8c4d31SAmit Kucheria u32 msr_pkg_therm_low;
533e8c4d31SAmit Kucheria u32 msr_pkg_therm_high;
543e8c4d31SAmit Kucheria struct delayed_work work;
553e8c4d31SAmit Kucheria struct thermal_zone_device *tzone;
56d3ecaf17SDaniel Lezcano struct thermal_trip *trips;
573e8c4d31SAmit Kucheria struct cpumask cpumask;
583e8c4d31SAmit Kucheria };
593e8c4d31SAmit Kucheria
603e8c4d31SAmit Kucheria static struct thermal_zone_params pkg_temp_tz_params = {
613e8c4d31SAmit Kucheria .no_hwmon = true,
623e8c4d31SAmit Kucheria };
633e8c4d31SAmit Kucheria
64b2ce1c88SLen Brown /* Keep track of how many zone pointers we allocated in init() */
65b2ce1c88SLen Brown static int max_id __read_mostly;
66b2ce1c88SLen Brown /* Array of zone pointers */
67b2ce1c88SLen Brown static struct zone_device **zones;
683e8c4d31SAmit Kucheria /* Serializes interrupt notification, work and hotplug */
69fc32150eSClark Williams static DEFINE_RAW_SPINLOCK(pkg_temp_lock);
703e8c4d31SAmit Kucheria /* Protects zone operation in the work function against hotplug removal */
713e8c4d31SAmit Kucheria static DEFINE_MUTEX(thermal_zone_mutex);
723e8c4d31SAmit Kucheria
733e8c4d31SAmit Kucheria /* The dynamically assigned cpu hotplug state for module_exit() */
743e8c4d31SAmit Kucheria static enum cpuhp_state pkg_thermal_hp_state __read_mostly;
753e8c4d31SAmit Kucheria
763e8c4d31SAmit Kucheria /* Debug counters to show using debugfs */
773e8c4d31SAmit Kucheria static struct dentry *debugfs;
783e8c4d31SAmit Kucheria static unsigned int pkg_interrupt_cnt;
793e8c4d31SAmit Kucheria static unsigned int pkg_work_cnt;
803e8c4d31SAmit Kucheria
pkg_temp_debugfs_init(void)8172c9f26bSGreg Kroah-Hartman static void pkg_temp_debugfs_init(void)
823e8c4d31SAmit Kucheria {
833e8c4d31SAmit Kucheria debugfs = debugfs_create_dir("pkg_temp_thermal", NULL);
843e8c4d31SAmit Kucheria
8572c9f26bSGreg Kroah-Hartman debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs,
863e8c4d31SAmit Kucheria &pkg_interrupt_cnt);
8772c9f26bSGreg Kroah-Hartman debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs,
883e8c4d31SAmit Kucheria &pkg_work_cnt);
893e8c4d31SAmit Kucheria }
903e8c4d31SAmit Kucheria
913e8c4d31SAmit Kucheria /*
923e8c4d31SAmit Kucheria * Protection:
933e8c4d31SAmit Kucheria *
943e8c4d31SAmit Kucheria * - cpu hotplug: Read serialized by cpu hotplug lock
953e8c4d31SAmit Kucheria * Write must hold pkg_temp_lock
963e8c4d31SAmit Kucheria *
973e8c4d31SAmit Kucheria * - Other callsites: Must hold pkg_temp_lock
983e8c4d31SAmit Kucheria */
pkg_temp_thermal_get_dev(unsigned int cpu)99b2ce1c88SLen Brown static struct zone_device *pkg_temp_thermal_get_dev(unsigned int cpu)
1003e8c4d31SAmit Kucheria {
101b2ce1c88SLen Brown int id = topology_logical_die_id(cpu);
1023e8c4d31SAmit Kucheria
103b2ce1c88SLen Brown if (id >= 0 && id < max_id)
104b2ce1c88SLen Brown return zones[id];
1053e8c4d31SAmit Kucheria return NULL;
1063e8c4d31SAmit Kucheria }
1073e8c4d31SAmit Kucheria
sys_get_curr_temp(struct thermal_zone_device * tzd,int * temp)1083e8c4d31SAmit Kucheria static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
1093e8c4d31SAmit Kucheria {
1105f68d078SDaniel Lezcano struct zone_device *zonedev = thermal_zone_device_priv(tzd);
111*9df6a7a3SZhang Rui int val, ret;
1123e8c4d31SAmit Kucheria
113*9df6a7a3SZhang Rui ret = intel_tcc_get_temp(zonedev->cpu, &val, true);
114*9df6a7a3SZhang Rui if (ret < 0)
115*9df6a7a3SZhang Rui return ret;
116983eb370SZhang Rui
117983eb370SZhang Rui *temp = val * 1000;
1183e8c4d31SAmit Kucheria pr_debug("sys_get_curr_temp %d\n", *temp);
1193e8c4d31SAmit Kucheria return 0;
1203e8c4d31SAmit Kucheria }
1213e8c4d31SAmit Kucheria
1223e8c4d31SAmit Kucheria static int
sys_set_trip_temp(struct thermal_zone_device * tzd,int trip,int temp)1233e8c4d31SAmit Kucheria sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
1243e8c4d31SAmit Kucheria {
1255f68d078SDaniel Lezcano struct zone_device *zonedev = thermal_zone_device_priv(tzd);
1263e8c4d31SAmit Kucheria u32 l, h, mask, shift, intr;
1272a96243eSZhang Rui int tj_max, val, ret;
1283e8c4d31SAmit Kucheria
12958374a39SZhang Rui tj_max = intel_tcc_get_tjmax(zonedev->cpu);
13058374a39SZhang Rui if (tj_max < 0)
13158374a39SZhang Rui return tj_max;
13258374a39SZhang Rui tj_max *= 1000;
13358374a39SZhang Rui
1342a96243eSZhang Rui val = (tj_max - temp)/1000;
1352a96243eSZhang Rui
1362a96243eSZhang Rui if (trip >= MAX_NUMBER_OF_TRIPS || val < 0 || val > 0x7f)
1373e8c4d31SAmit Kucheria return -EINVAL;
1383e8c4d31SAmit Kucheria
139b2ce1c88SLen Brown ret = rdmsr_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
1403e8c4d31SAmit Kucheria &l, &h);
1413e8c4d31SAmit Kucheria if (ret < 0)
1423e8c4d31SAmit Kucheria return ret;
1433e8c4d31SAmit Kucheria
1443e8c4d31SAmit Kucheria if (trip) {
1453e8c4d31SAmit Kucheria mask = THERM_MASK_THRESHOLD1;
1463e8c4d31SAmit Kucheria shift = THERM_SHIFT_THRESHOLD1;
1473e8c4d31SAmit Kucheria intr = THERM_INT_THRESHOLD1_ENABLE;
1483e8c4d31SAmit Kucheria } else {
1493e8c4d31SAmit Kucheria mask = THERM_MASK_THRESHOLD0;
1503e8c4d31SAmit Kucheria shift = THERM_SHIFT_THRESHOLD0;
1513e8c4d31SAmit Kucheria intr = THERM_INT_THRESHOLD0_ENABLE;
1523e8c4d31SAmit Kucheria }
1533e8c4d31SAmit Kucheria l &= ~mask;
1543e8c4d31SAmit Kucheria /*
1553e8c4d31SAmit Kucheria * When users space sets a trip temperature == 0, which is indication
1563e8c4d31SAmit Kucheria * that, it is no longer interested in receiving notifications.
1573e8c4d31SAmit Kucheria */
1583e8c4d31SAmit Kucheria if (!temp) {
1593e8c4d31SAmit Kucheria l &= ~intr;
1603e8c4d31SAmit Kucheria } else {
1612a96243eSZhang Rui l |= val << shift;
1623e8c4d31SAmit Kucheria l |= intr;
1633e8c4d31SAmit Kucheria }
1643e8c4d31SAmit Kucheria
165b2ce1c88SLen Brown return wrmsr_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
166b2ce1c88SLen Brown l, h);
1673e8c4d31SAmit Kucheria }
1683e8c4d31SAmit Kucheria
1693e8c4d31SAmit Kucheria /* Thermal zone callback registry */
1703e8c4d31SAmit Kucheria static struct thermal_zone_device_ops tzone_ops = {
1713e8c4d31SAmit Kucheria .get_temp = sys_get_curr_temp,
1723e8c4d31SAmit Kucheria .set_trip_temp = sys_set_trip_temp,
1733e8c4d31SAmit Kucheria };
1743e8c4d31SAmit Kucheria
pkg_thermal_rate_control(void)1753e8c4d31SAmit Kucheria static bool pkg_thermal_rate_control(void)
1763e8c4d31SAmit Kucheria {
1773e8c4d31SAmit Kucheria return true;
1783e8c4d31SAmit Kucheria }
1793e8c4d31SAmit Kucheria
1803e8c4d31SAmit Kucheria /* Enable threshold interrupt on local package/cpu */
enable_pkg_thres_interrupt(void)1813e8c4d31SAmit Kucheria static inline void enable_pkg_thres_interrupt(void)
1823e8c4d31SAmit Kucheria {
1833e8c4d31SAmit Kucheria u8 thres_0, thres_1;
1843e8c4d31SAmit Kucheria u32 l, h;
1853e8c4d31SAmit Kucheria
1863e8c4d31SAmit Kucheria rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
1873e8c4d31SAmit Kucheria /* only enable/disable if it had valid threshold value */
1883e8c4d31SAmit Kucheria thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0;
1893e8c4d31SAmit Kucheria thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1;
1903e8c4d31SAmit Kucheria if (thres_0)
1913e8c4d31SAmit Kucheria l |= THERM_INT_THRESHOLD0_ENABLE;
1923e8c4d31SAmit Kucheria if (thres_1)
1933e8c4d31SAmit Kucheria l |= THERM_INT_THRESHOLD1_ENABLE;
1943e8c4d31SAmit Kucheria wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
1953e8c4d31SAmit Kucheria }
1963e8c4d31SAmit Kucheria
1973e8c4d31SAmit Kucheria /* Disable threshold interrupt on local package/cpu */
disable_pkg_thres_interrupt(void)1983e8c4d31SAmit Kucheria static inline void disable_pkg_thres_interrupt(void)
1993e8c4d31SAmit Kucheria {
2003e8c4d31SAmit Kucheria u32 l, h;
2013e8c4d31SAmit Kucheria
2023e8c4d31SAmit Kucheria rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
2033e8c4d31SAmit Kucheria
2043e8c4d31SAmit Kucheria l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
2053e8c4d31SAmit Kucheria wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
2063e8c4d31SAmit Kucheria }
2073e8c4d31SAmit Kucheria
pkg_temp_thermal_threshold_work_fn(struct work_struct * work)2083e8c4d31SAmit Kucheria static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
2093e8c4d31SAmit Kucheria {
2103e8c4d31SAmit Kucheria struct thermal_zone_device *tzone = NULL;
2113e8c4d31SAmit Kucheria int cpu = smp_processor_id();
212b2ce1c88SLen Brown struct zone_device *zonedev;
2133e8c4d31SAmit Kucheria
2143e8c4d31SAmit Kucheria mutex_lock(&thermal_zone_mutex);
215fc32150eSClark Williams raw_spin_lock_irq(&pkg_temp_lock);
2163e8c4d31SAmit Kucheria ++pkg_work_cnt;
2173e8c4d31SAmit Kucheria
218b2ce1c88SLen Brown zonedev = pkg_temp_thermal_get_dev(cpu);
219b2ce1c88SLen Brown if (!zonedev) {
220fc32150eSClark Williams raw_spin_unlock_irq(&pkg_temp_lock);
2213e8c4d31SAmit Kucheria mutex_unlock(&thermal_zone_mutex);
2223e8c4d31SAmit Kucheria return;
2233e8c4d31SAmit Kucheria }
224b2ce1c88SLen Brown zonedev->work_scheduled = false;
2253e8c4d31SAmit Kucheria
226930d06bfSSrinivas Pandruvada thermal_clear_package_intr_status(PACKAGE_LEVEL, THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
227b2ce1c88SLen Brown tzone = zonedev->tzone;
2283e8c4d31SAmit Kucheria
2293e8c4d31SAmit Kucheria enable_pkg_thres_interrupt();
230fc32150eSClark Williams raw_spin_unlock_irq(&pkg_temp_lock);
2313e8c4d31SAmit Kucheria
2323e8c4d31SAmit Kucheria /*
2333e8c4d31SAmit Kucheria * If tzone is not NULL, then thermal_zone_mutex will prevent the
2343e8c4d31SAmit Kucheria * concurrent removal in the cpu offline callback.
2353e8c4d31SAmit Kucheria */
2363e8c4d31SAmit Kucheria if (tzone)
2373e8c4d31SAmit Kucheria thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED);
2383e8c4d31SAmit Kucheria
2393e8c4d31SAmit Kucheria mutex_unlock(&thermal_zone_mutex);
2403e8c4d31SAmit Kucheria }
2413e8c4d31SAmit Kucheria
pkg_thermal_schedule_work(int cpu,struct delayed_work * work)2423e8c4d31SAmit Kucheria static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work)
2433e8c4d31SAmit Kucheria {
2443e8c4d31SAmit Kucheria unsigned long ms = msecs_to_jiffies(notify_delay_ms);
2453e8c4d31SAmit Kucheria
2463e8c4d31SAmit Kucheria schedule_delayed_work_on(cpu, work, ms);
2473e8c4d31SAmit Kucheria }
2483e8c4d31SAmit Kucheria
pkg_thermal_notify(u64 msr_val)2493e8c4d31SAmit Kucheria static int pkg_thermal_notify(u64 msr_val)
2503e8c4d31SAmit Kucheria {
2513e8c4d31SAmit Kucheria int cpu = smp_processor_id();
252b2ce1c88SLen Brown struct zone_device *zonedev;
2533e8c4d31SAmit Kucheria unsigned long flags;
2543e8c4d31SAmit Kucheria
255fc32150eSClark Williams raw_spin_lock_irqsave(&pkg_temp_lock, flags);
2563e8c4d31SAmit Kucheria ++pkg_interrupt_cnt;
2573e8c4d31SAmit Kucheria
2583e8c4d31SAmit Kucheria disable_pkg_thres_interrupt();
2593e8c4d31SAmit Kucheria
2603e8c4d31SAmit Kucheria /* Work is per package, so scheduling it once is enough. */
261b2ce1c88SLen Brown zonedev = pkg_temp_thermal_get_dev(cpu);
262b2ce1c88SLen Brown if (zonedev && !zonedev->work_scheduled) {
263b2ce1c88SLen Brown zonedev->work_scheduled = true;
264b2ce1c88SLen Brown pkg_thermal_schedule_work(zonedev->cpu, &zonedev->work);
2653e8c4d31SAmit Kucheria }
2663e8c4d31SAmit Kucheria
267fc32150eSClark Williams raw_spin_unlock_irqrestore(&pkg_temp_lock, flags);
2683e8c4d31SAmit Kucheria return 0;
2693e8c4d31SAmit Kucheria }
2703e8c4d31SAmit Kucheria
pkg_temp_thermal_trips_init(int cpu,int tj_max,int num_trips)271d3ecaf17SDaniel Lezcano static struct thermal_trip *pkg_temp_thermal_trips_init(int cpu, int tj_max, int num_trips)
272d3ecaf17SDaniel Lezcano {
273d3ecaf17SDaniel Lezcano struct thermal_trip *trips;
274d3ecaf17SDaniel Lezcano unsigned long thres_reg_value;
275d3ecaf17SDaniel Lezcano u32 mask, shift, eax, edx;
276d3ecaf17SDaniel Lezcano int ret, i;
277d3ecaf17SDaniel Lezcano
278d3ecaf17SDaniel Lezcano trips = kzalloc(sizeof(*trips) * num_trips, GFP_KERNEL);
279d3ecaf17SDaniel Lezcano if (!trips)
280d3ecaf17SDaniel Lezcano return ERR_PTR(-ENOMEM);
281d3ecaf17SDaniel Lezcano
282d3ecaf17SDaniel Lezcano for (i = 0; i < num_trips; i++) {
283d3ecaf17SDaniel Lezcano
284d3ecaf17SDaniel Lezcano if (i) {
285d3ecaf17SDaniel Lezcano mask = THERM_MASK_THRESHOLD1;
286d3ecaf17SDaniel Lezcano shift = THERM_SHIFT_THRESHOLD1;
287d3ecaf17SDaniel Lezcano } else {
288d3ecaf17SDaniel Lezcano mask = THERM_MASK_THRESHOLD0;
289d3ecaf17SDaniel Lezcano shift = THERM_SHIFT_THRESHOLD0;
290d3ecaf17SDaniel Lezcano }
291d3ecaf17SDaniel Lezcano
292d3ecaf17SDaniel Lezcano ret = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
293d3ecaf17SDaniel Lezcano &eax, &edx);
294d3ecaf17SDaniel Lezcano if (ret < 0) {
295d3ecaf17SDaniel Lezcano kfree(trips);
296d3ecaf17SDaniel Lezcano return ERR_PTR(ret);
297d3ecaf17SDaniel Lezcano }
298d3ecaf17SDaniel Lezcano
299d3ecaf17SDaniel Lezcano thres_reg_value = (eax & mask) >> shift;
300d3ecaf17SDaniel Lezcano
301d3ecaf17SDaniel Lezcano trips[i].temperature = thres_reg_value ?
302d3ecaf17SDaniel Lezcano tj_max - thres_reg_value * 1000 : THERMAL_TEMP_INVALID;
303d3ecaf17SDaniel Lezcano
304d3ecaf17SDaniel Lezcano trips[i].type = THERMAL_TRIP_PASSIVE;
305d3ecaf17SDaniel Lezcano
306d3ecaf17SDaniel Lezcano pr_debug("%s: cpu=%d, trip=%d, temp=%d\n",
307d3ecaf17SDaniel Lezcano __func__, cpu, i, trips[i].temperature);
308d3ecaf17SDaniel Lezcano }
309d3ecaf17SDaniel Lezcano
310d3ecaf17SDaniel Lezcano return trips;
311d3ecaf17SDaniel Lezcano }
312d3ecaf17SDaniel Lezcano
pkg_temp_thermal_device_add(unsigned int cpu)3133e8c4d31SAmit Kucheria static int pkg_temp_thermal_device_add(unsigned int cpu)
3143e8c4d31SAmit Kucheria {
315b2ce1c88SLen Brown int id = topology_logical_die_id(cpu);
31658374a39SZhang Rui u32 eax, ebx, ecx, edx;
317b2ce1c88SLen Brown struct zone_device *zonedev;
3183e8c4d31SAmit Kucheria int thres_count, err;
3198ef0ca4aSRafael J. Wysocki int tj_max;
3203e8c4d31SAmit Kucheria
321b2ce1c88SLen Brown if (id >= max_id)
3223e8c4d31SAmit Kucheria return -ENOMEM;
3233e8c4d31SAmit Kucheria
3243e8c4d31SAmit Kucheria cpuid(6, &eax, &ebx, &ecx, &edx);
3253e8c4d31SAmit Kucheria thres_count = ebx & 0x07;
3263e8c4d31SAmit Kucheria if (!thres_count)
3273e8c4d31SAmit Kucheria return -ENODEV;
3283e8c4d31SAmit Kucheria
3293e8c4d31SAmit Kucheria thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS);
3303e8c4d31SAmit Kucheria
3318ef0ca4aSRafael J. Wysocki tj_max = intel_tcc_get_tjmax(cpu);
3328ef0ca4aSRafael J. Wysocki if (tj_max < 0)
3338ef0ca4aSRafael J. Wysocki return tj_max;
3343e8c4d31SAmit Kucheria
335b2ce1c88SLen Brown zonedev = kzalloc(sizeof(*zonedev), GFP_KERNEL);
336b2ce1c88SLen Brown if (!zonedev)
3373e8c4d31SAmit Kucheria return -ENOMEM;
3383e8c4d31SAmit Kucheria
339d3ecaf17SDaniel Lezcano zonedev->trips = pkg_temp_thermal_trips_init(cpu, tj_max, thres_count);
340d3ecaf17SDaniel Lezcano if (IS_ERR(zonedev->trips)) {
341d3ecaf17SDaniel Lezcano err = PTR_ERR(zonedev->trips);
342d3ecaf17SDaniel Lezcano goto out_kfree_zonedev;
343d3ecaf17SDaniel Lezcano }
344d3ecaf17SDaniel Lezcano
345b2ce1c88SLen Brown INIT_DELAYED_WORK(&zonedev->work, pkg_temp_thermal_threshold_work_fn);
346b2ce1c88SLen Brown zonedev->cpu = cpu;
347d3ecaf17SDaniel Lezcano zonedev->tzone = thermal_zone_device_register_with_trips("x86_pkg_temp",
348d3ecaf17SDaniel Lezcano zonedev->trips, thres_count,
3493e8c4d31SAmit Kucheria (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
350b2ce1c88SLen Brown zonedev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
351b2ce1c88SLen Brown if (IS_ERR(zonedev->tzone)) {
352b2ce1c88SLen Brown err = PTR_ERR(zonedev->tzone);
353d3ecaf17SDaniel Lezcano goto out_kfree_trips;
3543e8c4d31SAmit Kucheria }
355bbcf90c0SAndrzej Pietrasiewicz err = thermal_zone_device_enable(zonedev->tzone);
356d3ecaf17SDaniel Lezcano if (err)
357d3ecaf17SDaniel Lezcano goto out_unregister_tz;
358d3ecaf17SDaniel Lezcano
3593e8c4d31SAmit Kucheria /* Store MSR value for package thermal interrupt, to restore at exit */
360b2ce1c88SLen Brown rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm_low,
361b2ce1c88SLen Brown zonedev->msr_pkg_therm_high);
3623e8c4d31SAmit Kucheria
363b2ce1c88SLen Brown cpumask_set_cpu(cpu, &zonedev->cpumask);
364fc32150eSClark Williams raw_spin_lock_irq(&pkg_temp_lock);
365b2ce1c88SLen Brown zones[id] = zonedev;
366fc32150eSClark Williams raw_spin_unlock_irq(&pkg_temp_lock);
367d3ecaf17SDaniel Lezcano
3683e8c4d31SAmit Kucheria return 0;
369d3ecaf17SDaniel Lezcano
370d3ecaf17SDaniel Lezcano out_unregister_tz:
371d3ecaf17SDaniel Lezcano thermal_zone_device_unregister(zonedev->tzone);
372d3ecaf17SDaniel Lezcano out_kfree_trips:
373d3ecaf17SDaniel Lezcano kfree(zonedev->trips);
374d3ecaf17SDaniel Lezcano out_kfree_zonedev:
375d3ecaf17SDaniel Lezcano kfree(zonedev);
376d3ecaf17SDaniel Lezcano return err;
3773e8c4d31SAmit Kucheria }
3783e8c4d31SAmit Kucheria
pkg_thermal_cpu_offline(unsigned int cpu)3793e8c4d31SAmit Kucheria static int pkg_thermal_cpu_offline(unsigned int cpu)
3803e8c4d31SAmit Kucheria {
381b2ce1c88SLen Brown struct zone_device *zonedev = pkg_temp_thermal_get_dev(cpu);
3823e8c4d31SAmit Kucheria bool lastcpu, was_target;
3833e8c4d31SAmit Kucheria int target;
3843e8c4d31SAmit Kucheria
385b2ce1c88SLen Brown if (!zonedev)
3863e8c4d31SAmit Kucheria return 0;
3873e8c4d31SAmit Kucheria
388b2ce1c88SLen Brown target = cpumask_any_but(&zonedev->cpumask, cpu);
389b2ce1c88SLen Brown cpumask_clear_cpu(cpu, &zonedev->cpumask);
3903e8c4d31SAmit Kucheria lastcpu = target >= nr_cpu_ids;
3913e8c4d31SAmit Kucheria /*
3923e8c4d31SAmit Kucheria * Remove the sysfs files, if this is the last cpu in the package
3933e8c4d31SAmit Kucheria * before doing further cleanups.
3943e8c4d31SAmit Kucheria */
3953e8c4d31SAmit Kucheria if (lastcpu) {
396b2ce1c88SLen Brown struct thermal_zone_device *tzone = zonedev->tzone;
3973e8c4d31SAmit Kucheria
3983e8c4d31SAmit Kucheria /*
3993e8c4d31SAmit Kucheria * We must protect against a work function calling
4003e8c4d31SAmit Kucheria * thermal_zone_update, after/while unregister. We null out
4013e8c4d31SAmit Kucheria * the pointer under the zone mutex, so the worker function
4023e8c4d31SAmit Kucheria * won't try to call.
4033e8c4d31SAmit Kucheria */
4043e8c4d31SAmit Kucheria mutex_lock(&thermal_zone_mutex);
405b2ce1c88SLen Brown zonedev->tzone = NULL;
4063e8c4d31SAmit Kucheria mutex_unlock(&thermal_zone_mutex);
4073e8c4d31SAmit Kucheria
4083e8c4d31SAmit Kucheria thermal_zone_device_unregister(tzone);
4093e8c4d31SAmit Kucheria }
4103e8c4d31SAmit Kucheria
4113e8c4d31SAmit Kucheria /* Protect against work and interrupts */
412fc32150eSClark Williams raw_spin_lock_irq(&pkg_temp_lock);
4133e8c4d31SAmit Kucheria
4143e8c4d31SAmit Kucheria /*
4153e8c4d31SAmit Kucheria * Check whether this cpu was the current target and store the new
4163e8c4d31SAmit Kucheria * one. When we drop the lock, then the interrupt notify function
4173e8c4d31SAmit Kucheria * will see the new target.
4183e8c4d31SAmit Kucheria */
419b2ce1c88SLen Brown was_target = zonedev->cpu == cpu;
420b2ce1c88SLen Brown zonedev->cpu = target;
4213e8c4d31SAmit Kucheria
4223e8c4d31SAmit Kucheria /*
4233e8c4d31SAmit Kucheria * If this is the last CPU in the package remove the package
4243e8c4d31SAmit Kucheria * reference from the array and restore the interrupt MSR. When we
4253e8c4d31SAmit Kucheria * drop the lock neither the interrupt notify function nor the
4263e8c4d31SAmit Kucheria * worker will see the package anymore.
4273e8c4d31SAmit Kucheria */
4283e8c4d31SAmit Kucheria if (lastcpu) {
429b2ce1c88SLen Brown zones[topology_logical_die_id(cpu)] = NULL;
4303e8c4d31SAmit Kucheria /* After this point nothing touches the MSR anymore. */
4313e8c4d31SAmit Kucheria wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
432b2ce1c88SLen Brown zonedev->msr_pkg_therm_low, zonedev->msr_pkg_therm_high);
4333e8c4d31SAmit Kucheria }
4343e8c4d31SAmit Kucheria
4353e8c4d31SAmit Kucheria /*
4363e8c4d31SAmit Kucheria * Check whether there is work scheduled and whether the work is
4373e8c4d31SAmit Kucheria * targeted at the outgoing CPU.
4383e8c4d31SAmit Kucheria */
439b2ce1c88SLen Brown if (zonedev->work_scheduled && was_target) {
4403e8c4d31SAmit Kucheria /*
4413e8c4d31SAmit Kucheria * To cancel the work we need to drop the lock, otherwise
4423e8c4d31SAmit Kucheria * we might deadlock if the work needs to be flushed.
4433e8c4d31SAmit Kucheria */
444fc32150eSClark Williams raw_spin_unlock_irq(&pkg_temp_lock);
445b2ce1c88SLen Brown cancel_delayed_work_sync(&zonedev->work);
446fc32150eSClark Williams raw_spin_lock_irq(&pkg_temp_lock);
4473e8c4d31SAmit Kucheria /*
4483e8c4d31SAmit Kucheria * If this is not the last cpu in the package and the work
4493e8c4d31SAmit Kucheria * did not run after we dropped the lock above, then we
4503e8c4d31SAmit Kucheria * need to reschedule the work, otherwise the interrupt
4513e8c4d31SAmit Kucheria * stays disabled forever.
4523e8c4d31SAmit Kucheria */
453b2ce1c88SLen Brown if (!lastcpu && zonedev->work_scheduled)
454b2ce1c88SLen Brown pkg_thermal_schedule_work(target, &zonedev->work);
4553e8c4d31SAmit Kucheria }
4563e8c4d31SAmit Kucheria
457fc32150eSClark Williams raw_spin_unlock_irq(&pkg_temp_lock);
4583e8c4d31SAmit Kucheria
4593e8c4d31SAmit Kucheria /* Final cleanup if this is the last cpu */
460d3ecaf17SDaniel Lezcano if (lastcpu) {
461d3ecaf17SDaniel Lezcano kfree(zonedev->trips);
462b2ce1c88SLen Brown kfree(zonedev);
463d3ecaf17SDaniel Lezcano }
4643e8c4d31SAmit Kucheria return 0;
4653e8c4d31SAmit Kucheria }
4663e8c4d31SAmit Kucheria
pkg_thermal_cpu_online(unsigned int cpu)4673e8c4d31SAmit Kucheria static int pkg_thermal_cpu_online(unsigned int cpu)
4683e8c4d31SAmit Kucheria {
469b2ce1c88SLen Brown struct zone_device *zonedev = pkg_temp_thermal_get_dev(cpu);
4703e8c4d31SAmit Kucheria struct cpuinfo_x86 *c = &cpu_data(cpu);
4713e8c4d31SAmit Kucheria
4723e8c4d31SAmit Kucheria /* Paranoia check */
4733e8c4d31SAmit Kucheria if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS))
4743e8c4d31SAmit Kucheria return -ENODEV;
4753e8c4d31SAmit Kucheria
4763e8c4d31SAmit Kucheria /* If the package exists, nothing to do */
477b2ce1c88SLen Brown if (zonedev) {
478b2ce1c88SLen Brown cpumask_set_cpu(cpu, &zonedev->cpumask);
4793e8c4d31SAmit Kucheria return 0;
4803e8c4d31SAmit Kucheria }
4813e8c4d31SAmit Kucheria return pkg_temp_thermal_device_add(cpu);
4823e8c4d31SAmit Kucheria }
4833e8c4d31SAmit Kucheria
4843e8c4d31SAmit Kucheria static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
4859c51044cSThomas Gleixner X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_PTS, NULL),
4863e8c4d31SAmit Kucheria {}
4873e8c4d31SAmit Kucheria };
4883e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
4893e8c4d31SAmit Kucheria
pkg_temp_thermal_init(void)4903e8c4d31SAmit Kucheria static int __init pkg_temp_thermal_init(void)
4913e8c4d31SAmit Kucheria {
4923e8c4d31SAmit Kucheria int ret;
4933e8c4d31SAmit Kucheria
4943e8c4d31SAmit Kucheria if (!x86_match_cpu(pkg_temp_thermal_ids))
4953e8c4d31SAmit Kucheria return -ENODEV;
4963e8c4d31SAmit Kucheria
497b2ce1c88SLen Brown max_id = topology_max_packages() * topology_max_die_per_package();
498b2ce1c88SLen Brown zones = kcalloc(max_id, sizeof(struct zone_device *),
4993e8c4d31SAmit Kucheria GFP_KERNEL);
500b2ce1c88SLen Brown if (!zones)
5013e8c4d31SAmit Kucheria return -ENOMEM;
5023e8c4d31SAmit Kucheria
5033e8c4d31SAmit Kucheria ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online",
5043e8c4d31SAmit Kucheria pkg_thermal_cpu_online, pkg_thermal_cpu_offline);
5053e8c4d31SAmit Kucheria if (ret < 0)
5063e8c4d31SAmit Kucheria goto err;
5073e8c4d31SAmit Kucheria
5083e8c4d31SAmit Kucheria /* Store the state for module exit */
5093e8c4d31SAmit Kucheria pkg_thermal_hp_state = ret;
5103e8c4d31SAmit Kucheria
5113e8c4d31SAmit Kucheria platform_thermal_package_notify = pkg_thermal_notify;
5123e8c4d31SAmit Kucheria platform_thermal_package_rate_control = pkg_thermal_rate_control;
5133e8c4d31SAmit Kucheria
5143e8c4d31SAmit Kucheria /* Don't care if it fails */
5153e8c4d31SAmit Kucheria pkg_temp_debugfs_init();
5163e8c4d31SAmit Kucheria return 0;
5173e8c4d31SAmit Kucheria
5183e8c4d31SAmit Kucheria err:
519b2ce1c88SLen Brown kfree(zones);
5203e8c4d31SAmit Kucheria return ret;
5213e8c4d31SAmit Kucheria }
module_init(pkg_temp_thermal_init)5223e8c4d31SAmit Kucheria module_init(pkg_temp_thermal_init)
5233e8c4d31SAmit Kucheria
5243e8c4d31SAmit Kucheria static void __exit pkg_temp_thermal_exit(void)
5253e8c4d31SAmit Kucheria {
5263e8c4d31SAmit Kucheria platform_thermal_package_notify = NULL;
5273e8c4d31SAmit Kucheria platform_thermal_package_rate_control = NULL;
5283e8c4d31SAmit Kucheria
5293e8c4d31SAmit Kucheria cpuhp_remove_state(pkg_thermal_hp_state);
5303e8c4d31SAmit Kucheria debugfs_remove_recursive(debugfs);
531b2ce1c88SLen Brown kfree(zones);
5323e8c4d31SAmit Kucheria }
5333e8c4d31SAmit Kucheria module_exit(pkg_temp_thermal_exit)
5343e8c4d31SAmit Kucheria
535983eb370SZhang Rui MODULE_IMPORT_NS(INTEL_TCC);
5363e8c4d31SAmit Kucheria MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver");
5373e8c4d31SAmit Kucheria MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
5383e8c4d31SAmit Kucheria MODULE_LICENSE("GPL v2");
539