xref: /openbmc/linux/drivers/thermal/thermal_core.c (revision ecc23d0a422a3118fcf6e4f0a46e17a6c2047b02)
17e3c0381SLina Iyer // SPDX-License-Identifier: GPL-2.0
25fc024abSZhang Rui /*
35fc024abSZhang Rui  *  thermal.c - Generic Thermal Management Sysfs support.
45fc024abSZhang Rui  *
55fc024abSZhang Rui  *  Copyright (C) 2008 Intel Corp
65fc024abSZhang Rui  *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
75fc024abSZhang Rui  *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
85fc024abSZhang Rui  */
95fc024abSZhang Rui 
105fc024abSZhang Rui #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
115fc024abSZhang Rui 
125fc024abSZhang Rui #include <linux/device.h>
135fc024abSZhang Rui #include <linux/err.h>
143f0cfea3SAmit Kucheria #include <linux/export.h>
155fc024abSZhang Rui #include <linux/slab.h>
165fc024abSZhang Rui #include <linux/kdev_t.h>
175fc024abSZhang Rui #include <linux/idr.h>
185fc024abSZhang Rui #include <linux/thermal.h>
195fc024abSZhang Rui #include <linux/reboot.h>
2042a5bf50SAndy Shevchenko #include <linux/string.h>
21a116b5d4SEduardo Valentin #include <linux/of.h>
22ff140feaSZhang Rui #include <linux/suspend.h>
235fc024abSZhang Rui 
24100a8fdbSPunit Agrawal #define CREATE_TRACE_POINTS
2532a7a021SDaniel Lezcano #include "thermal_trace.h"
26100a8fdbSPunit Agrawal 
275fc024abSZhang Rui #include "thermal_core.h"
280dd88793SEduardo Valentin #include "thermal_hwmon.h"
295fc024abSZhang Rui 
30b31ef828SMatthew Wilcox static DEFINE_IDA(thermal_tz_ida);
31b31ef828SMatthew Wilcox static DEFINE_IDA(thermal_cdev_ida);
325fc024abSZhang Rui 
335fc024abSZhang Rui static LIST_HEAD(thermal_tz_list);
345fc024abSZhang Rui static LIST_HEAD(thermal_cdev_list);
355fc024abSZhang Rui static LIST_HEAD(thermal_governor_list);
365fc024abSZhang Rui 
375fc024abSZhang Rui static DEFINE_MUTEX(thermal_list_lock);
385fc024abSZhang Rui static DEFINE_MUTEX(thermal_governor_lock);
395fc024abSZhang Rui 
40f2234bcdSZhang Rui static struct thermal_governor *def_governor;
41f2234bcdSZhang Rui 
421b4f4849SEduardo Valentin /*
431b4f4849SEduardo Valentin  * Governor section: set of functions to handle thermal governors
441b4f4849SEduardo Valentin  *
451b4f4849SEduardo Valentin  * Functions to help in the life cycle of thermal governors within
461b4f4849SEduardo Valentin  * the thermal core and by the thermal governor code.
471b4f4849SEduardo Valentin  */
481b4f4849SEduardo Valentin 
__find_governor(const char * name)495fc024abSZhang Rui static struct thermal_governor *__find_governor(const char *name)
505fc024abSZhang Rui {
515fc024abSZhang Rui 	struct thermal_governor *pos;
525fc024abSZhang Rui 
53f2234bcdSZhang Rui 	if (!name || !name[0])
54f2234bcdSZhang Rui 		return def_governor;
55f2234bcdSZhang Rui 
565fc024abSZhang Rui 	list_for_each_entry(pos, &thermal_governor_list, governor_list)
57484ac2f3SRasmus Villemoes 		if (!strncasecmp(name, pos->name, THERMAL_NAME_LENGTH))
585fc024abSZhang Rui 			return pos;
595fc024abSZhang Rui 
605fc024abSZhang Rui 	return NULL;
615fc024abSZhang Rui }
625fc024abSZhang Rui 
63e33df1d2SJavi Merino /**
64e33df1d2SJavi Merino  * bind_previous_governor() - bind the previous governor of the thermal zone
65e33df1d2SJavi Merino  * @tz:		a valid pointer to a struct thermal_zone_device
66e33df1d2SJavi Merino  * @failed_gov_name:	the name of the governor that failed to register
67e33df1d2SJavi Merino  *
68e33df1d2SJavi Merino  * Register the previous governor of the thermal zone after a new
69e33df1d2SJavi Merino  * governor has failed to be bound.
70e33df1d2SJavi Merino  */
bind_previous_governor(struct thermal_zone_device * tz,const char * failed_gov_name)71e33df1d2SJavi Merino static void bind_previous_governor(struct thermal_zone_device *tz,
72e33df1d2SJavi Merino 				   const char *failed_gov_name)
73e33df1d2SJavi Merino {
74e33df1d2SJavi Merino 	if (tz->governor && tz->governor->bind_to_tz) {
75e33df1d2SJavi Merino 		if (tz->governor->bind_to_tz(tz)) {
76e33df1d2SJavi Merino 			dev_err(&tz->device,
77e33df1d2SJavi Merino 				"governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",
78e33df1d2SJavi Merino 				failed_gov_name, tz->governor->name, tz->type);
79e33df1d2SJavi Merino 			tz->governor = NULL;
80e33df1d2SJavi Merino 		}
81e33df1d2SJavi Merino 	}
82e33df1d2SJavi Merino }
83e33df1d2SJavi Merino 
84e33df1d2SJavi Merino /**
85e33df1d2SJavi Merino  * thermal_set_governor() - Switch to another governor
86e33df1d2SJavi Merino  * @tz:		a valid pointer to a struct thermal_zone_device
87e33df1d2SJavi Merino  * @new_gov:	pointer to the new governor
88e33df1d2SJavi Merino  *
89e33df1d2SJavi Merino  * Change the governor of thermal zone @tz.
90e33df1d2SJavi Merino  *
91e33df1d2SJavi Merino  * Return: 0 on success, an error if the new governor's bind_to_tz() failed.
92e33df1d2SJavi Merino  */
thermal_set_governor(struct thermal_zone_device * tz,struct thermal_governor * new_gov)93e33df1d2SJavi Merino static int thermal_set_governor(struct thermal_zone_device *tz,
94e33df1d2SJavi Merino 				struct thermal_governor *new_gov)
95e33df1d2SJavi Merino {
96e33df1d2SJavi Merino 	int ret = 0;
97e33df1d2SJavi Merino 
98e33df1d2SJavi Merino 	if (tz->governor && tz->governor->unbind_from_tz)
99e33df1d2SJavi Merino 		tz->governor->unbind_from_tz(tz);
100e33df1d2SJavi Merino 
101e33df1d2SJavi Merino 	if (new_gov && new_gov->bind_to_tz) {
102e33df1d2SJavi Merino 		ret = new_gov->bind_to_tz(tz);
103e33df1d2SJavi Merino 		if (ret) {
104e33df1d2SJavi Merino 			bind_previous_governor(tz, new_gov->name);
105e33df1d2SJavi Merino 
106e33df1d2SJavi Merino 			return ret;
107e33df1d2SJavi Merino 		}
108e33df1d2SJavi Merino 	}
109e33df1d2SJavi Merino 
110e33df1d2SJavi Merino 	tz->governor = new_gov;
111e33df1d2SJavi Merino 
112e33df1d2SJavi Merino 	return ret;
113e33df1d2SJavi Merino }
114e33df1d2SJavi Merino 
thermal_register_governor(struct thermal_governor * governor)1155fc024abSZhang Rui int thermal_register_governor(struct thermal_governor *governor)
1165fc024abSZhang Rui {
1175fc024abSZhang Rui 	int err;
1185fc024abSZhang Rui 	const char *name;
1195fc024abSZhang Rui 	struct thermal_zone_device *pos;
1205fc024abSZhang Rui 
1215fc024abSZhang Rui 	if (!governor)
1225fc024abSZhang Rui 		return -EINVAL;
1235fc024abSZhang Rui 
1245fc024abSZhang Rui 	mutex_lock(&thermal_governor_lock);
1255fc024abSZhang Rui 
1265fc024abSZhang Rui 	err = -EBUSY;
1275027ba36SEduardo Valentin 	if (!__find_governor(governor->name)) {
128eb7be329SEduardo Valentin 		bool match_default;
129eb7be329SEduardo Valentin 
1305fc024abSZhang Rui 		err = 0;
1315fc024abSZhang Rui 		list_add(&governor->governor_list, &thermal_governor_list);
132eb7be329SEduardo Valentin 		match_default = !strncmp(governor->name,
133eb7be329SEduardo Valentin 					 DEFAULT_THERMAL_GOVERNOR,
134eb7be329SEduardo Valentin 					 THERMAL_NAME_LENGTH);
135eb7be329SEduardo Valentin 
136eb7be329SEduardo Valentin 		if (!def_governor && match_default)
137f2234bcdSZhang Rui 			def_governor = governor;
1385fc024abSZhang Rui 	}
1395fc024abSZhang Rui 
1405fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
1415fc024abSZhang Rui 
1425fc024abSZhang Rui 	list_for_each_entry(pos, &thermal_tz_list, node) {
143f2234bcdSZhang Rui 		/*
144f2234bcdSZhang Rui 		 * only thermal zones with specified tz->tzp->governor_name
145f2234bcdSZhang Rui 		 * may run with tz->govenor unset
146f2234bcdSZhang Rui 		 */
1475fc024abSZhang Rui 		if (pos->governor)
1485fc024abSZhang Rui 			continue;
149f2234bcdSZhang Rui 
1505fc024abSZhang Rui 		name = pos->tzp->governor_name;
151f2234bcdSZhang Rui 
152e33df1d2SJavi Merino 		if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
153e33df1d2SJavi Merino 			int ret;
154e33df1d2SJavi Merino 
155e33df1d2SJavi Merino 			ret = thermal_set_governor(pos, governor);
156e33df1d2SJavi Merino 			if (ret)
157e33df1d2SJavi Merino 				dev_err(&pos->device,
158e33df1d2SJavi Merino 					"Failed to set governor %s for thermal zone %s: %d\n",
159e33df1d2SJavi Merino 					governor->name, pos->type, ret);
160e33df1d2SJavi Merino 		}
1615fc024abSZhang Rui 	}
1625fc024abSZhang Rui 
1635fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
1645fc024abSZhang Rui 	mutex_unlock(&thermal_governor_lock);
1655fc024abSZhang Rui 
1665fc024abSZhang Rui 	return err;
1675fc024abSZhang Rui }
1685fc024abSZhang Rui 
thermal_unregister_governor(struct thermal_governor * governor)1695fc024abSZhang Rui void thermal_unregister_governor(struct thermal_governor *governor)
1705fc024abSZhang Rui {
1715fc024abSZhang Rui 	struct thermal_zone_device *pos;
1725fc024abSZhang Rui 
1735fc024abSZhang Rui 	if (!governor)
1745fc024abSZhang Rui 		return;
1755fc024abSZhang Rui 
1765fc024abSZhang Rui 	mutex_lock(&thermal_governor_lock);
1775fc024abSZhang Rui 
1785027ba36SEduardo Valentin 	if (!__find_governor(governor->name))
1795fc024abSZhang Rui 		goto exit;
1805fc024abSZhang Rui 
1815fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
1825fc024abSZhang Rui 
1835fc024abSZhang Rui 	list_for_each_entry(pos, &thermal_tz_list, node) {
184484ac2f3SRasmus Villemoes 		if (!strncasecmp(pos->governor->name, governor->name,
1855fc024abSZhang Rui 				 THERMAL_NAME_LENGTH))
186e33df1d2SJavi Merino 			thermal_set_governor(pos, NULL);
1875fc024abSZhang Rui 	}
1885fc024abSZhang Rui 
1895fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
1905fc024abSZhang Rui 	list_del(&governor->governor_list);
1915fc024abSZhang Rui exit:
1925fc024abSZhang Rui 	mutex_unlock(&thermal_governor_lock);
1935fc024abSZhang Rui }
1945fc024abSZhang Rui 
thermal_zone_device_set_policy(struct thermal_zone_device * tz,char * policy)1951b4f4849SEduardo Valentin int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
1961b4f4849SEduardo Valentin 				   char *policy)
1975fc024abSZhang Rui {
1981b4f4849SEduardo Valentin 	struct thermal_governor *gov;
1991b4f4849SEduardo Valentin 	int ret = -EINVAL;
2005fc024abSZhang Rui 
2011b4f4849SEduardo Valentin 	mutex_lock(&thermal_governor_lock);
2025fc024abSZhang Rui 	mutex_lock(&tz->lock);
2035fc024abSZhang Rui 
204b778b4d7SGuenter Roeck 	if (!device_is_registered(&tz->device))
205b778b4d7SGuenter Roeck 		goto exit;
206b778b4d7SGuenter Roeck 
2071b4f4849SEduardo Valentin 	gov = __find_governor(strim(policy));
2081b4f4849SEduardo Valentin 	if (!gov)
2095fc024abSZhang Rui 		goto exit;
2105fc024abSZhang Rui 
2111b4f4849SEduardo Valentin 	ret = thermal_set_governor(tz, gov);
2121b4f4849SEduardo Valentin 
2135fc024abSZhang Rui exit:
2141b4f4849SEduardo Valentin 	mutex_unlock(&tz->lock);
2151b4f4849SEduardo Valentin 	mutex_unlock(&thermal_governor_lock);
2161b4f4849SEduardo Valentin 
21755cdf0a2SDaniel Lezcano 	thermal_notify_tz_gov_change(tz->id, policy);
21855cdf0a2SDaniel Lezcano 
2191b4f4849SEduardo Valentin 	return ret;
2205fc024abSZhang Rui }
2215fc024abSZhang Rui 
thermal_build_list_of_policies(char * buf)2221b4f4849SEduardo Valentin int thermal_build_list_of_policies(char *buf)
2231b4f4849SEduardo Valentin {
2241b4f4849SEduardo Valentin 	struct thermal_governor *pos;
2251b4f4849SEduardo Valentin 	ssize_t count = 0;
2261b4f4849SEduardo Valentin 
2271b4f4849SEduardo Valentin 	mutex_lock(&thermal_governor_lock);
2281b4f4849SEduardo Valentin 
2291b4f4849SEduardo Valentin 	list_for_each_entry(pos, &thermal_governor_list, governor_list) {
2305bbafd43Sye xingchen 		count += sysfs_emit_at(buf, count, "%s ", pos->name);
2311b4f4849SEduardo Valentin 	}
2325bbafd43Sye xingchen 	count += sysfs_emit_at(buf, count, "\n");
2331b4f4849SEduardo Valentin 
2341b4f4849SEduardo Valentin 	mutex_unlock(&thermal_governor_lock);
2351b4f4849SEduardo Valentin 
2361b4f4849SEduardo Valentin 	return count;
2371b4f4849SEduardo Valentin }
2381b4f4849SEduardo Valentin 
thermal_unregister_governors(void)23977e1dd46SDaniel Lezcano static void __init thermal_unregister_governors(void)
2401b4f4849SEduardo Valentin {
24157c5b2ecSDaniel Lezcano 	struct thermal_governor **governor;
24257c5b2ecSDaniel Lezcano 
24357c5b2ecSDaniel Lezcano 	for_each_governor_table(governor)
24457c5b2ecSDaniel Lezcano 		thermal_unregister_governor(*governor);
24557c5b2ecSDaniel Lezcano }
24657c5b2ecSDaniel Lezcano 
thermal_register_governors(void)24757c5b2ecSDaniel Lezcano static int __init thermal_register_governors(void)
24857c5b2ecSDaniel Lezcano {
24957c5b2ecSDaniel Lezcano 	int ret = 0;
25057c5b2ecSDaniel Lezcano 	struct thermal_governor **governor;
25157c5b2ecSDaniel Lezcano 
25257c5b2ecSDaniel Lezcano 	for_each_governor_table(governor) {
25357c5b2ecSDaniel Lezcano 		ret = thermal_register_governor(*governor);
25457c5b2ecSDaniel Lezcano 		if (ret) {
25557c5b2ecSDaniel Lezcano 			pr_err("Failed to register governor: '%s'",
25657c5b2ecSDaniel Lezcano 			       (*governor)->name);
25757c5b2ecSDaniel Lezcano 			break;
25857c5b2ecSDaniel Lezcano 		}
25957c5b2ecSDaniel Lezcano 
26057c5b2ecSDaniel Lezcano 		pr_info("Registered thermal governor '%s'",
26157c5b2ecSDaniel Lezcano 			(*governor)->name);
26257c5b2ecSDaniel Lezcano 	}
26357c5b2ecSDaniel Lezcano 
26457c5b2ecSDaniel Lezcano 	if (ret) {
26557c5b2ecSDaniel Lezcano 		struct thermal_governor **gov;
26657c5b2ecSDaniel Lezcano 
26757c5b2ecSDaniel Lezcano 		for_each_governor_table(gov) {
26857c5b2ecSDaniel Lezcano 			if (gov == governor)
26957c5b2ecSDaniel Lezcano 				break;
27057c5b2ecSDaniel Lezcano 			thermal_unregister_governor(*gov);
27157c5b2ecSDaniel Lezcano 		}
27257c5b2ecSDaniel Lezcano 	}
27357c5b2ecSDaniel Lezcano 
27457c5b2ecSDaniel Lezcano 	return ret;
2751b4f4849SEduardo Valentin }
2761b4f4849SEduardo Valentin 
2778772e185SEduardo Valentin /*
2788772e185SEduardo Valentin  * Zone update section: main control loop applied to each zone while monitoring
2798772e185SEduardo Valentin  *
2808772e185SEduardo Valentin  * in polling mode. The monitoring is done using a workqueue.
2818772e185SEduardo Valentin  * Same update may be done on a zone by calling thermal_zone_device_update().
2828772e185SEduardo Valentin  *
2838772e185SEduardo Valentin  * An update means:
2848772e185SEduardo Valentin  * - Non-critical trips will invoke the governor responsible for that zone;
2858772e185SEduardo Valentin  * - Hot trips will produce a notification to userspace;
2868772e185SEduardo Valentin  * - Critical trip point will cause a system shutdown.
2878772e185SEduardo Valentin  */
thermal_zone_device_set_polling(struct thermal_zone_device * tz,unsigned long delay)2885fc024abSZhang Rui static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
28939a38808SDaniel Lezcano 					    unsigned long delay)
2905fc024abSZhang Rui {
29139a38808SDaniel Lezcano 	if (delay)
292c2b59d27SJeson Gao 		mod_delayed_work(system_freezable_power_efficient_wq,
29339a38808SDaniel Lezcano 				 &tz->poll_queue, delay);
2945fc024abSZhang Rui 	else
295163b00cdSWei Wang 		cancel_delayed_work(&tz->poll_queue);
2965fc024abSZhang Rui }
2975fc024abSZhang Rui 
monitor_thermal_zone(struct thermal_zone_device * tz)2985fc024abSZhang Rui static void monitor_thermal_zone(struct thermal_zone_device *tz)
2995fc024abSZhang Rui {
30015a73839SDaniel Lezcano 	if (tz->mode != THERMAL_DEVICE_ENABLED)
3015fc024abSZhang Rui 		thermal_zone_device_set_polling(tz, 0);
30215a73839SDaniel Lezcano 	else if (tz->passive)
30315a73839SDaniel Lezcano 		thermal_zone_device_set_polling(tz, tz->passive_delay_jiffies);
30415a73839SDaniel Lezcano 	else if (tz->polling_delay_jiffies)
30515a73839SDaniel Lezcano 		thermal_zone_device_set_polling(tz, tz->polling_delay_jiffies);
3065fc024abSZhang Rui }
3075fc024abSZhang Rui 
handle_non_critical_trips(struct thermal_zone_device * tz,int trip)3085be52fccSLukasz Luba static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip)
3095fc024abSZhang Rui {
310f2234bcdSZhang Rui 	tz->governor ? tz->governor->throttle(tz, trip) :
311f2234bcdSZhang Rui 		       def_governor->throttle(tz, trip);
3125fc024abSZhang Rui }
3135fc024abSZhang Rui 
thermal_zone_device_critical(struct thermal_zone_device * tz)314d7203eedSDaniel Lezcano void thermal_zone_device_critical(struct thermal_zone_device *tz)
315d7203eedSDaniel Lezcano {
316db0aeb4fSMatti Vaittinen 	/*
317db0aeb4fSMatti Vaittinen 	 * poweroff_delay_ms must be a carefully profiled positive value.
318db0aeb4fSMatti Vaittinen 	 * Its a must for forced_emergency_poweroff_work to be scheduled.
319db0aeb4fSMatti Vaittinen 	 */
320db0aeb4fSMatti Vaittinen 	int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
321db0aeb4fSMatti Vaittinen 
322d7203eedSDaniel Lezcano 	dev_emerg(&tz->device, "%s: critical temperature reached, "
323d7203eedSDaniel Lezcano 		  "shutting down\n", tz->type);
324d7203eedSDaniel Lezcano 
325db0aeb4fSMatti Vaittinen 	hw_protection_shutdown("Temperature too high", poweroff_delay_ms);
326d7203eedSDaniel Lezcano }
327d7203eedSDaniel Lezcano EXPORT_SYMBOL(thermal_zone_device_critical);
328d7203eedSDaniel Lezcano 
handle_critical_trips(struct thermal_zone_device * tz,int trip,int trip_temp,enum thermal_trip_type trip_type)3295fc024abSZhang Rui static void handle_critical_trips(struct thermal_zone_device *tz,
33050e53291SDaniel Lezcano 				  int trip, int trip_temp, enum thermal_trip_type trip_type)
3315fc024abSZhang Rui {
3325fc024abSZhang Rui 	/* If we have not crossed the trip_temp, we do not care. */
33384ffe3ecSSrinivas Pandruvada 	if (trip_temp <= 0 || tz->temperature < trip_temp)
3345fc024abSZhang Rui 		return;
3355fc024abSZhang Rui 
336208cd822SPunit Agrawal 	trace_thermal_zone_trip(tz, trip, trip_type);
337208cd822SPunit Agrawal 
338d7203eedSDaniel Lezcano 	if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot)
339d7203eedSDaniel Lezcano 		tz->ops->hot(tz);
340d7203eedSDaniel Lezcano 	else if (trip_type == THERMAL_TRIP_CRITICAL)
341d7203eedSDaniel Lezcano 		tz->ops->critical(tz);
3425fc024abSZhang Rui }
3435fc024abSZhang Rui 
handle_thermal_trip(struct thermal_zone_device * tz,int trip_id)3447c3d5c20SDaniel Lezcano static void handle_thermal_trip(struct thermal_zone_device *tz, int trip_id)
3455fc024abSZhang Rui {
3467c3d5c20SDaniel Lezcano 	struct thermal_trip trip;
3475fc024abSZhang Rui 
34881ad4276SZhang Rui 	/* Ignore disabled trip points */
349fb2c1024SRafael J. Wysocki 	if (test_bit(trip_id, &tz->trips_disabled))
35081ad4276SZhang Rui 		return;
35181ad4276SZhang Rui 
3527c3d5c20SDaniel Lezcano 	__thermal_zone_get_trip(tz, trip_id, &trip);
35355cdf0a2SDaniel Lezcano 
354fb2c1024SRafael J. Wysocki 	if (trip.temperature == THERMAL_TEMP_INVALID)
355fb2c1024SRafael J. Wysocki 		return;
356fb2c1024SRafael J. Wysocki 
35755cdf0a2SDaniel Lezcano 	if (tz->last_temperature != THERMAL_TEMP_INVALID) {
3587c3d5c20SDaniel Lezcano 		if (tz->last_temperature < trip.temperature &&
3597c3d5c20SDaniel Lezcano 		    tz->temperature >= trip.temperature)
3607c3d5c20SDaniel Lezcano 			thermal_notify_tz_trip_up(tz->id, trip_id,
361fc656fa1SDaniel Lezcano 						  tz->temperature);
3627c3d5c20SDaniel Lezcano 		if (tz->last_temperature >= trip.temperature &&
3637c3d5c20SDaniel Lezcano 		    tz->temperature < (trip.temperature - trip.hysteresis))
3647c3d5c20SDaniel Lezcano 			thermal_notify_tz_trip_down(tz->id, trip_id,
365fc656fa1SDaniel Lezcano 						    tz->temperature);
36655cdf0a2SDaniel Lezcano 	}
3675fc024abSZhang Rui 
3687c3d5c20SDaniel Lezcano 	if (trip.type == THERMAL_TRIP_CRITICAL || trip.type == THERMAL_TRIP_HOT)
3697c3d5c20SDaniel Lezcano 		handle_critical_trips(tz, trip_id, trip.temperature, trip.type);
3705fc024abSZhang Rui 	else
3717c3d5c20SDaniel Lezcano 		handle_non_critical_trips(tz, trip_id);
3725fc024abSZhang Rui }
3735fc024abSZhang Rui 
update_temperature(struct thermal_zone_device * tz)3745fc024abSZhang Rui static void update_temperature(struct thermal_zone_device *tz)
3755fc024abSZhang Rui {
37617e8351aSSascha Hauer 	int temp, ret;
3775fc024abSZhang Rui 
378a930da9bSDaniel Lezcano 	ret = __thermal_zone_get_temp(tz, &temp);
3795fc024abSZhang Rui 	if (ret) {
3807e497a73SHans de Goede 		if (ret != -EAGAIN)
3817e497a73SHans de Goede 			dev_warn(&tz->device,
3827e497a73SHans de Goede 				 "failed to read out thermal zone (%d)\n",
3837e497a73SHans de Goede 				 ret);
3845fc024abSZhang Rui 		return;
3855fc024abSZhang Rui 	}
3865fc024abSZhang Rui 
3875fc024abSZhang Rui 	tz->last_temperature = tz->temperature;
3885fc024abSZhang Rui 	tz->temperature = temp;
38906475b55SAaron Lu 
390100a8fdbSPunit Agrawal 	trace_thermal_temperature(tz);
39155cdf0a2SDaniel Lezcano 
39255cdf0a2SDaniel Lezcano 	thermal_genl_sampling_temp(tz->id, temp);
3935fc024abSZhang Rui }
3945fc024abSZhang Rui 
thermal_zone_device_init(struct thermal_zone_device * tz)395964f4843SWei Wang static void thermal_zone_device_init(struct thermal_zone_device *tz)
396bb431ba2SZhang Rui {
397bb431ba2SZhang Rui 	struct thermal_instance *pos;
398bb431ba2SZhang Rui 	tz->temperature = THERMAL_TEMP_INVALID;
39999b63316SManaf Meethalavalappu Pallikunhi 	tz->prev_low_trip = -INT_MAX;
40099b63316SManaf Meethalavalappu Pallikunhi 	tz->prev_high_trip = INT_MAX;
401bb431ba2SZhang Rui 	list_for_each_entry(pos, &tz->thermal_instances, tz_node)
402bb431ba2SZhang Rui 		pos->initialized = false;
403bb431ba2SZhang Rui }
404bb431ba2SZhang Rui 
__thermal_zone_device_update(struct thermal_zone_device * tz,enum thermal_notify_event event)40505eeee2bSGuenter Roeck void __thermal_zone_device_update(struct thermal_zone_device *tz,
4061c439decSGuenter Roeck 				  enum thermal_notify_event event)
4071c439decSGuenter Roeck {
4081c439decSGuenter Roeck 	int count;
4091c439decSGuenter Roeck 
410fcecef9aSRafael J. Wysocki 	if (tz->suspended)
4111c439decSGuenter Roeck 		return;
4121c439decSGuenter Roeck 
4131c439decSGuenter Roeck 	if (WARN_ONCE(!tz->ops->get_temp,
4141c439decSGuenter Roeck 		      "'%s' must not be called without 'get_temp' ops set\n",
4151c439decSGuenter Roeck 		      __func__))
4161c439decSGuenter Roeck 		return;
4171c439decSGuenter Roeck 
4181c439decSGuenter Roeck 	if (!thermal_zone_device_is_enabled(tz))
4191c439decSGuenter Roeck 		return;
4201c439decSGuenter Roeck 
4211c439decSGuenter Roeck 	update_temperature(tz);
4221c439decSGuenter Roeck 
4231c439decSGuenter Roeck 	__thermal_zone_set_trips(tz);
4241c439decSGuenter Roeck 
4251c439decSGuenter Roeck 	tz->notify_event = event;
4261c439decSGuenter Roeck 
4271c439decSGuenter Roeck 	for (count = 0; count < tz->num_trips; count++)
4281c439decSGuenter Roeck 		handle_thermal_trip(tz, count);
4291c439decSGuenter Roeck 
4301c439decSGuenter Roeck 	monitor_thermal_zone(tz);
4311c439decSGuenter Roeck }
4321c439decSGuenter Roeck 
thermal_zone_device_set_mode(struct thermal_zone_device * tz,enum thermal_device_mode mode)433ac5d9eccSAndrzej Pietrasiewicz static int thermal_zone_device_set_mode(struct thermal_zone_device *tz,
434ac5d9eccSAndrzej Pietrasiewicz 					enum thermal_device_mode mode)
435ac5d9eccSAndrzej Pietrasiewicz {
436ac5d9eccSAndrzej Pietrasiewicz 	int ret = 0;
437ac5d9eccSAndrzej Pietrasiewicz 
438ac5d9eccSAndrzej Pietrasiewicz 	mutex_lock(&tz->lock);
439ac5d9eccSAndrzej Pietrasiewicz 
440ac5d9eccSAndrzej Pietrasiewicz 	/* do nothing if mode isn't changing */
441ac5d9eccSAndrzej Pietrasiewicz 	if (mode == tz->mode) {
442ac5d9eccSAndrzej Pietrasiewicz 		mutex_unlock(&tz->lock);
443ac5d9eccSAndrzej Pietrasiewicz 
444ac5d9eccSAndrzej Pietrasiewicz 		return ret;
445ac5d9eccSAndrzej Pietrasiewicz 	}
446ac5d9eccSAndrzej Pietrasiewicz 
447b778b4d7SGuenter Roeck 	if (!device_is_registered(&tz->device)) {
448b778b4d7SGuenter Roeck 		mutex_unlock(&tz->lock);
449b778b4d7SGuenter Roeck 
450b778b4d7SGuenter Roeck 		return -ENODEV;
451b778b4d7SGuenter Roeck 	}
452b778b4d7SGuenter Roeck 
453f5e50bf4SAndrzej Pietrasiewicz 	if (tz->ops->change_mode)
454f5e50bf4SAndrzej Pietrasiewicz 		ret = tz->ops->change_mode(tz, mode);
455ac5d9eccSAndrzej Pietrasiewicz 
456ac5d9eccSAndrzej Pietrasiewicz 	if (!ret)
457ac5d9eccSAndrzej Pietrasiewicz 		tz->mode = mode;
458ac5d9eccSAndrzej Pietrasiewicz 
4591c439decSGuenter Roeck 	__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
460ac5d9eccSAndrzej Pietrasiewicz 
4611c439decSGuenter Roeck 	mutex_unlock(&tz->lock);
462ac5d9eccSAndrzej Pietrasiewicz 
46325be77e5SDaniel Lezcano 	if (mode == THERMAL_DEVICE_ENABLED)
46425be77e5SDaniel Lezcano 		thermal_notify_tz_enable(tz->id);
46525be77e5SDaniel Lezcano 	else
46625be77e5SDaniel Lezcano 		thermal_notify_tz_disable(tz->id);
46725be77e5SDaniel Lezcano 
468ac5d9eccSAndrzej Pietrasiewicz 	return ret;
469ac5d9eccSAndrzej Pietrasiewicz }
470ac5d9eccSAndrzej Pietrasiewicz 
thermal_zone_device_enable(struct thermal_zone_device * tz)471ac5d9eccSAndrzej Pietrasiewicz int thermal_zone_device_enable(struct thermal_zone_device *tz)
472ac5d9eccSAndrzej Pietrasiewicz {
473ac5d9eccSAndrzej Pietrasiewicz 	return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_ENABLED);
474ac5d9eccSAndrzej Pietrasiewicz }
475ac5d9eccSAndrzej Pietrasiewicz EXPORT_SYMBOL_GPL(thermal_zone_device_enable);
476ac5d9eccSAndrzej Pietrasiewicz 
thermal_zone_device_disable(struct thermal_zone_device * tz)477ac5d9eccSAndrzej Pietrasiewicz int thermal_zone_device_disable(struct thermal_zone_device *tz)
478ac5d9eccSAndrzej Pietrasiewicz {
479ac5d9eccSAndrzej Pietrasiewicz 	return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
480ac5d9eccSAndrzej Pietrasiewicz }
481ac5d9eccSAndrzej Pietrasiewicz EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
482ac5d9eccSAndrzej Pietrasiewicz 
thermal_zone_device_is_enabled(struct thermal_zone_device * tz)483ac5d9eccSAndrzej Pietrasiewicz int thermal_zone_device_is_enabled(struct thermal_zone_device *tz)
484ac5d9eccSAndrzej Pietrasiewicz {
485a930da9bSDaniel Lezcano 	lockdep_assert_held(&tz->lock);
486ac5d9eccSAndrzej Pietrasiewicz 
487a930da9bSDaniel Lezcano 	return tz->mode == THERMAL_DEVICE_ENABLED;
488ac5d9eccSAndrzej Pietrasiewicz }
489ac5d9eccSAndrzej Pietrasiewicz 
thermal_zone_is_present(struct thermal_zone_device * tz)4903eb073abSRafael J. Wysocki static bool thermal_zone_is_present(struct thermal_zone_device *tz)
4913eb073abSRafael J. Wysocki {
4923eb073abSRafael J. Wysocki 	return !list_empty(&tz->node);
4933eb073abSRafael J. Wysocki }
4943eb073abSRafael J. Wysocki 
thermal_zone_device_update(struct thermal_zone_device * tz,enum thermal_notify_event event)4950e70f466SSrinivas Pandruvada void thermal_zone_device_update(struct thermal_zone_device *tz,
4960e70f466SSrinivas Pandruvada 				enum thermal_notify_event event)
4975fc024abSZhang Rui {
498a930da9bSDaniel Lezcano 	mutex_lock(&tz->lock);
4993eb073abSRafael J. Wysocki 	if (thermal_zone_is_present(tz))
5001c439decSGuenter Roeck 		__thermal_zone_device_update(tz, event);
501a930da9bSDaniel Lezcano 	mutex_unlock(&tz->lock);
5025fc024abSZhang Rui }
503910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_device_update);
5045fc024abSZhang Rui 
5059a99a996SRafael J. Wysocki /**
5069a99a996SRafael J. Wysocki  * thermal_zone_device_exec - Run a callback under the zone lock.
5079a99a996SRafael J. Wysocki  * @tz: Thermal zone.
5089a99a996SRafael J. Wysocki  * @cb: Callback to run.
5099a99a996SRafael J. Wysocki  * @data: Data to pass to the callback.
5109a99a996SRafael J. Wysocki  */
thermal_zone_device_exec(struct thermal_zone_device * tz,void (* cb)(struct thermal_zone_device *,unsigned long),unsigned long data)5119a99a996SRafael J. Wysocki void thermal_zone_device_exec(struct thermal_zone_device *tz,
5129a99a996SRafael J. Wysocki 			      void (*cb)(struct thermal_zone_device *,
5139a99a996SRafael J. Wysocki 					 unsigned long),
5149a99a996SRafael J. Wysocki 			      unsigned long data)
5159a99a996SRafael J. Wysocki {
5169a99a996SRafael J. Wysocki 	mutex_lock(&tz->lock);
5179a99a996SRafael J. Wysocki 
5189a99a996SRafael J. Wysocki 	cb(tz, data);
5199a99a996SRafael J. Wysocki 
5209a99a996SRafael J. Wysocki 	mutex_unlock(&tz->lock);
5219a99a996SRafael J. Wysocki }
5229a99a996SRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_zone_device_exec);
5239a99a996SRafael J. Wysocki 
thermal_zone_device_check(struct work_struct * work)5245fc024abSZhang Rui static void thermal_zone_device_check(struct work_struct *work)
5255fc024abSZhang Rui {
5265fc024abSZhang Rui 	struct thermal_zone_device *tz = container_of(work, struct
5275fc024abSZhang Rui 						      thermal_zone_device,
5285fc024abSZhang Rui 						      poll_queue.work);
5290e70f466SSrinivas Pandruvada 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
5305fc024abSZhang Rui }
5315fc024abSZhang Rui 
for_each_thermal_governor(int (* cb)(struct thermal_governor *,void *),void * data)5323d44a509SDaniel Lezcano int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
5333d44a509SDaniel Lezcano 			      void *data)
5343d44a509SDaniel Lezcano {
5353d44a509SDaniel Lezcano 	struct thermal_governor *gov;
5363d44a509SDaniel Lezcano 	int ret = 0;
5373d44a509SDaniel Lezcano 
5383d44a509SDaniel Lezcano 	mutex_lock(&thermal_governor_lock);
5393d44a509SDaniel Lezcano 	list_for_each_entry(gov, &thermal_governor_list, governor_list) {
5403d44a509SDaniel Lezcano 		ret = cb(gov, data);
5413d44a509SDaniel Lezcano 		if (ret)
5423d44a509SDaniel Lezcano 			break;
5433d44a509SDaniel Lezcano 	}
5443d44a509SDaniel Lezcano 	mutex_unlock(&thermal_governor_lock);
5453d44a509SDaniel Lezcano 
5463d44a509SDaniel Lezcano 	return ret;
5473d44a509SDaniel Lezcano }
5483d44a509SDaniel Lezcano 
for_each_thermal_cooling_device(int (* cb)(struct thermal_cooling_device *,void *),void * data)5493d44a509SDaniel Lezcano int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
5503d44a509SDaniel Lezcano 					      void *), void *data)
5513d44a509SDaniel Lezcano {
5523d44a509SDaniel Lezcano 	struct thermal_cooling_device *cdev;
5533d44a509SDaniel Lezcano 	int ret = 0;
5543d44a509SDaniel Lezcano 
5553d44a509SDaniel Lezcano 	mutex_lock(&thermal_list_lock);
5563d44a509SDaniel Lezcano 	list_for_each_entry(cdev, &thermal_cdev_list, node) {
5573d44a509SDaniel Lezcano 		ret = cb(cdev, data);
5583d44a509SDaniel Lezcano 		if (ret)
5593d44a509SDaniel Lezcano 			break;
5603d44a509SDaniel Lezcano 	}
5613d44a509SDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
5623d44a509SDaniel Lezcano 
5633d44a509SDaniel Lezcano 	return ret;
5643d44a509SDaniel Lezcano }
5653d44a509SDaniel Lezcano 
for_each_thermal_zone(int (* cb)(struct thermal_zone_device *,void *),void * data)5663d44a509SDaniel Lezcano int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
5673d44a509SDaniel Lezcano 			  void *data)
5683d44a509SDaniel Lezcano {
5693d44a509SDaniel Lezcano 	struct thermal_zone_device *tz;
5703d44a509SDaniel Lezcano 	int ret = 0;
5713d44a509SDaniel Lezcano 
5723d44a509SDaniel Lezcano 	mutex_lock(&thermal_list_lock);
5733d44a509SDaniel Lezcano 	list_for_each_entry(tz, &thermal_tz_list, node) {
5743d44a509SDaniel Lezcano 		ret = cb(tz, data);
5753d44a509SDaniel Lezcano 		if (ret)
5763d44a509SDaniel Lezcano 			break;
5773d44a509SDaniel Lezcano 	}
5783d44a509SDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
5793d44a509SDaniel Lezcano 
5803d44a509SDaniel Lezcano 	return ret;
5813d44a509SDaniel Lezcano }
5823d44a509SDaniel Lezcano 
thermal_zone_get_by_id(int id)583329b064fSDaniel Lezcano struct thermal_zone_device *thermal_zone_get_by_id(int id)
584329b064fSDaniel Lezcano {
58582aa68afSThierry Reding 	struct thermal_zone_device *tz, *match = NULL;
586329b064fSDaniel Lezcano 
587329b064fSDaniel Lezcano 	mutex_lock(&thermal_list_lock);
588329b064fSDaniel Lezcano 	list_for_each_entry(tz, &thermal_tz_list, node) {
58982aa68afSThierry Reding 		if (tz->id == id) {
59082aa68afSThierry Reding 			match = tz;
591329b064fSDaniel Lezcano 			break;
592329b064fSDaniel Lezcano 		}
59382aa68afSThierry Reding 	}
594329b064fSDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
595329b064fSDaniel Lezcano 
59682aa68afSThierry Reding 	return match;
597329b064fSDaniel Lezcano }
598329b064fSDaniel Lezcano 
59981193e2eSEduardo Valentin /*
60081193e2eSEduardo Valentin  * Device management section: cooling devices, zones devices, and binding
60181193e2eSEduardo Valentin  *
60281193e2eSEduardo Valentin  * Set of functions provided by the thermal core for:
60381193e2eSEduardo Valentin  * - cooling devices lifecycle: registration, unregistration,
60481193e2eSEduardo Valentin  *				binding, and unbinding.
60581193e2eSEduardo Valentin  * - thermal zone devices lifecycle: registration, unregistration,
60681193e2eSEduardo Valentin  *				     binding, and unbinding.
60781193e2eSEduardo Valentin  */
6085fc024abSZhang Rui 
6095fc024abSZhang Rui /**
610d2e4eb83SEduardo Valentin  * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
611d2e4eb83SEduardo Valentin  * @tz:		pointer to struct thermal_zone_device
61277451ef5SRafael J. Wysocki  * @trip_index:	indicates which trip point the cooling devices is
6135fc024abSZhang Rui  *		associated with in this thermal zone.
614d2e4eb83SEduardo Valentin  * @cdev:	pointer to struct thermal_cooling_device
615d2e4eb83SEduardo Valentin  * @upper:	the Maximum cooling state for this trip point.
616d2e4eb83SEduardo Valentin  *		THERMAL_NO_LIMIT means no upper limit,
617d2e4eb83SEduardo Valentin  *		and the cooling device can be in max_state.
618d2e4eb83SEduardo Valentin  * @lower:	the Minimum cooling state can be used for this trip point.
619d2e4eb83SEduardo Valentin  *		THERMAL_NO_LIMIT means no lower limit,
620d2e4eb83SEduardo Valentin  *		and the cooling device can be in cooling state 0.
6216cd9e9f6SKapileshwar Singh  * @weight:	The weight of the cooling device to be bound to the
6226cd9e9f6SKapileshwar Singh  *		thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
6236cd9e9f6SKapileshwar Singh  *		default value
6245fc024abSZhang Rui  *
625d2e4eb83SEduardo Valentin  * This interface function bind a thermal cooling device to the certain trip
626d2e4eb83SEduardo Valentin  * point of a thermal zone device.
6275fc024abSZhang Rui  * This function is usually called in the thermal zone device .bind callback.
628d2e4eb83SEduardo Valentin  *
629d2e4eb83SEduardo Valentin  * Return: 0 on success, the proper error value otherwise.
6305fc024abSZhang Rui  */
thermal_zone_bind_cooling_device(struct thermal_zone_device * tz,int trip_index,struct thermal_cooling_device * cdev,unsigned long upper,unsigned long lower,unsigned int weight)6315fc024abSZhang Rui int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
63277451ef5SRafael J. Wysocki 				     int trip_index,
6335fc024abSZhang Rui 				     struct thermal_cooling_device *cdev,
6346cd9e9f6SKapileshwar Singh 				     unsigned long upper, unsigned long lower,
6356cd9e9f6SKapileshwar Singh 				     unsigned int weight)
6365fc024abSZhang Rui {
6375fc024abSZhang Rui 	struct thermal_instance *dev;
6385fc024abSZhang Rui 	struct thermal_instance *pos;
6395fc024abSZhang Rui 	struct thermal_zone_device *pos1;
6405fc024abSZhang Rui 	struct thermal_cooling_device *pos2;
64177451ef5SRafael J. Wysocki 	const struct thermal_trip *trip;
642790930f4SRafael J. Wysocki 	bool upper_no_limit;
643c408b3d1SViresh Kumar 	int result;
6445fc024abSZhang Rui 
64577451ef5SRafael J. Wysocki 	if (trip_index >= tz->num_trips || trip_index < 0)
6465fc024abSZhang Rui 		return -EINVAL;
6475fc024abSZhang Rui 
64877451ef5SRafael J. Wysocki 	trip = &tz->trips[trip_index];
64977451ef5SRafael J. Wysocki 
6505fc024abSZhang Rui 	list_for_each_entry(pos1, &thermal_tz_list, node) {
6515fc024abSZhang Rui 		if (pos1 == tz)
6525fc024abSZhang Rui 			break;
6535fc024abSZhang Rui 	}
6545fc024abSZhang Rui 	list_for_each_entry(pos2, &thermal_cdev_list, node) {
6555fc024abSZhang Rui 		if (pos2 == cdev)
6565fc024abSZhang Rui 			break;
6575fc024abSZhang Rui 	}
6585fc024abSZhang Rui 
6595fc024abSZhang Rui 	if (tz != pos1 || cdev != pos2)
6605fc024abSZhang Rui 		return -EINVAL;
6615fc024abSZhang Rui 
6625fc024abSZhang Rui 	/* lower default 0, upper default max_state */
6635fc024abSZhang Rui 	lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
664790930f4SRafael J. Wysocki 
665790930f4SRafael J. Wysocki 	if (upper == THERMAL_NO_LIMIT) {
666790930f4SRafael J. Wysocki 		upper = cdev->max_state;
667790930f4SRafael J. Wysocki 		upper_no_limit = true;
668790930f4SRafael J. Wysocki 	} else {
669790930f4SRafael J. Wysocki 		upper_no_limit = false;
670790930f4SRafael J. Wysocki 	}
6715fc024abSZhang Rui 
672c408b3d1SViresh Kumar 	if (lower > upper || upper > cdev->max_state)
6735fc024abSZhang Rui 		return -EINVAL;
6745fc024abSZhang Rui 
67595e3ed15SEduardo Valentin 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
6765fc024abSZhang Rui 	if (!dev)
6775fc024abSZhang Rui 		return -ENOMEM;
6785fc024abSZhang Rui 	dev->tz = tz;
6795fc024abSZhang Rui 	dev->cdev = cdev;
6805fc024abSZhang Rui 	dev->trip = trip;
6815fc024abSZhang Rui 	dev->upper = upper;
682790930f4SRafael J. Wysocki 	dev->upper_no_limit = upper_no_limit;
6835fc024abSZhang Rui 	dev->lower = lower;
6845fc024abSZhang Rui 	dev->target = THERMAL_NO_TARGET;
6856cd9e9f6SKapileshwar Singh 	dev->weight = weight;
6865fc024abSZhang Rui 
6875a5b7d8dSkeliu 	result = ida_alloc(&tz->ida, GFP_KERNEL);
688b31ef828SMatthew Wilcox 	if (result < 0)
6895fc024abSZhang Rui 		goto free_mem;
6905fc024abSZhang Rui 
691b31ef828SMatthew Wilcox 	dev->id = result;
6925fc024abSZhang Rui 	sprintf(dev->name, "cdev%d", dev->id);
6935fc024abSZhang Rui 	result =
6945fc024abSZhang Rui 	    sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
6955fc024abSZhang Rui 	if (result)
696b31ef828SMatthew Wilcox 		goto release_ida;
6975fc024abSZhang Rui 
698edbd6bbeSDan Carpenter 	snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point",
699edbd6bbeSDan Carpenter 		 dev->id);
7005fc024abSZhang Rui 	sysfs_attr_init(&dev->attr.attr);
7015fc024abSZhang Rui 	dev->attr.attr.name = dev->attr_name;
7025fc024abSZhang Rui 	dev->attr.attr.mode = 0444;
70333e678d4SViresh Kumar 	dev->attr.show = trip_point_show;
7045fc024abSZhang Rui 	result = device_create_file(&tz->device, &dev->attr);
7055fc024abSZhang Rui 	if (result)
7065fc024abSZhang Rui 		goto remove_symbol_link;
7075fc024abSZhang Rui 
708edbd6bbeSDan Carpenter 	snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),
709edbd6bbeSDan Carpenter 		 "cdev%d_weight", dev->id);
710db916513SJavi Merino 	sysfs_attr_init(&dev->weight_attr.attr);
711db916513SJavi Merino 	dev->weight_attr.attr.name = dev->weight_attr_name;
712db916513SJavi Merino 	dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
71333e678d4SViresh Kumar 	dev->weight_attr.show = weight_show;
71433e678d4SViresh Kumar 	dev->weight_attr.store = weight_store;
715db916513SJavi Merino 	result = device_create_file(&tz->device, &dev->weight_attr);
716db916513SJavi Merino 	if (result)
717db916513SJavi Merino 		goto remove_trip_file;
718db916513SJavi Merino 
7195fc024abSZhang Rui 	mutex_lock(&tz->lock);
7205fc024abSZhang Rui 	mutex_lock(&cdev->lock);
7215fc024abSZhang Rui 	list_for_each_entry(pos, &tz->thermal_instances, tz_node)
7225fc024abSZhang Rui 		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
7235fc024abSZhang Rui 			result = -EEXIST;
7245fc024abSZhang Rui 			break;
7255fc024abSZhang Rui 		}
7265fc024abSZhang Rui 	if (!result) {
7275fc024abSZhang Rui 		list_add_tail(&dev->tz_node, &tz->thermal_instances);
7285fc024abSZhang Rui 		list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
7294511f716SChen Yu 		atomic_set(&tz->need_update, 1);
7305fc024abSZhang Rui 	}
7315fc024abSZhang Rui 	mutex_unlock(&cdev->lock);
7325fc024abSZhang Rui 	mutex_unlock(&tz->lock);
7335fc024abSZhang Rui 
7345fc024abSZhang Rui 	if (!result)
7355fc024abSZhang Rui 		return 0;
7365fc024abSZhang Rui 
737db916513SJavi Merino 	device_remove_file(&tz->device, &dev->weight_attr);
738db916513SJavi Merino remove_trip_file:
7395fc024abSZhang Rui 	device_remove_file(&tz->device, &dev->attr);
7405fc024abSZhang Rui remove_symbol_link:
7415fc024abSZhang Rui 	sysfs_remove_link(&tz->device.kobj, dev->name);
742b31ef828SMatthew Wilcox release_ida:
7435a5b7d8dSkeliu 	ida_free(&tz->ida, dev->id);
7445fc024abSZhang Rui free_mem:
7455fc024abSZhang Rui 	kfree(dev);
7465fc024abSZhang Rui 	return result;
7475fc024abSZhang Rui }
748910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
7495fc024abSZhang Rui 
7505fc024abSZhang Rui /**
7519892e5dcSEduardo Valentin  * thermal_zone_unbind_cooling_device() - unbind a cooling device from a
7529892e5dcSEduardo Valentin  *					  thermal zone.
7539892e5dcSEduardo Valentin  * @tz:		pointer to a struct thermal_zone_device.
75477451ef5SRafael J. Wysocki  * @trip_index:	indicates which trip point the cooling devices is
7555fc024abSZhang Rui  *		associated with in this thermal zone.
7569892e5dcSEduardo Valentin  * @cdev:	pointer to a struct thermal_cooling_device.
7575fc024abSZhang Rui  *
7589892e5dcSEduardo Valentin  * This interface function unbind a thermal cooling device from the certain
7599892e5dcSEduardo Valentin  * trip point of a thermal zone device.
7605fc024abSZhang Rui  * This function is usually called in the thermal zone device .unbind callback.
7619892e5dcSEduardo Valentin  *
7629892e5dcSEduardo Valentin  * Return: 0 on success, the proper error value otherwise.
7635fc024abSZhang Rui  */
thermal_zone_unbind_cooling_device(struct thermal_zone_device * tz,int trip_index,struct thermal_cooling_device * cdev)7645fc024abSZhang Rui int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
76577451ef5SRafael J. Wysocki 				       int trip_index,
7665fc024abSZhang Rui 				       struct thermal_cooling_device *cdev)
7675fc024abSZhang Rui {
7685fc024abSZhang Rui 	struct thermal_instance *pos, *next;
76977451ef5SRafael J. Wysocki 	const struct thermal_trip *trip;
7705fc024abSZhang Rui 
7715fc024abSZhang Rui 	mutex_lock(&tz->lock);
7725fc024abSZhang Rui 	mutex_lock(&cdev->lock);
77377451ef5SRafael J. Wysocki 	trip = &tz->trips[trip_index];
7745fc024abSZhang Rui 	list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
7755fc024abSZhang Rui 		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
7765fc024abSZhang Rui 			list_del(&pos->tz_node);
7775fc024abSZhang Rui 			list_del(&pos->cdev_node);
7785fc024abSZhang Rui 			mutex_unlock(&cdev->lock);
7795fc024abSZhang Rui 			mutex_unlock(&tz->lock);
7805fc024abSZhang Rui 			goto unbind;
7815fc024abSZhang Rui 		}
7825fc024abSZhang Rui 	}
7835fc024abSZhang Rui 	mutex_unlock(&cdev->lock);
7845fc024abSZhang Rui 	mutex_unlock(&tz->lock);
7855fc024abSZhang Rui 
7865fc024abSZhang Rui 	return -ENODEV;
7875fc024abSZhang Rui 
7885fc024abSZhang Rui unbind:
789528464eaSViresh Kumar 	device_remove_file(&tz->device, &pos->weight_attr);
7905fc024abSZhang Rui 	device_remove_file(&tz->device, &pos->attr);
7915fc024abSZhang Rui 	sysfs_remove_link(&tz->device.kobj, pos->name);
7925a5b7d8dSkeliu 	ida_free(&tz->ida, pos->id);
7935fc024abSZhang Rui 	kfree(pos);
7945fc024abSZhang Rui 	return 0;
7955fc024abSZhang Rui }
796910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
7975fc024abSZhang Rui 
thermal_release(struct device * dev)7985fc024abSZhang Rui static void thermal_release(struct device *dev)
7995fc024abSZhang Rui {
8005fc024abSZhang Rui 	struct thermal_zone_device *tz;
8015fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
8025fc024abSZhang Rui 
8035fc024abSZhang Rui 	if (!strncmp(dev_name(dev), "thermal_zone",
8045fc024abSZhang Rui 		     sizeof("thermal_zone") - 1)) {
8055fc024abSZhang Rui 		tz = to_thermal_zone(dev);
8066a6cd25bSChristophe Jaillet 		thermal_zone_destroy_device_groups(tz);
807d35f29edSGuenter Roeck 		mutex_destroy(&tz->lock);
808a95a9e30SRafael J. Wysocki 		complete(&tz->removal);
809732e4c8dSdurgadoss.r@intel.com 	} else if (!strncmp(dev_name(dev), "cooling_device",
810732e4c8dSdurgadoss.r@intel.com 			    sizeof("cooling_device") - 1)) {
8115fc024abSZhang Rui 		cdev = to_cooling_device(dev);
812e398421fSViresh Kumar 		thermal_cooling_device_destroy_sysfs(cdev);
813e398421fSViresh Kumar 		kfree(cdev->type);
814e398421fSViresh Kumar 		ida_free(&thermal_cdev_ida, cdev->id);
8155fc024abSZhang Rui 		kfree(cdev);
8165fc024abSZhang Rui 	}
8175fc024abSZhang Rui }
8185fc024abSZhang Rui 
8199e0a9be2SRafael J. Wysocki static struct class *thermal_class;
8205fc024abSZhang Rui 
8214b0d3c2dSEduardo Valentin static inline
print_bind_err_msg(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev,int ret)8224b0d3c2dSEduardo Valentin void print_bind_err_msg(struct thermal_zone_device *tz,
823f502ab84SEduardo Valentin 			struct thermal_cooling_device *cdev, int ret)
824f502ab84SEduardo Valentin {
825f502ab84SEduardo Valentin 	dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
826f502ab84SEduardo Valentin 		tz->type, cdev->type, ret);
827f502ab84SEduardo Valentin }
828f502ab84SEduardo Valentin 
bind_cdev(struct thermal_cooling_device * cdev)829949aad83SEduardo Valentin static void bind_cdev(struct thermal_cooling_device *cdev)
830949aad83SEduardo Valentin {
831ded2d383SZhang Rui 	int ret;
832949aad83SEduardo Valentin 	struct thermal_zone_device *pos = NULL;
833949aad83SEduardo Valentin 
834949aad83SEduardo Valentin 	list_for_each_entry(pos, &thermal_tz_list, node) {
835949aad83SEduardo Valentin 		if (pos->ops->bind) {
836949aad83SEduardo Valentin 			ret = pos->ops->bind(pos, cdev);
837949aad83SEduardo Valentin 			if (ret)
838949aad83SEduardo Valentin 				print_bind_err_msg(pos, cdev, ret);
839949aad83SEduardo Valentin 		}
840949aad83SEduardo Valentin 	}
841949aad83SEduardo Valentin }
842949aad83SEduardo Valentin 
8435fc024abSZhang Rui /**
844a116b5d4SEduardo Valentin  * __thermal_cooling_device_register() - register a new thermal cooling device
845a116b5d4SEduardo Valentin  * @np:		a pointer to a device tree node.
8465fc024abSZhang Rui  * @type:	the thermal cooling device type.
8475fc024abSZhang Rui  * @devdata:	device private data.
8485fc024abSZhang Rui  * @ops:		standard thermal cooling devices callbacks.
8493a6eccb3SEduardo Valentin  *
8503a6eccb3SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
8513a6eccb3SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
8523a6eccb3SEduardo Valentin  * to all the thermal zone devices registered at the same time.
853a116b5d4SEduardo Valentin  * It also gives the opportunity to link the cooling device to a device tree
854a116b5d4SEduardo Valentin  * node, so that it can be bound to a thermal zone created out of device tree.
8553a6eccb3SEduardo Valentin  *
8563a6eccb3SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
8573a6eccb3SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
8585fc024abSZhang Rui  */
859a116b5d4SEduardo Valentin static struct thermal_cooling_device *
__thermal_cooling_device_register(struct device_node * np,const char * type,void * devdata,const struct thermal_cooling_device_ops * ops)860a116b5d4SEduardo Valentin __thermal_cooling_device_register(struct device_node *np,
861f991de53SJean-Francois Dagenais 				  const char *type, void *devdata,
8625fc024abSZhang Rui 				  const struct thermal_cooling_device_ops *ops)
8635fc024abSZhang Rui {
8645fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
8654511f716SChen Yu 	struct thermal_zone_device *pos = NULL;
8660a5c2671SZiyang Xuan 	int id, ret;
8675fc024abSZhang Rui 
8685fc024abSZhang Rui 	if (!ops || !ops->get_max_state || !ops->get_cur_state ||
8695fc024abSZhang Rui 	    !ops->set_cur_state)
8705fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
8715fc024abSZhang Rui 
8729e0a9be2SRafael J. Wysocki 	if (!thermal_class)
8739e0a9be2SRafael J. Wysocki 		return ERR_PTR(-ENODEV);
8749e0a9be2SRafael J. Wysocki 
87595e3ed15SEduardo Valentin 	cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
8765fc024abSZhang Rui 	if (!cdev)
8775fc024abSZhang Rui 		return ERR_PTR(-ENOMEM);
8785fc024abSZhang Rui 
8795a5b7d8dSkeliu 	ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
88058483761SDaniel Lezcano 	if (ret < 0)
88158483761SDaniel Lezcano 		goto out_kfree_cdev;
88258483761SDaniel Lezcano 	cdev->id = ret;
8830a5c2671SZiyang Xuan 	id = ret;
88458483761SDaniel Lezcano 
88558483761SDaniel Lezcano 	cdev->type = kstrdup(type ? type : "", GFP_KERNEL);
88658483761SDaniel Lezcano 	if (!cdev->type) {
88758483761SDaniel Lezcano 		ret = -ENOMEM;
88858483761SDaniel Lezcano 		goto out_ida_remove;
8895fc024abSZhang Rui 	}
8905fc024abSZhang Rui 
8915fc024abSZhang Rui 	mutex_init(&cdev->lock);
8925fc024abSZhang Rui 	INIT_LIST_HEAD(&cdev->thermal_instances);
893a116b5d4SEduardo Valentin 	cdev->np = np;
8945fc024abSZhang Rui 	cdev->ops = ops;
8955ca0cce5SNi Wade 	cdev->updated = false;
8969e0a9be2SRafael J. Wysocki 	cdev->device.class = thermal_class;
8975fc024abSZhang Rui 	cdev->devdata = devdata;
898c408b3d1SViresh Kumar 
899e49a1e1eSDan Carpenter 	ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
900e398421fSViresh Kumar 	if (ret)
901e398421fSViresh Kumar 		goto out_cdev_type;
902c408b3d1SViresh Kumar 
9038ea22951SViresh Kumar 	thermal_cooling_device_setup_sysfs(cdev);
9046c54b7bcSViresh Kumar 
9054748f968SYang Yingliang 	ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
906e398421fSViresh Kumar 	if (ret)
907e398421fSViresh Kumar 		goto out_cooling_dev;
9086c54b7bcSViresh Kumar 
90958483761SDaniel Lezcano 	ret = device_register(&cdev->device);
910e398421fSViresh Kumar 	if (ret) {
911e398421fSViresh Kumar 		/* thermal_release() handles rest of the cleanup */
912e398421fSViresh Kumar 		put_device(&cdev->device);
913e398421fSViresh Kumar 		return ERR_PTR(ret);
914e398421fSViresh Kumar 	}
9155fc024abSZhang Rui 
9165fc024abSZhang Rui 	/* Add 'this' new cdev to the global cdev list */
9175fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
918cd246fa9SRafael J. Wysocki 
9195fc024abSZhang Rui 	list_add(&cdev->node, &thermal_cdev_list);
9205fc024abSZhang Rui 
9215fc024abSZhang Rui 	/* Update binding information for 'this' new cdev */
9225fc024abSZhang Rui 	bind_cdev(cdev);
9235fc024abSZhang Rui 
9244511f716SChen Yu 	list_for_each_entry(pos, &thermal_tz_list, node)
9254511f716SChen Yu 		if (atomic_cmpxchg(&pos->need_update, 1, 0))
9260e70f466SSrinivas Pandruvada 			thermal_zone_device_update(pos,
9270e70f466SSrinivas Pandruvada 						   THERMAL_EVENT_UNSPECIFIED);
928cd246fa9SRafael J. Wysocki 
9294511f716SChen Yu 	mutex_unlock(&thermal_list_lock);
9304511f716SChen Yu 
9315fc024abSZhang Rui 	return cdev;
93258483761SDaniel Lezcano 
933e398421fSViresh Kumar out_cooling_dev:
93498a160e8SYang Yingliang 	thermal_cooling_device_destroy_sysfs(cdev);
935e398421fSViresh Kumar out_cdev_type:
93658483761SDaniel Lezcano 	kfree(cdev->type);
93758483761SDaniel Lezcano out_ida_remove:
9385a5b7d8dSkeliu 	ida_free(&thermal_cdev_ida, id);
93958483761SDaniel Lezcano out_kfree_cdev:
940d44616c6SDaniel Lezcano 	kfree(cdev);
94158483761SDaniel Lezcano 	return ERR_PTR(ret);
9425fc024abSZhang Rui }
943a116b5d4SEduardo Valentin 
944a116b5d4SEduardo Valentin /**
945a116b5d4SEduardo Valentin  * thermal_cooling_device_register() - register a new thermal cooling device
946a116b5d4SEduardo Valentin  * @type:	the thermal cooling device type.
947a116b5d4SEduardo Valentin  * @devdata:	device private data.
948a116b5d4SEduardo Valentin  * @ops:		standard thermal cooling devices callbacks.
949a116b5d4SEduardo Valentin  *
950a116b5d4SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
951a116b5d4SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
952a116b5d4SEduardo Valentin  * to all the thermal zone devices registered at the same time.
953a116b5d4SEduardo Valentin  *
954a116b5d4SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
955a116b5d4SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
956a116b5d4SEduardo Valentin  */
957a116b5d4SEduardo Valentin struct thermal_cooling_device *
thermal_cooling_device_register(const char * type,void * devdata,const struct thermal_cooling_device_ops * ops)958f991de53SJean-Francois Dagenais thermal_cooling_device_register(const char *type, void *devdata,
959a116b5d4SEduardo Valentin 				const struct thermal_cooling_device_ops *ops)
960a116b5d4SEduardo Valentin {
961a116b5d4SEduardo Valentin 	return __thermal_cooling_device_register(NULL, type, devdata, ops);
962a116b5d4SEduardo Valentin }
963910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
9645fc024abSZhang Rui 
9655fc024abSZhang Rui /**
966a116b5d4SEduardo Valentin  * thermal_of_cooling_device_register() - register an OF thermal cooling device
967a116b5d4SEduardo Valentin  * @np:		a pointer to a device tree node.
968a116b5d4SEduardo Valentin  * @type:	the thermal cooling device type.
969a116b5d4SEduardo Valentin  * @devdata:	device private data.
970a116b5d4SEduardo Valentin  * @ops:		standard thermal cooling devices callbacks.
971a116b5d4SEduardo Valentin  *
972a116b5d4SEduardo Valentin  * This function will register a cooling device with device tree node reference.
973a116b5d4SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
974a116b5d4SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
975a116b5d4SEduardo Valentin  * to all the thermal zone devices registered at the same time.
976a116b5d4SEduardo Valentin  *
977a116b5d4SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
978a116b5d4SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
979a116b5d4SEduardo Valentin  */
980a116b5d4SEduardo Valentin struct thermal_cooling_device *
thermal_of_cooling_device_register(struct device_node * np,const char * type,void * devdata,const struct thermal_cooling_device_ops * ops)981a116b5d4SEduardo Valentin thermal_of_cooling_device_register(struct device_node *np,
982f991de53SJean-Francois Dagenais 				   const char *type, void *devdata,
983a116b5d4SEduardo Valentin 				   const struct thermal_cooling_device_ops *ops)
984a116b5d4SEduardo Valentin {
985a116b5d4SEduardo Valentin 	return __thermal_cooling_device_register(np, type, devdata, ops);
986a116b5d4SEduardo Valentin }
987a116b5d4SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
988a116b5d4SEduardo Valentin 
thermal_cooling_device_release(struct device * dev,void * res)989b4ab114cSGuenter Roeck static void thermal_cooling_device_release(struct device *dev, void *res)
990b4ab114cSGuenter Roeck {
991b4ab114cSGuenter Roeck 	thermal_cooling_device_unregister(
992b4ab114cSGuenter Roeck 				*(struct thermal_cooling_device **)res);
993b4ab114cSGuenter Roeck }
994b4ab114cSGuenter Roeck 
995b4ab114cSGuenter Roeck /**
996b4ab114cSGuenter Roeck  * devm_thermal_of_cooling_device_register() - register an OF thermal cooling
997b4ab114cSGuenter Roeck  *					       device
998b4ab114cSGuenter Roeck  * @dev:	a valid struct device pointer of a sensor device.
999b4ab114cSGuenter Roeck  * @np:		a pointer to a device tree node.
1000b4ab114cSGuenter Roeck  * @type:	the thermal cooling device type.
1001b4ab114cSGuenter Roeck  * @devdata:	device private data.
1002b4ab114cSGuenter Roeck  * @ops:	standard thermal cooling devices callbacks.
1003b4ab114cSGuenter Roeck  *
1004b4ab114cSGuenter Roeck  * This function will register a cooling device with device tree node reference.
1005b4ab114cSGuenter Roeck  * This interface function adds a new thermal cooling device (fan/processor/...)
1006b4ab114cSGuenter Roeck  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
1007b4ab114cSGuenter Roeck  * to all the thermal zone devices registered at the same time.
1008b4ab114cSGuenter Roeck  *
1009b4ab114cSGuenter Roeck  * Return: a pointer to the created struct thermal_cooling_device or an
1010b4ab114cSGuenter Roeck  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
1011b4ab114cSGuenter Roeck  */
1012b4ab114cSGuenter Roeck struct thermal_cooling_device *
devm_thermal_of_cooling_device_register(struct device * dev,struct device_node * np,char * type,void * devdata,const struct thermal_cooling_device_ops * ops)1013b4ab114cSGuenter Roeck devm_thermal_of_cooling_device_register(struct device *dev,
1014b4ab114cSGuenter Roeck 				struct device_node *np,
1015b4ab114cSGuenter Roeck 				char *type, void *devdata,
1016b4ab114cSGuenter Roeck 				const struct thermal_cooling_device_ops *ops)
1017b4ab114cSGuenter Roeck {
1018b4ab114cSGuenter Roeck 	struct thermal_cooling_device **ptr, *tcd;
1019b4ab114cSGuenter Roeck 
1020b4ab114cSGuenter Roeck 	ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr),
1021b4ab114cSGuenter Roeck 			   GFP_KERNEL);
1022b4ab114cSGuenter Roeck 	if (!ptr)
1023b4ab114cSGuenter Roeck 		return ERR_PTR(-ENOMEM);
1024b4ab114cSGuenter Roeck 
1025b4ab114cSGuenter Roeck 	tcd = __thermal_cooling_device_register(np, type, devdata, ops);
1026b4ab114cSGuenter Roeck 	if (IS_ERR(tcd)) {
1027b4ab114cSGuenter Roeck 		devres_free(ptr);
1028b4ab114cSGuenter Roeck 		return tcd;
1029b4ab114cSGuenter Roeck 	}
1030b4ab114cSGuenter Roeck 
1031b4ab114cSGuenter Roeck 	*ptr = tcd;
1032b4ab114cSGuenter Roeck 	devres_add(dev, ptr);
1033b4ab114cSGuenter Roeck 
1034b4ab114cSGuenter Roeck 	return tcd;
1035b4ab114cSGuenter Roeck }
1036b4ab114cSGuenter Roeck EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
1037b4ab114cSGuenter Roeck 
thermal_cooling_device_present(struct thermal_cooling_device * cdev)1038c43198afSRafael J. Wysocki static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev)
1039c43198afSRafael J. Wysocki {
1040c43198afSRafael J. Wysocki 	struct thermal_cooling_device *pos = NULL;
1041c43198afSRafael J. Wysocki 
1042c43198afSRafael J. Wysocki 	list_for_each_entry(pos, &thermal_cdev_list, node) {
1043c43198afSRafael J. Wysocki 		if (pos == cdev)
1044c43198afSRafael J. Wysocki 			return true;
1045c43198afSRafael J. Wysocki 	}
1046c43198afSRafael J. Wysocki 
1047c43198afSRafael J. Wysocki 	return false;
1048c43198afSRafael J. Wysocki }
1049c43198afSRafael J. Wysocki 
1050790930f4SRafael J. Wysocki /**
1051790930f4SRafael J. Wysocki  * thermal_cooling_device_update - Update a cooling device object
1052790930f4SRafael J. Wysocki  * @cdev: Target cooling device.
1053790930f4SRafael J. Wysocki  *
1054790930f4SRafael J. Wysocki  * Update @cdev to reflect a change of the underlying hardware or platform.
1055790930f4SRafael J. Wysocki  *
1056790930f4SRafael J. Wysocki  * Must be called when the maximum cooling state of @cdev becomes invalid and so
1057790930f4SRafael J. Wysocki  * its .get_max_state() callback needs to be run to produce the new maximum
1058790930f4SRafael J. Wysocki  * cooling state value.
1059790930f4SRafael J. Wysocki  */
thermal_cooling_device_update(struct thermal_cooling_device * cdev)1060790930f4SRafael J. Wysocki void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
1061790930f4SRafael J. Wysocki {
1062790930f4SRafael J. Wysocki 	struct thermal_instance *ti;
1063790930f4SRafael J. Wysocki 	unsigned long state;
1064790930f4SRafael J. Wysocki 
1065790930f4SRafael J. Wysocki 	if (IS_ERR_OR_NULL(cdev))
1066790930f4SRafael J. Wysocki 		return;
1067790930f4SRafael J. Wysocki 
1068790930f4SRafael J. Wysocki 	/*
1069790930f4SRafael J. Wysocki 	 * Hold thermal_list_lock throughout the update to prevent the device
1070790930f4SRafael J. Wysocki 	 * from going away while being updated.
1071790930f4SRafael J. Wysocki 	 */
1072790930f4SRafael J. Wysocki 	mutex_lock(&thermal_list_lock);
1073790930f4SRafael J. Wysocki 
1074790930f4SRafael J. Wysocki 	if (!thermal_cooling_device_present(cdev))
1075790930f4SRafael J. Wysocki 		goto unlock_list;
1076790930f4SRafael J. Wysocki 
1077790930f4SRafael J. Wysocki 	/*
1078790930f4SRafael J. Wysocki 	 * Update under the cdev lock to prevent the state from being set beyond
1079790930f4SRafael J. Wysocki 	 * the new limit concurrently.
1080790930f4SRafael J. Wysocki 	 */
1081790930f4SRafael J. Wysocki 	mutex_lock(&cdev->lock);
1082790930f4SRafael J. Wysocki 
1083790930f4SRafael J. Wysocki 	if (cdev->ops->get_max_state(cdev, &cdev->max_state))
1084790930f4SRafael J. Wysocki 		goto unlock;
1085790930f4SRafael J. Wysocki 
1086790930f4SRafael J. Wysocki 	thermal_cooling_device_stats_reinit(cdev);
1087790930f4SRafael J. Wysocki 
1088790930f4SRafael J. Wysocki 	list_for_each_entry(ti, &cdev->thermal_instances, cdev_node) {
1089790930f4SRafael J. Wysocki 		if (ti->upper == cdev->max_state)
1090790930f4SRafael J. Wysocki 			continue;
1091790930f4SRafael J. Wysocki 
1092790930f4SRafael J. Wysocki 		if (ti->upper < cdev->max_state) {
1093790930f4SRafael J. Wysocki 			if (ti->upper_no_limit)
1094790930f4SRafael J. Wysocki 				ti->upper = cdev->max_state;
1095790930f4SRafael J. Wysocki 
1096790930f4SRafael J. Wysocki 			continue;
1097790930f4SRafael J. Wysocki 		}
1098790930f4SRafael J. Wysocki 
1099790930f4SRafael J. Wysocki 		ti->upper = cdev->max_state;
1100790930f4SRafael J. Wysocki 		if (ti->lower > ti->upper)
1101790930f4SRafael J. Wysocki 			ti->lower = ti->upper;
1102790930f4SRafael J. Wysocki 
1103790930f4SRafael J. Wysocki 		if (ti->target == THERMAL_NO_TARGET)
1104790930f4SRafael J. Wysocki 			continue;
1105790930f4SRafael J. Wysocki 
1106790930f4SRafael J. Wysocki 		if (ti->target > ti->upper)
1107790930f4SRafael J. Wysocki 			ti->target = ti->upper;
1108790930f4SRafael J. Wysocki 	}
1109790930f4SRafael J. Wysocki 
1110790930f4SRafael J. Wysocki 	if (cdev->ops->get_cur_state(cdev, &state) || state > cdev->max_state)
1111790930f4SRafael J. Wysocki 		goto unlock;
1112790930f4SRafael J. Wysocki 
1113790930f4SRafael J. Wysocki 	thermal_cooling_device_stats_update(cdev, state);
1114790930f4SRafael J. Wysocki 
1115790930f4SRafael J. Wysocki unlock:
1116790930f4SRafael J. Wysocki 	mutex_unlock(&cdev->lock);
1117790930f4SRafael J. Wysocki 
1118790930f4SRafael J. Wysocki unlock_list:
1119790930f4SRafael J. Wysocki 	mutex_unlock(&thermal_list_lock);
1120790930f4SRafael J. Wysocki }
1121790930f4SRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
1122790930f4SRafael J. Wysocki 
1123a116b5d4SEduardo Valentin /**
112438e7b549SEduardo Valentin  * thermal_cooling_device_unregister - removes a thermal cooling device
11255fc024abSZhang Rui  * @cdev:	the thermal cooling device to remove.
11265fc024abSZhang Rui  *
112738e7b549SEduardo Valentin  * thermal_cooling_device_unregister() must be called when a registered
112838e7b549SEduardo Valentin  * thermal cooling device is no longer needed.
11295fc024abSZhang Rui  */
thermal_cooling_device_unregister(struct thermal_cooling_device * cdev)11305fc024abSZhang Rui void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
11315fc024abSZhang Rui {
11325fc024abSZhang Rui 	struct thermal_zone_device *tz;
11335fc024abSZhang Rui 
11345fc024abSZhang Rui 	if (!cdev)
11355fc024abSZhang Rui 		return;
11365fc024abSZhang Rui 
11375fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
1138c43198afSRafael J. Wysocki 
1139c43198afSRafael J. Wysocki 	if (!thermal_cooling_device_present(cdev)) {
11405fc024abSZhang Rui 		mutex_unlock(&thermal_list_lock);
11415fc024abSZhang Rui 		return;
11425fc024abSZhang Rui 	}
1143c43198afSRafael J. Wysocki 
11445fc024abSZhang Rui 	list_del(&cdev->node);
11455fc024abSZhang Rui 
11465fc024abSZhang Rui 	/* Unbind all thermal zones associated with 'this' cdev */
11475fc024abSZhang Rui 	list_for_each_entry(tz, &thermal_tz_list, node) {
1148ded2d383SZhang Rui 		if (tz->ops->unbind)
11495fc024abSZhang Rui 			tz->ops->unbind(tz, cdev);
11505fc024abSZhang Rui 	}
11515fc024abSZhang Rui 
11525fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
11535fc024abSZhang Rui 
115447e3f000SViresh Kumar 	device_unregister(&cdev->device);
11555fc024abSZhang Rui }
1156910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
11575fc024abSZhang Rui 
bind_tz(struct thermal_zone_device * tz)115890f5b5bbSEduardo Valentin static void bind_tz(struct thermal_zone_device *tz)
11595fc024abSZhang Rui {
1160ded2d383SZhang Rui 	int ret;
116190f5b5bbSEduardo Valentin 	struct thermal_cooling_device *pos = NULL;
11625fc024abSZhang Rui 
1163ded2d383SZhang Rui 	if (!tz->ops->bind)
1164d0b7306dSMichele Di Giorgio 		return;
116590f5b5bbSEduardo Valentin 
116690f5b5bbSEduardo Valentin 	mutex_lock(&thermal_list_lock);
116790f5b5bbSEduardo Valentin 
116890f5b5bbSEduardo Valentin 	list_for_each_entry(pos, &thermal_cdev_list, node) {
116990f5b5bbSEduardo Valentin 		ret = tz->ops->bind(tz, pos);
117090f5b5bbSEduardo Valentin 		if (ret)
117190f5b5bbSEduardo Valentin 			print_bind_err_msg(tz, pos, ret);
117290f5b5bbSEduardo Valentin 	}
1173d0b7306dSMichele Di Giorgio 
117490f5b5bbSEduardo Valentin 	mutex_unlock(&thermal_list_lock);
11755fc024abSZhang Rui }
11765fc024abSZhang Rui 
thermal_set_delay_jiffies(unsigned long * delay_jiffies,int delay_ms)1177e5f2cda6SDaniel Lezcano static void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
1178e5f2cda6SDaniel Lezcano {
1179e5f2cda6SDaniel Lezcano 	*delay_jiffies = msecs_to_jiffies(delay_ms);
1180e5f2cda6SDaniel Lezcano 	if (delay_ms > 1000)
1181e5f2cda6SDaniel Lezcano 		*delay_jiffies = round_jiffies(*delay_jiffies);
1182e5f2cda6SDaniel Lezcano }
1183e5f2cda6SDaniel Lezcano 
thermal_zone_get_crit_temp(struct thermal_zone_device * tz,int * temp)11847c3d5c20SDaniel Lezcano int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
11857c3d5c20SDaniel Lezcano {
11867c3d5c20SDaniel Lezcano 	int i, ret = -EINVAL;
11877c3d5c20SDaniel Lezcano 
11887c3d5c20SDaniel Lezcano 	if (tz->ops->get_crit_temp)
11897c3d5c20SDaniel Lezcano 		return tz->ops->get_crit_temp(tz, temp);
11907c3d5c20SDaniel Lezcano 
11917c3d5c20SDaniel Lezcano 	if (!tz->trips)
11927c3d5c20SDaniel Lezcano 		return -EINVAL;
11937c3d5c20SDaniel Lezcano 
11947c3d5c20SDaniel Lezcano 	mutex_lock(&tz->lock);
11957c3d5c20SDaniel Lezcano 
11967c3d5c20SDaniel Lezcano 	for (i = 0; i < tz->num_trips; i++) {
11977c3d5c20SDaniel Lezcano 		if (tz->trips[i].type == THERMAL_TRIP_CRITICAL) {
11987c3d5c20SDaniel Lezcano 			*temp = tz->trips[i].temperature;
11997c3d5c20SDaniel Lezcano 			ret = 0;
12007c3d5c20SDaniel Lezcano 			break;
12017c3d5c20SDaniel Lezcano 		}
12027c3d5c20SDaniel Lezcano 	}
12037c3d5c20SDaniel Lezcano 
12047c3d5c20SDaniel Lezcano 	mutex_unlock(&tz->lock);
12057c3d5c20SDaniel Lezcano 
12067c3d5c20SDaniel Lezcano 	return ret;
12077c3d5c20SDaniel Lezcano }
12087c3d5c20SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
12097c3d5c20SDaniel Lezcano 
12105fc024abSZhang Rui /**
1211fae11de5SDaniel Lezcano  * thermal_zone_device_register_with_trips() - register a new thermal zone device
12125fc024abSZhang Rui  * @type:	the thermal zone device type
1213fae11de5SDaniel Lezcano  * @trips:	a pointer to an array of thermal trips
1214e5bfcd30SDaniel Lezcano  * @num_trips:	the number of trip points the thermal zone support
12155fc024abSZhang Rui  * @mask:	a bit string indicating the writeablility of trip points
12165fc024abSZhang Rui  * @devdata:	private device data
12175fc024abSZhang Rui  * @ops:	standard thermal zone device callbacks
12185fc024abSZhang Rui  * @tzp:	thermal zone platform parameters
12195fc024abSZhang Rui  * @passive_delay: number of milliseconds to wait between polls when
12205fc024abSZhang Rui  *		   performing passive cooling
12215fc024abSZhang Rui  * @polling_delay: number of milliseconds to wait between polls when checking
12225fc024abSZhang Rui  *		   whether trip points have been crossed (0 for interrupt
12235fc024abSZhang Rui  *		   driven systems)
12245fc024abSZhang Rui  *
1225a00e55f9SEduardo Valentin  * This interface function adds a new thermal zone device (sensor) to
1226a00e55f9SEduardo Valentin  * /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the
1227a00e55f9SEduardo Valentin  * thermal cooling devices registered at the same time.
12285fc024abSZhang Rui  * thermal_zone_device_unregister() must be called when the device is no
12295fc024abSZhang Rui  * longer needed. The passive cooling depends on the .get_trend() return value.
1230a00e55f9SEduardo Valentin  *
1231a00e55f9SEduardo Valentin  * Return: a pointer to the created struct thermal_zone_device or an
1232a00e55f9SEduardo Valentin  * in case of error, an ERR_PTR. Caller must check return value with
1233a00e55f9SEduardo Valentin  * IS_ERR*() helpers.
12345fc024abSZhang Rui  */
1235eb7be329SEduardo Valentin struct thermal_zone_device *
thermal_zone_device_register_with_trips(const char * type,struct thermal_trip * trips,int num_trips,int mask,void * devdata,struct thermal_zone_device_ops * ops,const struct thermal_zone_params * tzp,int passive_delay,int polling_delay)1236fae11de5SDaniel Lezcano thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *trips, int num_trips, int mask,
1237eb7be329SEduardo Valentin 					void *devdata, struct thermal_zone_device_ops *ops,
123880ddce5fSAhmad Fatoum 					const struct thermal_zone_params *tzp, int passive_delay,
1239eb7be329SEduardo Valentin 					int polling_delay)
12405fc024abSZhang Rui {
12415fc024abSZhang Rui 	struct thermal_zone_device *tz;
1242adc8749bSYue Hu 	int id;
12435fc024abSZhang Rui 	int result;
12445fc024abSZhang Rui 	int count;
1245e33df1d2SJavi Merino 	struct thermal_governor *governor;
12465fc024abSZhang Rui 
124767eed44bSAmit Kucheria 	if (!type || strlen(type) == 0) {
12483f95ac32SDaniel Lezcano 		pr_err("No thermal zone type defined\n");
124954fa38ccSEduardo Valentin 		return ERR_PTR(-EINVAL);
125067eed44bSAmit Kucheria 	}
125154fa38ccSEduardo Valentin 
1252c71d8035SLad Prabhakar 	if (strlen(type) >= THERMAL_NAME_LENGTH) {
12533f95ac32SDaniel Lezcano 		pr_err("Thermal zone name (%s) too long, should be under %d chars\n",
125467eed44bSAmit Kucheria 		       type, THERMAL_NAME_LENGTH);
12555fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
125667eed44bSAmit Kucheria 	}
12575fc024abSZhang Rui 
125882b1ec79SSumeet Pawnikar 	/*
125982b1ec79SSumeet Pawnikar 	 * Max trip count can't exceed 31 as the "mask >> num_trips" condition.
126082b1ec79SSumeet Pawnikar 	 * For example, shifting by 32 will result in compiler warning:
126182b1ec79SSumeet Pawnikar 	 * warning: right shift count >= width of type [-Wshift-count- overflow]
126282b1ec79SSumeet Pawnikar 	 *
126382b1ec79SSumeet Pawnikar 	 * Also "mask >> num_trips" will always be true with 32 bit shift.
126482b1ec79SSumeet Pawnikar 	 * E.g. mask = 0x80000000 for trip id 31 to be RW. Then
126582b1ec79SSumeet Pawnikar 	 * mask >> 32 = 0x80000000
126682b1ec79SSumeet Pawnikar 	 * This will result in failure for the below condition.
126782b1ec79SSumeet Pawnikar 	 *
126882b1ec79SSumeet Pawnikar 	 * Check will be true when the bit 31 of the mask is set.
126982b1ec79SSumeet Pawnikar 	 * 32 bit shift will cause overflow of 4 byte integer.
127082b1ec79SSumeet Pawnikar 	 */
127182b1ec79SSumeet Pawnikar 	if (num_trips > (BITS_PER_TYPE(int) - 1) || num_trips < 0 || mask >> num_trips) {
12723f95ac32SDaniel Lezcano 		pr_err("Incorrect number of thermal trips\n");
12735fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
127467eed44bSAmit Kucheria 	}
12755fc024abSZhang Rui 
127667eed44bSAmit Kucheria 	if (!ops) {
12773f95ac32SDaniel Lezcano 		pr_err("Thermal zone device ops not defined\n");
12785fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
127967eed44bSAmit Kucheria 	}
12805fc024abSZhang Rui 
128135d8dbbbSRafael J. Wysocki 	if (num_trips > 0 && !trips)
12825fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
12835fc024abSZhang Rui 
12849e0a9be2SRafael J. Wysocki 	if (!thermal_class)
12859e0a9be2SRafael J. Wysocki 		return ERR_PTR(-ENODEV);
12869e0a9be2SRafael J. Wysocki 
128795e3ed15SEduardo Valentin 	tz = kzalloc(sizeof(*tz), GFP_KERNEL);
12885fc024abSZhang Rui 	if (!tz)
12895fc024abSZhang Rui 		return ERR_PTR(-ENOMEM);
12905fc024abSZhang Rui 
12913d439b1aSDaniel Lezcano 	if (tzp) {
12923d439b1aSDaniel Lezcano 		tz->tzp = kmemdup(tzp, sizeof(*tzp), GFP_KERNEL);
12933d439b1aSDaniel Lezcano 		if (!tz->tzp) {
12943d439b1aSDaniel Lezcano 			result = -ENOMEM;
12953d439b1aSDaniel Lezcano 			goto free_tz;
12963d439b1aSDaniel Lezcano 		}
12973d439b1aSDaniel Lezcano 	}
12983d439b1aSDaniel Lezcano 
12995fc024abSZhang Rui 	INIT_LIST_HEAD(&tz->thermal_instances);
13003eb073abSRafael J. Wysocki 	INIT_LIST_HEAD(&tz->node);
1301b31ef828SMatthew Wilcox 	ida_init(&tz->ida);
13025fc024abSZhang Rui 	mutex_init(&tz->lock);
1303a95a9e30SRafael J. Wysocki 	init_completion(&tz->removal);
13045a5b7d8dSkeliu 	id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);
1305adc8749bSYue Hu 	if (id < 0) {
1306adc8749bSYue Hu 		result = id;
13073d439b1aSDaniel Lezcano 		goto free_tzp;
1308adc8749bSYue Hu 	}
13095fc024abSZhang Rui 
1310adc8749bSYue Hu 	tz->id = id;
13111e6c8fb8SWolfram Sang 	strscpy(tz->type, type, sizeof(tz->type));
1312d7203eedSDaniel Lezcano 
1313d7203eedSDaniel Lezcano 	if (!ops->critical)
1314d7203eedSDaniel Lezcano 		ops->critical = thermal_zone_device_critical;
1315d7203eedSDaniel Lezcano 
13165fc024abSZhang Rui 	tz->ops = ops;
13179e0a9be2SRafael J. Wysocki 	tz->device.class = thermal_class;
13185fc024abSZhang Rui 	tz->devdata = devdata;
13195fc024abSZhang Rui 	tz->trips = trips;
1320e5bfcd30SDaniel Lezcano 	tz->num_trips = num_trips;
13211c600861SEduardo Valentin 
132217d399cdSDaniel Lezcano 	thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay);
132317d399cdSDaniel Lezcano 	thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay);
132417d399cdSDaniel Lezcano 
13254d0fe749SEduardo Valentin 	/* sys I/F */
13261c600861SEduardo Valentin 	/* Add nodes that are always present via .groups */
13274d0fe749SEduardo Valentin 	result = thermal_zone_create_device_groups(tz, mask);
13284d0fe749SEduardo Valentin 	if (result)
13299d9ca1f9SChristophe Jaillet 		goto remove_id;
13304d0fe749SEduardo Valentin 
13314511f716SChen Yu 	/* A new thermal zone needs to be updated anyway. */
13324511f716SChen Yu 	atomic_set(&tz->need_update, 1);
13335fc024abSZhang Rui 
13344748f968SYang Yingliang 	result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
13354748f968SYang Yingliang 	if (result) {
13364748f968SYang Yingliang 		thermal_zone_destroy_device_groups(tz);
13374748f968SYang Yingliang 		goto remove_id;
13384748f968SYang Yingliang 	}
1339*ac7dfac6SRafael J. Wysocki 	thermal_zone_device_init(tz);
13405fc024abSZhang Rui 	result = device_register(&tz->device);
13419d9ca1f9SChristophe Jaillet 	if (result)
1342adc8749bSYue Hu 		goto release_device;
13435fc024abSZhang Rui 
1344e5bfcd30SDaniel Lezcano 	for (count = 0; count < num_trips; count++) {
13457c3d5c20SDaniel Lezcano 		struct thermal_trip trip;
13467c3d5c20SDaniel Lezcano 
13477c3d5c20SDaniel Lezcano 		result = thermal_zone_get_trip(tz, count, &trip);
1348f1b80a38SIdo Schimmel 		if (result || !trip.temperature)
134981ad4276SZhang Rui 			set_bit(count, &tz->trips_disabled);
13505fc024abSZhang Rui 	}
13515fc024abSZhang Rui 
13525fc024abSZhang Rui 	/* Update 'this' zone's governor information */
13535fc024abSZhang Rui 	mutex_lock(&thermal_governor_lock);
13545fc024abSZhang Rui 
13555fc024abSZhang Rui 	if (tz->tzp)
1356e33df1d2SJavi Merino 		governor = __find_governor(tz->tzp->governor_name);
13575fc024abSZhang Rui 	else
1358e33df1d2SJavi Merino 		governor = def_governor;
1359e33df1d2SJavi Merino 
1360e33df1d2SJavi Merino 	result = thermal_set_governor(tz, governor);
1361e33df1d2SJavi Merino 	if (result) {
1362e33df1d2SJavi Merino 		mutex_unlock(&thermal_governor_lock);
1363e33df1d2SJavi Merino 		goto unregister;
1364e33df1d2SJavi Merino 	}
13655fc024abSZhang Rui 
13665fc024abSZhang Rui 	mutex_unlock(&thermal_governor_lock);
13675fc024abSZhang Rui 
1368ccba4ffdSEduardo Valentin 	if (!tz->tzp || !tz->tzp->no_hwmon) {
13695fc024abSZhang Rui 		result = thermal_add_hwmon_sysfs(tz);
13705fc024abSZhang Rui 		if (result)
13715fc024abSZhang Rui 			goto unregister;
1372ccba4ffdSEduardo Valentin 	}
13735fc024abSZhang Rui 
13745fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
13753eb073abSRafael J. Wysocki 	mutex_lock(&tz->lock);
13765fc024abSZhang Rui 	list_add_tail(&tz->node, &thermal_tz_list);
13773eb073abSRafael J. Wysocki 	mutex_unlock(&tz->lock);
13785fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
13795fc024abSZhang Rui 
13805fc024abSZhang Rui 	/* Bind cooling devices for this zone */
13815fc024abSZhang Rui 	bind_tz(tz);
13825fc024abSZhang Rui 
1383b659a30dSEduardo Valentin 	INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
13845fc024abSZhang Rui 
13854511f716SChen Yu 	/* Update the new thermal zone and mark it as already updated. */
13864511f716SChen Yu 	if (atomic_cmpxchg(&tz->need_update, 1, 0))
13870e70f466SSrinivas Pandruvada 		thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
13885fc024abSZhang Rui 
138955cdf0a2SDaniel Lezcano 	thermal_notify_tz_create(tz->id, tz->type);
139055cdf0a2SDaniel Lezcano 
13915fc024abSZhang Rui 	return tz;
13925fc024abSZhang Rui 
13935fc024abSZhang Rui unregister:
1394adc8749bSYue Hu 	device_del(&tz->device);
1395adc8749bSYue Hu release_device:
1396adc8749bSYue Hu 	put_device(&tz->device);
13979d9ca1f9SChristophe Jaillet remove_id:
13985a5b7d8dSkeliu 	ida_free(&thermal_tz_ida, id);
13993d439b1aSDaniel Lezcano free_tzp:
14003d439b1aSDaniel Lezcano 	kfree(tz->tzp);
14019d9ca1f9SChristophe Jaillet free_tz:
14029d9ca1f9SChristophe Jaillet 	kfree(tz);
14039d9ca1f9SChristophe Jaillet 	return ERR_PTR(result);
14045fc024abSZhang Rui }
1405a921be53SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
1406fae11de5SDaniel Lezcano 
thermal_tripless_zone_device_register(const char * type,void * devdata,struct thermal_zone_device_ops * ops,const struct thermal_zone_params * tzp)1407d332db8fSRafael J. Wysocki struct thermal_zone_device *thermal_tripless_zone_device_register(
1408d332db8fSRafael J. Wysocki 					const char *type,
1409d332db8fSRafael J. Wysocki 					void *devdata,
1410d332db8fSRafael J. Wysocki 					struct thermal_zone_device_ops *ops,
1411d332db8fSRafael J. Wysocki 					const struct thermal_zone_params *tzp)
1412d332db8fSRafael J. Wysocki {
1413d332db8fSRafael J. Wysocki 	return thermal_zone_device_register_with_trips(type, NULL, 0, 0, devdata,
1414d332db8fSRafael J. Wysocki 						       ops, tzp, 0, 0);
1415d332db8fSRafael J. Wysocki }
1416d332db8fSRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_tripless_zone_device_register);
1417d332db8fSRafael J. Wysocki 
thermal_zone_device_priv(struct thermal_zone_device * tzd)1418a6ff3c00SDaniel Lezcano void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
1419a6ff3c00SDaniel Lezcano {
1420a6ff3c00SDaniel Lezcano 	return tzd->devdata;
1421a6ff3c00SDaniel Lezcano }
1422a6ff3c00SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_priv);
1423a6ff3c00SDaniel Lezcano 
thermal_zone_device_type(struct thermal_zone_device * tzd)1424072e35c9SDaniel Lezcano const char *thermal_zone_device_type(struct thermal_zone_device *tzd)
1425072e35c9SDaniel Lezcano {
1426072e35c9SDaniel Lezcano 	return tzd->type;
1427072e35c9SDaniel Lezcano }
1428072e35c9SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_type);
1429072e35c9SDaniel Lezcano 
thermal_zone_device_id(struct thermal_zone_device * tzd)14303034f859SDaniel Lezcano int thermal_zone_device_id(struct thermal_zone_device *tzd)
14313034f859SDaniel Lezcano {
14323034f859SDaniel Lezcano 	return tzd->id;
14333034f859SDaniel Lezcano }
14343034f859SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_id);
14353034f859SDaniel Lezcano 
thermal_zone_device(struct thermal_zone_device * tzd)14367cefbaf0SDaniel Lezcano struct device *thermal_zone_device(struct thermal_zone_device *tzd)
14377cefbaf0SDaniel Lezcano {
14387cefbaf0SDaniel Lezcano 	return &tzd->device;
14397cefbaf0SDaniel Lezcano }
14407cefbaf0SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device);
14417cefbaf0SDaniel Lezcano 
14425fc024abSZhang Rui /**
1443a052b511SYang Yingliang  * thermal_zone_device_unregister - removes the registered thermal zone device
14445fc024abSZhang Rui  * @tz: the thermal zone device to remove
14455fc024abSZhang Rui  */
thermal_zone_device_unregister(struct thermal_zone_device * tz)14465fc024abSZhang Rui void thermal_zone_device_unregister(struct thermal_zone_device *tz)
14475fc024abSZhang Rui {
1448ded2d383SZhang Rui 	int tz_id;
14495fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
14505fc024abSZhang Rui 	struct thermal_zone_device *pos = NULL;
14515fc024abSZhang Rui 
14525fc024abSZhang Rui 	if (!tz)
14535fc024abSZhang Rui 		return;
14545fc024abSZhang Rui 
1455a5f785ceSDmitry Osipenko 	tz_id = tz->id;
14565fc024abSZhang Rui 
14575fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
14585fc024abSZhang Rui 	list_for_each_entry(pos, &thermal_tz_list, node)
14595fc024abSZhang Rui 		if (pos == tz)
14605fc024abSZhang Rui 			break;
14615fc024abSZhang Rui 	if (pos != tz) {
14625fc024abSZhang Rui 		/* thermal zone device not found */
14635fc024abSZhang Rui 		mutex_unlock(&thermal_list_lock);
14645fc024abSZhang Rui 		return;
14655fc024abSZhang Rui 	}
14663eb073abSRafael J. Wysocki 
14673eb073abSRafael J. Wysocki 	mutex_lock(&tz->lock);
14685fc024abSZhang Rui 	list_del(&tz->node);
14693eb073abSRafael J. Wysocki 	mutex_unlock(&tz->lock);
14705fc024abSZhang Rui 
14715fc024abSZhang Rui 	/* Unbind all cdevs associated with 'this' thermal zone */
1472ded2d383SZhang Rui 	list_for_each_entry(cdev, &thermal_cdev_list, node)
1473ded2d383SZhang Rui 		if (tz->ops->unbind)
14745fc024abSZhang Rui 			tz->ops->unbind(tz, cdev);
14755fc024abSZhang Rui 
14765fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
14775fc024abSZhang Rui 
1478163b00cdSWei Wang 	cancel_delayed_work_sync(&tz->poll_queue);
14795fc024abSZhang Rui 
1480e33df1d2SJavi Merino 	thermal_set_governor(tz, NULL);
14815fc024abSZhang Rui 
14825fc024abSZhang Rui 	thermal_remove_hwmon_sysfs(tz);
14835a5b7d8dSkeliu 	ida_free(&thermal_tz_ida, tz->id);
1484b31ef828SMatthew Wilcox 	ida_destroy(&tz->ida);
148530b2ae07SGuenter Roeck 
148630b2ae07SGuenter Roeck 	device_del(&tz->device);
148730b2ae07SGuenter Roeck 	put_device(&tz->device);
148855cdf0a2SDaniel Lezcano 
1489a5f785ceSDmitry Osipenko 	thermal_notify_tz_delete(tz_id);
1490a95a9e30SRafael J. Wysocki 
1491a95a9e30SRafael J. Wysocki 	wait_for_completion(&tz->removal);
1492eabe285eSRafael J. Wysocki 	kfree(tz->tzp);
1493a95a9e30SRafael J. Wysocki 	kfree(tz);
14945fc024abSZhang Rui }
1495910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
14965fc024abSZhang Rui 
149763c4d919SEduardo Valentin /**
149863c4d919SEduardo Valentin  * thermal_zone_get_zone_by_name() - search for a zone and returns its ref
149963c4d919SEduardo Valentin  * @name: thermal zone name to fetch the temperature
150063c4d919SEduardo Valentin  *
150163c4d919SEduardo Valentin  * When only one zone is found with the passed name, returns a reference to it.
150263c4d919SEduardo Valentin  *
150363c4d919SEduardo Valentin  * Return: On success returns a reference to an unique thermal zone with
150463c4d919SEduardo Valentin  * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
150563c4d919SEduardo Valentin  * paramenters, -ENODEV for not found and -EEXIST for multiple matches).
150663c4d919SEduardo Valentin  */
thermal_zone_get_zone_by_name(const char * name)150763c4d919SEduardo Valentin struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
150863c4d919SEduardo Valentin {
150963c4d919SEduardo Valentin 	struct thermal_zone_device *pos = NULL, *ref = ERR_PTR(-EINVAL);
151063c4d919SEduardo Valentin 	unsigned int found = 0;
151163c4d919SEduardo Valentin 
151263c4d919SEduardo Valentin 	if (!name)
151363c4d919SEduardo Valentin 		goto exit;
151463c4d919SEduardo Valentin 
151563c4d919SEduardo Valentin 	mutex_lock(&thermal_list_lock);
151663c4d919SEduardo Valentin 	list_for_each_entry(pos, &thermal_tz_list, node)
1517484ac2f3SRasmus Villemoes 		if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
151863c4d919SEduardo Valentin 			found++;
151963c4d919SEduardo Valentin 			ref = pos;
152063c4d919SEduardo Valentin 		}
152163c4d919SEduardo Valentin 	mutex_unlock(&thermal_list_lock);
152263c4d919SEduardo Valentin 
152363c4d919SEduardo Valentin 	/* nothing has been found, thus an error code for it */
152463c4d919SEduardo Valentin 	if (found == 0)
152563c4d919SEduardo Valentin 		ref = ERR_PTR(-ENODEV);
152663c4d919SEduardo Valentin 	else if (found > 1)
152763c4d919SEduardo Valentin 	/* Success only when an unique zone is found */
152863c4d919SEduardo Valentin 		ref = ERR_PTR(-EEXIST);
152963c4d919SEduardo Valentin 
153063c4d919SEduardo Valentin exit:
153163c4d919SEduardo Valentin 	return ref;
153263c4d919SEduardo Valentin }
153363c4d919SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
153463c4d919SEduardo Valentin 
thermal_pm_notify(struct notifier_block * nb,unsigned long mode,void * _unused)1535ff140feaSZhang Rui static int thermal_pm_notify(struct notifier_block *nb,
1536ff140feaSZhang Rui 			     unsigned long mode, void *_unused)
1537ff140feaSZhang Rui {
1538ff140feaSZhang Rui 	struct thermal_zone_device *tz;
1539ff140feaSZhang Rui 
1540ff140feaSZhang Rui 	switch (mode) {
1541ff140feaSZhang Rui 	case PM_HIBERNATION_PREPARE:
1542ff140feaSZhang Rui 	case PM_RESTORE_PREPARE:
1543ff140feaSZhang Rui 	case PM_SUSPEND_PREPARE:
1544fcecef9aSRafael J. Wysocki 		mutex_lock(&thermal_list_lock);
1545fcecef9aSRafael J. Wysocki 
1546fcecef9aSRafael J. Wysocki 		list_for_each_entry(tz, &thermal_tz_list, node) {
1547fcecef9aSRafael J. Wysocki 			mutex_lock(&tz->lock);
1548fcecef9aSRafael J. Wysocki 
1549fcecef9aSRafael J. Wysocki 			tz->suspended = true;
1550fcecef9aSRafael J. Wysocki 
1551fcecef9aSRafael J. Wysocki 			mutex_unlock(&tz->lock);
1552fcecef9aSRafael J. Wysocki 		}
1553fcecef9aSRafael J. Wysocki 
1554fcecef9aSRafael J. Wysocki 		mutex_unlock(&thermal_list_lock);
1555ff140feaSZhang Rui 		break;
1556ff140feaSZhang Rui 	case PM_POST_HIBERNATION:
1557ff140feaSZhang Rui 	case PM_POST_RESTORE:
1558ff140feaSZhang Rui 	case PM_POST_SUSPEND:
1559fcecef9aSRafael J. Wysocki 		mutex_lock(&thermal_list_lock);
1560fcecef9aSRafael J. Wysocki 
1561ff140feaSZhang Rui 		list_for_each_entry(tz, &thermal_tz_list, node) {
1562fcecef9aSRafael J. Wysocki 			mutex_lock(&tz->lock);
1563fcecef9aSRafael J. Wysocki 
1564fcecef9aSRafael J. Wysocki 			tz->suspended = false;
1565fcecef9aSRafael J. Wysocki 
1566964f4843SWei Wang 			thermal_zone_device_init(tz);
1567fcecef9aSRafael J. Wysocki 			__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
1568fcecef9aSRafael J. Wysocki 
1569fcecef9aSRafael J. Wysocki 			mutex_unlock(&tz->lock);
1570ff140feaSZhang Rui 		}
1571fcecef9aSRafael J. Wysocki 
1572fcecef9aSRafael J. Wysocki 		mutex_unlock(&thermal_list_lock);
1573ff140feaSZhang Rui 		break;
1574ff140feaSZhang Rui 	default:
1575ff140feaSZhang Rui 		break;
1576ff140feaSZhang Rui 	}
1577ff140feaSZhang Rui 	return 0;
1578ff140feaSZhang Rui }
1579ff140feaSZhang Rui 
1580ff140feaSZhang Rui static struct notifier_block thermal_pm_nb = {
1581ff140feaSZhang Rui 	.notifier_call = thermal_pm_notify,
1582ff140feaSZhang Rui };
1583ff140feaSZhang Rui 
thermal_init(void)15845fc024abSZhang Rui static int __init thermal_init(void)
15855fc024abSZhang Rui {
158680a26a5cSZhang Rui 	int result;
158780a26a5cSZhang Rui 
1588d2a89b52SDaniel Lezcano 	result = thermal_netlink_init();
1589d2a89b52SDaniel Lezcano 	if (result)
1590d2a89b52SDaniel Lezcano 		goto error;
1591d2a89b52SDaniel Lezcano 
159280a26a5cSZhang Rui 	result = thermal_register_governors();
159380a26a5cSZhang Rui 	if (result)
159458d1c9fdSDaniel Lezcano 		goto unregister_netlink;
15955fc024abSZhang Rui 
15969e0a9be2SRafael J. Wysocki 	thermal_class = kzalloc(sizeof(*thermal_class), GFP_KERNEL);
15979e0a9be2SRafael J. Wysocki 	if (!thermal_class) {
15989e0a9be2SRafael J. Wysocki 		result = -ENOMEM;
159980a26a5cSZhang Rui 		goto unregister_governors;
16009e0a9be2SRafael J. Wysocki 	}
16019e0a9be2SRafael J. Wysocki 
16029e0a9be2SRafael J. Wysocki 	thermal_class->name = "thermal";
16039e0a9be2SRafael J. Wysocki 	thermal_class->dev_release = thermal_release;
16049e0a9be2SRafael J. Wysocki 
16059e0a9be2SRafael J. Wysocki 	result = class_register(thermal_class);
16069e0a9be2SRafael J. Wysocki 	if (result) {
16079e0a9be2SRafael J. Wysocki 		kfree(thermal_class);
16089e0a9be2SRafael J. Wysocki 		thermal_class = NULL;
16099e0a9be2SRafael J. Wysocki 		goto unregister_governors;
16109e0a9be2SRafael J. Wysocki 	}
161180a26a5cSZhang Rui 
1612ff140feaSZhang Rui 	result = register_pm_notifier(&thermal_pm_nb);
1613ff140feaSZhang Rui 	if (result)
1614ff140feaSZhang Rui 		pr_warn("Thermal: Can not register suspend notifier, return %d\n",
1615ff140feaSZhang Rui 			result);
1616ff140feaSZhang Rui 
161780a26a5cSZhang Rui 	return 0;
161880a26a5cSZhang Rui 
16199d367e5eSLuis Henriques unregister_governors:
16209d367e5eSLuis Henriques 	thermal_unregister_governors();
162158d1c9fdSDaniel Lezcano unregister_netlink:
162258d1c9fdSDaniel Lezcano 	thermal_netlink_exit();
162380a26a5cSZhang Rui error:
16245fc024abSZhang Rui 	mutex_destroy(&thermal_list_lock);
162580a26a5cSZhang Rui 	mutex_destroy(&thermal_governor_lock);
16265fc024abSZhang Rui 	return result;
16275fc024abSZhang Rui }
16283f5a2cbeSDaniel Lezcano postcore_initcall(thermal_init);
1629