thermal_sys.c 31.6 KB
Newer Older
Z
Zhang Rui 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 *  thermal.c - Generic Thermal Management Sysfs support.
 *
 *  Copyright (C) 2008 Intel Corp
 *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
 *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
 *
 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
29
#include <linux/slab.h>
Z
Zhang Rui 已提交
30 31 32 33
#include <linux/kdev_t.h>
#include <linux/idr.h>
#include <linux/thermal.h>
#include <linux/spinlock.h>
34
#include <linux/reboot.h>
Z
Zhang Rui 已提交
35

36
MODULE_AUTHOR("Zhang Rui");
Z
Zhang Rui 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
MODULE_DESCRIPTION("Generic thermal management sysfs support");
MODULE_LICENSE("GPL");

#define PREFIX "Thermal: "

struct thermal_cooling_device_instance {
	int id;
	char name[THERMAL_NAME_LENGTH];
	struct thermal_zone_device *tz;
	struct thermal_cooling_device *cdev;
	int trip;
	char attr_name[THERMAL_NAME_LENGTH];
	struct device_attribute attr;
	struct list_head node;
};

static DEFINE_IDR(thermal_tz_idr);
static DEFINE_IDR(thermal_cdev_idr);
static DEFINE_MUTEX(thermal_idr_lock);

static LIST_HEAD(thermal_tz_list);
static LIST_HEAD(thermal_cdev_list);
static DEFINE_MUTEX(thermal_list_lock);

static int get_idr(struct idr *idr, struct mutex *lock, int *id)
{
	int err;

      again:
	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
		return -ENOMEM;

	if (lock)
		mutex_lock(lock);
	err = idr_get_new(idr, NULL, id);
	if (lock)
		mutex_unlock(lock);
	if (unlikely(err == -EAGAIN))
		goto again;
	else if (unlikely(err))
		return err;

	*id = *id & MAX_ID_MASK;
	return 0;
}

static void release_idr(struct idr *idr, struct mutex *lock, int id)
{
	if (lock)
		mutex_lock(lock);
	idr_remove(idr, id);
	if (lock)
		mutex_unlock(lock);
}

/* sys I/F for thermal zone */

#define to_thermal_zone(_dev) \
	container_of(_dev, struct thermal_zone_device, device)

static ssize_t
type_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);

	return sprintf(buf, "%s\n", tz->type);
}

static ssize_t
temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
109 110
	long temperature;
	int ret;
Z
Zhang Rui 已提交
111 112 113 114

	if (!tz->ops->get_temp)
		return -EPERM;

115 116 117 118 119 120
	ret = tz->ops->get_temp(tz, &temperature);

	if (ret)
		return ret;

	return sprintf(buf, "%ld\n", temperature);
Z
Zhang Rui 已提交
121 122 123 124 125 126
}

static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
127 128
	enum thermal_device_mode mode;
	int result;
Z
Zhang Rui 已提交
129 130 131 132

	if (!tz->ops->get_mode)
		return -EPERM;

133 134 135 136 137 138
	result = tz->ops->get_mode(tz, &mode);
	if (result)
		return result;

	return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
		       : "disabled");
Z
Zhang Rui 已提交
139 140 141 142 143 144 145 146 147 148 149 150
}

static ssize_t
mode_store(struct device *dev, struct device_attribute *attr,
	   const char *buf, size_t count)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
	int result;

	if (!tz->ops->set_mode)
		return -EPERM;

151 152 153 154 155 156 157
	if (!strncmp(buf, "enabled", sizeof("enabled")))
		result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
	else if (!strncmp(buf, "disabled", sizeof("disabled")))
		result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
	else
		result = -EINVAL;

Z
Zhang Rui 已提交
158 159 160 161 162 163 164 165 166 167 168
	if (result)
		return result;

	return count;
}

static ssize_t
trip_point_type_show(struct device *dev, struct device_attribute *attr,
		     char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
169 170
	enum thermal_trip_type type;
	int trip, result;
Z
Zhang Rui 已提交
171 172 173 174 175 176 177

	if (!tz->ops->get_trip_type)
		return -EPERM;

	if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip))
		return -EINVAL;

178 179 180 181 182 183
	result = tz->ops->get_trip_type(tz, trip, &type);
	if (result)
		return result;

	switch (type) {
	case THERMAL_TRIP_CRITICAL:
184
		return sprintf(buf, "critical\n");
185
	case THERMAL_TRIP_HOT:
186
		return sprintf(buf, "hot\n");
187
	case THERMAL_TRIP_PASSIVE:
188
		return sprintf(buf, "passive\n");
189
	case THERMAL_TRIP_ACTIVE:
190
		return sprintf(buf, "active\n");
191
	default:
192
		return sprintf(buf, "unknown\n");
193
	}
Z
Zhang Rui 已提交
194 195 196 197 198 199 200
}

static ssize_t
trip_point_temp_show(struct device *dev, struct device_attribute *attr,
		     char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
201 202
	int trip, ret;
	long temperature;
Z
Zhang Rui 已提交
203 204 205 206 207 208 209

	if (!tz->ops->get_trip_temp)
		return -EPERM;

	if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
		return -EINVAL;

210 211 212 213 214 215
	ret = tz->ops->get_trip_temp(tz, trip, &temperature);

	if (ret)
		return ret;

	return sprintf(buf, "%ld\n", temperature);
Z
Zhang Rui 已提交
216 217
}

218 219 220 221 222 223 224 225 226 227 228
static ssize_t
passive_store(struct device *dev, struct device_attribute *attr,
		    const char *buf, size_t count)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);
	struct thermal_cooling_device *cdev = NULL;
	int state;

	if (!sscanf(buf, "%d\n", &state))
		return -EINVAL;

229 230 231 232 233 234
	/* sanity check: values below 1000 millicelcius don't make sense
	 * and can cause the system to go into a thermal heart attack
	 */
	if (state && state < 1000)
		return -EINVAL;

235 236 237 238 239 240 241 242 243 244
	if (state && !tz->forced_passive) {
		mutex_lock(&thermal_list_lock);
		list_for_each_entry(cdev, &thermal_cdev_list, node) {
			if (!strncmp("Processor", cdev->type,
				     sizeof("Processor")))
				thermal_zone_bind_cooling_device(tz,
								 THERMAL_TRIPS_NONE,
								 cdev);
		}
		mutex_unlock(&thermal_list_lock);
245 246
		if (!tz->passive_delay)
			tz->passive_delay = 1000;
247 248 249 250 251 252 253 254 255 256
	} else if (!state && tz->forced_passive) {
		mutex_lock(&thermal_list_lock);
		list_for_each_entry(cdev, &thermal_cdev_list, node) {
			if (!strncmp("Processor", cdev->type,
				     sizeof("Processor")))
				thermal_zone_unbind_cooling_device(tz,
								   THERMAL_TRIPS_NONE,
								   cdev);
		}
		mutex_unlock(&thermal_list_lock);
257
		tz->passive_delay = 0;
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
	}

	tz->tc1 = 1;
	tz->tc2 = 1;

	tz->forced_passive = state;

	thermal_zone_device_update(tz);

	return count;
}

static ssize_t
passive_show(struct device *dev, struct device_attribute *attr,
		   char *buf)
{
	struct thermal_zone_device *tz = to_thermal_zone(dev);

	return sprintf(buf, "%d\n", tz->forced_passive);
}

Z
Zhang Rui 已提交
279 280 281
static DEVICE_ATTR(type, 0444, type_show, NULL);
static DEVICE_ATTR(temp, 0444, temp_show, NULL);
static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
282 283
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, \
		   passive_store);
Z
Zhang Rui 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

static struct device_attribute trip_point_attrs[] = {
	__ATTR(trip_point_0_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_0_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_1_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_1_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_2_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_2_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_3_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_3_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_4_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_4_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_5_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_5_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_6_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_6_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_7_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_7_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_8_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_8_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_9_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_9_temp, 0444, trip_point_temp_show, NULL),
306 307 308 309
	__ATTR(trip_point_10_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_10_temp, 0444, trip_point_temp_show, NULL),
	__ATTR(trip_point_11_type, 0444, trip_point_type_show, NULL),
	__ATTR(trip_point_11_temp, 0444, trip_point_temp_show, NULL),
Z
Zhang Rui 已提交
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
};

#define TRIP_POINT_ATTR_ADD(_dev, _index, result)     \
do {    \
	result = device_create_file(_dev,	\
				&trip_point_attrs[_index * 2]);	\
	if (result)	\
		break;	\
	result = device_create_file(_dev,	\
			&trip_point_attrs[_index * 2 + 1]);	\
} while (0)

#define TRIP_POINT_ATTR_REMOVE(_dev, _index)	\
do {	\
	device_remove_file(_dev, &trip_point_attrs[_index * 2]);	\
	device_remove_file(_dev, &trip_point_attrs[_index * 2 + 1]);	\
} while (0)

/* sys I/F for cooling device */
#define to_cooling_device(_dev)	\
	container_of(_dev, struct thermal_cooling_device, device)

static ssize_t
thermal_cooling_device_type_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
	struct thermal_cooling_device *cdev = to_cooling_device(dev);

	return sprintf(buf, "%s\n", cdev->type);
}

static ssize_t
thermal_cooling_device_max_state_show(struct device *dev,
				      struct device_attribute *attr, char *buf)
{
	struct thermal_cooling_device *cdev = to_cooling_device(dev);
346 347
	unsigned long state;
	int ret;
Z
Zhang Rui 已提交
348

349 350 351 352
	ret = cdev->ops->get_max_state(cdev, &state);
	if (ret)
		return ret;
	return sprintf(buf, "%ld\n", state);
Z
Zhang Rui 已提交
353 354 355 356 357 358 359
}

static ssize_t
thermal_cooling_device_cur_state_show(struct device *dev,
				      struct device_attribute *attr, char *buf)
{
	struct thermal_cooling_device *cdev = to_cooling_device(dev);
360 361
	unsigned long state;
	int ret;
Z
Zhang Rui 已提交
362

363 364 365 366
	ret = cdev->ops->get_cur_state(cdev, &state);
	if (ret)
		return ret;
	return sprintf(buf, "%ld\n", state);
Z
Zhang Rui 已提交
367 368 369 370 371 372 373 374
}

static ssize_t
thermal_cooling_device_cur_state_store(struct device *dev,
				       struct device_attribute *attr,
				       const char *buf, size_t count)
{
	struct thermal_cooling_device *cdev = to_cooling_device(dev);
375
	unsigned long state;
Z
Zhang Rui 已提交
376 377
	int result;

378
	if (!sscanf(buf, "%ld\n", &state))
Z
Zhang Rui 已提交
379 380
		return -EINVAL;

381
	if ((long)state < 0)
Z
Zhang Rui 已提交
382 383 384 385 386 387 388 389 390
		return -EINVAL;

	result = cdev->ops->set_cur_state(cdev, state);
	if (result)
		return result;
	return count;
}

static struct device_attribute dev_attr_cdev_type =
391
__ATTR(type, 0444, thermal_cooling_device_type_show, NULL);
Z
Zhang Rui 已提交
392 393 394 395 396 397 398 399
static DEVICE_ATTR(max_state, 0444,
		   thermal_cooling_device_max_state_show, NULL);
static DEVICE_ATTR(cur_state, 0644,
		   thermal_cooling_device_cur_state_show,
		   thermal_cooling_device_cur_state_store);

static ssize_t
thermal_cooling_device_trip_point_show(struct device *dev,
400
				       struct device_attribute *attr, char *buf)
Z
Zhang Rui 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414
{
	struct thermal_cooling_device_instance *instance;

	instance =
	    container_of(attr, struct thermal_cooling_device_instance, attr);

	if (instance->trip == THERMAL_TRIPS_NONE)
		return sprintf(buf, "-1\n");
	else
		return sprintf(buf, "%d\n", instance->trip);
}

/* Device management */

415 416
#if defined(CONFIG_THERMAL_HWMON)

Z
Zhang Rui 已提交
417 418 419 420 421 422 423
/* hwmon sys I/F */
#include <linux/hwmon.h>
static LIST_HEAD(thermal_hwmon_list);

static ssize_t
name_show(struct device *dev, struct device_attribute *attr, char *buf)
{
424
	struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
Z
Zhang Rui 已提交
425 426 427 428 429 430 431
	return sprintf(buf, "%s\n", hwmon->type);
}
static DEVICE_ATTR(name, 0444, name_show, NULL);

static ssize_t
temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
{
432 433
	long temperature;
	int ret;
Z
Zhang Rui 已提交
434 435 436 437 438 439
	struct thermal_hwmon_attr *hwmon_attr
			= container_of(attr, struct thermal_hwmon_attr, attr);
	struct thermal_zone_device *tz
			= container_of(hwmon_attr, struct thermal_zone_device,
				       temp_input);

440 441 442 443 444 445
	ret = tz->ops->get_temp(tz, &temperature);

	if (ret)
		return ret;

	return sprintf(buf, "%ld\n", temperature);
Z
Zhang Rui 已提交
446 447 448 449 450 451 452 453 454 455 456
}

static ssize_t
temp_crit_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	struct thermal_hwmon_attr *hwmon_attr
			= container_of(attr, struct thermal_hwmon_attr, attr);
	struct thermal_zone_device *tz
			= container_of(hwmon_attr, struct thermal_zone_device,
				       temp_crit);
457 458 459 460 461 462
	long temperature;
	int ret;

	ret = tz->ops->get_trip_temp(tz, 0, &temperature);
	if (ret)
		return ret;
Z
Zhang Rui 已提交
463

464
	return sprintf(buf, "%ld\n", temperature);
Z
Zhang Rui 已提交
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
}


static int
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
{
	struct thermal_hwmon_device *hwmon;
	int new_hwmon_device = 1;
	int result;

	mutex_lock(&thermal_list_lock);
	list_for_each_entry(hwmon, &thermal_hwmon_list, node)
		if (!strcmp(hwmon->type, tz->type)) {
			new_hwmon_device = 0;
			mutex_unlock(&thermal_list_lock);
			goto register_sys_interface;
		}
	mutex_unlock(&thermal_list_lock);

	hwmon = kzalloc(sizeof(struct thermal_hwmon_device), GFP_KERNEL);
	if (!hwmon)
		return -ENOMEM;

	INIT_LIST_HEAD(&hwmon->tz_list);
	strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
	hwmon->device = hwmon_device_register(NULL);
	if (IS_ERR(hwmon->device)) {
		result = PTR_ERR(hwmon->device);
		goto free_mem;
	}
495
	dev_set_drvdata(hwmon->device, hwmon);
Z
Zhang Rui 已提交
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
	result = device_create_file(hwmon->device, &dev_attr_name);
	if (result)
		goto unregister_hwmon_device;

 register_sys_interface:
	tz->hwmon = hwmon;
	hwmon->count++;

	snprintf(tz->temp_input.name, THERMAL_NAME_LENGTH,
		 "temp%d_input", hwmon->count);
	tz->temp_input.attr.attr.name = tz->temp_input.name;
	tz->temp_input.attr.attr.mode = 0444;
	tz->temp_input.attr.show = temp_input_show;
	result = device_create_file(hwmon->device, &tz->temp_input.attr);
	if (result)
		goto unregister_hwmon_device;

	if (tz->ops->get_crit_temp) {
		unsigned long temperature;
		if (!tz->ops->get_crit_temp(tz, &temperature)) {
			snprintf(tz->temp_crit.name, THERMAL_NAME_LENGTH,
				"temp%d_crit", hwmon->count);
			tz->temp_crit.attr.attr.name = tz->temp_crit.name;
			tz->temp_crit.attr.attr.mode = 0444;
			tz->temp_crit.attr.show = temp_crit_show;
			result = device_create_file(hwmon->device,
						    &tz->temp_crit.attr);
			if (result)
				goto unregister_hwmon_device;
		}
	}

	mutex_lock(&thermal_list_lock);
	if (new_hwmon_device)
		list_add_tail(&hwmon->node, &thermal_hwmon_list);
	list_add_tail(&tz->hwmon_node, &hwmon->tz_list);
	mutex_unlock(&thermal_list_lock);

	return 0;

 unregister_hwmon_device:
	device_remove_file(hwmon->device, &tz->temp_crit.attr);
	device_remove_file(hwmon->device, &tz->temp_input.attr);
	if (new_hwmon_device) {
		device_remove_file(hwmon->device, &dev_attr_name);
		hwmon_device_unregister(hwmon->device);
	}
 free_mem:
	if (new_hwmon_device)
		kfree(hwmon);

	return result;
}

static void
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
{
	struct thermal_hwmon_device *hwmon = tz->hwmon;

	tz->hwmon = NULL;
	device_remove_file(hwmon->device, &tz->temp_input.attr);
	device_remove_file(hwmon->device, &tz->temp_crit.attr);

	mutex_lock(&thermal_list_lock);
	list_del(&tz->hwmon_node);
	if (!list_empty(&hwmon->tz_list)) {
		mutex_unlock(&thermal_list_lock);
		return;
	}
	list_del(&hwmon->node);
	mutex_unlock(&thermal_list_lock);

	device_remove_file(hwmon->device, &dev_attr_name);
	hwmon_device_unregister(hwmon->device);
	kfree(hwmon);
}
#else
static int
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
{
	return 0;
}

static void
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
{
}
#endif

585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
					    int delay)
{
	cancel_delayed_work(&(tz->poll_queue));

	if (!delay)
		return;

	if (delay > 1000)
		schedule_delayed_work(&(tz->poll_queue),
				      round_jiffies(msecs_to_jiffies(delay)));
	else
		schedule_delayed_work(&(tz->poll_queue),
				      msecs_to_jiffies(delay));
}

static void thermal_zone_device_passive(struct thermal_zone_device *tz,
					int temp, int trip_temp, int trip)
{
	int trend = 0;
	struct thermal_cooling_device_instance *instance;
	struct thermal_cooling_device *cdev;
	long state, max_state;

	/*
	 * Above Trip?
	 * -----------
	 * Calculate the thermal trend (using the passive cooling equation)
	 * and modify the performance limit for all passive cooling devices
	 * accordingly.  Note that we assume symmetry.
	 */
	if (temp >= trip_temp) {
		tz->passive = true;

		trend = (tz->tc1 * (temp - tz->last_temperature)) +
			(tz->tc2 * (temp - trip_temp));

		/* Heating up? */
		if (trend > 0) {
			list_for_each_entry(instance, &tz->cooling_devices,
					    node) {
				if (instance->trip != trip)
					continue;
				cdev = instance->cdev;
				cdev->ops->get_cur_state(cdev, &state);
				cdev->ops->get_max_state(cdev, &max_state);
				if (state++ < max_state)
					cdev->ops->set_cur_state(cdev, state);
			}
		} else if (trend < 0) { /* Cooling off? */
			list_for_each_entry(instance, &tz->cooling_devices,
					    node) {
				if (instance->trip != trip)
					continue;
				cdev = instance->cdev;
				cdev->ops->get_cur_state(cdev, &state);
				cdev->ops->get_max_state(cdev, &max_state);
				if (state > 0)
					cdev->ops->set_cur_state(cdev, --state);
			}
		}
		return;
	}

	/*
	 * Below Trip?
	 * -----------
	 * Implement passive cooling hysteresis to slowly increase performance
	 * and avoid thrashing around the passive trip point.  Note that we
	 * assume symmetry.
	 */
	list_for_each_entry(instance, &tz->cooling_devices, node) {
		if (instance->trip != trip)
			continue;
		cdev = instance->cdev;
		cdev->ops->get_cur_state(cdev, &state);
		cdev->ops->get_max_state(cdev, &max_state);
		if (state > 0)
			cdev->ops->set_cur_state(cdev, --state);
		if (state == 0)
			tz->passive = false;
	}
}

static void thermal_zone_device_check(struct work_struct *work)
{
	struct thermal_zone_device *tz = container_of(work, struct
						      thermal_zone_device,
						      poll_queue.work);
	thermal_zone_device_update(tz);
}
Z
Zhang Rui 已提交
676

Z
Zhang Rui 已提交
677 678 679 680 681 682
/**
 * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone
 * @tz:		thermal zone device
 * @trip:	indicates which trip point the cooling devices is
 *		associated with in this thermal zone.
 * @cdev:	thermal cooling device
683 684
 *
 * This function is usually called in the thermal zone device .bind callback.
Z
Zhang Rui 已提交
685 686 687 688 689 690 691
 */
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
				     int trip,
				     struct thermal_cooling_device *cdev)
{
	struct thermal_cooling_device_instance *dev;
	struct thermal_cooling_device_instance *pos;
692 693
	struct thermal_zone_device *pos1;
	struct thermal_cooling_device *pos2;
Z
Zhang Rui 已提交
694 695
	int result;

696
	if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
Z
Zhang Rui 已提交
697 698
		return -EINVAL;

699 700 701 702 703 704 705 706 707 708
	list_for_each_entry(pos1, &thermal_tz_list, node) {
		if (pos1 == tz)
			break;
	}
	list_for_each_entry(pos2, &thermal_cdev_list, node) {
		if (pos2 == cdev)
			break;
	}

	if (tz != pos1 || cdev != pos2)
Z
Zhang Rui 已提交
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
		return -EINVAL;

	dev =
	    kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;
	dev->tz = tz;
	dev->cdev = cdev;
	dev->trip = trip;
	result = get_idr(&tz->idr, &tz->lock, &dev->id);
	if (result)
		goto free_mem;

	sprintf(dev->name, "cdev%d", dev->id);
	result =
	    sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
	if (result)
		goto release_idr;

	sprintf(dev->attr_name, "cdev%d_trip_point", dev->id);
	dev->attr.attr.name = dev->attr_name;
	dev->attr.attr.mode = 0444;
	dev->attr.show = thermal_cooling_device_trip_point_show;
	result = device_create_file(&tz->device, &dev->attr);
	if (result)
		goto remove_symbol_link;

	mutex_lock(&tz->lock);
	list_for_each_entry(pos, &tz->cooling_devices, node)
	    if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
		result = -EEXIST;
		break;
	}
	if (!result)
		list_add_tail(&dev->node, &tz->cooling_devices);
	mutex_unlock(&tz->lock);

	if (!result)
		return 0;

	device_remove_file(&tz->device, &dev->attr);
      remove_symbol_link:
	sysfs_remove_link(&tz->device.kobj, dev->name);
      release_idr:
	release_idr(&tz->idr, &tz->lock, dev->id);
      free_mem:
	kfree(dev);
	return result;
}
758

Z
Zhang Rui 已提交
759 760 761 762 763 764 765 766
EXPORT_SYMBOL(thermal_zone_bind_cooling_device);

/**
 * thermal_zone_unbind_cooling_device - unbind a cooling device from a thermal zone
 * @tz:		thermal zone device
 * @trip:	indicates which trip point the cooling devices is
 *		associated with in this thermal zone.
 * @cdev:	thermal cooling device
767 768
 *
 * This function is usually called in the thermal zone device .unbind callback.
Z
Zhang Rui 已提交
769 770 771 772 773 774 775 776 777
 */
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
				       int trip,
				       struct thermal_cooling_device *cdev)
{
	struct thermal_cooling_device_instance *pos, *next;

	mutex_lock(&tz->lock);
	list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) {
778
		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
Z
Zhang Rui 已提交
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
			list_del(&pos->node);
			mutex_unlock(&tz->lock);
			goto unbind;
		}
	}
	mutex_unlock(&tz->lock);

	return -ENODEV;

      unbind:
	device_remove_file(&tz->device, &pos->attr);
	sysfs_remove_link(&tz->device.kobj, pos->name);
	release_idr(&tz->idr, &tz->lock, pos->id);
	kfree(pos);
	return 0;
}
795

Z
Zhang Rui 已提交
796 797 798 799 800 801 802
EXPORT_SYMBOL(thermal_zone_unbind_cooling_device);

static void thermal_release(struct device *dev)
{
	struct thermal_zone_device *tz;
	struct thermal_cooling_device *cdev;

803
	if (!strncmp(dev_name(dev), "thermal_zone", sizeof "thermal_zone" - 1)) {
Z
Zhang Rui 已提交
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
		tz = to_thermal_zone(dev);
		kfree(tz);
	} else {
		cdev = to_cooling_device(dev);
		kfree(cdev);
	}
}

static struct class thermal_class = {
	.name = "thermal",
	.dev_release = thermal_release,
};

/**
 * thermal_cooling_device_register - register a new thermal cooling device
 * @type:	the thermal cooling device type.
 * @devdata:	device private data.
 * @ops:		standard thermal cooling devices callbacks.
 */
struct thermal_cooling_device *thermal_cooling_device_register(char *type,
824 825 826 827
							       void *devdata,
							       struct
							       thermal_cooling_device_ops
							       *ops)
Z
Zhang Rui 已提交
828 829 830 831 832 833
{
	struct thermal_cooling_device *cdev;
	struct thermal_zone_device *pos;
	int result;

	if (strlen(type) >= THERMAL_NAME_LENGTH)
834
		return ERR_PTR(-EINVAL);
Z
Zhang Rui 已提交
835 836

	if (!ops || !ops->get_max_state || !ops->get_cur_state ||
837
	    !ops->set_cur_state)
838
		return ERR_PTR(-EINVAL);
Z
Zhang Rui 已提交
839 840 841

	cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL);
	if (!cdev)
842
		return ERR_PTR(-ENOMEM);
Z
Zhang Rui 已提交
843 844 845 846

	result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id);
	if (result) {
		kfree(cdev);
847
		return ERR_PTR(result);
Z
Zhang Rui 已提交
848 849 850 851 852 853
	}

	strcpy(cdev->type, type);
	cdev->ops = ops;
	cdev->device.class = &thermal_class;
	cdev->devdata = devdata;
854
	dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
Z
Zhang Rui 已提交
855 856 857 858
	result = device_register(&cdev->device);
	if (result) {
		release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
		kfree(cdev);
859
		return ERR_PTR(result);
Z
Zhang Rui 已提交
860 861 862 863
	}

	/* sys I/F */
	if (type) {
864
		result = device_create_file(&cdev->device, &dev_attr_cdev_type);
Z
Zhang Rui 已提交
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
		if (result)
			goto unregister;
	}

	result = device_create_file(&cdev->device, &dev_attr_max_state);
	if (result)
		goto unregister;

	result = device_create_file(&cdev->device, &dev_attr_cur_state);
	if (result)
		goto unregister;

	mutex_lock(&thermal_list_lock);
	list_add(&cdev->node, &thermal_cdev_list);
	list_for_each_entry(pos, &thermal_tz_list, node) {
		if (!pos->ops->bind)
			continue;
		result = pos->ops->bind(pos, cdev);
		if (result)
			break;

	}
	mutex_unlock(&thermal_list_lock);

	if (!result)
		return cdev;

      unregister:
	release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
	device_unregister(&cdev->device);
895
	return ERR_PTR(result);
Z
Zhang Rui 已提交
896
}
897

Z
Zhang Rui 已提交
898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
EXPORT_SYMBOL(thermal_cooling_device_register);

/**
 * thermal_cooling_device_unregister - removes the registered thermal cooling device
 * @cdev:	the thermal cooling device to remove.
 *
 * thermal_cooling_device_unregister() must be called when the device is no
 * longer needed.
 */
void thermal_cooling_device_unregister(struct
				       thermal_cooling_device
				       *cdev)
{
	struct thermal_zone_device *tz;
	struct thermal_cooling_device *pos = NULL;

	if (!cdev)
		return;

	mutex_lock(&thermal_list_lock);
	list_for_each_entry(pos, &thermal_cdev_list, node)
	    if (pos == cdev)
		break;
	if (pos != cdev) {
		/* thermal cooling device not found */
		mutex_unlock(&thermal_list_lock);
		return;
	}
	list_del(&cdev->node);
	list_for_each_entry(tz, &thermal_tz_list, node) {
		if (!tz->ops->unbind)
			continue;
		tz->ops->unbind(tz, cdev);
	}
	mutex_unlock(&thermal_list_lock);
	if (cdev->type[0])
934
		device_remove_file(&cdev->device, &dev_attr_cdev_type);
Z
Zhang Rui 已提交
935 936 937 938 939 940 941
	device_remove_file(&cdev->device, &dev_attr_max_state);
	device_remove_file(&cdev->device, &dev_attr_cur_state);

	release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
	device_unregister(&cdev->device);
	return;
}
942

Z
Zhang Rui 已提交
943 944
EXPORT_SYMBOL(thermal_cooling_device_unregister);

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
/**
 * thermal_zone_device_update - force an update of a thermal zone's state
 * @ttz:	the thermal zone to update
 */

void thermal_zone_device_update(struct thermal_zone_device *tz)
{
	int count, ret = 0;
	long temp, trip_temp;
	enum thermal_trip_type trip_type;
	struct thermal_cooling_device_instance *instance;
	struct thermal_cooling_device *cdev;

	mutex_lock(&tz->lock);

960 961 962 963 964 965
	if (tz->ops->get_temp(tz, &temp)) {
		/* get_temp failed - retry it later */
		printk(KERN_WARNING PREFIX "failed to read out thermal zone "
		       "%d\n", tz->id);
		goto leave;
	}
966 967 968 969 970 971 972

	for (count = 0; count < tz->trips; count++) {
		tz->ops->get_trip_type(tz, count, &trip_type);
		tz->ops->get_trip_temp(tz, count, &trip_temp);

		switch (trip_type) {
		case THERMAL_TRIP_CRITICAL:
973
			if (temp >= trip_temp) {
974 975 976 977 978 979 980 981 982 983 984 985
				if (tz->ops->notify)
					ret = tz->ops->notify(tz, count,
							      trip_type);
				if (!ret) {
					printk(KERN_EMERG
					       "Critical temperature reached (%ld C), shutting down.\n",
					       temp/1000);
					orderly_poweroff(true);
				}
			}
			break;
		case THERMAL_TRIP_HOT:
986
			if (temp >= trip_temp)
987 988 989 990 991 992 993 994 995 996 997
				if (tz->ops->notify)
					tz->ops->notify(tz, count, trip_type);
			break;
		case THERMAL_TRIP_ACTIVE:
			list_for_each_entry(instance, &tz->cooling_devices,
					    node) {
				if (instance->trip != count)
					continue;

				cdev = instance->cdev;

998
				if (temp >= trip_temp)
999 1000 1001 1002 1003 1004
					cdev->ops->set_cur_state(cdev, 1);
				else
					cdev->ops->set_cur_state(cdev, 0);
			}
			break;
		case THERMAL_TRIP_PASSIVE:
1005
			if (temp >= trip_temp || tz->passive)
1006 1007 1008 1009 1010
				thermal_zone_device_passive(tz, temp,
							    trip_temp, count);
			break;
		}
	}
1011 1012 1013 1014 1015

	if (tz->forced_passive)
		thermal_zone_device_passive(tz, temp, tz->forced_passive,
					    THERMAL_TRIPS_NONE);

1016
	tz->last_temperature = temp;
1017 1018

      leave:
1019 1020 1021 1022
	if (tz->passive)
		thermal_zone_device_set_polling(tz, tz->passive_delay);
	else if (tz->polling_delay)
		thermal_zone_device_set_polling(tz, tz->polling_delay);
1023 1024
	else
		thermal_zone_device_set_polling(tz, 0);
1025 1026 1027 1028
	mutex_unlock(&tz->lock);
}
EXPORT_SYMBOL(thermal_zone_device_update);

Z
Zhang Rui 已提交
1029 1030 1031 1032 1033 1034
/**
 * thermal_zone_device_register - register a new thermal zone device
 * @type:	the thermal zone device type
 * @trips:	the number of trip points the thermal zone support
 * @devdata:	private device data
 * @ops:	standard thermal zone device callbacks
1035 1036 1037 1038 1039 1040 1041
 * @tc1:	thermal coefficient 1 for passive calculations
 * @tc2:	thermal coefficient 2 for passive calculations
 * @passive_delay: number of milliseconds to wait between polls when
 *		   performing passive cooling
 * @polling_delay: number of milliseconds to wait between polls when checking
 *		   whether trip points have been crossed (0 for interrupt
 *		   driven systems)
Z
Zhang Rui 已提交
1042 1043
 *
 * thermal_zone_device_unregister() must be called when the device is no
1044 1045
 * longer needed. The passive cooling formula uses tc1 and tc2 as described in
 * section 11.1.5.1 of the ACPI specification 3.0.
Z
Zhang Rui 已提交
1046 1047
 */
struct thermal_zone_device *thermal_zone_device_register(char *type,
1048 1049 1050
							 int trips,
							 void *devdata, struct
							 thermal_zone_device_ops
1051 1052 1053 1054
							 *ops, int tc1, int
							 tc2,
							 int passive_delay,
							 int polling_delay)
Z
Zhang Rui 已提交
1055 1056 1057
{
	struct thermal_zone_device *tz;
	struct thermal_cooling_device *pos;
1058
	enum thermal_trip_type trip_type;
Z
Zhang Rui 已提交
1059 1060
	int result;
	int count;
1061
	int passive = 0;
Z
Zhang Rui 已提交
1062 1063

	if (strlen(type) >= THERMAL_NAME_LENGTH)
1064
		return ERR_PTR(-EINVAL);
Z
Zhang Rui 已提交
1065 1066

	if (trips > THERMAL_MAX_TRIPS || trips < 0)
1067
		return ERR_PTR(-EINVAL);
Z
Zhang Rui 已提交
1068 1069

	if (!ops || !ops->get_temp)
1070
		return ERR_PTR(-EINVAL);
Z
Zhang Rui 已提交
1071 1072 1073

	tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL);
	if (!tz)
1074
		return ERR_PTR(-ENOMEM);
Z
Zhang Rui 已提交
1075 1076 1077 1078 1079 1080 1081

	INIT_LIST_HEAD(&tz->cooling_devices);
	idr_init(&tz->idr);
	mutex_init(&tz->lock);
	result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id);
	if (result) {
		kfree(tz);
1082
		return ERR_PTR(result);
Z
Zhang Rui 已提交
1083 1084 1085 1086 1087 1088 1089
	}

	strcpy(tz->type, type);
	tz->ops = ops;
	tz->device.class = &thermal_class;
	tz->devdata = devdata;
	tz->trips = trips;
1090 1091 1092 1093 1094
	tz->tc1 = tc1;
	tz->tc2 = tc2;
	tz->passive_delay = passive_delay;
	tz->polling_delay = polling_delay;

1095
	dev_set_name(&tz->device, "thermal_zone%d", tz->id);
Z
Zhang Rui 已提交
1096 1097 1098 1099
	result = device_register(&tz->device);
	if (result) {
		release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
		kfree(tz);
1100
		return ERR_PTR(result);
Z
Zhang Rui 已提交
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
	}

	/* sys I/F */
	if (type) {
		result = device_create_file(&tz->device, &dev_attr_type);
		if (result)
			goto unregister;
	}

	result = device_create_file(&tz->device, &dev_attr_temp);
	if (result)
		goto unregister;

	if (ops->get_mode) {
		result = device_create_file(&tz->device, &dev_attr_mode);
		if (result)
			goto unregister;
	}

	for (count = 0; count < trips; count++) {
		TRIP_POINT_ATTR_ADD(&tz->device, count, result);
		if (result)
			goto unregister;
1124 1125 1126
		tz->ops->get_trip_type(tz, count, &trip_type);
		if (trip_type == THERMAL_TRIP_PASSIVE)
			passive = 1;
Z
Zhang Rui 已提交
1127 1128
	}

1129 1130 1131 1132 1133 1134 1135
	if (!passive)
		result = device_create_file(&tz->device,
					    &dev_attr_passive);

	if (result)
		goto unregister;

Z
Zhang Rui 已提交
1136 1137 1138 1139
	result = thermal_add_hwmon_sysfs(tz);
	if (result)
		goto unregister;

Z
Zhang Rui 已提交
1140 1141 1142 1143
	mutex_lock(&thermal_list_lock);
	list_add_tail(&tz->node, &thermal_tz_list);
	if (ops->bind)
		list_for_each_entry(pos, &thermal_cdev_list, node) {
1144 1145 1146
		result = ops->bind(tz, pos);
		if (result)
			break;
Z
Zhang Rui 已提交
1147 1148 1149
		}
	mutex_unlock(&thermal_list_lock);

1150 1151 1152 1153
	INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);

	thermal_zone_device_update(tz);

Z
Zhang Rui 已提交
1154 1155 1156 1157 1158 1159
	if (!result)
		return tz;

      unregister:
	release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
	device_unregister(&tz->device);
1160
	return ERR_PTR(result);
Z
Zhang Rui 已提交
1161
}
1162

Z
Zhang Rui 已提交
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
EXPORT_SYMBOL(thermal_zone_device_register);

/**
 * thermal_device_unregister - removes the registered thermal zone device
 * @tz: the thermal zone device to remove
 */
void thermal_zone_device_unregister(struct thermal_zone_device *tz)
{
	struct thermal_cooling_device *cdev;
	struct thermal_zone_device *pos = NULL;
	int count;

	if (!tz)
		return;

	mutex_lock(&thermal_list_lock);
	list_for_each_entry(pos, &thermal_tz_list, node)
	    if (pos == tz)
		break;
	if (pos != tz) {
		/* thermal zone device not found */
		mutex_unlock(&thermal_list_lock);
		return;
	}
	list_del(&tz->node);
	if (tz->ops->unbind)
		list_for_each_entry(cdev, &thermal_cdev_list, node)
		    tz->ops->unbind(tz, cdev);
	mutex_unlock(&thermal_list_lock);

1193 1194
	thermal_zone_device_set_polling(tz, 0);

Z
Zhang Rui 已提交
1195 1196 1197 1198 1199 1200 1201 1202 1203
	if (tz->type[0])
		device_remove_file(&tz->device, &dev_attr_type);
	device_remove_file(&tz->device, &dev_attr_temp);
	if (tz->ops->get_mode)
		device_remove_file(&tz->device, &dev_attr_mode);

	for (count = 0; count < tz->trips; count++)
		TRIP_POINT_ATTR_REMOVE(&tz->device, count);

Z
Zhang Rui 已提交
1204
	thermal_remove_hwmon_sysfs(tz);
Z
Zhang Rui 已提交
1205 1206 1207 1208 1209 1210
	release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
	idr_destroy(&tz->idr);
	mutex_destroy(&tz->lock);
	device_unregister(&tz->device);
	return;
}
1211

Z
Zhang Rui 已提交
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
EXPORT_SYMBOL(thermal_zone_device_unregister);

static int __init thermal_init(void)
{
	int result = 0;

	result = class_register(&thermal_class);
	if (result) {
		idr_destroy(&thermal_tz_idr);
		idr_destroy(&thermal_cdev_idr);
		mutex_destroy(&thermal_idr_lock);
		mutex_destroy(&thermal_list_lock);
	}
	return result;
}

static void __exit thermal_exit(void)
{
	class_unregister(&thermal_class);
	idr_destroy(&thermal_tz_idr);
	idr_destroy(&thermal_cdev_idr);
	mutex_destroy(&thermal_idr_lock);
	mutex_destroy(&thermal_list_lock);
}

subsys_initcall(thermal_init);
module_exit(thermal_exit);