#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
#include "sleep.h"
#define PREFIX "ACPI: "
#define _COMPONENT ACPI_POWER_COMPONENT
ACPI_MODULE_NAME("power");
#define ACPI_POWER_CLASS "power_resource"
#define ACPI_POWER_DEVICE_NAME "Power Resource"
#define ACPI_POWER_FILE_INFO "info"
#define ACPI_POWER_FILE_STATUS "state"
#define ACPI_POWER_RESOURCE_STATE_OFF 0x00
#define ACPI_POWER_RESOURCE_STATE_ON 0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
static int acpi_power_add(struct acpi_device *device);
static int acpi_power_remove(struct acpi_device *device, int type);
static int acpi_power_resume(struct acpi_device *device);
static const struct acpi_device_id power_device_ids[] = {
{ACPI_POWER_HID, 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, power_device_ids);
static struct acpi_driver acpi_power_driver = {
.name = "power",
.class = ACPI_POWER_CLASS,
.ids = power_device_ids,
.ops = {
.add = acpi_power_add,
.remove = acpi_power_remove,
.resume = acpi_power_resume,
},
};
struct acpi_power_resource {
struct acpi_device * device;
acpi_bus_id name;
u32 system_level;
u32 order;
unsigned int ref_count;
struct mutex resource_lock;
};
static struct list_head acpi_power_resource_list;
static int
acpi_power_get_context(acpi_handle handle,
struct acpi_power_resource **resource)
{
int result = 0;
struct acpi_device *device = NULL;
if (!resource)
return -ENODEV;
result = acpi_bus_get_device(handle, &device);
if (result) {
printk(KERN_WARNING PREFIX "Getting context [%p]\n", handle);
return result;
}
*resource = acpi_driver_data(device);
if (!*resource)
return -ENODEV;
return 0;
}
static int acpi_power_get_state(acpi_handle handle, int *state)
{
acpi_status status = AE_OK;
unsigned long long sta = 0;
char node_name[5];
struct acpi_buffer buffer = { sizeof(node_name), node_name };
if (!handle || !state)
return -EINVAL;
status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
return -ENODEV;
*state = (sta & 0x01)?ACPI_POWER_RESOURCE_STATE_ON:
ACPI_POWER_RESOURCE_STATE_OFF;
acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] is %s\n",
node_name,
*state ? "on" : "off"));
return 0;
}
static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
{
int cur_state;
int i = 0;
if (!list || !state)
return -EINVAL;
for (i = 0; i < list->count; i++) {
struct acpi_power_resource *resource;
acpi_handle handle = list->handles[i];
int result;
result = acpi_power_get_context(handle, &resource);
if (result)
return result;
mutex_lock(&resource->resource_lock);
result = acpi_power_get_state(handle, &cur_state);
mutex_unlock(&resource->resource_lock);
if (result)
return result;
if (cur_state != ACPI_POWER_RESOURCE_STATE_ON)
break;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource list is %s\n",
cur_state ? "on" : "off"));
*state = cur_state;
return 0;
}
static int __acpi_power_on(struct acpi_power_resource *resource)
{
acpi_status status = AE_OK;
status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
if (ACPI_FAILURE(status))
return -ENODEV;
resource->device->power.state = ACPI_STATE_D0;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Power resource [%s] turned on\n",
resource->name));
return 0;
}
static int acpi_power_on(acpi_handle handle)
{
int result = 0;
struct acpi_power_resource *resource = NULL;
result = acpi_power_get_context(handle, &resource);
if (result)
return result;
mutex_lock(&resource->resource_lock);
if (resource->ref_count++) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Power resource [%s] already on",
resource->name));
} else {
result = __acpi_power_on(resource);
if (result)
resource->ref_count--;
}
mutex_unlock(&resource->resource_lock);
return result;
}
static int acpi_power_off(acpi_handle handle)
{
int result = 0;
acpi_status status = AE_OK;
struct acpi_power_resource *resource = NULL;
result = acpi_power_get_context(handle, &resource);
if (result)
return result;
mutex_lock(&resource->resource_lock);
if (!resource->ref_count) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Power resource [%s] already off",
resource->name));
goto unlock;
}
if (--resource->ref_count) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Power resource [%s] still in use\n",
resource->name));
goto unlock;
}
status = acpi_evaluate_object(resource->device->handle, "_OFF", NULL, NULL);
if (ACPI_FAILURE(status)) {
result = -ENODEV;
} else {
resource->device->power.state = ACPI_STATE_D3;
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Power resource [%s] turned off\n",
resource->name));
}
unlock:
mutex_unlock(&resource->resource_lock);
return result;
}
static void __acpi_power_off_list(struct acpi_handle_list *list, int num_res)
{
int i;
for (i = num_res - 1; i >= 0 ; i--)
acpi_power_off(list->handles[i]);
}
static void acpi_power_off_list(struct acpi_handle_list *list)
{
__acpi_power_off_list(list, list->count);
}
static int acpi_power_on_list(struct acpi_handle_list *list)
{
int result = 0;
int i;
for (i = 0; i < list->count; i++) {
result = acpi_power_on(list->handles[i]);
if (result) {
__acpi_power_off_list(list, i);
break;
}
}
return result;
}
int acpi_device_sleep_wake(struct acpi_device *dev,
int enable, int sleep_state, int dev_state)
{
union acpi_object in_arg[3];
struct acpi_object_list arg_list = { 3, in_arg };
acpi_status status = AE_OK;
in_arg[0].type = ACPI_TYPE_INTEGER;
in_arg[0].integer.value = enable;
in_arg[1].type = ACPI_TYPE_INTEGER;
in_arg[1].integer.value = sleep_state;
in_arg[2].type = ACPI_TYPE_INTEGER;
in_arg[2].integer.value = dev_state;
status = acpi_evaluate_object(dev->handle, "_DSW", &arg_list, NULL);
if (ACPI_SUCCESS(status)) {
return 0;
} else if (status != AE_NOT_FOUND) {
printk(KERN_ERR PREFIX "_DSW execution failed\n");
dev->wakeup.flags.valid = 0;
return -ENODEV;
}
arg_list.count = 1;
in_arg[0].integer.value = enable;
status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
printk(KERN_ERR PREFIX "_PSW execution failed\n");
dev->wakeup.flags.valid = 0;
return -ENODEV;
}
return 0;
}
int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state)
{
int i, err = 0;
if (!dev || !dev->wakeup.flags.valid)
return -EINVAL;
mutex_lock(&acpi_device_lock);
if (dev->wakeup.prepare_count++)
goto out;
for (i = 0; i < dev->wakeup.resources.count; i++) {
int ret = acpi_power_on(dev->wakeup.resources.handles[i]);
if (ret) {
printk(KERN_ERR PREFIX "Transition power state\n");
dev->wakeup.flags.valid = 0;
err = -ENODEV;
goto err_out;
}
}
err = acpi_device_sleep_wake(dev, 1, sleep_state, 3);
err_out:
if (err)
dev->wakeup.prepare_count = 0;
out:
mutex_unlock(&acpi_device_lock);
return err;
}
int acpi_disable_wakeup_device_power(struct acpi_device *dev)
{
int i, err = 0;
if (!dev || !dev->wakeup.flags.valid)
return -EINVAL;
mutex_lock(&acpi_device_lock);
if (--dev->wakeup.prepare_count > 0)
goto out;
if (dev->wakeup.prepare_count < 0)
dev->wakeup.prepare_count = 0;
err = acpi_device_sleep_wake(dev, 0, 0, 0);
if (err)
goto out;
for (i = 0; i < dev->wakeup.resources.count; i++) {
int ret = acpi_power_off(dev->wakeup.resources.handles[i]);
if (ret) {
printk(KERN_ERR PREFIX "Transition power state\n");
dev->wakeup.flags.valid = 0;
err = -ENODEV;
goto out;
}
}
out:
mutex_unlock(&acpi_device_lock);
return err;
}
int acpi_power_get_inferred_state(struct acpi_device *device, int *state)
{
int result = 0;
struct acpi_handle_list *list = NULL;
int list_state = 0;
int i = 0;
if (!device || !state)
return -EINVAL;
for (i = ACPI_STATE_D0; i < ACPI_STATE_D3; i++) {
list = &device->power.states[i].resources;
if (list->count < 1)
continue;
result = acpi_power_get_list_state(list, &list_state);
if (result)
return result;
if (list_state == ACPI_POWER_RESOURCE_STATE_ON) {
*state = i;
return 0;
}
}
*state = ACPI_STATE_D3;
return 0;
}
int acpi_power_on_resources(struct acpi_device *device, int state)
{
if (!device || state < ACPI_STATE_D0 || state > ACPI_STATE_D3)
return -EINVAL;
return acpi_power_on_list(&device->power.states[state].resources);
}
int acpi_power_transition(struct acpi_device *device, int state)
{
int result;
if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3))
return -EINVAL;
if (device->power.state == state)
return 0;
if ((device->power.state < ACPI_STATE_D0)
|| (device->power.state > ACPI_STATE_D3))
return -ENODEV;
result = acpi_power_on_list(&device->power.states[state].resources);
if (!result)
acpi_power_off_list(
&device->power.states[device->power.state].resources);
device->power.state = result ? ACPI_STATE_UNKNOWN : state;
return result;
}
static int acpi_power_add(struct acpi_device *device)
{
int result = 0, state;
acpi_status status = AE_OK;
struct acpi_power_resource *resource = NULL;
union acpi_object acpi_object;
struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
if (!device)
return -EINVAL;
resource = kzalloc(sizeof(struct acpi_power_resource), GFP_KERNEL);
if (!resource)
return -ENOMEM;
resource->device = device;
mutex_init(&resource->resource_lock);
strcpy(resource->name, device->pnp.bus_id);
strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
device->driver_data = resource;
status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer);
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(device->handle, &state);
if (result)
goto end;
switch (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;
}
printk(KERN_INFO PREFIX "%s [%s] (%s)\n", acpi_device_name(device),
acpi_device_bid(device), state ? "on" : "off");
end:
if (result)
kfree(resource);
return result;
}
static int acpi_power_remove(struct acpi_device *device, int type)
{
struct acpi_power_resource *resource;
if (!device)
return -EINVAL;
resource = acpi_driver_data(device);
if (!resource)
return -EINVAL;
kfree(resource);
return 0;
}
static int acpi_power_resume(struct acpi_device *device)
{
int result = 0, state;
struct acpi_power_resource *resource;
if (!device)
return -EINVAL;
resource = acpi_driver_data(device);
if (!resource)
return -EINVAL;
mutex_lock(&resource->resource_lock);
result = acpi_power_get_state(device->handle, &state);
if (result)
goto unlock;
if (state == ACPI_POWER_RESOURCE_STATE_OFF && resource->ref_count)
result = __acpi_power_on(resource);
unlock:
mutex_unlock(&resource->resource_lock);
return result;
}
int __init acpi_power_init(void)
{
INIT_LIST_HEAD(&acpi_power_resource_list);
return acpi_bus_register_driver(&acpi_power_driver);
}