blob: e768eb362932df602e1013afacfdc34ceff809c4 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * acpi_power.c - ACPI Bus Power Management ($Revision: 39 $)
3 *
4 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
5 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
6 *
7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or (at
12 * your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
22 *
23 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24 */
25
26/*
27 * ACPI power-managed devices may be controlled in two ways:
28 * 1. via "Device Specific (D-State) Control"
29 * 2. via "Power Resource Control".
30 * This module is used to manage devices relying on Power Resource Control.
31 *
32 * An ACPI "power resource object" describes a software controllable power
33 * plane, clock plane, or other resource used by a power managed device.
34 * A device may rely on multiple power resources, and a power resource
35 * may be shared by multiple devices.
36 */
37
38#include <linux/kernel.h>
39#include <linux/module.h>
40#include <linux/init.h>
41#include <linux/types.h>
42#include <linux/proc_fs.h>
43#include <linux/seq_file.h>
44#include <acpi/acpi_bus.h>
45#include <acpi/acpi_drivers.h>
46
Linus Torvalds1da177e2005-04-16 15:20:36 -070047#define _COMPONENT ACPI_POWER_COMPONENT
Len Brown4be44fc2005-08-05 00:44:28 -040048ACPI_MODULE_NAME("acpi_power")
Linus Torvalds1da177e2005-04-16 15:20:36 -070049#define ACPI_POWER_COMPONENT 0x00800000
50#define ACPI_POWER_CLASS "power_resource"
51#define ACPI_POWER_DRIVER_NAME "ACPI Power Resource Driver"
52#define ACPI_POWER_DEVICE_NAME "Power Resource"
53#define ACPI_POWER_FILE_INFO "info"
54#define ACPI_POWER_FILE_STATUS "state"
55#define ACPI_POWER_RESOURCE_STATE_OFF 0x00
56#define ACPI_POWER_RESOURCE_STATE_ON 0x01
57#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
Len Brown4be44fc2005-08-05 00:44:28 -040058static int acpi_power_add(struct acpi_device *device);
59static int acpi_power_remove(struct acpi_device *device, int type);
Len Browne8363f32007-02-16 02:05:39 -050060static int acpi_power_resume(struct acpi_device *device);
Linus Torvalds1da177e2005-04-16 15:20:36 -070061static int acpi_power_open_fs(struct inode *inode, struct file *file);
62
63static struct acpi_driver acpi_power_driver = {
Len Brown4be44fc2005-08-05 00:44:28 -040064 .name = ACPI_POWER_DRIVER_NAME,
65 .class = ACPI_POWER_CLASS,
66 .ids = ACPI_POWER_HID,
67 .ops = {
68 .add = acpi_power_add,
69 .remove = acpi_power_remove,
Konstantin Karasyov0a613902007-02-16 01:47:06 -050070 .resume = acpi_power_resume,
Len Brown4be44fc2005-08-05 00:44:28 -040071 },
Linus Torvalds1da177e2005-04-16 15:20:36 -070072};
73
Konstantin Karasyov0a613902007-02-16 01:47:06 -050074struct acpi_power_reference {
75 struct list_head node;
76 struct acpi_device *device;
77};
78
Len Brown4be44fc2005-08-05 00:44:28 -040079struct acpi_power_resource {
Patrick Mochel41598572006-05-19 16:54:40 -040080 struct acpi_device * device;
Len Brown4be44fc2005-08-05 00:44:28 -040081 acpi_bus_id name;
82 u32 system_level;
83 u32 order;
84 int state;
Konstantin Karasyov0a613902007-02-16 01:47:06 -050085 struct mutex resource_lock;
86 struct list_head reference;
Linus Torvalds1da177e2005-04-16 15:20:36 -070087};
88
Len Brown4be44fc2005-08-05 00:44:28 -040089static struct list_head acpi_power_resource_list;
Linus Torvalds1da177e2005-04-16 15:20:36 -070090
Arjan van de Vend7508032006-07-04 13:06:00 -040091static const struct file_operations acpi_power_fops = {
Len Brown4be44fc2005-08-05 00:44:28 -040092 .open = acpi_power_open_fs,
93 .read = seq_read,
94 .llseek = seq_lseek,
95 .release = single_release,
Linus Torvalds1da177e2005-04-16 15:20:36 -070096};
97
98/* --------------------------------------------------------------------------
99 Power Resource Management
100 -------------------------------------------------------------------------- */
101
102static int
Len Brown4be44fc2005-08-05 00:44:28 -0400103acpi_power_get_context(acpi_handle handle,
104 struct acpi_power_resource **resource)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700105{
Len Brown4be44fc2005-08-05 00:44:28 -0400106 int result = 0;
107 struct acpi_device *device = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700108
Linus Torvalds1da177e2005-04-16 15:20:36 -0700109
110 if (!resource)
Patrick Mocheld550d982006-06-27 00:41:40 -0400111 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700112
113 result = acpi_bus_get_device(handle, &device);
114 if (result) {
Len Browncece9292006-06-26 23:04:31 -0400115 printk(KERN_WARNING PREFIX "Getting context [%p]\n", handle);
Patrick Mocheld550d982006-06-27 00:41:40 -0400116 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 }
118
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200119 *resource = acpi_driver_data(device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 if (!resource)
Patrick Mocheld550d982006-06-27 00:41:40 -0400121 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122
Patrick Mocheld550d982006-06-27 00:41:40 -0400123 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124}
125
Len Brown4be44fc2005-08-05 00:44:28 -0400126static int acpi_power_get_state(struct acpi_power_resource *resource)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127{
Len Brown4be44fc2005-08-05 00:44:28 -0400128 acpi_status status = AE_OK;
129 unsigned long sta = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700130
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131
132 if (!resource)
Patrick Mocheld550d982006-06-27 00:41:40 -0400133 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134
Patrick Mochel5fbc19e2006-05-19 16:54:43 -0400135 status = acpi_evaluate_integer(resource->device->handle, "_STA", NULL, &sta);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700136 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400137 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138
139 if (sta & 0x01)
140 resource->state = ACPI_POWER_RESOURCE_STATE_ON;
141 else
142 resource->state = ACPI_POWER_RESOURCE_STATE_OFF;
143
144 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] is %s\n",
Len Brown4be44fc2005-08-05 00:44:28 -0400145 resource->name, resource->state ? "on" : "off"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700146
Patrick Mocheld550d982006-06-27 00:41:40 -0400147 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148}
149
Len Brown4be44fc2005-08-05 00:44:28 -0400150static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700151{
Len Brown4be44fc2005-08-05 00:44:28 -0400152 int result = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700153 struct acpi_power_resource *resource = NULL;
Len Brown4be44fc2005-08-05 00:44:28 -0400154 u32 i = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156
157 if (!list || !state)
Patrick Mocheld550d982006-06-27 00:41:40 -0400158 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159
160 /* The state of the list is 'on' IFF all resources are 'on'. */
161
Len Brown4be44fc2005-08-05 00:44:28 -0400162 for (i = 0; i < list->count; i++) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700163 result = acpi_power_get_context(list->handles[i], &resource);
164 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400165 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 result = acpi_power_get_state(resource);
167 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400168 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169
170 *state = resource->state;
171
172 if (*state != ACPI_POWER_RESOURCE_STATE_ON)
173 break;
174 }
175
176 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s\n",
Len Brown4be44fc2005-08-05 00:44:28 -0400177 *state ? "on" : "off"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178
Patrick Mocheld550d982006-06-27 00:41:40 -0400179 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180}
181
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500182static int acpi_power_on(acpi_handle handle, struct acpi_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700183{
Len Brown4be44fc2005-08-05 00:44:28 -0400184 int result = 0;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500185 int found = 0;
Len Brown4be44fc2005-08-05 00:44:28 -0400186 acpi_status status = AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187 struct acpi_power_resource *resource = NULL;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500188 struct list_head *node, *next;
189 struct acpi_power_reference *ref;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191
192 result = acpi_power_get_context(handle, &resource);
193 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400194 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700195
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500196 mutex_lock(&resource->resource_lock);
197 list_for_each_safe(node, next, &resource->reference) {
198 ref = container_of(node, struct acpi_power_reference, node);
199 if (dev->handle == ref->device->handle) {
200 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] already referenced by resource [%s]\n",
201 dev->pnp.bus_id, resource->name));
202 found = 1;
203 break;
204 }
205 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500207 if (!found) {
208 ref = kmalloc(sizeof (struct acpi_power_reference),
209 irqs_disabled() ? GFP_ATOMIC : GFP_KERNEL);
210 if (!ref) {
211 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "kmalloc() failed\n"));
212 mutex_unlock(&resource->resource_lock);
213 return -ENOMEM;
214 }
215 list_add_tail(&ref->node, &resource->reference);
216 ref->device = dev;
217 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] added to resource [%s] references\n",
218 dev->pnp.bus_id, resource->name));
219 }
220 mutex_unlock(&resource->resource_lock);
221
222 if (resource->state == ACPI_POWER_RESOURCE_STATE_ON) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already on\n",
Len Brown4be44fc2005-08-05 00:44:28 -0400224 resource->name));
Patrick Mocheld550d982006-06-27 00:41:40 -0400225 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226 }
227
Patrick Mochel5fbc19e2006-05-19 16:54:43 -0400228 status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400230 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231
232 result = acpi_power_get_state(resource);
233 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400234 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235 if (resource->state != ACPI_POWER_RESOURCE_STATE_ON)
Patrick Mocheld550d982006-06-27 00:41:40 -0400236 return -ENOEXEC;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700237
238 /* Update the power resource's _device_ power state */
Patrick Mochel41598572006-05-19 16:54:40 -0400239 resource->device->power.state = ACPI_STATE_D0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240
241 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned on\n",
Len Brown4be44fc2005-08-05 00:44:28 -0400242 resource->name));
Patrick Mocheld550d982006-06-27 00:41:40 -0400243 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244}
245
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500246static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247{
Len Brown4be44fc2005-08-05 00:44:28 -0400248 int result = 0;
249 acpi_status status = AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700250 struct acpi_power_resource *resource = NULL;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500251 struct list_head *node, *next;
252 struct acpi_power_reference *ref;
253
Linus Torvalds1da177e2005-04-16 15:20:36 -0700254
Linus Torvalds1da177e2005-04-16 15:20:36 -0700255 result = acpi_power_get_context(handle, &resource);
256 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400257 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500259 mutex_lock(&resource->resource_lock);
260 list_for_each_safe(node, next, &resource->reference) {
261 ref = container_of(node, struct acpi_power_reference, node);
262 if (dev->handle == ref->device->handle) {
263 list_del(&ref->node);
264 kfree(ref);
265 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] removed from resource [%s] references\n",
266 dev->pnp.bus_id, resource->name));
267 break;
268 }
269 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500271 if (!list_empty(&resource->reference)) {
272 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cannot turn resource [%s] off - resource is in use\n",
273 resource->name));
274 mutex_unlock(&resource->resource_lock);
Patrick Mocheld550d982006-06-27 00:41:40 -0400275 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276 }
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500277 mutex_unlock(&resource->resource_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700278
279 if (resource->state == ACPI_POWER_RESOURCE_STATE_OFF) {
280 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] already off\n",
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500281 resource->name));
Patrick Mocheld550d982006-06-27 00:41:40 -0400282 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700283 }
284
Patrick Mochel5fbc19e2006-05-19 16:54:43 -0400285 status = acpi_evaluate_object(resource->device->handle, "_OFF", NULL, NULL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700286 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400287 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288
289 result = acpi_power_get_state(resource);
290 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400291 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700292 if (resource->state != ACPI_POWER_RESOURCE_STATE_OFF)
Patrick Mocheld550d982006-06-27 00:41:40 -0400293 return -ENOEXEC;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294
295 /* Update the power resource's _device_ power state */
Dmitry Torokhov786f18c2006-08-23 23:18:06 -0400296 resource->device->power.state = ACPI_STATE_D3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700297
298 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned off\n",
Len Brown4be44fc2005-08-05 00:44:28 -0400299 resource->name));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700300
Patrick Mocheld550d982006-06-27 00:41:40 -0400301 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700302}
303
304/*
305 * Prepare a wakeup device, two steps (Ref ACPI 2.0:P229):
306 * 1. Power on the power resources required for the wakeup device
307 * 2. Enable _PSW (power state wake) for the device if present
308 */
Len Brown4be44fc2005-08-05 00:44:28 -0400309int acpi_enable_wakeup_device_power(struct acpi_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700310{
Len Brown4be44fc2005-08-05 00:44:28 -0400311 union acpi_object arg = { ACPI_TYPE_INTEGER };
312 struct acpi_object_list arg_list = { 1, &arg };
313 acpi_status status = AE_OK;
314 int i;
315 int ret = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700316
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317 if (!dev || !dev->wakeup.flags.valid)
Patrick Mocheld550d982006-06-27 00:41:40 -0400318 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319
320 arg.integer.value = 1;
321 /* Open power resource */
322 for (i = 0; i < dev->wakeup.resources.count; i++) {
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500323 ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700324 if (ret) {
Len Brown64684632006-06-26 23:41:38 -0400325 printk(KERN_ERR PREFIX "Transition power state\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700326 dev->wakeup.flags.valid = 0;
Patrick Mocheld550d982006-06-27 00:41:40 -0400327 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700328 }
329 }
330
331 /* Execute PSW */
332 status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
333 if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
Len Brown64684632006-06-26 23:41:38 -0400334 printk(KERN_ERR PREFIX "Evaluate _PSW\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335 dev->wakeup.flags.valid = 0;
336 ret = -1;
337 }
338
Patrick Mocheld550d982006-06-27 00:41:40 -0400339 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340}
341
342/*
343 * Shutdown a wakeup device, counterpart of above method
344 * 1. Disable _PSW (power state wake)
345 * 2. Shutdown down the power resources
346 */
Len Brown4be44fc2005-08-05 00:44:28 -0400347int acpi_disable_wakeup_device_power(struct acpi_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348{
Len Brown4be44fc2005-08-05 00:44:28 -0400349 union acpi_object arg = { ACPI_TYPE_INTEGER };
350 struct acpi_object_list arg_list = { 1, &arg };
351 acpi_status status = AE_OK;
352 int i;
353 int ret = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700354
Linus Torvalds1da177e2005-04-16 15:20:36 -0700355
356 if (!dev || !dev->wakeup.flags.valid)
Patrick Mocheld550d982006-06-27 00:41:40 -0400357 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700358
Len Brown4be44fc2005-08-05 00:44:28 -0400359 arg.integer.value = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700360 /* Execute PSW */
361 status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
362 if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
Len Brown64684632006-06-26 23:41:38 -0400363 printk(KERN_ERR PREFIX "Evaluate _PSW\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700364 dev->wakeup.flags.valid = 0;
Patrick Mocheld550d982006-06-27 00:41:40 -0400365 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700366 }
367
368 /* Close power resource */
369 for (i = 0; i < dev->wakeup.resources.count; i++) {
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500370 ret = acpi_power_off_device(dev->wakeup.resources.handles[i], dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700371 if (ret) {
Len Brown64684632006-06-26 23:41:38 -0400372 printk(KERN_ERR PREFIX "Transition power state\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700373 dev->wakeup.flags.valid = 0;
Patrick Mocheld550d982006-06-27 00:41:40 -0400374 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700375 }
376 }
377
Patrick Mocheld550d982006-06-27 00:41:40 -0400378 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700379}
380
381/* --------------------------------------------------------------------------
382 Device Power Management
383 -------------------------------------------------------------------------- */
384
Len Brown4be44fc2005-08-05 00:44:28 -0400385int acpi_power_get_inferred_state(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700386{
Len Brown4be44fc2005-08-05 00:44:28 -0400387 int result = 0;
388 struct acpi_handle_list *list = NULL;
389 int list_state = 0;
390 int i = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391
Linus Torvalds1da177e2005-04-16 15:20:36 -0700392
393 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400394 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700395
396 device->power.state = ACPI_STATE_UNKNOWN;
397
398 /*
399 * We know a device's inferred power state when all the resources
400 * required for a given D-state are 'on'.
401 */
Len Brown4be44fc2005-08-05 00:44:28 -0400402 for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700403 list = &device->power.states[i].resources;
404 if (list->count < 1)
405 continue;
406
407 result = acpi_power_get_list_state(list, &list_state);
408 if (result)
Patrick Mocheld550d982006-06-27 00:41:40 -0400409 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700410
411 if (list_state == ACPI_POWER_RESOURCE_STATE_ON) {
412 device->power.state = i;
Patrick Mocheld550d982006-06-27 00:41:40 -0400413 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414 }
415 }
416
417 device->power.state = ACPI_STATE_D3;
418
Patrick Mocheld550d982006-06-27 00:41:40 -0400419 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700420}
421
Len Brown4be44fc2005-08-05 00:44:28 -0400422int acpi_power_transition(struct acpi_device *device, int state)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423{
Len Brown4be44fc2005-08-05 00:44:28 -0400424 int result = 0;
425 struct acpi_handle_list *cl = NULL; /* Current Resources */
426 struct acpi_handle_list *tl = NULL; /* Target Resources */
427 int i = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700428
Linus Torvalds1da177e2005-04-16 15:20:36 -0700429
430 if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
Patrick Mocheld550d982006-06-27 00:41:40 -0400431 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700432
Len Brown4be44fc2005-08-05 00:44:28 -0400433 if ((device->power.state < ACPI_STATE_D0)
434 || (device->power.state > ACPI_STATE_D3))
Patrick Mocheld550d982006-06-27 00:41:40 -0400435 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700436
437 cl = &device->power.states[device->power.state].resources;
438 tl = &device->power.states[state].resources;
439
440 device->power.state = ACPI_STATE_UNKNOWN;
441
442 if (!cl->count && !tl->count) {
443 result = -ENODEV;
444 goto end;
445 }
446
447 /* TBD: Resources must be ordered. */
448
449 /*
450 * First we reference all power resources required in the target list
451 * (e.g. so the device doesn't lose power while transitioning).
452 */
Len Brown4be44fc2005-08-05 00:44:28 -0400453 for (i = 0; i < tl->count; i++) {
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500454 result = acpi_power_on(tl->handles[i], device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700455 if (result)
456 goto end;
457 }
458
459 /*
460 * Then we dereference all power resources used in the current list.
461 */
Len Brown4be44fc2005-08-05 00:44:28 -0400462 for (i = 0; i < cl->count; i++) {
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500463 result = acpi_power_off_device(cl->handles[i], device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700464 if (result)
465 goto end;
466 }
467
468 /* We shouldn't change the state till all above operations succeed */
469 device->power.state = state;
Len Brown4be44fc2005-08-05 00:44:28 -0400470 end:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700471 if (result)
Len Browncece9292006-06-26 23:04:31 -0400472 printk(KERN_WARNING PREFIX "Transitioning device [%s] to D%d\n",
473 device->pnp.bus_id, state);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700474
Patrick Mocheld550d982006-06-27 00:41:40 -0400475 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700476}
477
Linus Torvalds1da177e2005-04-16 15:20:36 -0700478/* --------------------------------------------------------------------------
479 FS Interface (/proc)
480 -------------------------------------------------------------------------- */
481
Len Brown4be44fc2005-08-05 00:44:28 -0400482static struct proc_dir_entry *acpi_power_dir;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700483
484static int acpi_power_seq_show(struct seq_file *seq, void *offset)
485{
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500486 int count = 0;
487 int result = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700488 struct acpi_power_resource *resource = NULL;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500489 struct list_head *node, *next;
490 struct acpi_power_reference *ref;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700491
Linus Torvalds1da177e2005-04-16 15:20:36 -0700492
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200493 resource = seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700494
495 if (!resource)
496 goto end;
497
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500498 result = acpi_power_get_state(resource);
499 if (result)
500 goto end;
501
Linus Torvalds1da177e2005-04-16 15:20:36 -0700502 seq_puts(seq, "state: ");
503 switch (resource->state) {
504 case ACPI_POWER_RESOURCE_STATE_ON:
505 seq_puts(seq, "on\n");
506 break;
507 case ACPI_POWER_RESOURCE_STATE_OFF:
508 seq_puts(seq, "off\n");
509 break;
510 default:
511 seq_puts(seq, "unknown\n");
512 break;
513 }
514
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500515 mutex_lock(&resource->resource_lock);
516 list_for_each_safe(node, next, &resource->reference) {
517 ref = container_of(node, struct acpi_power_reference, node);
518 count++;
519 }
520 mutex_unlock(&resource->resource_lock);
521
Linus Torvalds1da177e2005-04-16 15:20:36 -0700522 seq_printf(seq, "system level: S%d\n"
Len Brown4be44fc2005-08-05 00:44:28 -0400523 "order: %d\n"
524 "reference count: %d\n",
525 resource->system_level,
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500526 resource->order, count);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700527
Len Brown4be44fc2005-08-05 00:44:28 -0400528 end:
Patrick Mocheld550d982006-06-27 00:41:40 -0400529 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700530}
531
532static int acpi_power_open_fs(struct inode *inode, struct file *file)
533{
534 return single_open(file, acpi_power_seq_show, PDE(inode)->data);
535}
536
Len Brown4be44fc2005-08-05 00:44:28 -0400537static int acpi_power_add_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700538{
Len Brown4be44fc2005-08-05 00:44:28 -0400539 struct proc_dir_entry *entry = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700540
Linus Torvalds1da177e2005-04-16 15:20:36 -0700541
542 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400543 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700544
545 if (!acpi_device_dir(device)) {
546 acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
Len Brown4be44fc2005-08-05 00:44:28 -0400547 acpi_power_dir);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700548 if (!acpi_device_dir(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400549 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700550 }
551
552 /* 'status' [R] */
553 entry = create_proc_entry(ACPI_POWER_FILE_STATUS,
Len Brown4be44fc2005-08-05 00:44:28 -0400554 S_IRUGO, acpi_device_dir(device));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700555 if (!entry)
Patrick Mocheld550d982006-06-27 00:41:40 -0400556 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557 else {
558 entry->proc_fops = &acpi_power_fops;
559 entry->data = acpi_driver_data(device);
560 }
561
Patrick Mocheld550d982006-06-27 00:41:40 -0400562 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700563}
564
Len Brown4be44fc2005-08-05 00:44:28 -0400565static int acpi_power_remove_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700566{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700567
568 if (acpi_device_dir(device)) {
569 remove_proc_entry(ACPI_POWER_FILE_STATUS,
570 acpi_device_dir(device));
571 remove_proc_entry(acpi_device_bid(device), acpi_power_dir);
572 acpi_device_dir(device) = NULL;
573 }
574
Patrick Mocheld550d982006-06-27 00:41:40 -0400575 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700576}
577
Linus Torvalds1da177e2005-04-16 15:20:36 -0700578/* --------------------------------------------------------------------------
579 Driver Interface
580 -------------------------------------------------------------------------- */
581
Len Brown4be44fc2005-08-05 00:44:28 -0400582static int acpi_power_add(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700583{
Len Brown4be44fc2005-08-05 00:44:28 -0400584 int result = 0;
585 acpi_status status = AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586 struct acpi_power_resource *resource = NULL;
Len Brown4be44fc2005-08-05 00:44:28 -0400587 union acpi_object acpi_object;
588 struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589
Linus Torvalds1da177e2005-04-16 15:20:36 -0700590
591 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400592 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700593
Burman Yan36bcbec2006-12-19 12:56:11 -0800594 resource = kzalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700595 if (!resource)
Patrick Mocheld550d982006-06-27 00:41:40 -0400596 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700597
Patrick Mochel41598572006-05-19 16:54:40 -0400598 resource->device = device;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500599 mutex_init(&resource->resource_lock);
600 INIT_LIST_HEAD(&resource->reference);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700601 strcpy(resource->name, device->pnp.bus_id);
602 strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
603 strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
604 acpi_driver_data(device) = resource;
605
606 /* Evalute the object to get the system level and resource order. */
Patrick Mochel5fbc19e2006-05-19 16:54:43 -0400607 status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700608 if (ACPI_FAILURE(status)) {
609 result = -ENODEV;
610 goto end;
611 }
612 resource->system_level = acpi_object.power_resource.system_level;
613 resource->order = acpi_object.power_resource.resource_order;
614
615 result = acpi_power_get_state(resource);
616 if (result)
617 goto end;
618
619 switch (resource->state) {
620 case ACPI_POWER_RESOURCE_STATE_ON:
621 device->power.state = ACPI_STATE_D0;
622 break;
623 case ACPI_POWER_RESOURCE_STATE_OFF:
624 device->power.state = ACPI_STATE_D3;
625 break;
626 default:
627 device->power.state = ACPI_STATE_UNKNOWN;
628 break;
629 }
630
631 result = acpi_power_add_fs(device);
632 if (result)
633 goto end;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700634
Len Brown4be44fc2005-08-05 00:44:28 -0400635 printk(KERN_INFO PREFIX "%s [%s] (%s)\n", acpi_device_name(device),
636 acpi_device_bid(device), resource->state ? "on" : "off");
637
638 end:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700639 if (result)
640 kfree(resource);
Len Brown4be44fc2005-08-05 00:44:28 -0400641
Patrick Mocheld550d982006-06-27 00:41:40 -0400642 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700643}
644
Len Brown4be44fc2005-08-05 00:44:28 -0400645static int acpi_power_remove(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700646{
647 struct acpi_power_resource *resource = NULL;
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500648 struct list_head *node, *next;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649
Linus Torvalds1da177e2005-04-16 15:20:36 -0700650
651 if (!device || !acpi_driver_data(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400652 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700653
Jan Engelhardt50dd0962006-10-01 00:28:50 +0200654 resource = acpi_driver_data(device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700655
656 acpi_power_remove_fs(device);
657
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500658 mutex_lock(&resource->resource_lock);
659 list_for_each_safe(node, next, &resource->reference) {
660 struct acpi_power_reference *ref = container_of(node, struct acpi_power_reference, node);
661 list_del(&ref->node);
662 kfree(ref);
663 }
664 mutex_unlock(&resource->resource_lock);
665
Linus Torvalds1da177e2005-04-16 15:20:36 -0700666 kfree(resource);
667
Patrick Mocheld550d982006-06-27 00:41:40 -0400668 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700669}
670
Len Browne8363f32007-02-16 02:05:39 -0500671static int acpi_power_resume(struct acpi_device *device)
Konstantin Karasyov0a613902007-02-16 01:47:06 -0500672{
673 int result = 0;
674 struct acpi_power_resource *resource = NULL;
675 struct acpi_power_reference *ref;
676
677 if (!device || !acpi_driver_data(device))
678 return -EINVAL;
679
680 resource = (struct acpi_power_resource *)acpi_driver_data(device);
681
682 result = acpi_power_get_state(resource);
683 if (result)
684 return result;
685
686 mutex_lock(&resource->resource_lock);
687 if ((resource->state == ACPI_POWER_RESOURCE_STATE_ON) &&
688 list_empty(&resource->reference)) {
689 mutex_unlock(&resource->resource_lock);
690 result = acpi_power_off_device(device->handle, NULL);
691 return result;
692 }
693
694 if ((resource->state == ACPI_POWER_RESOURCE_STATE_OFF) &&
695 !list_empty(&resource->reference)) {
696 ref = container_of(resource->reference.next, struct acpi_power_reference, node);
697 mutex_unlock(&resource->resource_lock);
698 result = acpi_power_on(device->handle, ref->device);
699 return result;
700 }
701
702 mutex_unlock(&resource->resource_lock);
703 return 0;
704}
705
Len Brown4be44fc2005-08-05 00:44:28 -0400706static int __init acpi_power_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700707{
Len Brown4be44fc2005-08-05 00:44:28 -0400708 int result = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700709
Linus Torvalds1da177e2005-04-16 15:20:36 -0700710
711 if (acpi_disabled)
Patrick Mocheld550d982006-06-27 00:41:40 -0400712 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700713
714 INIT_LIST_HEAD(&acpi_power_resource_list);
715
716 acpi_power_dir = proc_mkdir(ACPI_POWER_CLASS, acpi_root_dir);
717 if (!acpi_power_dir)
Patrick Mocheld550d982006-06-27 00:41:40 -0400718 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700719
720 result = acpi_bus_register_driver(&acpi_power_driver);
721 if (result < 0) {
722 remove_proc_entry(ACPI_POWER_CLASS, acpi_root_dir);
Patrick Mocheld550d982006-06-27 00:41:40 -0400723 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700724 }
725
Patrick Mocheld550d982006-06-27 00:41:40 -0400726 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700727}
728
729subsys_initcall(acpi_power_init);