// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/acpi/power.c - ACPI Power Resources management.
*
* Copyright (C) 2001 - 2015 Intel Corp.
* Author: Andy Grover <andrew.grover@intel.com>
* Author: Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
* Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
*/
/*
* ACPI power-managed devices may be controlled in two ways:
* 1. via "Device Specific (D-State) Control"
* 2. via "Power Resource Control".
* The code below deals with ACPI Power Resources control.
*
* An ACPI "power resource object" represents a software controllable power
* plane, clock plane, or other resource depended on by a device.
*
* A device may rely on multiple power resources, and a power resource
* may be shared by multiple devices.
*/
#define pr_fmt(fmt) "ACPI: PM: " fmt
#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/sysfs.h>
#include <linux/acpi.h>
#include "sleep.h"
#include "internal.h"
#define ACPI_POWER_CLASS "power_resource"
#define ACPI_POWER_DEVICE_NAME "Power Resource"
#define ACPI_POWER_RESOURCE_STATE_OFF 0x00
#define ACPI_POWER_RESOURCE_STATE_ON 0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
struct acpi_power_dependent_device {
struct device *dev;
struct list_head node;
};
struct acpi_power_resource {
struct acpi_device device;
struct list_head list_node;
u32 system_level;
u32 order;
unsigned int ref_count;
u8 state;
struct mutex resource_lock;
struct list_head dependents;
};
struct acpi_power_resource_entry {
struct list_head node;
struct acpi_power_resource *resource;
};
static LIST_HEAD(acpi_power_resource_list);
static DEFINE_MUTEX(power_resource_list_lock);
/* --------------------------------------------------------------------------
Power Resource Management
-------------------------------------------------------------------------- */
static inline const char *resource_dev_name(struct acpi_power_resource *pr)
{
return dev_name(&pr->device.dev);
}
static inline
struct acpi_power_resource *to_power_resource(struct acpi_device *device)
{
return container_of(device, struct acpi_power_resource, device);
}
static struct acpi_power_resource *acpi_power_get_context(acpi_handle handle)
{
struct acpi_device *device = acpi_fetch_acpi_dev(handle);
if (!device)
return NULL;
return to_power_resource(device);
}
static int acpi_power_resources_list_add(acpi_handle handle,
struct list_head *list)
{
struct acpi_power_resource *resource = acpi_power_get_context(handle);
struct acpi_power_resource_entry *entry;
if (!resource || !list)
return -EINVAL;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
entry->resource = resource;
if (!list_empty(list)) {
struct acpi_power_resource_entry *e;
list_for_each_entry(e, list, node)
if (e->resource->order > resource->order) {
list_add_tail(&entry->node, &e->node);
return 0;
}
}
list_add_tail(&entry->node, list);
return 0;
}
void acpi_power_resources_list_free(struct list_head *list)
{
struct acpi_power_resource_entry *entry, *e;
list_for_each_entry_safe(entry, e, list, node) {
list_del(&entry->node);
kfree(entry);
}
}
static bool acpi_power_resource_is_dup(union acpi_object *package,
unsigned int start, unsigned int i)
{
acpi_handle rhandle, dup;
unsigned int j;
/* The caller is expected to check the package element types */
rhandle = package->package.elements[i].reference.handle;
for (j = start; j < i; j++) {
dup = package->package.elements[j].reference.handle;
if (dup == rhandle)
return true;
}
return false;
}
int acpi_extract_power_resources(union acpi_object *package, unsigned int start,
struct list_head *list)
{
unsigned int i;
int err = 0;
for (i = start; i < package->package.count; i++) {
union acpi_object *element = &package->package.elements[i];
struct acpi_device *rdev;
acpi_handle rhandle;
if (element->type != ACPI_TYPE_LOCAL_REFERENCE) {
err = -ENODATA;
break;
}
rhandle = element->reference.handle;
if (!rhandle) {
err = -ENODEV;
break;
}
/* Some ACPI tables contain duplicate power resource references */
if (acpi_power_resource_is_dup(package, start, i))
continue;
rdev = acpi_add_power_resource(rhandle);
if (!rdev) {
err = -ENODEV;
break;
}
err = acpi_power_resources_list_add(rhandle, list);
if (err)
break;
}
if (err)
acpi_power_resources_list_free(list);
return err;
}
static int __get_state(acpi_handle handle, u8 *state)
{
acpi_status status = AE_OK;
unsigned long long sta = 0;
u8