189 lines
6.1 KiB
C
189 lines
6.1 KiB
C
/* Copyright (C) 2013 Google, Inc.
|
|
*
|
|
* Author:
|
|
* Derek Basehore <dbasehore@chromium.org>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
* A dark resume is meant to be a reduced functionality state to resume the
|
|
* system to. It is not meant for user interaction, but to determine whether to
|
|
* shut down/hibernate or suspend again. This is done for power saving measures.
|
|
* To make the experience as seamless as possible drivers may want to alter
|
|
* their resume path to do something different in this case (prevent flashing
|
|
* screens and blinking usb ports).
|
|
*
|
|
* To make this system agnostic, much of the configuration is done in user
|
|
* space.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/pm_dark_resume.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "power.h"
|
|
|
|
LIST_HEAD(source_list);
|
|
static DEFINE_MUTEX(source_list_lock);
|
|
static bool dark_resume_state;
|
|
static struct pm_dark_resume_ops *dark_resume_ops;
|
|
|
|
/**
|
|
* dev_dark_resume_set_source - Set whether a device is a dark resume source.
|
|
* @dev: the struct that contains a pointer to the device we are either adding
|
|
* or removing as a dark resume source
|
|
* @is_source: Set the device to a source if true and remove as source if false
|
|
*/
|
|
int dev_dark_resume_set_source(struct device *dev, bool is_source)
|
|
{
|
|
/*
|
|
* This can happen if 'enabled' is written to the dark_resume_source
|
|
* attribute and the driver did not setup a dev_dark_resume struct.
|
|
*/
|
|
if (!dev->power.dark_resume) {
|
|
dev_dbg(dev, "Tried to set device as dark resume source, but "
|
|
"there is no driver support.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&source_list_lock);
|
|
if (is_source == dev->power.dark_resume->is_source) {
|
|
mutex_unlock(&source_list_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (is_source)
|
|
list_add(&dev->power.dark_resume->list_node, &source_list);
|
|
else
|
|
list_del(&dev->power.dark_resume->list_node);
|
|
|
|
dev->power.dark_resume->is_source = is_source;
|
|
mutex_unlock(&source_list_lock);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_dark_resume_set_source);
|
|
|
|
/**
|
|
* dev_dark_resume_set_active - This sets up the device to check the dark resume
|
|
* state during resume.
|
|
* @dev: device to set querying of the dark resume state to active or inactive
|
|
* @is_active: true for the device to query the dark resume state, false to not
|
|
* query the dark resume state.
|
|
*
|
|
* Part of dark resume is that devices may resume differently (such as the
|
|
* backlight coming on). This enables a driver to do something different in the
|
|
* resume path by calling dev_dark_resume_active.
|
|
*/
|
|
void dev_dark_resume_set_active(struct device *dev, bool is_active)
|
|
{
|
|
dev->power.use_dark_resume = is_active;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_dark_resume_set_active);
|
|
|
|
/**
|
|
* dev_dark_resume_init - Initialize the dev_dark_resume struct.
|
|
* @dev: The device struct that the dev_dark_resume struct will be associated
|
|
* with.
|
|
* @dark_resume: The dev_dark_resume struct to initialize.
|
|
* @irq: The platform callback will check (if supported) if this irq is the wake
|
|
* source. Note: probably want a hardware irq instead of virtual irq.
|
|
* @caused_resume: The function pointer that is called by the platform callback
|
|
* (if supported) before devices are resumed to see if dev caused the resume of
|
|
* the system.
|
|
*
|
|
* Devices that query dark resume, but cannot be a source should call this
|
|
* function when initialized with dark_resume and caused_resume as NULL.
|
|
*/
|
|
int dev_dark_resume_init(struct device *dev,
|
|
struct dev_dark_resume *dark_resume,
|
|
int irq,
|
|
bool (*caused_resume)(struct device *dev))
|
|
{
|
|
/* Must be called after device_add since sysfs attributes are added */
|
|
if (!device_is_registered(dev))
|
|
return -EINVAL;
|
|
|
|
dev->power.dark_resume = dark_resume;
|
|
dev_dark_resume_set_active(dev, false);
|
|
/* Happens for devices that cannot be a dark resume source. */
|
|
if (!dark_resume)
|
|
return dark_resume_sysfs_add(dev);
|
|
|
|
dark_resume->dev = dev;
|
|
dark_resume->is_source = false;
|
|
dark_resume->irq = irq;
|
|
dark_resume->caused_resume = caused_resume;
|
|
INIT_LIST_HEAD(&dark_resume->list_node);
|
|
/* Don't really need to clean up anything if this fails */
|
|
return dark_resume_sysfs_add(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_dark_resume_init);
|
|
|
|
/**
|
|
* dev_dark_resume_remove - Remove all of the associations of the device to dark
|
|
* resume.
|
|
* @dev: device struct to remove associations to dark resume from.
|
|
*
|
|
* Makes sure that the device is no longer active for dark resume and is not a
|
|
* source.
|
|
*/
|
|
void dev_dark_resume_remove(struct device *dev)
|
|
{
|
|
dev_dark_resume_set_active(dev, false);
|
|
dark_resume_sysfs_remove(dev);
|
|
if (!dev->power.dark_resume)
|
|
return;
|
|
|
|
dev_dark_resume_set_source(dev, false);
|
|
dev->power.dark_resume->caused_resume = NULL;
|
|
dev->power.dark_resume->irq = 0;
|
|
dev->power.dark_resume->dev = NULL;
|
|
dev->power.dark_resume = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_dark_resume_remove);
|
|
|
|
/**
|
|
* pm_dark_resume_check - Call into the platform specific check function if it
|
|
* exists to check if one of the dark resume sources woke the system.
|
|
*/
|
|
bool pm_dark_resume_check(void)
|
|
{
|
|
if (!dark_resume_ops || !dark_resume_ops->check) {
|
|
dark_resume_state = false;
|
|
return dark_resume_state;
|
|
}
|
|
|
|
mutex_lock(&source_list_lock);
|
|
dark_resume_state = dark_resume_ops->check(&source_list);
|
|
mutex_unlock(&source_list_lock);
|
|
return dark_resume_state;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pm_dark_resume_check);
|
|
|
|
/**
|
|
* pm_dark_resume_active - Returns the state of dark resume.
|
|
*/
|
|
bool pm_dark_resume_active(void)
|
|
{
|
|
return dark_resume_state;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pm_dark_resume_active);
|
|
|
|
/**
|
|
* pm_dark_resume_register_ops - Registers the callback function to check
|
|
* whether the system was resumed by something in the source_list.
|
|
* @ops: Container for the callback function
|
|
*/
|
|
void pm_dark_resume_register_ops(struct pm_dark_resume_ops *ops)
|
|
{
|
|
dark_resume_ops = ops;
|
|
}
|