Blame view

drivers/acpi/power.c 18.3 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
  /*
   *  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>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
46
  #define _COMPONENT		ACPI_POWER_COMPONENT
f52fd66d2   Len Brown   ACPI: clean up AC...
47
  ACPI_MODULE_NAME("power");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
48
49
  #define ACPI_POWER_COMPONENT		0x00800000
  #define ACPI_POWER_CLASS		"power_resource"
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
50
51
52
53
54
55
  #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
4be44fcd3   Len Brown   [ACPI] Lindent al...
56
57
  static int acpi_power_add(struct acpi_device *device);
  static int acpi_power_remove(struct acpi_device *device, int type);
e8363f332   Len Brown   ACPI: update acpi...
58
  static int acpi_power_resume(struct acpi_device *device);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
59
60
61
  static int acpi_power_open_fs(struct inode *inode, struct file *file);
  
  static struct acpi_driver acpi_power_driver = {
c2b6705b7   Len Brown   ACPI: fix acpi_dr...
62
  	.name = "power",
4be44fcd3   Len Brown   [ACPI] Lindent al...
63
64
65
66
67
  	.class = ACPI_POWER_CLASS,
  	.ids = ACPI_POWER_HID,
  	.ops = {
  		.add = acpi_power_add,
  		.remove = acpi_power_remove,
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
68
  		.resume = acpi_power_resume,
4be44fcd3   Len Brown   [ACPI] Lindent al...
69
  		},
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
70
  };
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
71
72
73
74
  struct acpi_power_reference {
  	struct list_head node;
  	struct acpi_device *device;
  };
4be44fcd3   Len Brown   [ACPI] Lindent al...
75
  struct acpi_power_resource {
415985728   Patrick Mochel   ACPI: power: add ...
76
  	struct acpi_device * device;
4be44fcd3   Len Brown   [ACPI] Lindent al...
77
78
79
80
  	acpi_bus_id name;
  	u32 system_level;
  	u32 order;
  	int state;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
81
82
  	struct mutex resource_lock;
  	struct list_head reference;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
83
  };
4be44fcd3   Len Brown   [ACPI] Lindent al...
84
  static struct list_head acpi_power_resource_list;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
85

d75080328   Arjan van de Ven   ACPI: add 'const'...
86
  static const struct file_operations acpi_power_fops = {
4be44fcd3   Len Brown   [ACPI] Lindent al...
87
88
89
90
  	.open = acpi_power_open_fs,
  	.read = seq_read,
  	.llseek = seq_lseek,
  	.release = single_release,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
91
92
93
94
95
96
97
  };
  
  /* --------------------------------------------------------------------------
                               Power Resource Management
     -------------------------------------------------------------------------- */
  
  static int
4be44fcd3   Len Brown   [ACPI] Lindent al...
98
99
  acpi_power_get_context(acpi_handle handle,
  		       struct acpi_power_resource **resource)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
100
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
101
102
  	int result = 0;
  	struct acpi_device *device = NULL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
103

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
104
105
  
  	if (!resource)
d550d98d3   Patrick Mochel   ACPI: delete trac...
106
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
107
108
109
  
  	result = acpi_bus_get_device(handle, &device);
  	if (result) {
cece92969   Len Brown   ACPI: un-export A...
110
111
  		printk(KERN_WARNING PREFIX "Getting context [%p]
  ", handle);
d550d98d3   Patrick Mochel   ACPI: delete trac...
112
  		return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
113
  	}
50dd09697   Jan Engelhardt   ACPI: Remove unne...
114
  	*resource = acpi_driver_data(device);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
115
  	if (!resource)
d550d98d3   Patrick Mochel   ACPI: delete trac...
116
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
117

d550d98d3   Patrick Mochel   ACPI: delete trac...
118
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
119
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
120
  static int acpi_power_get_state(struct acpi_power_resource *resource)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
121
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
122
123
  	acpi_status status = AE_OK;
  	unsigned long sta = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
124

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
125
126
  
  	if (!resource)
d550d98d3   Patrick Mochel   ACPI: delete trac...
127
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
128

5fbc19efd   Patrick Mochel   ACPI: power: Use ...
129
  	status = acpi_evaluate_integer(resource->device->handle, "_STA", NULL, &sta);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
  	if (ACPI_FAILURE(status))
d550d98d3   Patrick Mochel   ACPI: delete trac...
131
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
132
133
134
135
136
137
138
139
  
  	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
  ",
4be44fcd3   Len Brown   [ACPI] Lindent al...
140
  			  resource->name, resource->state ? "on" : "off"));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
141

d550d98d3   Patrick Mochel   ACPI: delete trac...
142
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
143
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
144
  static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
146
  	int result = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
147
  	struct acpi_power_resource *resource = NULL;
4be44fcd3   Len Brown   [ACPI] Lindent al...
148
  	u32 i = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
149

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
150
151
  
  	if (!list || !state)
d550d98d3   Patrick Mochel   ACPI: delete trac...
152
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
153
154
  
  	/* The state of the list is 'on' IFF all resources are 'on'. */
4be44fcd3   Len Brown   [ACPI] Lindent al...
155
  	for (i = 0; i < list->count; i++) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
156
157
  		result = acpi_power_get_context(list->handles[i], &resource);
  		if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
158
  			return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
159
160
  		result = acpi_power_get_state(resource);
  		if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
161
  			return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
162
163
164
165
166
167
168
169
170
  
  		*state = resource->state;
  
  		if (*state != ACPI_POWER_RESOURCE_STATE_ON)
  			break;
  	}
  
  	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s
  ",
4be44fcd3   Len Brown   [ACPI] Lindent al...
171
  			  *state ? "on" : "off"));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
172

d550d98d3   Patrick Mochel   ACPI: delete trac...
173
  	return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
174
  }
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
175
  static int acpi_power_on(acpi_handle handle, struct acpi_device *dev)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
176
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
177
  	int result = 0;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
178
  	int found = 0;
4be44fcd3   Len Brown   [ACPI] Lindent al...
179
  	acpi_status status = AE_OK;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
  	struct acpi_power_resource *resource = NULL;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
181
182
  	struct list_head *node, *next;
  	struct acpi_power_reference *ref;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
183

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
184
185
186
  
  	result = acpi_power_get_context(handle, &resource);
  	if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
187
  		return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
188

0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  	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]
  ",
  				  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
  "));
  			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
  ",
  			  dev->pnp.bus_id, resource->name));
  	}
  	mutex_unlock(&resource->resource_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
217

0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
218
  	if (resource->state == ACPI_POWER_RESOURCE_STATE_ON) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
219
220
  		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already on
  ",
4be44fcd3   Len Brown   [ACPI] Lindent al...
221
  				  resource->name));
d550d98d3   Patrick Mochel   ACPI: delete trac...
222
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
223
  	}
5fbc19efd   Patrick Mochel   ACPI: power: Use ...
224
  	status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
225
  	if (ACPI_FAILURE(status))
d550d98d3   Patrick Mochel   ACPI: delete trac...
226
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227
228
229
  
  	result = acpi_power_get_state(resource);
  	if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
230
  		return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
231
  	if (resource->state != ACPI_POWER_RESOURCE_STATE_ON)
d550d98d3   Patrick Mochel   ACPI: delete trac...
232
  		return -ENOEXEC;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
233
234
  
  	/* Update the power resource's _device_ power state */
415985728   Patrick Mochel   ACPI: power: add ...
235
  	resource->device->power.state = ACPI_STATE_D0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
237
238
  
  	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned on
  ",
4be44fcd3   Len Brown   [ACPI] Lindent al...
239
  			  resource->name));
d550d98d3   Patrick Mochel   ACPI: delete trac...
240
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
241
  }
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
242
  static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
243
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
244
245
  	int result = 0;
  	acpi_status status = AE_OK;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
246
  	struct acpi_power_resource *resource = NULL;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
247
248
  	struct list_head *node, *next;
  	struct acpi_power_reference *ref;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
249

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
250
251
  	result = acpi_power_get_context(handle, &resource);
  	if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
252
  		return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
253

0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
254
255
256
257
258
259
260
261
262
263
264
265
  	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
  ",
  			    dev->pnp.bus_id, resource->name));
  			break;
  		}
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
266

0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
267
268
269
270
271
  	if (!list_empty(&resource->reference)) {
  		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cannot turn resource [%s] off - resource is in use
  ",
  		    resource->name));
  		mutex_unlock(&resource->resource_lock);
d550d98d3   Patrick Mochel   ACPI: delete trac...
272
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
273
  	}
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
274
  	mutex_unlock(&resource->resource_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
275
276
277
278
  
  	if (resource->state == ACPI_POWER_RESOURCE_STATE_OFF) {
  		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already off
  ",
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
279
  				  resource->name));
d550d98d3   Patrick Mochel   ACPI: delete trac...
280
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
281
  	}
5fbc19efd   Patrick Mochel   ACPI: power: Use ...
282
  	status = acpi_evaluate_object(resource->device->handle, "_OFF", NULL, NULL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
283
  	if (ACPI_FAILURE(status))
d550d98d3   Patrick Mochel   ACPI: delete trac...
284
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
285
286
287
  
  	result = acpi_power_get_state(resource);
  	if (result)
d550d98d3   Patrick Mochel   ACPI: delete trac...
288
  		return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
289
  	if (resource->state != ACPI_POWER_RESOURCE_STATE_OFF)
d550d98d3   Patrick Mochel   ACPI: delete trac...
290
  		return -ENOEXEC;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
291
292
  
  	/* Update the power resource's _device_ power state */
786f18c66   Dmitry Torokhov   ACPI: fix potenti...
293
  	resource->device->power.state = ACPI_STATE_D3;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
294
295
296
  
  	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned off
  ",
4be44fcd3   Len Brown   [ACPI] Lindent al...
297
  			  resource->name));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
298

d550d98d3   Patrick Mochel   ACPI: delete trac...
299
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
300
301
302
303
304
305
306
  }
  
  /*
   * 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
   */
4be44fcd3   Len Brown   [ACPI] Lindent al...
307
  int acpi_enable_wakeup_device_power(struct acpi_device *dev)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
308
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
309
310
311
312
313
  	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;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
314

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
315
  	if (!dev || !dev->wakeup.flags.valid)
d550d98d3   Patrick Mochel   ACPI: delete trac...
316
  		return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
317
318
319
320
  
  	arg.integer.value = 1;
  	/* Open power resource */
  	for (i = 0; i < dev->wakeup.resources.count; i++) {
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
321
  		ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
322
  		if (ret) {
6468463ab   Len Brown   ACPI: un-export A...
323
324
  			printk(KERN_ERR PREFIX "Transition power state
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
325
  			dev->wakeup.flags.valid = 0;
d550d98d3   Patrick Mochel   ACPI: delete trac...
326
  			return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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)) {
6468463ab   Len Brown   ACPI: un-export A...
333
334
  		printk(KERN_ERR PREFIX "Evaluate _PSW
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
335
336
337
  		dev->wakeup.flags.valid = 0;
  		ret = -1;
  	}
d550d98d3   Patrick Mochel   ACPI: delete trac...
338
  	return ret;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
   */
4be44fcd3   Len Brown   [ACPI] Lindent al...
346
  int acpi_disable_wakeup_device_power(struct acpi_device *dev)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
347
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
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;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
353

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
354
355
  
  	if (!dev || !dev->wakeup.flags.valid)
d550d98d3   Patrick Mochel   ACPI: delete trac...
356
  		return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
357

4be44fcd3   Len Brown   [ACPI] Lindent al...
358
  	arg.integer.value = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
359
360
361
  	/* Execute PSW */
  	status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
  	if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
6468463ab   Len Brown   ACPI: un-export A...
362
363
  		printk(KERN_ERR PREFIX "Evaluate _PSW
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
364
  		dev->wakeup.flags.valid = 0;
d550d98d3   Patrick Mochel   ACPI: delete trac...
365
  		return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
366
367
368
369
  	}
  
  	/* Close power resource */
  	for (i = 0; i < dev->wakeup.resources.count; i++) {
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
370
  		ret = acpi_power_off_device(dev->wakeup.resources.handles[i], dev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
371
  		if (ret) {
6468463ab   Len Brown   ACPI: un-export A...
372
373
  			printk(KERN_ERR PREFIX "Transition power state
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
374
  			dev->wakeup.flags.valid = 0;
d550d98d3   Patrick Mochel   ACPI: delete trac...
375
  			return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
376
377
  		}
  	}
d550d98d3   Patrick Mochel   ACPI: delete trac...
378
  	return ret;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
379
380
381
382
383
  }
  
  /* --------------------------------------------------------------------------
                               Device Power Management
     -------------------------------------------------------------------------- */
4be44fcd3   Len Brown   [ACPI] Lindent al...
384
  int acpi_power_get_inferred_state(struct acpi_device *device)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
385
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
386
387
388
389
  	int result = 0;
  	struct acpi_handle_list *list = NULL;
  	int list_state = 0;
  	int i = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
390

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
391
392
  
  	if (!device)
d550d98d3   Patrick Mochel   ACPI: delete trac...
393
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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'.
  	 */
4be44fcd3   Len Brown   [ACPI] Lindent al...
401
  	for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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)
d550d98d3   Patrick Mochel   ACPI: delete trac...
408
  			return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
409
410
411
  
  		if (list_state == ACPI_POWER_RESOURCE_STATE_ON) {
  			device->power.state = i;
d550d98d3   Patrick Mochel   ACPI: delete trac...
412
  			return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
413
414
415
416
  		}
  	}
  
  	device->power.state = ACPI_STATE_D3;
d550d98d3   Patrick Mochel   ACPI: delete trac...
417
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
418
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
419
  int acpi_power_transition(struct acpi_device *device, int state)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
420
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
421
422
423
424
  	int result = 0;
  	struct acpi_handle_list *cl = NULL;	/* Current Resources */
  	struct acpi_handle_list *tl = NULL;	/* Target Resources */
  	int i = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
425

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
426
427
  
  	if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
d550d98d3   Patrick Mochel   ACPI: delete trac...
428
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
429

4be44fcd3   Len Brown   [ACPI] Lindent al...
430
431
  	if ((device->power.state < ACPI_STATE_D0)
  	    || (device->power.state > ACPI_STATE_D3))
d550d98d3   Patrick Mochel   ACPI: delete trac...
432
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
433
434
435
  
  	cl = &device->power.states[device->power.state].resources;
  	tl = &device->power.states[state].resources;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
436
437
438
439
440
441
442
443
444
445
446
  	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).
  	 */
4be44fcd3   Len Brown   [ACPI] Lindent al...
447
  	for (i = 0; i < tl->count; i++) {
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
448
  		result = acpi_power_on(tl->handles[i], device);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
449
450
451
  		if (result)
  			goto end;
  	}
b1028c545   Konstantin Karasyov   ACPI: fix fan aft...
452
453
454
  	if (device->power.state == state) {
  		goto end;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
455
456
457
  	/*
  	 * Then we dereference all power resources used in the current list.
  	 */
4be44fcd3   Len Brown   [ACPI] Lindent al...
458
  	for (i = 0; i < cl->count; i++) {
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
459
  		result = acpi_power_off_device(cl->handles[i], device);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
460
461
462
  		if (result)
  			goto end;
  	}
729257604   Konstantin Karasyov   ACPI: fix S3 fan ...
463
464
465
       end:
  	if (result) {
  		device->power.state = ACPI_STATE_UNKNOWN;
cece92969   Len Brown   ACPI: un-export A...
466
467
468
  		printk(KERN_WARNING PREFIX "Transitioning device [%s] to D%d
  ",
  			      device->pnp.bus_id, state);
729257604   Konstantin Karasyov   ACPI: fix S3 fan ...
469
470
471
472
  	} else {
  	/* We shouldn't change the state till all above operations succeed */
  		device->power.state = state;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
473

d550d98d3   Patrick Mochel   ACPI: delete trac...
474
  	return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
475
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
476
477
478
  /* --------------------------------------------------------------------------
                                FS Interface (/proc)
     -------------------------------------------------------------------------- */
4be44fcd3   Len Brown   [ACPI] Lindent al...
479
  static struct proc_dir_entry *acpi_power_dir;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
480
481
482
  
  static int acpi_power_seq_show(struct seq_file *seq, void *offset)
  {
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
483
484
  	int count = 0;
  	int result = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
485
  	struct acpi_power_resource *resource = NULL;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
486
487
  	struct list_head *node, *next;
  	struct acpi_power_reference *ref;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
488

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
489

50dd09697   Jan Engelhardt   ACPI: Remove unne...
490
  	resource = seq->private;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
491
492
493
  
  	if (!resource)
  		goto end;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
494
495
496
  	result = acpi_power_get_state(resource);
  	if (result)
  		goto end;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
  	seq_puts(seq, "state:                   ");
  	switch (resource->state) {
  	case ACPI_POWER_RESOURCE_STATE_ON:
  		seq_puts(seq, "on
  ");
  		break;
  	case ACPI_POWER_RESOURCE_STATE_OFF:
  		seq_puts(seq, "off
  ");
  		break;
  	default:
  		seq_puts(seq, "unknown
  ");
  		break;
  	}
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
512
513
514
515
516
517
  	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);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
518
519
  	seq_printf(seq, "system level:            S%d
  "
4be44fcd3   Len Brown   [ACPI] Lindent al...
520
521
522
523
524
  		   "order:                   %d
  "
  		   "reference count:         %d
  ",
  		   resource->system_level,
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
525
  		   resource->order, count);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
526

4be44fcd3   Len Brown   [ACPI] Lindent al...
527
        end:
d550d98d3   Patrick Mochel   ACPI: delete trac...
528
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
529
530
531
532
533
534
  }
  
  static int acpi_power_open_fs(struct inode *inode, struct file *file)
  {
  	return single_open(file, acpi_power_seq_show, PDE(inode)->data);
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
535
  static int acpi_power_add_fs(struct acpi_device *device)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
536
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
537
  	struct proc_dir_entry *entry = NULL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
538

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
539
540
  
  	if (!device)
d550d98d3   Patrick Mochel   ACPI: delete trac...
541
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
542
543
544
  
  	if (!acpi_device_dir(device)) {
  		acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
4be44fcd3   Len Brown   [ACPI] Lindent al...
545
  						     acpi_power_dir);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
546
  		if (!acpi_device_dir(device))
d550d98d3   Patrick Mochel   ACPI: delete trac...
547
  			return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
548
549
550
551
  	}
  
  	/* 'status' [R] */
  	entry = create_proc_entry(ACPI_POWER_FILE_STATUS,
4be44fcd3   Len Brown   [ACPI] Lindent al...
552
  				  S_IRUGO, acpi_device_dir(device));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
553
  	if (!entry)
d550d98d3   Patrick Mochel   ACPI: delete trac...
554
  		return -EIO;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
555
556
557
558
  	else {
  		entry->proc_fops = &acpi_power_fops;
  		entry->data = acpi_driver_data(device);
  	}
d550d98d3   Patrick Mochel   ACPI: delete trac...
559
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
560
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
561
  static int acpi_power_remove_fs(struct acpi_device *device)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
562
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
563
564
565
566
567
568
569
  
  	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;
  	}
d550d98d3   Patrick Mochel   ACPI: delete trac...
570
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
571
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
572
573
574
  /* --------------------------------------------------------------------------
                                  Driver Interface
     -------------------------------------------------------------------------- */
4be44fcd3   Len Brown   [ACPI] Lindent al...
575
  static int acpi_power_add(struct acpi_device *device)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
576
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
577
578
  	int result = 0;
  	acpi_status status = AE_OK;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
579
  	struct acpi_power_resource *resource = NULL;
4be44fcd3   Len Brown   [ACPI] Lindent al...
580
581
  	union acpi_object acpi_object;
  	struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
582

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
583
584
  
  	if (!device)
d550d98d3   Patrick Mochel   ACPI: delete trac...
585
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
586

36bcbec7c   Burman Yan   ACPI: replace kma...
587
  	resource = kzalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
588
  	if (!resource)
d550d98d3   Patrick Mochel   ACPI: delete trac...
589
  		return -ENOMEM;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
590

415985728   Patrick Mochel   ACPI: power: add ...
591
  	resource->device = device;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
592
593
  	mutex_init(&resource->resource_lock);
  	INIT_LIST_HEAD(&resource->reference);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
594
595
596
597
598
599
  	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. */
5fbc19efd   Patrick Mochel   ACPI: power: Use ...
600
  	status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
  	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;
4be44fcd3   Len Brown   [ACPI] Lindent al...
627

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
628
629
  	printk(KERN_INFO PREFIX "%s [%s] (%s)
  ", acpi_device_name(device),
4be44fcd3   Len Brown   [ACPI] Lindent al...
630
  	       acpi_device_bid(device), resource->state ? "on" : "off");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
631

4be44fcd3   Len Brown   [ACPI] Lindent al...
632
        end:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
633
634
  	if (result)
  		kfree(resource);
4be44fcd3   Len Brown   [ACPI] Lindent al...
635

d550d98d3   Patrick Mochel   ACPI: delete trac...
636
  	return result;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
637
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
638
  static int acpi_power_remove(struct acpi_device *device, int type)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
639
640
  {
  	struct acpi_power_resource *resource = NULL;
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
641
  	struct list_head *node, *next;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
642

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
643
644
  
  	if (!device || !acpi_driver_data(device))
d550d98d3   Patrick Mochel   ACPI: delete trac...
645
  		return -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
646

50dd09697   Jan Engelhardt   ACPI: Remove unne...
647
  	resource = acpi_driver_data(device);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
648
649
  
  	acpi_power_remove_fs(device);
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
650
651
652
653
654
655
656
  	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);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
657
  	kfree(resource);
d550d98d3   Patrick Mochel   ACPI: delete trac...
658
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
659
  }
e8363f332   Len Brown   ACPI: update acpi...
660
  static int acpi_power_resume(struct acpi_device *device)
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
  {
  	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);
0a6139027   Konstantin Karasyov   ACPI: Thermal iss...
676
677
678
679
680
681
682
683
684
685
686
  	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;
  }
4be44fcd3   Len Brown   [ACPI] Lindent al...
687
  static int __init acpi_power_init(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
688
  {
4be44fcd3   Len Brown   [ACPI] Lindent al...
689
  	int result = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
690

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
691
692
  
  	if (acpi_disabled)
d550d98d3   Patrick Mochel   ACPI: delete trac...
693
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
694
695
696
697
698
  
  	INIT_LIST_HEAD(&acpi_power_resource_list);
  
  	acpi_power_dir = proc_mkdir(ACPI_POWER_CLASS, acpi_root_dir);
  	if (!acpi_power_dir)
d550d98d3   Patrick Mochel   ACPI: delete trac...
699
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
700
701
702
703
  
  	result = acpi_bus_register_driver(&acpi_power_driver);
  	if (result < 0) {
  		remove_proc_entry(ACPI_POWER_CLASS, acpi_root_dir);
d550d98d3   Patrick Mochel   ACPI: delete trac...
704
  		return -ENODEV;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
705
  	}
d550d98d3   Patrick Mochel   ACPI: delete trac...
706
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
707
708
709
  }
  
  subsys_initcall(acpi_power_init);