power.c 18.5 KB
Newer Older
L
Linus Torvalds 已提交
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
/*
 *  acpi_power.c - ACPI Bus Power Management ($Revision: 39 $)
 *
 *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
 *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@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; either version 2 of the License, or (at
 *  your option) any later version.
 *
 *  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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

/*
 * ACPI power-managed devices may be controlled in two ways:
 * 1. via "Device Specific (D-State) Control"
 * 2. via "Power Resource Control".
 * This module is used to manage devices relying on Power Resource Control.
 * 
 * An ACPI "power resource object" describes a software controllable power
 * plane, clock plane, or other resource used by a power managed device.
 * A device may rely on multiple power resources, and a power resource
 * may be shared by multiple devices.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>

#define _COMPONENT		ACPI_POWER_COMPONENT
48
ACPI_MODULE_NAME("power");
L
Linus Torvalds 已提交
49 50 51 52 53 54 55 56
#define ACPI_POWER_COMPONENT		0x00800000
#define ACPI_POWER_CLASS		"power_resource"
#define ACPI_POWER_DEVICE_NAME		"Power Resource"
#define ACPI_POWER_FILE_INFO		"info"
#define ACPI_POWER_FILE_STATUS		"state"
#define ACPI_POWER_RESOURCE_STATE_OFF	0x00
#define ACPI_POWER_RESOURCE_STATE_ON	0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
L
Len Brown 已提交
57 58
static int acpi_power_add(struct acpi_device *device);
static int acpi_power_remove(struct acpi_device *device, int type);
59
static int acpi_power_resume(struct acpi_device *device);
L
Linus Torvalds 已提交
60 61 62
static int acpi_power_open_fs(struct inode *inode, struct file *file);

static struct acpi_driver acpi_power_driver = {
L
Len Brown 已提交
63
	.name = "power",
L
Len Brown 已提交
64 65 66 67 68
	.class = ACPI_POWER_CLASS,
	.ids = ACPI_POWER_HID,
	.ops = {
		.add = acpi_power_add,
		.remove = acpi_power_remove,
69
		.resume = acpi_power_resume,
L
Len Brown 已提交
70
		},
L
Linus Torvalds 已提交
71 72
};

73 74 75 76 77
struct acpi_power_reference {
	struct list_head node;
	struct acpi_device *device;
};

L
Len Brown 已提交
78
struct acpi_power_resource {
79
	struct acpi_device * device;
L
Len Brown 已提交
80 81 82 83
	acpi_bus_id name;
	u32 system_level;
	u32 order;
	int state;
84 85
	struct mutex resource_lock;
	struct list_head reference;
L
Linus Torvalds 已提交
86 87
};

L
Len Brown 已提交
88
static struct list_head acpi_power_resource_list;
L
Linus Torvalds 已提交
89

90
static const struct file_operations acpi_power_fops = {
L
Len Brown 已提交
91 92 93 94
	.open = acpi_power_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
L
Linus Torvalds 已提交
95 96 97 98 99 100 101
};

/* --------------------------------------------------------------------------
                             Power Resource Management
   -------------------------------------------------------------------------- */

static int
L
Len Brown 已提交
102 103
acpi_power_get_context(acpi_handle handle,
		       struct acpi_power_resource **resource)
L
Linus Torvalds 已提交
104
{
L
Len Brown 已提交
105 106
	int result = 0;
	struct acpi_device *device = NULL;
L
Linus Torvalds 已提交
107 108 109


	if (!resource)
110
		return -ENODEV;
L
Linus Torvalds 已提交
111 112 113

	result = acpi_bus_get_device(handle, &device);
	if (result) {
114
		printk(KERN_WARNING PREFIX "Getting context [%p]\n", handle);
115
		return result;
L
Linus Torvalds 已提交
116 117
	}

118
	*resource = acpi_driver_data(device);
L
Linus Torvalds 已提交
119
	if (!resource)
120
		return -ENODEV;
L
Linus Torvalds 已提交
121

122
	return 0;
L
Linus Torvalds 已提交
123 124
}

L
Len Brown 已提交
125
static int acpi_power_get_state(struct acpi_power_resource *resource)
L
Linus Torvalds 已提交
126
{
L
Len Brown 已提交
127 128
	acpi_status status = AE_OK;
	unsigned long sta = 0;
L
Linus Torvalds 已提交
129 130 131


	if (!resource)
132
		return -EINVAL;
L
Linus Torvalds 已提交
133

134
	status = acpi_evaluate_integer(resource->device->handle, "_STA", NULL, &sta);
L
Linus Torvalds 已提交
135
	if (ACPI_FAILURE(status))
136
		return -ENODEV;
L
Linus Torvalds 已提交
137 138 139 140 141 142 143

	if (sta & 0x01)
		resource->state = ACPI_POWER_RESOURCE_STATE_ON;
	else
		resource->state = ACPI_POWER_RESOURCE_STATE_OFF;

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] is %s\n",
L
Len Brown 已提交
144
			  resource->name, resource->state ? "on" : "off"));
L
Linus Torvalds 已提交
145

146
	return 0;
L
Linus Torvalds 已提交
147 148
}

L
Len Brown 已提交
149
static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
L
Linus Torvalds 已提交
150
{
L
Len Brown 已提交
151
	int result = 0;
L
Linus Torvalds 已提交
152
	struct acpi_power_resource *resource = NULL;
L
Len Brown 已提交
153
	u32 i = 0;
L
Linus Torvalds 已提交
154 155 156


	if (!list || !state)
157
		return -EINVAL;
L
Linus Torvalds 已提交
158 159 160

	/* The state of the list is 'on' IFF all resources are 'on'. */

L
Len Brown 已提交
161
	for (i = 0; i < list->count; i++) {
L
Linus Torvalds 已提交
162 163
		result = acpi_power_get_context(list->handles[i], &resource);
		if (result)
164
			return result;
L
Linus Torvalds 已提交
165 166
		result = acpi_power_get_state(resource);
		if (result)
167
			return result;
L
Linus Torvalds 已提交
168 169 170 171 172 173 174 175

		*state = resource->state;

		if (*state != ACPI_POWER_RESOURCE_STATE_ON)
			break;
	}

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s\n",
L
Len Brown 已提交
176
			  *state ? "on" : "off"));
L
Linus Torvalds 已提交
177

178
	return result;
L
Linus Torvalds 已提交
179 180
}

181
static int acpi_power_on(acpi_handle handle, struct acpi_device *dev)
L
Linus Torvalds 已提交
182
{
L
Len Brown 已提交
183
	int result = 0;
184
	int found = 0;
L
Len Brown 已提交
185
	acpi_status status = AE_OK;
L
Linus Torvalds 已提交
186
	struct acpi_power_resource *resource = NULL;
187 188
	struct list_head *node, *next;
	struct acpi_power_reference *ref;
L
Linus Torvalds 已提交
189 190 191 192


	result = acpi_power_get_context(handle, &resource);
	if (result)
193
		return result;
L
Linus Torvalds 已提交
194

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
	mutex_lock(&resource->resource_lock);
	list_for_each_safe(node, next, &resource->reference) {
		ref = container_of(node, struct acpi_power_reference, node);
		if (dev->handle == ref->device->handle) {
			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] already referenced by resource [%s]\n",
				  dev->pnp.bus_id, resource->name));
			found = 1;
			break;
		}
	}

	if (!found) {
		ref = kmalloc(sizeof (struct acpi_power_reference),
		    irqs_disabled() ? GFP_ATOMIC : GFP_KERNEL);
		if (!ref) {
			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "kmalloc() failed\n"));
			mutex_unlock(&resource->resource_lock);
			return -ENOMEM;
		}
		list_add_tail(&ref->node, &resource->reference);
		ref->device = dev;
		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] added to resource [%s] references\n",
			  dev->pnp.bus_id, resource->name));
	}
	mutex_unlock(&resource->resource_lock);
L
Linus Torvalds 已提交
220

221
	if (resource->state == ACPI_POWER_RESOURCE_STATE_ON) {
L
Linus Torvalds 已提交
222
		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already on\n",
L
Len Brown 已提交
223
				  resource->name));
224
		return 0;
L
Linus Torvalds 已提交
225 226
	}

227
	status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
L
Linus Torvalds 已提交
228
	if (ACPI_FAILURE(status))
229
		return -ENODEV;
L
Linus Torvalds 已提交
230 231 232

	result = acpi_power_get_state(resource);
	if (result)
233
		return result;
L
Linus Torvalds 已提交
234
	if (resource->state != ACPI_POWER_RESOURCE_STATE_ON)
235
		return -ENOEXEC;
L
Linus Torvalds 已提交
236 237

	/* Update the power resource's _device_ power state */
238
	resource->device->power.state = ACPI_STATE_D0;
L
Linus Torvalds 已提交
239 240

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned on\n",
L
Len Brown 已提交
241
			  resource->name));
242
	return 0;
L
Linus Torvalds 已提交
243 244
}

245
static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev)
L
Linus Torvalds 已提交
246
{
L
Len Brown 已提交
247 248
	int result = 0;
	acpi_status status = AE_OK;
L
Linus Torvalds 已提交
249
	struct acpi_power_resource *resource = NULL;
250 251 252
	struct list_head *node, *next;
	struct acpi_power_reference *ref;

L
Linus Torvalds 已提交
253 254 255

	result = acpi_power_get_context(handle, &resource);
	if (result)
256
		return result;
L
Linus Torvalds 已提交
257

258 259 260 261 262 263 264 265 266 267 268
	mutex_lock(&resource->resource_lock);
	list_for_each_safe(node, next, &resource->reference) {
		ref = container_of(node, struct acpi_power_reference, node);
		if (dev->handle == ref->device->handle) {
			list_del(&ref->node);
			kfree(ref);
			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] removed from resource [%s] references\n",
			    dev->pnp.bus_id, resource->name));
			break;
		}
	}
L
Linus Torvalds 已提交
269

270 271 272 273
	if (!list_empty(&resource->reference)) {
		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cannot turn resource [%s] off - resource is in use\n",
		    resource->name));
		mutex_unlock(&resource->resource_lock);
274
		return 0;
L
Linus Torvalds 已提交
275
	}
276
	mutex_unlock(&resource->resource_lock);
L
Linus Torvalds 已提交
277 278 279

	if (resource->state == ACPI_POWER_RESOURCE_STATE_OFF) {
		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already off\n",
280
				  resource->name));
281
		return 0;
L
Linus Torvalds 已提交
282 283
	}

284
	status = acpi_evaluate_object(resource->device->handle, "_OFF", NULL, NULL);
L
Linus Torvalds 已提交
285
	if (ACPI_FAILURE(status))
286
		return -ENODEV;
L
Linus Torvalds 已提交
287 288 289

	result = acpi_power_get_state(resource);
	if (result)
290
		return result;
L
Linus Torvalds 已提交
291
	if (resource->state != ACPI_POWER_RESOURCE_STATE_OFF)
292
		return -ENOEXEC;
L
Linus Torvalds 已提交
293 294

	/* Update the power resource's _device_ power state */
295
	resource->device->power.state = ACPI_STATE_D3;
L
Linus Torvalds 已提交
296 297

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned off\n",
L
Len Brown 已提交
298
			  resource->name));
L
Linus Torvalds 已提交
299

300
	return 0;
L
Linus Torvalds 已提交
301 302 303 304 305 306 307
}

/*
 * Prepare a wakeup device, two steps (Ref ACPI 2.0:P229):
 * 1. Power on the power resources required for the wakeup device 
 * 2. Enable _PSW (power state wake) for the device if present
 */
L
Len Brown 已提交
308
int acpi_enable_wakeup_device_power(struct acpi_device *dev)
L
Linus Torvalds 已提交
309
{
L
Len Brown 已提交
310 311 312 313 314
	union acpi_object arg = { ACPI_TYPE_INTEGER };
	struct acpi_object_list arg_list = { 1, &arg };
	acpi_status status = AE_OK;
	int i;
	int ret = 0;
L
Linus Torvalds 已提交
315 316

	if (!dev || !dev->wakeup.flags.valid)
317
		return -1;
L
Linus Torvalds 已提交
318 319 320 321

	arg.integer.value = 1;
	/* Open power resource */
	for (i = 0; i < dev->wakeup.resources.count; i++) {
322
		ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
L
Linus Torvalds 已提交
323
		if (ret) {
324
			printk(KERN_ERR PREFIX "Transition power state\n");
L
Linus Torvalds 已提交
325
			dev->wakeup.flags.valid = 0;
326
			return -1;
L
Linus Torvalds 已提交
327 328 329 330 331 332
		}
	}

	/* Execute PSW */
	status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
	if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
333
		printk(KERN_ERR PREFIX "Evaluate _PSW\n");
L
Linus Torvalds 已提交
334 335 336 337
		dev->wakeup.flags.valid = 0;
		ret = -1;
	}

338
	return ret;
L
Linus Torvalds 已提交
339 340 341 342 343 344 345
}

/*
 * Shutdown a wakeup device, counterpart of above method
 * 1. Disable _PSW (power state wake)
 * 2. Shutdown down the power resources
 */
L
Len Brown 已提交
346
int acpi_disable_wakeup_device_power(struct acpi_device *dev)
L
Linus Torvalds 已提交
347
{
L
Len Brown 已提交
348 349 350 351 352
	union acpi_object arg = { ACPI_TYPE_INTEGER };
	struct acpi_object_list arg_list = { 1, &arg };
	acpi_status status = AE_OK;
	int i;
	int ret = 0;
L
Linus Torvalds 已提交
353 354 355


	if (!dev || !dev->wakeup.flags.valid)
356
		return -1;
L
Linus Torvalds 已提交
357

L
Len Brown 已提交
358
	arg.integer.value = 0;
L
Linus Torvalds 已提交
359 360 361
	/* Execute PSW */
	status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
	if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
362
		printk(KERN_ERR PREFIX "Evaluate _PSW\n");
L
Linus Torvalds 已提交
363
		dev->wakeup.flags.valid = 0;
364
		return -1;
L
Linus Torvalds 已提交
365 366 367 368
	}

	/* Close power resource */
	for (i = 0; i < dev->wakeup.resources.count; i++) {
369
		ret = acpi_power_off_device(dev->wakeup.resources.handles[i], dev);
L
Linus Torvalds 已提交
370
		if (ret) {
371
			printk(KERN_ERR PREFIX "Transition power state\n");
L
Linus Torvalds 已提交
372
			dev->wakeup.flags.valid = 0;
373
			return -1;
L
Linus Torvalds 已提交
374 375 376
		}
	}

377
	return ret;
L
Linus Torvalds 已提交
378 379 380 381 382 383
}

/* --------------------------------------------------------------------------
                             Device Power Management
   -------------------------------------------------------------------------- */

L
Len Brown 已提交
384
int acpi_power_get_inferred_state(struct acpi_device *device)
L
Linus Torvalds 已提交
385
{
L
Len Brown 已提交
386 387 388 389
	int result = 0;
	struct acpi_handle_list *list = NULL;
	int list_state = 0;
	int i = 0;
L
Linus Torvalds 已提交
390 391 392


	if (!device)
393
		return -EINVAL;
L
Linus Torvalds 已提交
394 395 396 397 398 399 400

	device->power.state = ACPI_STATE_UNKNOWN;

	/*
	 * We know a device's inferred power state when all the resources
	 * required for a given D-state are 'on'.
	 */
L
Len Brown 已提交
401
	for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
L
Linus Torvalds 已提交
402 403 404 405 406 407
		list = &device->power.states[i].resources;
		if (list->count < 1)
			continue;

		result = acpi_power_get_list_state(list, &list_state);
		if (result)
408
			return result;
L
Linus Torvalds 已提交
409 410 411

		if (list_state == ACPI_POWER_RESOURCE_STATE_ON) {
			device->power.state = i;
412
			return 0;
L
Linus Torvalds 已提交
413 414 415 416 417
		}
	}

	device->power.state = ACPI_STATE_D3;

418
	return 0;
L
Linus Torvalds 已提交
419 420
}

L
Len Brown 已提交
421
int acpi_power_transition(struct acpi_device *device, int state)
L
Linus Torvalds 已提交
422
{
L
Len Brown 已提交
423 424 425 426
	int result = 0;
	struct acpi_handle_list *cl = NULL;	/* Current Resources */
	struct acpi_handle_list *tl = NULL;	/* Target Resources */
	int i = 0;
L
Linus Torvalds 已提交
427 428 429


	if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
430
		return -EINVAL;
L
Linus Torvalds 已提交
431

L
Len Brown 已提交
432 433
	if ((device->power.state < ACPI_STATE_D0)
	    || (device->power.state > ACPI_STATE_D3))
434
		return -ENODEV;
L
Linus Torvalds 已提交
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451

	cl = &device->power.states[device->power.state].resources;
	tl = &device->power.states[state].resources;

	device->power.state = ACPI_STATE_UNKNOWN;

	if (!cl->count && !tl->count) {
		result = -ENODEV;
		goto end;
	}

	/* TBD: Resources must be ordered. */

	/*
	 * First we reference all power resources required in the target list
	 * (e.g. so the device doesn't lose power while transitioning).
	 */
L
Len Brown 已提交
452
	for (i = 0; i < tl->count; i++) {
453
		result = acpi_power_on(tl->handles[i], device);
L
Linus Torvalds 已提交
454 455 456 457
		if (result)
			goto end;
	}

458 459 460 461
	if (device->power.state == state) {
		goto end;
	}

L
Linus Torvalds 已提交
462 463 464
	/*
	 * Then we dereference all power resources used in the current list.
	 */
L
Len Brown 已提交
465
	for (i = 0; i < cl->count; i++) {
466
		result = acpi_power_off_device(cl->handles[i], device);
L
Linus Torvalds 已提交
467 468 469 470 471 472
		if (result)
			goto end;
	}

	/* We shouldn't change the state till all above operations succeed */
	device->power.state = state;
L
Len Brown 已提交
473
      end:
L
Linus Torvalds 已提交
474
	if (result)
475 476
		printk(KERN_WARNING PREFIX "Transitioning device [%s] to D%d\n",
			      device->pnp.bus_id, state);
L
Linus Torvalds 已提交
477

478
	return result;
L
Linus Torvalds 已提交
479 480 481 482 483 484
}

/* --------------------------------------------------------------------------
                              FS Interface (/proc)
   -------------------------------------------------------------------------- */

L
Len Brown 已提交
485
static struct proc_dir_entry *acpi_power_dir;
L
Linus Torvalds 已提交
486 487 488

static int acpi_power_seq_show(struct seq_file *seq, void *offset)
{
489 490
	int count = 0;
	int result = 0;
L
Linus Torvalds 已提交
491
	struct acpi_power_resource *resource = NULL;
492 493
	struct list_head *node, *next;
	struct acpi_power_reference *ref;
L
Linus Torvalds 已提交
494 495


496
	resource = seq->private;
L
Linus Torvalds 已提交
497 498 499 500

	if (!resource)
		goto end;

501 502 503 504
	result = acpi_power_get_state(resource);
	if (result)
		goto end;

L
Linus Torvalds 已提交
505 506 507 508 509 510 511 512 513 514 515 516 517
	seq_puts(seq, "state:                   ");
	switch (resource->state) {
	case ACPI_POWER_RESOURCE_STATE_ON:
		seq_puts(seq, "on\n");
		break;
	case ACPI_POWER_RESOURCE_STATE_OFF:
		seq_puts(seq, "off\n");
		break;
	default:
		seq_puts(seq, "unknown\n");
		break;
	}

518 519 520 521 522 523 524
	mutex_lock(&resource->resource_lock);
	list_for_each_safe(node, next, &resource->reference) {
		ref = container_of(node, struct acpi_power_reference, node);
		count++;
	}
	mutex_unlock(&resource->resource_lock);

L
Linus Torvalds 已提交
525
	seq_printf(seq, "system level:            S%d\n"
L
Len Brown 已提交
526 527 528
		   "order:                   %d\n"
		   "reference count:         %d\n",
		   resource->system_level,
529
		   resource->order, count);
L
Linus Torvalds 已提交
530

L
Len Brown 已提交
531
      end:
532
	return 0;
L
Linus Torvalds 已提交
533 534 535 536 537 538 539
}

static int acpi_power_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_power_seq_show, PDE(inode)->data);
}

L
Len Brown 已提交
540
static int acpi_power_add_fs(struct acpi_device *device)
L
Linus Torvalds 已提交
541
{
L
Len Brown 已提交
542
	struct proc_dir_entry *entry = NULL;
L
Linus Torvalds 已提交
543 544 545


	if (!device)
546
		return -EINVAL;
L
Linus Torvalds 已提交
547 548 549

	if (!acpi_device_dir(device)) {
		acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
L
Len Brown 已提交
550
						     acpi_power_dir);
L
Linus Torvalds 已提交
551
		if (!acpi_device_dir(device))
552
			return -ENODEV;
L
Linus Torvalds 已提交
553 554 555 556
	}

	/* 'status' [R] */
	entry = create_proc_entry(ACPI_POWER_FILE_STATUS,
L
Len Brown 已提交
557
				  S_IRUGO, acpi_device_dir(device));
L
Linus Torvalds 已提交
558
	if (!entry)
559
		return -EIO;
L
Linus Torvalds 已提交
560 561 562 563 564
	else {
		entry->proc_fops = &acpi_power_fops;
		entry->data = acpi_driver_data(device);
	}

565
	return 0;
L
Linus Torvalds 已提交
566 567
}

L
Len Brown 已提交
568
static int acpi_power_remove_fs(struct acpi_device *device)
L
Linus Torvalds 已提交
569 570 571 572 573 574 575 576 577
{

	if (acpi_device_dir(device)) {
		remove_proc_entry(ACPI_POWER_FILE_STATUS,
				  acpi_device_dir(device));
		remove_proc_entry(acpi_device_bid(device), acpi_power_dir);
		acpi_device_dir(device) = NULL;
	}

578
	return 0;
L
Linus Torvalds 已提交
579 580 581 582 583 584
}

/* --------------------------------------------------------------------------
                                Driver Interface
   -------------------------------------------------------------------------- */

L
Len Brown 已提交
585
static int acpi_power_add(struct acpi_device *device)
L
Linus Torvalds 已提交
586
{
L
Len Brown 已提交
587 588
	int result = 0;
	acpi_status status = AE_OK;
L
Linus Torvalds 已提交
589
	struct acpi_power_resource *resource = NULL;
L
Len Brown 已提交
590 591
	union acpi_object acpi_object;
	struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
L
Linus Torvalds 已提交
592 593 594


	if (!device)
595
		return -EINVAL;
L
Linus Torvalds 已提交
596

597
	resource = kzalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
L
Linus Torvalds 已提交
598
	if (!resource)
599
		return -ENOMEM;
L
Linus Torvalds 已提交
600

601
	resource->device = device;
602 603
	mutex_init(&resource->resource_lock);
	INIT_LIST_HEAD(&resource->reference);
L
Linus Torvalds 已提交
604 605 606 607 608 609
	strcpy(resource->name, device->pnp.bus_id);
	strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
	strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
	acpi_driver_data(device) = resource;

	/* Evalute the object to get the system level and resource order. */
610
	status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer);
L
Linus Torvalds 已提交
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
	if (ACPI_FAILURE(status)) {
		result = -ENODEV;
		goto end;
	}
	resource->system_level = acpi_object.power_resource.system_level;
	resource->order = acpi_object.power_resource.resource_order;

	result = acpi_power_get_state(resource);
	if (result)
		goto end;

	switch (resource->state) {
	case ACPI_POWER_RESOURCE_STATE_ON:
		device->power.state = ACPI_STATE_D0;
		break;
	case ACPI_POWER_RESOURCE_STATE_OFF:
		device->power.state = ACPI_STATE_D3;
		break;
	default:
		device->power.state = ACPI_STATE_UNKNOWN;
		break;
	}

	result = acpi_power_add_fs(device);
	if (result)
		goto end;
L
Len Brown 已提交
637

L
Linus Torvalds 已提交
638
	printk(KERN_INFO PREFIX "%s [%s] (%s)\n", acpi_device_name(device),
L
Len Brown 已提交
639
	       acpi_device_bid(device), resource->state ? "on" : "off");
L
Linus Torvalds 已提交
640

L
Len Brown 已提交
641
      end:
L
Linus Torvalds 已提交
642 643
	if (result)
		kfree(resource);
L
Len Brown 已提交
644

645
	return result;
L
Linus Torvalds 已提交
646 647
}

L
Len Brown 已提交
648
static int acpi_power_remove(struct acpi_device *device, int type)
L
Linus Torvalds 已提交
649 650
{
	struct acpi_power_resource *resource = NULL;
651
	struct list_head *node, *next;
L
Linus Torvalds 已提交
652 653 654


	if (!device || !acpi_driver_data(device))
655
		return -EINVAL;
L
Linus Torvalds 已提交
656

657
	resource = acpi_driver_data(device);
L
Linus Torvalds 已提交
658 659 660

	acpi_power_remove_fs(device);

661 662 663 664 665 666 667 668
	mutex_lock(&resource->resource_lock);
	list_for_each_safe(node, next, &resource->reference) {
		struct acpi_power_reference *ref = container_of(node, struct acpi_power_reference, node);
		list_del(&ref->node);
		kfree(ref);
	}
	mutex_unlock(&resource->resource_lock);

L
Linus Torvalds 已提交
669 670
	kfree(resource);

671
	return 0;
L
Linus Torvalds 已提交
672 673
}

674
static int acpi_power_resume(struct acpi_device *device)
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
{
	int result = 0;
	struct acpi_power_resource *resource = NULL;
	struct acpi_power_reference *ref;

	if (!device || !acpi_driver_data(device))
		return -EINVAL;

	resource = (struct acpi_power_resource *)acpi_driver_data(device);

	result = acpi_power_get_state(resource);
	if (result)
		return result;

	mutex_lock(&resource->resource_lock);
	if ((resource->state == ACPI_POWER_RESOURCE_STATE_ON) &&
	    list_empty(&resource->reference)) {
		mutex_unlock(&resource->resource_lock);
		result = acpi_power_off_device(device->handle, NULL);
		return result;
	}

	if ((resource->state == ACPI_POWER_RESOURCE_STATE_OFF) &&
	    !list_empty(&resource->reference)) {
		ref = container_of(resource->reference.next, struct acpi_power_reference, node);
		mutex_unlock(&resource->resource_lock);
		result = acpi_power_on(device->handle, ref->device);
		return result;
	}

	mutex_unlock(&resource->resource_lock);
	return 0;
}

L
Len Brown 已提交
709
static int __init acpi_power_init(void)
L
Linus Torvalds 已提交
710
{
L
Len Brown 已提交
711
	int result = 0;
L
Linus Torvalds 已提交
712 713 714


	if (acpi_disabled)
715
		return 0;
L
Linus Torvalds 已提交
716 717 718 719 720

	INIT_LIST_HEAD(&acpi_power_resource_list);

	acpi_power_dir = proc_mkdir(ACPI_POWER_CLASS, acpi_root_dir);
	if (!acpi_power_dir)
721
		return -ENODEV;
L
Linus Torvalds 已提交
722 723 724 725

	result = acpi_bus_register_driver(&acpi_power_driver);
	if (result < 0) {
		remove_proc_entry(ACPI_POWER_CLASS, acpi_root_dir);
726
		return -ENODEV;
L
Linus Torvalds 已提交
727 728
	}

729
	return 0;
L
Linus Torvalds 已提交
730 731 732
}

subsys_initcall(acpi_power_init);