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);
1119df6a7a3SZhang Rui 	int val, ret;
1123e8c4d31SAmit Kucheria 
1139df6a7a3SZhang Rui 	ret = intel_tcc_get_temp(zonedev->cpu, &val, true);
1149df6a7a3SZhang Rui 	if (ret < 0)
1159df6a7a3SZhang 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