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
L
Len Brown 已提交
48
ACPI_MODULE_NAME("acpi_power")
L
Linus Torvalds 已提交
49 50 51 52 53 54 55 56 57
#define ACPI_POWER_COMPONENT		0x00800000
#define ACPI_POWER_CLASS		"power_resource"
#define ACPI_POWER_DRIVER_NAME		"ACPI Power Resource Driver"
#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 已提交
58 59
static int acpi_power_add(struct acpi_device *device);
static int acpi_power_remove(struct acpi_device *device, int type);
60
static int acpi_power_resume(struct acpi_device *device, int state);
L
Linus Torvalds 已提交
61 62 63
static int acpi_power_open_fs(struct inode *inode, struct file *file);

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

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

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

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

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

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

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


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

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

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

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

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


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

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

	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 已提交
145
			  resource->name, resource->state ? "on" : "off"));
L
Linus Torvalds 已提交
146

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

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


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

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

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

		*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 已提交
177
			  *state ? "on" : "off"));
L
Linus Torvalds 已提交
178

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

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


	result = acpi_power_get_context(handle, &resource);
	if (result)
194
		return result;
L
Linus Torvalds 已提交
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 220
	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 已提交
221

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

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

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

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

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

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

L
Linus Torvalds 已提交
254 255 256

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

259 260 261 262 263 264 265 266 267 268 269
	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 已提交
270

271 272 273 274
	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);
275
		return 0;
L
Linus Torvalds 已提交
276
	}
277
	mutex_unlock(&resource->resource_lock);
L
Linus Torvalds 已提交
278 279 280

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

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

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

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

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

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

/*
 * 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 已提交
309
int acpi_enable_wakeup_device_power(struct acpi_device *dev)
L
Linus Torvalds 已提交
310
{
L
Len Brown 已提交
311 312 313 314 315
	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 已提交
316 317

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

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

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

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

/*
 * Shutdown a wakeup device, counterpart of above method
 * 1. Disable _PSW (power state wake)
 * 2. Shutdown down the power resources
 */
L
Len Brown 已提交
347
int acpi_disable_wakeup_device_power(struct acpi_device *dev)
L
Linus Torvalds 已提交
348
{
L
Len Brown 已提交
349 350 351 352 353
	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 已提交
354 355 356


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

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

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

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

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

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


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

	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 已提交
402
	for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
L
Linus Torvalds 已提交
403 404 405 406 407 408
		list = &device->power.states[i].resources;
		if (list->count < 1)
			continue;

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

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

	device->power.state = ACPI_STATE_D3;

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

L
Len Brown 已提交
422
int acpi_power_transition(struct acpi_device *device, int state)
L
Linus Torvalds 已提交
423
{
L
Len Brown 已提交
424 425 426 427
	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 已提交
428 429 430


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

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

	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 已提交
453
	for (i = 0; i < tl->count; i++) {
454
		result = acpi_power_on(tl->handles[i], device);
L
Linus Torvalds 已提交
455 456 457 458 459 460 461
		if (result)
			goto end;
	}

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

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

475
	return result;
L
Linus Torvalds 已提交
476 477 478 479 480 481
}

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

L
Len Brown 已提交
482
static struct proc_dir_entry *acpi_power_dir;
L
Linus Torvalds 已提交
483 484 485

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


493
	resource = seq->private;
L
Linus Torvalds 已提交
494 495 496 497

	if (!resource)
		goto end;

498 499 500 501
	result = acpi_power_get_state(resource);
	if (result)
		goto end;

L
Linus Torvalds 已提交
502 503 504 505 506 507 508 509 510 511 512 513 514
	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;
	}

515 516 517 518 519 520 521
	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 已提交
522
	seq_printf(seq, "system level:            S%d\n"
L
Len Brown 已提交
523 524 525
		   "order:                   %d\n"
		   "reference count:         %d\n",
		   resource->system_level,
526
		   resource->order, count);
L
Linus Torvalds 已提交
527

L
Len Brown 已提交
528
      end:
529
	return 0;
L
Linus Torvalds 已提交
530 531 532 533 534 535 536
}

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 已提交
537
static int acpi_power_add_fs(struct acpi_device *device)
L
Linus Torvalds 已提交
538
{
L
Len Brown 已提交
539
	struct proc_dir_entry *entry = NULL;
L
Linus Torvalds 已提交
540 541 542


	if (!device)
543
		return -EINVAL;
L
Linus Torvalds 已提交
544 545 546

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

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

562
	return 0;
L
Linus Torvalds 已提交
563 564
}

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

	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;
	}

575
	return 0;
L
Linus Torvalds 已提交
576 577 578 579 580 581
}

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

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


	if (!device)
592
		return -EINVAL;
L
Linus Torvalds 已提交
593

594
	resource = kzalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
L
Linus Torvalds 已提交
595
	if (!resource)
596
		return -ENOMEM;
L
Linus Torvalds 已提交
597

598
	resource->device = device;
599 600
	mutex_init(&resource->resource_lock);
	INIT_LIST_HEAD(&resource->reference);
L
Linus Torvalds 已提交
601 602 603 604 605 606
	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. */
607
	status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer);
L
Linus Torvalds 已提交
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
	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 已提交
634

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

L
Len Brown 已提交
638
      end:
L
Linus Torvalds 已提交
639 640
	if (result)
		kfree(resource);
L
Len Brown 已提交
641

642
	return result;
L
Linus Torvalds 已提交
643 644
}

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


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

654
	resource = acpi_driver_data(device);
L
Linus Torvalds 已提交
655 656 657

	acpi_power_remove_fs(device);

658 659 660 661 662 663 664 665
	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 已提交
666 667
	kfree(resource);

668
	return 0;
L
Linus Torvalds 已提交
669 670
}

671 672 673 674 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
static int acpi_power_resume(struct acpi_device *device, int state)
{
	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 已提交
706
static int __init acpi_power_init(void)
L
Linus Torvalds 已提交
707
{
L
Len Brown 已提交
708
	int result = 0;
L
Linus Torvalds 已提交
709 710 711


	if (acpi_disabled)
712
		return 0;
L
Linus Torvalds 已提交
713 714 715 716 717

	INIT_LIST_HEAD(&acpi_power_resource_list);

	acpi_power_dir = proc_mkdir(ACPI_POWER_CLASS, acpi_root_dir);
	if (!acpi_power_dir)
718
		return -ENODEV;
L
Linus Torvalds 已提交
719 720 721 722

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

726
	return 0;
L
Linus Torvalds 已提交
727 728 729
}

subsys_initcall(acpi_power_init);