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 
410*fcecef9aSRafael 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_device_update(struct thermal_zone_device * tz,enum thermal_notify_event event)4900e70f466SSrinivas Pandruvada void thermal_zone_device_update(struct thermal_zone_device *tz,
4910e70f466SSrinivas Pandruvada 				enum thermal_notify_event event)
4925fc024abSZhang Rui {
493a930da9bSDaniel Lezcano 	mutex_lock(&tz->lock);
494b778b4d7SGuenter Roeck 	if (device_is_registered(&tz->device))
4951c439decSGuenter Roeck 		__thermal_zone_device_update(tz, event);
496a930da9bSDaniel Lezcano 	mutex_unlock(&tz->lock);
4975fc024abSZhang Rui }
498910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_device_update);
4995fc024abSZhang Rui 
5009a99a996SRafael J. Wysocki /**
5019a99a996SRafael J. Wysocki  * thermal_zone_device_exec - Run a callback under the zone lock.
5029a99a996SRafael J. Wysocki  * @tz: Thermal zone.
5039a99a996SRafael J. Wysocki  * @cb: Callback to run.
5049a99a996SRafael J. Wysocki  * @data: Data to pass to the callback.
5059a99a996SRafael J. Wysocki  */
thermal_zone_device_exec(struct thermal_zone_device * tz,void (* cb)(struct thermal_zone_device *,unsigned long),unsigned long data)5069a99a996SRafael J. Wysocki void thermal_zone_device_exec(struct thermal_zone_device *tz,
5079a99a996SRafael J. Wysocki 			      void (*cb)(struct thermal_zone_device *,
5089a99a996SRafael J. Wysocki 					 unsigned long),
5099a99a996SRafael J. Wysocki 			      unsigned long data)
5109a99a996SRafael J. Wysocki {
5119a99a996SRafael J. Wysocki 	mutex_lock(&tz->lock);
5129a99a996SRafael J. Wysocki 
5139a99a996SRafael J. Wysocki 	cb(tz, data);
5149a99a996SRafael J. Wysocki 
5159a99a996SRafael J. Wysocki 	mutex_unlock(&tz->lock);
5169a99a996SRafael J. Wysocki }
5179a99a996SRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_zone_device_exec);
5189a99a996SRafael J. Wysocki 
thermal_zone_device_check(struct work_struct * work)5195fc024abSZhang Rui static void thermal_zone_device_check(struct work_struct *work)
5205fc024abSZhang Rui {
5215fc024abSZhang Rui 	struct thermal_zone_device *tz = container_of(work, struct
5225fc024abSZhang Rui 						      thermal_zone_device,
5235fc024abSZhang Rui 						      poll_queue.work);
5240e70f466SSrinivas Pandruvada 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
5255fc024abSZhang Rui }
5265fc024abSZhang Rui 
for_each_thermal_governor(int (* cb)(struct thermal_governor *,void *),void * data)5273d44a509SDaniel Lezcano int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
5283d44a509SDaniel Lezcano 			      void *data)
5293d44a509SDaniel Lezcano {
5303d44a509SDaniel Lezcano 	struct thermal_governor *gov;
5313d44a509SDaniel Lezcano 	int ret = 0;
5323d44a509SDaniel Lezcano 
5333d44a509SDaniel Lezcano 	mutex_lock(&thermal_governor_lock);
5343d44a509SDaniel Lezcano 	list_for_each_entry(gov, &thermal_governor_list, governor_list) {
5353d44a509SDaniel Lezcano 		ret = cb(gov, data);
5363d44a509SDaniel Lezcano 		if (ret)
5373d44a509SDaniel Lezcano 			break;
5383d44a509SDaniel Lezcano 	}
5393d44a509SDaniel Lezcano 	mutex_unlock(&thermal_governor_lock);
5403d44a509SDaniel Lezcano 
5413d44a509SDaniel Lezcano 	return ret;
5423d44a509SDaniel Lezcano }
5433d44a509SDaniel Lezcano 
for_each_thermal_cooling_device(int (* cb)(struct thermal_cooling_device *,void *),void * data)5443d44a509SDaniel Lezcano int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
5453d44a509SDaniel Lezcano 					      void *), void *data)
5463d44a509SDaniel Lezcano {
5473d44a509SDaniel Lezcano 	struct thermal_cooling_device *cdev;
5483d44a509SDaniel Lezcano 	int ret = 0;
5493d44a509SDaniel Lezcano 
5503d44a509SDaniel Lezcano 	mutex_lock(&thermal_list_lock);
5513d44a509SDaniel Lezcano 	list_for_each_entry(cdev, &thermal_cdev_list, node) {
5523d44a509SDaniel Lezcano 		ret = cb(cdev, data);
5533d44a509SDaniel Lezcano 		if (ret)
5543d44a509SDaniel Lezcano 			break;
5553d44a509SDaniel Lezcano 	}
5563d44a509SDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
5573d44a509SDaniel Lezcano 
5583d44a509SDaniel Lezcano 	return ret;
5593d44a509SDaniel Lezcano }
5603d44a509SDaniel Lezcano 
for_each_thermal_zone(int (* cb)(struct thermal_zone_device *,void *),void * data)5613d44a509SDaniel Lezcano int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
5623d44a509SDaniel Lezcano 			  void *data)
5633d44a509SDaniel Lezcano {
5643d44a509SDaniel Lezcano 	struct thermal_zone_device *tz;
5653d44a509SDaniel Lezcano 	int ret = 0;
5663d44a509SDaniel Lezcano 
5673d44a509SDaniel Lezcano 	mutex_lock(&thermal_list_lock);
5683d44a509SDaniel Lezcano 	list_for_each_entry(tz, &thermal_tz_list, node) {
5693d44a509SDaniel Lezcano 		ret = cb(tz, data);
5703d44a509SDaniel Lezcano 		if (ret)
5713d44a509SDaniel Lezcano 			break;
5723d44a509SDaniel Lezcano 	}
5733d44a509SDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
5743d44a509SDaniel Lezcano 
5753d44a509SDaniel Lezcano 	return ret;
5763d44a509SDaniel Lezcano }
5773d44a509SDaniel Lezcano 
thermal_zone_get_by_id(int id)578329b064fSDaniel Lezcano struct thermal_zone_device *thermal_zone_get_by_id(int id)
579329b064fSDaniel Lezcano {
58082aa68afSThierry Reding 	struct thermal_zone_device *tz, *match = NULL;
581329b064fSDaniel Lezcano 
582329b064fSDaniel Lezcano 	mutex_lock(&thermal_list_lock);
583329b064fSDaniel Lezcano 	list_for_each_entry(tz, &thermal_tz_list, node) {
58482aa68afSThierry Reding 		if (tz->id == id) {
58582aa68afSThierry Reding 			match = tz;
586329b064fSDaniel Lezcano 			break;
587329b064fSDaniel Lezcano 		}
58882aa68afSThierry Reding 	}
589329b064fSDaniel Lezcano 	mutex_unlock(&thermal_list_lock);
590329b064fSDaniel Lezcano 
59182aa68afSThierry Reding 	return match;
592329b064fSDaniel Lezcano }
593329b064fSDaniel Lezcano 
59481193e2eSEduardo Valentin /*
59581193e2eSEduardo Valentin  * Device management section: cooling devices, zones devices, and binding
59681193e2eSEduardo Valentin  *
59781193e2eSEduardo Valentin  * Set of functions provided by the thermal core for:
59881193e2eSEduardo Valentin  * - cooling devices lifecycle: registration, unregistration,
59981193e2eSEduardo Valentin  *				binding, and unbinding.
60081193e2eSEduardo Valentin  * - thermal zone devices lifecycle: registration, unregistration,
60181193e2eSEduardo Valentin  *				     binding, and unbinding.
60281193e2eSEduardo Valentin  */
6035fc024abSZhang Rui 
6045fc024abSZhang Rui /**
605d2e4eb83SEduardo Valentin  * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
606d2e4eb83SEduardo Valentin  * @tz:		pointer to struct thermal_zone_device
60777451ef5SRafael J. Wysocki  * @trip_index:	indicates which trip point the cooling devices is
6085fc024abSZhang Rui  *		associated with in this thermal zone.
609d2e4eb83SEduardo Valentin  * @cdev:	pointer to struct thermal_cooling_device
610d2e4eb83SEduardo Valentin  * @upper:	the Maximum cooling state for this trip point.
611d2e4eb83SEduardo Valentin  *		THERMAL_NO_LIMIT means no upper limit,
612d2e4eb83SEduardo Valentin  *		and the cooling device can be in max_state.
613d2e4eb83SEduardo Valentin  * @lower:	the Minimum cooling state can be used for this trip point.
614d2e4eb83SEduardo Valentin  *		THERMAL_NO_LIMIT means no lower limit,
615d2e4eb83SEduardo Valentin  *		and the cooling device can be in cooling state 0.
6166cd9e9f6SKapileshwar Singh  * @weight:	The weight of the cooling device to be bound to the
6176cd9e9f6SKapileshwar Singh  *		thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
6186cd9e9f6SKapileshwar Singh  *		default value
6195fc024abSZhang Rui  *
620d2e4eb83SEduardo Valentin  * This interface function bind a thermal cooling device to the certain trip
621d2e4eb83SEduardo Valentin  * point of a thermal zone device.
6225fc024abSZhang Rui  * This function is usually called in the thermal zone device .bind callback.
623d2e4eb83SEduardo Valentin  *
624d2e4eb83SEduardo Valentin  * Return: 0 on success, the proper error value otherwise.
6255fc024abSZhang 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)6265fc024abSZhang Rui int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
62777451ef5SRafael J. Wysocki 				     int trip_index,
6285fc024abSZhang Rui 				     struct thermal_cooling_device *cdev,
6296cd9e9f6SKapileshwar Singh 				     unsigned long upper, unsigned long lower,
6306cd9e9f6SKapileshwar Singh 				     unsigned int weight)
6315fc024abSZhang Rui {
6325fc024abSZhang Rui 	struct thermal_instance *dev;
6335fc024abSZhang Rui 	struct thermal_instance *pos;
6345fc024abSZhang Rui 	struct thermal_zone_device *pos1;
6355fc024abSZhang Rui 	struct thermal_cooling_device *pos2;
63677451ef5SRafael J. Wysocki 	const struct thermal_trip *trip;
637790930f4SRafael J. Wysocki 	bool upper_no_limit;
638c408b3d1SViresh Kumar 	int result;
6395fc024abSZhang Rui 
64077451ef5SRafael J. Wysocki 	if (trip_index >= tz->num_trips || trip_index < 0)
6415fc024abSZhang Rui 		return -EINVAL;
6425fc024abSZhang Rui 
64377451ef5SRafael J. Wysocki 	trip = &tz->trips[trip_index];
64477451ef5SRafael J. Wysocki 
6455fc024abSZhang Rui 	list_for_each_entry(pos1, &thermal_tz_list, node) {
6465fc024abSZhang Rui 		if (pos1 == tz)
6475fc024abSZhang Rui 			break;
6485fc024abSZhang Rui 	}
6495fc024abSZhang Rui 	list_for_each_entry(pos2, &thermal_cdev_list, node) {
6505fc024abSZhang Rui 		if (pos2 == cdev)
6515fc024abSZhang Rui 			break;
6525fc024abSZhang Rui 	}
6535fc024abSZhang Rui 
6545fc024abSZhang Rui 	if (tz != pos1 || cdev != pos2)
6555fc024abSZhang Rui 		return -EINVAL;
6565fc024abSZhang Rui 
6575fc024abSZhang Rui 	/* lower default 0, upper default max_state */
6585fc024abSZhang Rui 	lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
659790930f4SRafael J. Wysocki 
660790930f4SRafael J. Wysocki 	if (upper == THERMAL_NO_LIMIT) {
661790930f4SRafael J. Wysocki 		upper = cdev->max_state;
662790930f4SRafael J. Wysocki 		upper_no_limit = true;
663790930f4SRafael J. Wysocki 	} else {
664790930f4SRafael J. Wysocki 		upper_no_limit = false;
665790930f4SRafael J. Wysocki 	}
6665fc024abSZhang Rui 
667c408b3d1SViresh Kumar 	if (lower > upper || upper > cdev->max_state)
6685fc024abSZhang Rui 		return -EINVAL;
6695fc024abSZhang Rui 
67095e3ed15SEduardo Valentin 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
6715fc024abSZhang Rui 	if (!dev)
6725fc024abSZhang Rui 		return -ENOMEM;
6735fc024abSZhang Rui 	dev->tz = tz;
6745fc024abSZhang Rui 	dev->cdev = cdev;
6755fc024abSZhang Rui 	dev->trip = trip;
6765fc024abSZhang Rui 	dev->upper = upper;
677790930f4SRafael J. Wysocki 	dev->upper_no_limit = upper_no_limit;
6785fc024abSZhang Rui 	dev->lower = lower;
6795fc024abSZhang Rui 	dev->target = THERMAL_NO_TARGET;
6806cd9e9f6SKapileshwar Singh 	dev->weight = weight;
6815fc024abSZhang Rui 
6825a5b7d8dSkeliu 	result = ida_alloc(&tz->ida, GFP_KERNEL);
683b31ef828SMatthew Wilcox 	if (result < 0)
6845fc024abSZhang Rui 		goto free_mem;
6855fc024abSZhang Rui 
686b31ef828SMatthew Wilcox 	dev->id = result;
6875fc024abSZhang Rui 	sprintf(dev->name, "cdev%d", dev->id);
6885fc024abSZhang Rui 	result =
6895fc024abSZhang Rui 	    sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
6905fc024abSZhang Rui 	if (result)
691b31ef828SMatthew Wilcox 		goto release_ida;
6925fc024abSZhang Rui 
693edbd6bbeSDan Carpenter 	snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point",
694edbd6bbeSDan Carpenter 		 dev->id);
6955fc024abSZhang Rui 	sysfs_attr_init(&dev->attr.attr);
6965fc024abSZhang Rui 	dev->attr.attr.name = dev->attr_name;
6975fc024abSZhang Rui 	dev->attr.attr.mode = 0444;
69833e678d4SViresh Kumar 	dev->attr.show = trip_point_show;
6995fc024abSZhang Rui 	result = device_create_file(&tz->device, &dev->attr);
7005fc024abSZhang Rui 	if (result)
7015fc024abSZhang Rui 		goto remove_symbol_link;
7025fc024abSZhang Rui 
703edbd6bbeSDan Carpenter 	snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),
704edbd6bbeSDan Carpenter 		 "cdev%d_weight", dev->id);
705db916513SJavi Merino 	sysfs_attr_init(&dev->weight_attr.attr);
706db916513SJavi Merino 	dev->weight_attr.attr.name = dev->weight_attr_name;
707db916513SJavi Merino 	dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
70833e678d4SViresh Kumar 	dev->weight_attr.show = weight_show;
70933e678d4SViresh Kumar 	dev->weight_attr.store = weight_store;
710db916513SJavi Merino 	result = device_create_file(&tz->device, &dev->weight_attr);
711db916513SJavi Merino 	if (result)
712db916513SJavi Merino 		goto remove_trip_file;
713db916513SJavi Merino 
7145fc024abSZhang Rui 	mutex_lock(&tz->lock);
7155fc024abSZhang Rui 	mutex_lock(&cdev->lock);
7165fc024abSZhang Rui 	list_for_each_entry(pos, &tz->thermal_instances, tz_node)
7175fc024abSZhang Rui 		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
7185fc024abSZhang Rui 			result = -EEXIST;
7195fc024abSZhang Rui 			break;
7205fc024abSZhang Rui 		}
7215fc024abSZhang Rui 	if (!result) {
7225fc024abSZhang Rui 		list_add_tail(&dev->tz_node, &tz->thermal_instances);
7235fc024abSZhang Rui 		list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
7244511f716SChen Yu 		atomic_set(&tz->need_update, 1);
7255fc024abSZhang Rui 	}
7265fc024abSZhang Rui 	mutex_unlock(&cdev->lock);
7275fc024abSZhang Rui 	mutex_unlock(&tz->lock);
7285fc024abSZhang Rui 
7295fc024abSZhang Rui 	if (!result)
7305fc024abSZhang Rui 		return 0;
7315fc024abSZhang Rui 
732db916513SJavi Merino 	device_remove_file(&tz->device, &dev->weight_attr);
733db916513SJavi Merino remove_trip_file:
7345fc024abSZhang Rui 	device_remove_file(&tz->device, &dev->attr);
7355fc024abSZhang Rui remove_symbol_link:
7365fc024abSZhang Rui 	sysfs_remove_link(&tz->device.kobj, dev->name);
737b31ef828SMatthew Wilcox release_ida:
7385a5b7d8dSkeliu 	ida_free(&tz->ida, dev->id);
7395fc024abSZhang Rui free_mem:
7405fc024abSZhang Rui 	kfree(dev);
7415fc024abSZhang Rui 	return result;
7425fc024abSZhang Rui }
743910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
7445fc024abSZhang Rui 
7455fc024abSZhang Rui /**
7469892e5dcSEduardo Valentin  * thermal_zone_unbind_cooling_device() - unbind a cooling device from a
7479892e5dcSEduardo Valentin  *					  thermal zone.
7489892e5dcSEduardo Valentin  * @tz:		pointer to a struct thermal_zone_device.
74977451ef5SRafael J. Wysocki  * @trip_index:	indicates which trip point the cooling devices is
7505fc024abSZhang Rui  *		associated with in this thermal zone.
7519892e5dcSEduardo Valentin  * @cdev:	pointer to a struct thermal_cooling_device.
7525fc024abSZhang Rui  *
7539892e5dcSEduardo Valentin  * This interface function unbind a thermal cooling device from the certain
7549892e5dcSEduardo Valentin  * trip point of a thermal zone device.
7555fc024abSZhang Rui  * This function is usually called in the thermal zone device .unbind callback.
7569892e5dcSEduardo Valentin  *
7579892e5dcSEduardo Valentin  * Return: 0 on success, the proper error value otherwise.
7585fc024abSZhang Rui  */
thermal_zone_unbind_cooling_device(struct thermal_zone_device * tz,int trip_index,struct thermal_cooling_device * cdev)7595fc024abSZhang Rui int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
76077451ef5SRafael J. Wysocki 				       int trip_index,
7615fc024abSZhang Rui 				       struct thermal_cooling_device *cdev)
7625fc024abSZhang Rui {
7635fc024abSZhang Rui 	struct thermal_instance *pos, *next;
76477451ef5SRafael J. Wysocki 	const struct thermal_trip *trip;
7655fc024abSZhang Rui 
7665fc024abSZhang Rui 	mutex_lock(&tz->lock);
7675fc024abSZhang Rui 	mutex_lock(&cdev->lock);
76877451ef5SRafael J. Wysocki 	trip = &tz->trips[trip_index];
7695fc024abSZhang Rui 	list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
7705fc024abSZhang Rui 		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
7715fc024abSZhang Rui 			list_del(&pos->tz_node);
7725fc024abSZhang Rui 			list_del(&pos->cdev_node);
7735fc024abSZhang Rui 			mutex_unlock(&cdev->lock);
7745fc024abSZhang Rui 			mutex_unlock(&tz->lock);
7755fc024abSZhang Rui 			goto unbind;
7765fc024abSZhang Rui 		}
7775fc024abSZhang Rui 	}
7785fc024abSZhang Rui 	mutex_unlock(&cdev->lock);
7795fc024abSZhang Rui 	mutex_unlock(&tz->lock);
7805fc024abSZhang Rui 
7815fc024abSZhang Rui 	return -ENODEV;
7825fc024abSZhang Rui 
7835fc024abSZhang Rui unbind:
784528464eaSViresh Kumar 	device_remove_file(&tz->device, &pos->weight_attr);
7855fc024abSZhang Rui 	device_remove_file(&tz->device, &pos->attr);
7865fc024abSZhang Rui 	sysfs_remove_link(&tz->device.kobj, pos->name);
7875a5b7d8dSkeliu 	ida_free(&tz->ida, pos->id);
7885fc024abSZhang Rui 	kfree(pos);
7895fc024abSZhang Rui 	return 0;
7905fc024abSZhang Rui }
791910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
7925fc024abSZhang Rui 
thermal_release(struct device * dev)7935fc024abSZhang Rui static void thermal_release(struct device *dev)
7945fc024abSZhang Rui {
7955fc024abSZhang Rui 	struct thermal_zone_device *tz;
7965fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
7975fc024abSZhang Rui 
7985fc024abSZhang Rui 	if (!strncmp(dev_name(dev), "thermal_zone",
7995fc024abSZhang Rui 		     sizeof("thermal_zone") - 1)) {
8005fc024abSZhang Rui 		tz = to_thermal_zone(dev);
8016a6cd25bSChristophe Jaillet 		thermal_zone_destroy_device_groups(tz);
802d35f29edSGuenter Roeck 		mutex_destroy(&tz->lock);
8035fc024abSZhang Rui 		kfree(tz);
804732e4c8dSdurgadoss.r@intel.com 	} else if (!strncmp(dev_name(dev), "cooling_device",
805732e4c8dSdurgadoss.r@intel.com 			    sizeof("cooling_device") - 1)) {
8065fc024abSZhang Rui 		cdev = to_cooling_device(dev);
807e398421fSViresh Kumar 		thermal_cooling_device_destroy_sysfs(cdev);
808e398421fSViresh Kumar 		kfree(cdev->type);
809e398421fSViresh Kumar 		ida_free(&thermal_cdev_ida, cdev->id);
8105fc024abSZhang Rui 		kfree(cdev);
8115fc024abSZhang Rui 	}
8125fc024abSZhang Rui }
8135fc024abSZhang Rui 
8149e0a9be2SRafael J. Wysocki static struct class *thermal_class;
8155fc024abSZhang Rui 
8164b0d3c2dSEduardo Valentin static inline
print_bind_err_msg(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev,int ret)8174b0d3c2dSEduardo Valentin void print_bind_err_msg(struct thermal_zone_device *tz,
818f502ab84SEduardo Valentin 			struct thermal_cooling_device *cdev, int ret)
819f502ab84SEduardo Valentin {
820f502ab84SEduardo Valentin 	dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
821f502ab84SEduardo Valentin 		tz->type, cdev->type, ret);
822f502ab84SEduardo Valentin }
823f502ab84SEduardo Valentin 
bind_cdev(struct thermal_cooling_device * cdev)824949aad83SEduardo Valentin static void bind_cdev(struct thermal_cooling_device *cdev)
825949aad83SEduardo Valentin {
826ded2d383SZhang Rui 	int ret;
827949aad83SEduardo Valentin 	struct thermal_zone_device *pos = NULL;
828949aad83SEduardo Valentin 
829949aad83SEduardo Valentin 	list_for_each_entry(pos, &thermal_tz_list, node) {
830949aad83SEduardo Valentin 		if (pos->ops->bind) {
831949aad83SEduardo Valentin 			ret = pos->ops->bind(pos, cdev);
832949aad83SEduardo Valentin 			if (ret)
833949aad83SEduardo Valentin 				print_bind_err_msg(pos, cdev, ret);
834949aad83SEduardo Valentin 		}
835949aad83SEduardo Valentin 	}
836949aad83SEduardo Valentin }
837949aad83SEduardo Valentin 
8385fc024abSZhang Rui /**
839a116b5d4SEduardo Valentin  * __thermal_cooling_device_register() - register a new thermal cooling device
840a116b5d4SEduardo Valentin  * @np:		a pointer to a device tree node.
8415fc024abSZhang Rui  * @type:	the thermal cooling device type.
8425fc024abSZhang Rui  * @devdata:	device private data.
8435fc024abSZhang Rui  * @ops:		standard thermal cooling devices callbacks.
8443a6eccb3SEduardo Valentin  *
8453a6eccb3SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
8463a6eccb3SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
8473a6eccb3SEduardo Valentin  * to all the thermal zone devices registered at the same time.
848a116b5d4SEduardo Valentin  * It also gives the opportunity to link the cooling device to a device tree
849a116b5d4SEduardo Valentin  * node, so that it can be bound to a thermal zone created out of device tree.
8503a6eccb3SEduardo Valentin  *
8513a6eccb3SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
8523a6eccb3SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
8535fc024abSZhang Rui  */
854a116b5d4SEduardo 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)855a116b5d4SEduardo Valentin __thermal_cooling_device_register(struct device_node *np,
856f991de53SJean-Francois Dagenais 				  const char *type, void *devdata,
8575fc024abSZhang Rui 				  const struct thermal_cooling_device_ops *ops)
8585fc024abSZhang Rui {
8595fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
8604511f716SChen Yu 	struct thermal_zone_device *pos = NULL;
8610a5c2671SZiyang Xuan 	int id, ret;
8625fc024abSZhang Rui 
8635fc024abSZhang Rui 	if (!ops || !ops->get_max_state || !ops->get_cur_state ||
8645fc024abSZhang Rui 	    !ops->set_cur_state)
8655fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
8665fc024abSZhang Rui 
8679e0a9be2SRafael J. Wysocki 	if (!thermal_class)
8689e0a9be2SRafael J. Wysocki 		return ERR_PTR(-ENODEV);
8699e0a9be2SRafael J. Wysocki 
87095e3ed15SEduardo Valentin 	cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
8715fc024abSZhang Rui 	if (!cdev)
8725fc024abSZhang Rui 		return ERR_PTR(-ENOMEM);
8735fc024abSZhang Rui 
8745a5b7d8dSkeliu 	ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
87558483761SDaniel Lezcano 	if (ret < 0)
87658483761SDaniel Lezcano 		goto out_kfree_cdev;
87758483761SDaniel Lezcano 	cdev->id = ret;
8780a5c2671SZiyang Xuan 	id = ret;
87958483761SDaniel Lezcano 
88058483761SDaniel Lezcano 	cdev->type = kstrdup(type ? type : "", GFP_KERNEL);
88158483761SDaniel Lezcano 	if (!cdev->type) {
88258483761SDaniel Lezcano 		ret = -ENOMEM;
88358483761SDaniel Lezcano 		goto out_ida_remove;
8845fc024abSZhang Rui 	}
8855fc024abSZhang Rui 
8865fc024abSZhang Rui 	mutex_init(&cdev->lock);
8875fc024abSZhang Rui 	INIT_LIST_HEAD(&cdev->thermal_instances);
888a116b5d4SEduardo Valentin 	cdev->np = np;
8895fc024abSZhang Rui 	cdev->ops = ops;
8905ca0cce5SNi Wade 	cdev->updated = false;
8919e0a9be2SRafael J. Wysocki 	cdev->device.class = thermal_class;
8925fc024abSZhang Rui 	cdev->devdata = devdata;
893c408b3d1SViresh Kumar 
894e49a1e1eSDan Carpenter 	ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
895e398421fSViresh Kumar 	if (ret)
896e398421fSViresh Kumar 		goto out_cdev_type;
897c408b3d1SViresh Kumar 
8988ea22951SViresh Kumar 	thermal_cooling_device_setup_sysfs(cdev);
8996c54b7bcSViresh Kumar 
9004748f968SYang Yingliang 	ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
901e398421fSViresh Kumar 	if (ret)
902e398421fSViresh Kumar 		goto out_cooling_dev;
9036c54b7bcSViresh Kumar 
90458483761SDaniel Lezcano 	ret = device_register(&cdev->device);
905e398421fSViresh Kumar 	if (ret) {
906e398421fSViresh Kumar 		/* thermal_release() handles rest of the cleanup */
907e398421fSViresh Kumar 		put_device(&cdev->device);
908e398421fSViresh Kumar 		return ERR_PTR(ret);
909e398421fSViresh Kumar 	}
9105fc024abSZhang Rui 
9115fc024abSZhang Rui 	/* Add 'this' new cdev to the global cdev list */
9125fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
913cd246fa9SRafael J. Wysocki 
9145fc024abSZhang Rui 	list_add(&cdev->node, &thermal_cdev_list);
9155fc024abSZhang Rui 
9165fc024abSZhang Rui 	/* Update binding information for 'this' new cdev */
9175fc024abSZhang Rui 	bind_cdev(cdev);
9185fc024abSZhang Rui 
9194511f716SChen Yu 	list_for_each_entry(pos, &thermal_tz_list, node)
9204511f716SChen Yu 		if (atomic_cmpxchg(&pos->need_update, 1, 0))
9210e70f466SSrinivas Pandruvada 			thermal_zone_device_update(pos,
9220e70f466SSrinivas Pandruvada 						   THERMAL_EVENT_UNSPECIFIED);
923cd246fa9SRafael J. Wysocki 
9244511f716SChen Yu 	mutex_unlock(&thermal_list_lock);
9254511f716SChen Yu 
9265fc024abSZhang Rui 	return cdev;
92758483761SDaniel Lezcano 
928e398421fSViresh Kumar out_cooling_dev:
92998a160e8SYang Yingliang 	thermal_cooling_device_destroy_sysfs(cdev);
930e398421fSViresh Kumar out_cdev_type:
93158483761SDaniel Lezcano 	kfree(cdev->type);
93258483761SDaniel Lezcano out_ida_remove:
9335a5b7d8dSkeliu 	ida_free(&thermal_cdev_ida, id);
93458483761SDaniel Lezcano out_kfree_cdev:
935d44616c6SDaniel Lezcano 	kfree(cdev);
93658483761SDaniel Lezcano 	return ERR_PTR(ret);
9375fc024abSZhang Rui }
938a116b5d4SEduardo Valentin 
939a116b5d4SEduardo Valentin /**
940a116b5d4SEduardo Valentin  * thermal_cooling_device_register() - register a new thermal cooling device
941a116b5d4SEduardo Valentin  * @type:	the thermal cooling device type.
942a116b5d4SEduardo Valentin  * @devdata:	device private data.
943a116b5d4SEduardo Valentin  * @ops:		standard thermal cooling devices callbacks.
944a116b5d4SEduardo Valentin  *
945a116b5d4SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
946a116b5d4SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
947a116b5d4SEduardo Valentin  * to all the thermal zone devices registered at the same time.
948a116b5d4SEduardo Valentin  *
949a116b5d4SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
950a116b5d4SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
951a116b5d4SEduardo Valentin  */
952a116b5d4SEduardo Valentin struct thermal_cooling_device *
thermal_cooling_device_register(const char * type,void * devdata,const struct thermal_cooling_device_ops * ops)953f991de53SJean-Francois Dagenais thermal_cooling_device_register(const char *type, void *devdata,
954a116b5d4SEduardo Valentin 				const struct thermal_cooling_device_ops *ops)
955a116b5d4SEduardo Valentin {
956a116b5d4SEduardo Valentin 	return __thermal_cooling_device_register(NULL, type, devdata, ops);
957a116b5d4SEduardo Valentin }
958910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
9595fc024abSZhang Rui 
9605fc024abSZhang Rui /**
961a116b5d4SEduardo Valentin  * thermal_of_cooling_device_register() - register an OF thermal cooling device
962a116b5d4SEduardo Valentin  * @np:		a pointer to a device tree node.
963a116b5d4SEduardo Valentin  * @type:	the thermal cooling device type.
964a116b5d4SEduardo Valentin  * @devdata:	device private data.
965a116b5d4SEduardo Valentin  * @ops:		standard thermal cooling devices callbacks.
966a116b5d4SEduardo Valentin  *
967a116b5d4SEduardo Valentin  * This function will register a cooling device with device tree node reference.
968a116b5d4SEduardo Valentin  * This interface function adds a new thermal cooling device (fan/processor/...)
969a116b5d4SEduardo Valentin  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
970a116b5d4SEduardo Valentin  * to all the thermal zone devices registered at the same time.
971a116b5d4SEduardo Valentin  *
972a116b5d4SEduardo Valentin  * Return: a pointer to the created struct thermal_cooling_device or an
973a116b5d4SEduardo Valentin  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
974a116b5d4SEduardo Valentin  */
975a116b5d4SEduardo 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)976a116b5d4SEduardo Valentin thermal_of_cooling_device_register(struct device_node *np,
977f991de53SJean-Francois Dagenais 				   const char *type, void *devdata,
978a116b5d4SEduardo Valentin 				   const struct thermal_cooling_device_ops *ops)
979a116b5d4SEduardo Valentin {
980a116b5d4SEduardo Valentin 	return __thermal_cooling_device_register(np, type, devdata, ops);
981a116b5d4SEduardo Valentin }
982a116b5d4SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
983a116b5d4SEduardo Valentin 
thermal_cooling_device_release(struct device * dev,void * res)984b4ab114cSGuenter Roeck static void thermal_cooling_device_release(struct device *dev, void *res)
985b4ab114cSGuenter Roeck {
986b4ab114cSGuenter Roeck 	thermal_cooling_device_unregister(
987b4ab114cSGuenter Roeck 				*(struct thermal_cooling_device **)res);
988b4ab114cSGuenter Roeck }
989b4ab114cSGuenter Roeck 
990b4ab114cSGuenter Roeck /**
991b4ab114cSGuenter Roeck  * devm_thermal_of_cooling_device_register() - register an OF thermal cooling
992b4ab114cSGuenter Roeck  *					       device
993b4ab114cSGuenter Roeck  * @dev:	a valid struct device pointer of a sensor device.
994b4ab114cSGuenter Roeck  * @np:		a pointer to a device tree node.
995b4ab114cSGuenter Roeck  * @type:	the thermal cooling device type.
996b4ab114cSGuenter Roeck  * @devdata:	device private data.
997b4ab114cSGuenter Roeck  * @ops:	standard thermal cooling devices callbacks.
998b4ab114cSGuenter Roeck  *
999b4ab114cSGuenter Roeck  * This function will register a cooling device with device tree node reference.
1000b4ab114cSGuenter Roeck  * This interface function adds a new thermal cooling device (fan/processor/...)
1001b4ab114cSGuenter Roeck  * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
1002b4ab114cSGuenter Roeck  * to all the thermal zone devices registered at the same time.
1003b4ab114cSGuenter Roeck  *
1004b4ab114cSGuenter Roeck  * Return: a pointer to the created struct thermal_cooling_device or an
1005b4ab114cSGuenter Roeck  * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
1006b4ab114cSGuenter Roeck  */
1007b4ab114cSGuenter 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)1008b4ab114cSGuenter Roeck devm_thermal_of_cooling_device_register(struct device *dev,
1009b4ab114cSGuenter Roeck 				struct device_node *np,
1010b4ab114cSGuenter Roeck 				char *type, void *devdata,
1011b4ab114cSGuenter Roeck 				const struct thermal_cooling_device_ops *ops)
1012b4ab114cSGuenter Roeck {
1013b4ab114cSGuenter Roeck 	struct thermal_cooling_device **ptr, *tcd;
1014b4ab114cSGuenter Roeck 
1015b4ab114cSGuenter Roeck 	ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr),
1016b4ab114cSGuenter Roeck 			   GFP_KERNEL);
1017b4ab114cSGuenter Roeck 	if (!ptr)
1018b4ab114cSGuenter Roeck 		return ERR_PTR(-ENOMEM);
1019b4ab114cSGuenter Roeck 
1020b4ab114cSGuenter Roeck 	tcd = __thermal_cooling_device_register(np, type, devdata, ops);
1021b4ab114cSGuenter Roeck 	if (IS_ERR(tcd)) {
1022b4ab114cSGuenter Roeck 		devres_free(ptr);
1023b4ab114cSGuenter Roeck 		return tcd;
1024b4ab114cSGuenter Roeck 	}
1025b4ab114cSGuenter Roeck 
1026b4ab114cSGuenter Roeck 	*ptr = tcd;
1027b4ab114cSGuenter Roeck 	devres_add(dev, ptr);
1028b4ab114cSGuenter Roeck 
1029b4ab114cSGuenter Roeck 	return tcd;
1030b4ab114cSGuenter Roeck }
1031b4ab114cSGuenter Roeck EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
1032b4ab114cSGuenter Roeck 
thermal_cooling_device_present(struct thermal_cooling_device * cdev)1033c43198afSRafael J. Wysocki static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev)
1034c43198afSRafael J. Wysocki {
1035c43198afSRafael J. Wysocki 	struct thermal_cooling_device *pos = NULL;
1036c43198afSRafael J. Wysocki 
1037c43198afSRafael J. Wysocki 	list_for_each_entry(pos, &thermal_cdev_list, node) {
1038c43198afSRafael J. Wysocki 		if (pos == cdev)
1039c43198afSRafael J. Wysocki 			return true;
1040c43198afSRafael J. Wysocki 	}
1041c43198afSRafael J. Wysocki 
1042c43198afSRafael J. Wysocki 	return false;
1043c43198afSRafael J. Wysocki }
1044c43198afSRafael J. Wysocki 
1045790930f4SRafael J. Wysocki /**
1046790930f4SRafael J. Wysocki  * thermal_cooling_device_update - Update a cooling device object
1047790930f4SRafael J. Wysocki  * @cdev: Target cooling device.
1048790930f4SRafael J. Wysocki  *
1049790930f4SRafael J. Wysocki  * Update @cdev to reflect a change of the underlying hardware or platform.
1050790930f4SRafael J. Wysocki  *
1051790930f4SRafael J. Wysocki  * Must be called when the maximum cooling state of @cdev becomes invalid and so
1052790930f4SRafael J. Wysocki  * its .get_max_state() callback needs to be run to produce the new maximum
1053790930f4SRafael J. Wysocki  * cooling state value.
1054790930f4SRafael J. Wysocki  */
thermal_cooling_device_update(struct thermal_cooling_device * cdev)1055790930f4SRafael J. Wysocki void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
1056790930f4SRafael J. Wysocki {
1057790930f4SRafael J. Wysocki 	struct thermal_instance *ti;
1058790930f4SRafael J. Wysocki 	unsigned long state;
1059790930f4SRafael J. Wysocki 
1060790930f4SRafael J. Wysocki 	if (IS_ERR_OR_NULL(cdev))
1061790930f4SRafael J. Wysocki 		return;
1062790930f4SRafael J. Wysocki 
1063790930f4SRafael J. Wysocki 	/*
1064790930f4SRafael J. Wysocki 	 * Hold thermal_list_lock throughout the update to prevent the device
1065790930f4SRafael J. Wysocki 	 * from going away while being updated.
1066790930f4SRafael J. Wysocki 	 */
1067790930f4SRafael J. Wysocki 	mutex_lock(&thermal_list_lock);
1068790930f4SRafael J. Wysocki 
1069790930f4SRafael J. Wysocki 	if (!thermal_cooling_device_present(cdev))
1070790930f4SRafael J. Wysocki 		goto unlock_list;
1071790930f4SRafael J. Wysocki 
1072790930f4SRafael J. Wysocki 	/*
1073790930f4SRafael J. Wysocki 	 * Update under the cdev lock to prevent the state from being set beyond
1074790930f4SRafael J. Wysocki 	 * the new limit concurrently.
1075790930f4SRafael J. Wysocki 	 */
1076790930f4SRafael J. Wysocki 	mutex_lock(&cdev->lock);
1077790930f4SRafael J. Wysocki 
1078790930f4SRafael J. Wysocki 	if (cdev->ops->get_max_state(cdev, &cdev->max_state))
1079790930f4SRafael J. Wysocki 		goto unlock;
1080790930f4SRafael J. Wysocki 
1081790930f4SRafael J. Wysocki 	thermal_cooling_device_stats_reinit(cdev);
1082790930f4SRafael J. Wysocki 
1083790930f4SRafael J. Wysocki 	list_for_each_entry(ti, &cdev->thermal_instances, cdev_node) {
1084790930f4SRafael J. Wysocki 		if (ti->upper == cdev->max_state)
1085790930f4SRafael J. Wysocki 			continue;
1086790930f4SRafael J. Wysocki 
1087790930f4SRafael J. Wysocki 		if (ti->upper < cdev->max_state) {
1088790930f4SRafael J. Wysocki 			if (ti->upper_no_limit)
1089790930f4SRafael J. Wysocki 				ti->upper = cdev->max_state;
1090790930f4SRafael J. Wysocki 
1091790930f4SRafael J. Wysocki 			continue;
1092790930f4SRafael J. Wysocki 		}
1093790930f4SRafael J. Wysocki 
1094790930f4SRafael J. Wysocki 		ti->upper = cdev->max_state;
1095790930f4SRafael J. Wysocki 		if (ti->lower > ti->upper)
1096790930f4SRafael J. Wysocki 			ti->lower = ti->upper;
1097790930f4SRafael J. Wysocki 
1098790930f4SRafael J. Wysocki 		if (ti->target == THERMAL_NO_TARGET)
1099790930f4SRafael J. Wysocki 			continue;
1100790930f4SRafael J. Wysocki 
1101790930f4SRafael J. Wysocki 		if (ti->target > ti->upper)
1102790930f4SRafael J. Wysocki 			ti->target = ti->upper;
1103790930f4SRafael J. Wysocki 	}
1104790930f4SRafael J. Wysocki 
1105790930f4SRafael J. Wysocki 	if (cdev->ops->get_cur_state(cdev, &state) || state > cdev->max_state)
1106790930f4SRafael J. Wysocki 		goto unlock;
1107790930f4SRafael J. Wysocki 
1108790930f4SRafael J. Wysocki 	thermal_cooling_device_stats_update(cdev, state);
1109790930f4SRafael J. Wysocki 
1110790930f4SRafael J. Wysocki unlock:
1111790930f4SRafael J. Wysocki 	mutex_unlock(&cdev->lock);
1112790930f4SRafael J. Wysocki 
1113790930f4SRafael J. Wysocki unlock_list:
1114790930f4SRafael J. Wysocki 	mutex_unlock(&thermal_list_lock);
1115790930f4SRafael J. Wysocki }
1116790930f4SRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
1117790930f4SRafael J. Wysocki 
1118a116b5d4SEduardo Valentin /**
111938e7b549SEduardo Valentin  * thermal_cooling_device_unregister - removes a thermal cooling device
11205fc024abSZhang Rui  * @cdev:	the thermal cooling device to remove.
11215fc024abSZhang Rui  *
112238e7b549SEduardo Valentin  * thermal_cooling_device_unregister() must be called when a registered
112338e7b549SEduardo Valentin  * thermal cooling device is no longer needed.
11245fc024abSZhang Rui  */
thermal_cooling_device_unregister(struct thermal_cooling_device * cdev)11255fc024abSZhang Rui void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
11265fc024abSZhang Rui {
11275fc024abSZhang Rui 	struct thermal_zone_device *tz;
11285fc024abSZhang Rui 
11295fc024abSZhang Rui 	if (!cdev)
11305fc024abSZhang Rui 		return;
11315fc024abSZhang Rui 
11325fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
1133c43198afSRafael J. Wysocki 
1134c43198afSRafael J. Wysocki 	if (!thermal_cooling_device_present(cdev)) {
11355fc024abSZhang Rui 		mutex_unlock(&thermal_list_lock);
11365fc024abSZhang Rui 		return;
11375fc024abSZhang Rui 	}
1138c43198afSRafael J. Wysocki 
11395fc024abSZhang Rui 	list_del(&cdev->node);
11405fc024abSZhang Rui 
11415fc024abSZhang Rui 	/* Unbind all thermal zones associated with 'this' cdev */
11425fc024abSZhang Rui 	list_for_each_entry(tz, &thermal_tz_list, node) {
1143ded2d383SZhang Rui 		if (tz->ops->unbind)
11445fc024abSZhang Rui 			tz->ops->unbind(tz, cdev);
11455fc024abSZhang Rui 	}
11465fc024abSZhang Rui 
11475fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
11485fc024abSZhang Rui 
114947e3f000SViresh Kumar 	device_unregister(&cdev->device);
11505fc024abSZhang Rui }
1151910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
11525fc024abSZhang Rui 
bind_tz(struct thermal_zone_device * tz)115390f5b5bbSEduardo Valentin static void bind_tz(struct thermal_zone_device *tz)
11545fc024abSZhang Rui {
1155ded2d383SZhang Rui 	int ret;
115690f5b5bbSEduardo Valentin 	struct thermal_cooling_device *pos = NULL;
11575fc024abSZhang Rui 
1158ded2d383SZhang Rui 	if (!tz->ops->bind)
1159d0b7306dSMichele Di Giorgio 		return;
116090f5b5bbSEduardo Valentin 
116190f5b5bbSEduardo Valentin 	mutex_lock(&thermal_list_lock);
116290f5b5bbSEduardo Valentin 
116390f5b5bbSEduardo Valentin 	list_for_each_entry(pos, &thermal_cdev_list, node) {
116490f5b5bbSEduardo Valentin 		ret = tz->ops->bind(tz, pos);
116590f5b5bbSEduardo Valentin 		if (ret)
116690f5b5bbSEduardo Valentin 			print_bind_err_msg(tz, pos, ret);
116790f5b5bbSEduardo Valentin 	}
1168d0b7306dSMichele Di Giorgio 
116990f5b5bbSEduardo Valentin 	mutex_unlock(&thermal_list_lock);
11705fc024abSZhang Rui }
11715fc024abSZhang Rui 
thermal_set_delay_jiffies(unsigned long * delay_jiffies,int delay_ms)1172e5f2cda6SDaniel Lezcano static void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
1173e5f2cda6SDaniel Lezcano {
1174e5f2cda6SDaniel Lezcano 	*delay_jiffies = msecs_to_jiffies(delay_ms);
1175e5f2cda6SDaniel Lezcano 	if (delay_ms > 1000)
1176e5f2cda6SDaniel Lezcano 		*delay_jiffies = round_jiffies(*delay_jiffies);
1177e5f2cda6SDaniel Lezcano }
1178e5f2cda6SDaniel Lezcano 
thermal_zone_get_crit_temp(struct thermal_zone_device * tz,int * temp)11797c3d5c20SDaniel Lezcano int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
11807c3d5c20SDaniel Lezcano {
11817c3d5c20SDaniel Lezcano 	int i, ret = -EINVAL;
11827c3d5c20SDaniel Lezcano 
11837c3d5c20SDaniel Lezcano 	if (tz->ops->get_crit_temp)
11847c3d5c20SDaniel Lezcano 		return tz->ops->get_crit_temp(tz, temp);
11857c3d5c20SDaniel Lezcano 
11867c3d5c20SDaniel Lezcano 	if (!tz->trips)
11877c3d5c20SDaniel Lezcano 		return -EINVAL;
11887c3d5c20SDaniel Lezcano 
11897c3d5c20SDaniel Lezcano 	mutex_lock(&tz->lock);
11907c3d5c20SDaniel Lezcano 
11917c3d5c20SDaniel Lezcano 	for (i = 0; i < tz->num_trips; i++) {
11927c3d5c20SDaniel Lezcano 		if (tz->trips[i].type == THERMAL_TRIP_CRITICAL) {
11937c3d5c20SDaniel Lezcano 			*temp = tz->trips[i].temperature;
11947c3d5c20SDaniel Lezcano 			ret = 0;
11957c3d5c20SDaniel Lezcano 			break;
11967c3d5c20SDaniel Lezcano 		}
11977c3d5c20SDaniel Lezcano 	}
11987c3d5c20SDaniel Lezcano 
11997c3d5c20SDaniel Lezcano 	mutex_unlock(&tz->lock);
12007c3d5c20SDaniel Lezcano 
12017c3d5c20SDaniel Lezcano 	return ret;
12027c3d5c20SDaniel Lezcano }
12037c3d5c20SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
12047c3d5c20SDaniel Lezcano 
12055fc024abSZhang Rui /**
1206fae11de5SDaniel Lezcano  * thermal_zone_device_register_with_trips() - register a new thermal zone device
12075fc024abSZhang Rui  * @type:	the thermal zone device type
1208fae11de5SDaniel Lezcano  * @trips:	a pointer to an array of thermal trips
1209e5bfcd30SDaniel Lezcano  * @num_trips:	the number of trip points the thermal zone support
12105fc024abSZhang Rui  * @mask:	a bit string indicating the writeablility of trip points
12115fc024abSZhang Rui  * @devdata:	private device data
12125fc024abSZhang Rui  * @ops:	standard thermal zone device callbacks
12135fc024abSZhang Rui  * @tzp:	thermal zone platform parameters
12145fc024abSZhang Rui  * @passive_delay: number of milliseconds to wait between polls when
12155fc024abSZhang Rui  *		   performing passive cooling
12165fc024abSZhang Rui  * @polling_delay: number of milliseconds to wait between polls when checking
12175fc024abSZhang Rui  *		   whether trip points have been crossed (0 for interrupt
12185fc024abSZhang Rui  *		   driven systems)
12195fc024abSZhang Rui  *
1220a00e55f9SEduardo Valentin  * This interface function adds a new thermal zone device (sensor) to
1221a00e55f9SEduardo Valentin  * /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the
1222a00e55f9SEduardo Valentin  * thermal cooling devices registered at the same time.
12235fc024abSZhang Rui  * thermal_zone_device_unregister() must be called when the device is no
12245fc024abSZhang Rui  * longer needed. The passive cooling depends on the .get_trend() return value.
1225a00e55f9SEduardo Valentin  *
1226a00e55f9SEduardo Valentin  * Return: a pointer to the created struct thermal_zone_device or an
1227a00e55f9SEduardo Valentin  * in case of error, an ERR_PTR. Caller must check return value with
1228a00e55f9SEduardo Valentin  * IS_ERR*() helpers.
12295fc024abSZhang Rui  */
1230eb7be329SEduardo 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)1231fae11de5SDaniel Lezcano thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *trips, int num_trips, int mask,
1232eb7be329SEduardo Valentin 					void *devdata, struct thermal_zone_device_ops *ops,
123380ddce5fSAhmad Fatoum 					const struct thermal_zone_params *tzp, int passive_delay,
1234eb7be329SEduardo Valentin 					int polling_delay)
12355fc024abSZhang Rui {
12365fc024abSZhang Rui 	struct thermal_zone_device *tz;
1237adc8749bSYue Hu 	int id;
12385fc024abSZhang Rui 	int result;
12395fc024abSZhang Rui 	int count;
1240e33df1d2SJavi Merino 	struct thermal_governor *governor;
12415fc024abSZhang Rui 
124267eed44bSAmit Kucheria 	if (!type || strlen(type) == 0) {
12433f95ac32SDaniel Lezcano 		pr_err("No thermal zone type defined\n");
124454fa38ccSEduardo Valentin 		return ERR_PTR(-EINVAL);
124567eed44bSAmit Kucheria 	}
124654fa38ccSEduardo Valentin 
1247c71d8035SLad Prabhakar 	if (strlen(type) >= THERMAL_NAME_LENGTH) {
12483f95ac32SDaniel Lezcano 		pr_err("Thermal zone name (%s) too long, should be under %d chars\n",
124967eed44bSAmit Kucheria 		       type, THERMAL_NAME_LENGTH);
12505fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
125167eed44bSAmit Kucheria 	}
12525fc024abSZhang Rui 
125382b1ec79SSumeet Pawnikar 	/*
125482b1ec79SSumeet Pawnikar 	 * Max trip count can't exceed 31 as the "mask >> num_trips" condition.
125582b1ec79SSumeet Pawnikar 	 * For example, shifting by 32 will result in compiler warning:
125682b1ec79SSumeet Pawnikar 	 * warning: right shift count >= width of type [-Wshift-count- overflow]
125782b1ec79SSumeet Pawnikar 	 *
125882b1ec79SSumeet Pawnikar 	 * Also "mask >> num_trips" will always be true with 32 bit shift.
125982b1ec79SSumeet Pawnikar 	 * E.g. mask = 0x80000000 for trip id 31 to be RW. Then
126082b1ec79SSumeet Pawnikar 	 * mask >> 32 = 0x80000000
126182b1ec79SSumeet Pawnikar 	 * This will result in failure for the below condition.
126282b1ec79SSumeet Pawnikar 	 *
126382b1ec79SSumeet Pawnikar 	 * Check will be true when the bit 31 of the mask is set.
126482b1ec79SSumeet Pawnikar 	 * 32 bit shift will cause overflow of 4 byte integer.
126582b1ec79SSumeet Pawnikar 	 */
126682b1ec79SSumeet Pawnikar 	if (num_trips > (BITS_PER_TYPE(int) - 1) || num_trips < 0 || mask >> num_trips) {
12673f95ac32SDaniel Lezcano 		pr_err("Incorrect number of thermal trips\n");
12685fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
126967eed44bSAmit Kucheria 	}
12705fc024abSZhang Rui 
127167eed44bSAmit Kucheria 	if (!ops) {
12723f95ac32SDaniel Lezcano 		pr_err("Thermal zone device ops not defined\n");
12735fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
127467eed44bSAmit Kucheria 	}
12755fc024abSZhang Rui 
127635d8dbbbSRafael J. Wysocki 	if (num_trips > 0 && !trips)
12775fc024abSZhang Rui 		return ERR_PTR(-EINVAL);
12785fc024abSZhang Rui 
12799e0a9be2SRafael J. Wysocki 	if (!thermal_class)
12809e0a9be2SRafael J. Wysocki 		return ERR_PTR(-ENODEV);
12819e0a9be2SRafael J. Wysocki 
128295e3ed15SEduardo Valentin 	tz = kzalloc(sizeof(*tz), GFP_KERNEL);
12835fc024abSZhang Rui 	if (!tz)
12845fc024abSZhang Rui 		return ERR_PTR(-ENOMEM);
12855fc024abSZhang Rui 
12863d439b1aSDaniel Lezcano 	if (tzp) {
12873d439b1aSDaniel Lezcano 		tz->tzp = kmemdup(tzp, sizeof(*tzp), GFP_KERNEL);
12883d439b1aSDaniel Lezcano 		if (!tz->tzp) {
12893d439b1aSDaniel Lezcano 			result = -ENOMEM;
12903d439b1aSDaniel Lezcano 			goto free_tz;
12913d439b1aSDaniel Lezcano 		}
12923d439b1aSDaniel Lezcano 	}
12933d439b1aSDaniel Lezcano 
12945fc024abSZhang Rui 	INIT_LIST_HEAD(&tz->thermal_instances);
1295b31ef828SMatthew Wilcox 	ida_init(&tz->ida);
12965fc024abSZhang Rui 	mutex_init(&tz->lock);
12975a5b7d8dSkeliu 	id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);
1298adc8749bSYue Hu 	if (id < 0) {
1299adc8749bSYue Hu 		result = id;
13003d439b1aSDaniel Lezcano 		goto free_tzp;
1301adc8749bSYue Hu 	}
13025fc024abSZhang Rui 
1303adc8749bSYue Hu 	tz->id = id;
13041e6c8fb8SWolfram Sang 	strscpy(tz->type, type, sizeof(tz->type));
1305d7203eedSDaniel Lezcano 
1306d7203eedSDaniel Lezcano 	if (!ops->critical)
1307d7203eedSDaniel Lezcano 		ops->critical = thermal_zone_device_critical;
1308d7203eedSDaniel Lezcano 
13095fc024abSZhang Rui 	tz->ops = ops;
13109e0a9be2SRafael J. Wysocki 	tz->device.class = thermal_class;
13115fc024abSZhang Rui 	tz->devdata = devdata;
13125fc024abSZhang Rui 	tz->trips = trips;
1313e5bfcd30SDaniel Lezcano 	tz->num_trips = num_trips;
13141c600861SEduardo Valentin 
131517d399cdSDaniel Lezcano 	thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay);
131617d399cdSDaniel Lezcano 	thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay);
131717d399cdSDaniel Lezcano 
13184d0fe749SEduardo Valentin 	/* sys I/F */
13191c600861SEduardo Valentin 	/* Add nodes that are always present via .groups */
13204d0fe749SEduardo Valentin 	result = thermal_zone_create_device_groups(tz, mask);
13214d0fe749SEduardo Valentin 	if (result)
13229d9ca1f9SChristophe Jaillet 		goto remove_id;
13234d0fe749SEduardo Valentin 
13244511f716SChen Yu 	/* A new thermal zone needs to be updated anyway. */
13254511f716SChen Yu 	atomic_set(&tz->need_update, 1);
13265fc024abSZhang Rui 
13274748f968SYang Yingliang 	result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
13284748f968SYang Yingliang 	if (result) {
13294748f968SYang Yingliang 		thermal_zone_destroy_device_groups(tz);
13304748f968SYang Yingliang 		goto remove_id;
13314748f968SYang Yingliang 	}
13325fc024abSZhang Rui 	result = device_register(&tz->device);
13339d9ca1f9SChristophe Jaillet 	if (result)
1334adc8749bSYue Hu 		goto release_device;
13355fc024abSZhang Rui 
1336e5bfcd30SDaniel Lezcano 	for (count = 0; count < num_trips; count++) {
13377c3d5c20SDaniel Lezcano 		struct thermal_trip trip;
13387c3d5c20SDaniel Lezcano 
13397c3d5c20SDaniel Lezcano 		result = thermal_zone_get_trip(tz, count, &trip);
1340f1b80a38SIdo Schimmel 		if (result || !trip.temperature)
134181ad4276SZhang Rui 			set_bit(count, &tz->trips_disabled);
13425fc024abSZhang Rui 	}
13435fc024abSZhang Rui 
13445fc024abSZhang Rui 	/* Update 'this' zone's governor information */
13455fc024abSZhang Rui 	mutex_lock(&thermal_governor_lock);
13465fc024abSZhang Rui 
13475fc024abSZhang Rui 	if (tz->tzp)
1348e33df1d2SJavi Merino 		governor = __find_governor(tz->tzp->governor_name);
13495fc024abSZhang Rui 	else
1350e33df1d2SJavi Merino 		governor = def_governor;
1351e33df1d2SJavi Merino 
1352e33df1d2SJavi Merino 	result = thermal_set_governor(tz, governor);
1353e33df1d2SJavi Merino 	if (result) {
1354e33df1d2SJavi Merino 		mutex_unlock(&thermal_governor_lock);
1355e33df1d2SJavi Merino 		goto unregister;
1356e33df1d2SJavi Merino 	}
13575fc024abSZhang Rui 
13585fc024abSZhang Rui 	mutex_unlock(&thermal_governor_lock);
13595fc024abSZhang Rui 
1360ccba4ffdSEduardo Valentin 	if (!tz->tzp || !tz->tzp->no_hwmon) {
13615fc024abSZhang Rui 		result = thermal_add_hwmon_sysfs(tz);
13625fc024abSZhang Rui 		if (result)
13635fc024abSZhang Rui 			goto unregister;
1364ccba4ffdSEduardo Valentin 	}
13655fc024abSZhang Rui 
13665fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
13675fc024abSZhang Rui 	list_add_tail(&tz->node, &thermal_tz_list);
13685fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
13695fc024abSZhang Rui 
13705fc024abSZhang Rui 	/* Bind cooling devices for this zone */
13715fc024abSZhang Rui 	bind_tz(tz);
13725fc024abSZhang Rui 
1373b659a30dSEduardo Valentin 	INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
13745fc024abSZhang Rui 
1375d0df264fSDaniel Lezcano 	thermal_zone_device_init(tz);
13764511f716SChen Yu 	/* Update the new thermal zone and mark it as already updated. */
13774511f716SChen Yu 	if (atomic_cmpxchg(&tz->need_update, 1, 0))
13780e70f466SSrinivas Pandruvada 		thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
13795fc024abSZhang Rui 
138055cdf0a2SDaniel Lezcano 	thermal_notify_tz_create(tz->id, tz->type);
138155cdf0a2SDaniel Lezcano 
13825fc024abSZhang Rui 	return tz;
13835fc024abSZhang Rui 
13845fc024abSZhang Rui unregister:
1385adc8749bSYue Hu 	device_del(&tz->device);
1386adc8749bSYue Hu release_device:
1387adc8749bSYue Hu 	put_device(&tz->device);
13889d9ca1f9SChristophe Jaillet remove_id:
13895a5b7d8dSkeliu 	ida_free(&thermal_tz_ida, id);
13903d439b1aSDaniel Lezcano free_tzp:
13913d439b1aSDaniel Lezcano 	kfree(tz->tzp);
13929d9ca1f9SChristophe Jaillet free_tz:
13939d9ca1f9SChristophe Jaillet 	kfree(tz);
13949d9ca1f9SChristophe Jaillet 	return ERR_PTR(result);
13955fc024abSZhang Rui }
1396a921be53SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
1397fae11de5SDaniel Lezcano 
thermal_tripless_zone_device_register(const char * type,void * devdata,struct thermal_zone_device_ops * ops,const struct thermal_zone_params * tzp)1398d332db8fSRafael J. Wysocki struct thermal_zone_device *thermal_tripless_zone_device_register(
1399d332db8fSRafael J. Wysocki 					const char *type,
1400d332db8fSRafael J. Wysocki 					void *devdata,
1401d332db8fSRafael J. Wysocki 					struct thermal_zone_device_ops *ops,
1402d332db8fSRafael J. Wysocki 					const struct thermal_zone_params *tzp)
1403d332db8fSRafael J. Wysocki {
1404d332db8fSRafael J. Wysocki 	return thermal_zone_device_register_with_trips(type, NULL, 0, 0, devdata,
1405d332db8fSRafael J. Wysocki 						       ops, tzp, 0, 0);
1406d332db8fSRafael J. Wysocki }
1407d332db8fSRafael J. Wysocki EXPORT_SYMBOL_GPL(thermal_tripless_zone_device_register);
1408d332db8fSRafael J. Wysocki 
thermal_zone_device_priv(struct thermal_zone_device * tzd)1409a6ff3c00SDaniel Lezcano void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
1410a6ff3c00SDaniel Lezcano {
1411a6ff3c00SDaniel Lezcano 	return tzd->devdata;
1412a6ff3c00SDaniel Lezcano }
1413a6ff3c00SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_priv);
1414a6ff3c00SDaniel Lezcano 
thermal_zone_device_type(struct thermal_zone_device * tzd)1415072e35c9SDaniel Lezcano const char *thermal_zone_device_type(struct thermal_zone_device *tzd)
1416072e35c9SDaniel Lezcano {
1417072e35c9SDaniel Lezcano 	return tzd->type;
1418072e35c9SDaniel Lezcano }
1419072e35c9SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_type);
1420072e35c9SDaniel Lezcano 
thermal_zone_device_id(struct thermal_zone_device * tzd)14213034f859SDaniel Lezcano int thermal_zone_device_id(struct thermal_zone_device *tzd)
14223034f859SDaniel Lezcano {
14233034f859SDaniel Lezcano 	return tzd->id;
14243034f859SDaniel Lezcano }
14253034f859SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device_id);
14263034f859SDaniel Lezcano 
thermal_zone_device(struct thermal_zone_device * tzd)14277cefbaf0SDaniel Lezcano struct device *thermal_zone_device(struct thermal_zone_device *tzd)
14287cefbaf0SDaniel Lezcano {
14297cefbaf0SDaniel Lezcano 	return &tzd->device;
14307cefbaf0SDaniel Lezcano }
14317cefbaf0SDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_device);
14327cefbaf0SDaniel Lezcano 
14335fc024abSZhang Rui /**
1434a052b511SYang Yingliang  * thermal_zone_device_unregister - removes the registered thermal zone device
14355fc024abSZhang Rui  * @tz: the thermal zone device to remove
14365fc024abSZhang Rui  */
thermal_zone_device_unregister(struct thermal_zone_device * tz)14375fc024abSZhang Rui void thermal_zone_device_unregister(struct thermal_zone_device *tz)
14385fc024abSZhang Rui {
1439ded2d383SZhang Rui 	int tz_id;
14405fc024abSZhang Rui 	struct thermal_cooling_device *cdev;
14415fc024abSZhang Rui 	struct thermal_zone_device *pos = NULL;
14425fc024abSZhang Rui 
14435fc024abSZhang Rui 	if (!tz)
14445fc024abSZhang Rui 		return;
14455fc024abSZhang Rui 
1446a5f785ceSDmitry Osipenko 	tz_id = tz->id;
14475fc024abSZhang Rui 
14485fc024abSZhang Rui 	mutex_lock(&thermal_list_lock);
14495fc024abSZhang Rui 	list_for_each_entry(pos, &thermal_tz_list, node)
14505fc024abSZhang Rui 		if (pos == tz)
14515fc024abSZhang Rui 			break;
14525fc024abSZhang Rui 	if (pos != tz) {
14535fc024abSZhang Rui 		/* thermal zone device not found */
14545fc024abSZhang Rui 		mutex_unlock(&thermal_list_lock);
14555fc024abSZhang Rui 		return;
14565fc024abSZhang Rui 	}
14575fc024abSZhang Rui 	list_del(&tz->node);
14585fc024abSZhang Rui 
14595fc024abSZhang Rui 	/* Unbind all cdevs associated with 'this' thermal zone */
1460ded2d383SZhang Rui 	list_for_each_entry(cdev, &thermal_cdev_list, node)
1461ded2d383SZhang Rui 		if (tz->ops->unbind)
14625fc024abSZhang Rui 			tz->ops->unbind(tz, cdev);
14635fc024abSZhang Rui 
14645fc024abSZhang Rui 	mutex_unlock(&thermal_list_lock);
14655fc024abSZhang Rui 
1466163b00cdSWei Wang 	cancel_delayed_work_sync(&tz->poll_queue);
14675fc024abSZhang Rui 
1468e33df1d2SJavi Merino 	thermal_set_governor(tz, NULL);
14695fc024abSZhang Rui 
14705fc024abSZhang Rui 	thermal_remove_hwmon_sysfs(tz);
14715a5b7d8dSkeliu 	ida_free(&thermal_tz_ida, tz->id);
1472b31ef828SMatthew Wilcox 	ida_destroy(&tz->ida);
147330b2ae07SGuenter Roeck 
147430b2ae07SGuenter Roeck 	mutex_lock(&tz->lock);
147530b2ae07SGuenter Roeck 	device_del(&tz->device);
147630b2ae07SGuenter Roeck 	mutex_unlock(&tz->lock);
147730b2ae07SGuenter Roeck 
14783d439b1aSDaniel Lezcano 	kfree(tz->tzp);
14793d439b1aSDaniel Lezcano 
148030b2ae07SGuenter Roeck 	put_device(&tz->device);
148155cdf0a2SDaniel Lezcano 
1482a5f785ceSDmitry Osipenko 	thermal_notify_tz_delete(tz_id);
14835fc024abSZhang Rui }
1484910cb1e3SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
14855fc024abSZhang Rui 
148663c4d919SEduardo Valentin /**
148763c4d919SEduardo Valentin  * thermal_zone_get_zone_by_name() - search for a zone and returns its ref
148863c4d919SEduardo Valentin  * @name: thermal zone name to fetch the temperature
148963c4d919SEduardo Valentin  *
149063c4d919SEduardo Valentin  * When only one zone is found with the passed name, returns a reference to it.
149163c4d919SEduardo Valentin  *
149263c4d919SEduardo Valentin  * Return: On success returns a reference to an unique thermal zone with
149363c4d919SEduardo Valentin  * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
149463c4d919SEduardo Valentin  * paramenters, -ENODEV for not found and -EEXIST for multiple matches).
149563c4d919SEduardo Valentin  */
thermal_zone_get_zone_by_name(const char * name)149663c4d919SEduardo Valentin struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
149763c4d919SEduardo Valentin {
149863c4d919SEduardo Valentin 	struct thermal_zone_device *pos = NULL, *ref = ERR_PTR(-EINVAL);
149963c4d919SEduardo Valentin 	unsigned int found = 0;
150063c4d919SEduardo Valentin 
150163c4d919SEduardo Valentin 	if (!name)
150263c4d919SEduardo Valentin 		goto exit;
150363c4d919SEduardo Valentin 
150463c4d919SEduardo Valentin 	mutex_lock(&thermal_list_lock);
150563c4d919SEduardo Valentin 	list_for_each_entry(pos, &thermal_tz_list, node)
1506484ac2f3SRasmus Villemoes 		if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
150763c4d919SEduardo Valentin 			found++;
150863c4d919SEduardo Valentin 			ref = pos;
150963c4d919SEduardo Valentin 		}
151063c4d919SEduardo Valentin 	mutex_unlock(&thermal_list_lock);
151163c4d919SEduardo Valentin 
151263c4d919SEduardo Valentin 	/* nothing has been found, thus an error code for it */
151363c4d919SEduardo Valentin 	if (found == 0)
151463c4d919SEduardo Valentin 		ref = ERR_PTR(-ENODEV);
151563c4d919SEduardo Valentin 	else if (found > 1)
151663c4d919SEduardo Valentin 	/* Success only when an unique zone is found */
151763c4d919SEduardo Valentin 		ref = ERR_PTR(-EEXIST);
151863c4d919SEduardo Valentin 
151963c4d919SEduardo Valentin exit:
152063c4d919SEduardo Valentin 	return ref;
152163c4d919SEduardo Valentin }
152263c4d919SEduardo Valentin EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
152363c4d919SEduardo Valentin 
thermal_pm_notify(struct notifier_block * nb,unsigned long mode,void * _unused)1524ff140feaSZhang Rui static int thermal_pm_notify(struct notifier_block *nb,
1525ff140feaSZhang Rui 			     unsigned long mode, void *_unused)
1526ff140feaSZhang Rui {
1527ff140feaSZhang Rui 	struct thermal_zone_device *tz;
1528ff140feaSZhang Rui 
1529ff140feaSZhang Rui 	switch (mode) {
1530ff140feaSZhang Rui 	case PM_HIBERNATION_PREPARE:
1531ff140feaSZhang Rui 	case PM_RESTORE_PREPARE:
1532ff140feaSZhang Rui 	case PM_SUSPEND_PREPARE:
1533*fcecef9aSRafael J. Wysocki 		mutex_lock(&thermal_list_lock);
1534*fcecef9aSRafael J. Wysocki 
1535*fcecef9aSRafael J. Wysocki 		list_for_each_entry(tz, &thermal_tz_list, node) {
1536*fcecef9aSRafael J. Wysocki 			mutex_lock(&tz->lock);
1537*fcecef9aSRafael J. Wysocki 
1538*fcecef9aSRafael J. Wysocki 			tz->suspended = true;
1539*fcecef9aSRafael J. Wysocki 
1540*fcecef9aSRafael J. Wysocki 			mutex_unlock(&tz->lock);
1541*fcecef9aSRafael J. Wysocki 		}
1542*fcecef9aSRafael J. Wysocki 
1543*fcecef9aSRafael J. Wysocki 		mutex_unlock(&thermal_list_lock);
1544ff140feaSZhang Rui 		break;
1545ff140feaSZhang Rui 	case PM_POST_HIBERNATION:
1546ff140feaSZhang Rui 	case PM_POST_RESTORE:
1547ff140feaSZhang Rui 	case PM_POST_SUSPEND:
1548*fcecef9aSRafael J. Wysocki 		mutex_lock(&thermal_list_lock);
1549*fcecef9aSRafael J. Wysocki 
1550ff140feaSZhang Rui 		list_for_each_entry(tz, &thermal_tz_list, node) {
1551*fcecef9aSRafael J. Wysocki 			mutex_lock(&tz->lock);
1552*fcecef9aSRafael J. Wysocki 
1553*fcecef9aSRafael J. Wysocki 			tz->suspended = false;
1554*fcecef9aSRafael J. Wysocki 
1555964f4843SWei Wang 			thermal_zone_device_init(tz);
1556*fcecef9aSRafael J. Wysocki 			__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
1557*fcecef9aSRafael J. Wysocki 
1558*fcecef9aSRafael J. Wysocki 			mutex_unlock(&tz->lock);
1559ff140feaSZhang Rui 		}
1560*fcecef9aSRafael J. Wysocki 
1561*fcecef9aSRafael J. Wysocki 		mutex_unlock(&thermal_list_lock);
1562ff140feaSZhang Rui 		break;
1563ff140feaSZhang Rui 	default:
1564ff140feaSZhang Rui 		break;
1565ff140feaSZhang Rui 	}
1566ff140feaSZhang Rui 	return 0;
1567ff140feaSZhang Rui }
1568ff140feaSZhang Rui 
1569ff140feaSZhang Rui static struct notifier_block thermal_pm_nb = {
1570ff140feaSZhang Rui 	.notifier_call = thermal_pm_notify,
1571ff140feaSZhang Rui };
1572ff140feaSZhang Rui 
thermal_init(void)15735fc024abSZhang Rui static int __init thermal_init(void)
15745fc024abSZhang Rui {
157580a26a5cSZhang Rui 	int result;
157680a26a5cSZhang Rui 
1577d2a89b52SDaniel Lezcano 	result = thermal_netlink_init();
1578d2a89b52SDaniel Lezcano 	if (result)
1579d2a89b52SDaniel Lezcano 		goto error;
1580d2a89b52SDaniel Lezcano 
158180a26a5cSZhang Rui 	result = thermal_register_governors();
158280a26a5cSZhang Rui 	if (result)
158358d1c9fdSDaniel Lezcano 		goto unregister_netlink;
15845fc024abSZhang Rui 
15859e0a9be2SRafael J. Wysocki 	thermal_class = kzalloc(sizeof(*thermal_class), GFP_KERNEL);
15869e0a9be2SRafael J. Wysocki 	if (!thermal_class) {
15879e0a9be2SRafael J. Wysocki 		result = -ENOMEM;
158880a26a5cSZhang Rui 		goto unregister_governors;
15899e0a9be2SRafael J. Wysocki 	}
15909e0a9be2SRafael J. Wysocki 
15919e0a9be2SRafael J. Wysocki 	thermal_class->name = "thermal";
15929e0a9be2SRafael J. Wysocki 	thermal_class->dev_release = thermal_release;
15939e0a9be2SRafael J. Wysocki 
15949e0a9be2SRafael J. Wysocki 	result = class_register(thermal_class);
15959e0a9be2SRafael J. Wysocki 	if (result) {
15969e0a9be2SRafael J. Wysocki 		kfree(thermal_class);
15979e0a9be2SRafael J. Wysocki 		thermal_class = NULL;
15989e0a9be2SRafael J. Wysocki 		goto unregister_governors;
15999e0a9be2SRafael J. Wysocki 	}
160080a26a5cSZhang Rui 
1601ff140feaSZhang Rui 	result = register_pm_notifier(&thermal_pm_nb);
1602ff140feaSZhang Rui 	if (result)
1603ff140feaSZhang Rui 		pr_warn("Thermal: Can not register suspend notifier, return %d\n",
1604ff140feaSZhang Rui 			result);
1605ff140feaSZhang Rui 
160680a26a5cSZhang Rui 	return 0;
160780a26a5cSZhang Rui 
16089d367e5eSLuis Henriques unregister_governors:
16099d367e5eSLuis Henriques 	thermal_unregister_governors();
161058d1c9fdSDaniel Lezcano unregister_netlink:
161158d1c9fdSDaniel Lezcano 	thermal_netlink_exit();
161280a26a5cSZhang Rui error:
16135fc024abSZhang Rui 	mutex_destroy(&thermal_list_lock);
161480a26a5cSZhang Rui 	mutex_destroy(&thermal_governor_lock);
16155fc024abSZhang Rui 	return result;
16165fc024abSZhang Rui }
16173f5a2cbeSDaniel Lezcano postcore_initcall(thermal_init);
1618