blob: 4cc4b0976e5afdbd9780826c82d347e75246e11c [file] [log] [blame]
/*
* drivers/amlogic/pm/lgcy_early_suspend.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#include <linux/pm.h>
#include <linux/suspend.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/amlogic/iomap.h>
#include <linux/init.h>
#include <linux/of.h>
#include <asm/compiler.h>
#include <linux/errno.h>
#include <linux/suspend.h>
#include <asm/suspend.h>
#include <linux/input.h>
#include <uapi/linux/psci.h>
#include <linux/arm-smccc.h>
#include <linux/amlogic/pm.h>
#include <linux/kobject.h>
#include <../kernel/power/power.h>
static DEFINE_MUTEX(early_suspend_lock);
static DEFINE_MUTEX(sysfs_trigger_lock);
static LIST_HEAD(early_suspend_handlers);
/* In order to handle legacy early_suspend driver,
* here we export sysfs interface
* for user space to write /sys/power/early_suspend_trigger to trigger
* early_suspend/late resume call back. If user space do not trigger
* early_suspend/late_resume, this op will be done
* by PM_SUSPEND_PREPARE notify.
*/
unsigned int sysfs_trigger;
unsigned int early_suspend_state;
/*
* Avoid run early_suspend/late_resume repeatly.
*/
unsigned int already_early_suspend;
void register_early_suspend(struct early_suspend *handler)
{
struct list_head *pos;
mutex_lock(&early_suspend_lock);
list_for_each(pos, &early_suspend_handlers) {
struct early_suspend *e;
e = list_entry(pos, struct early_suspend, link);
if (e->level > handler->level)
break;
}
list_add_tail(&handler->link, pos);
mutex_unlock(&early_suspend_lock);
}
EXPORT_SYMBOL(register_early_suspend);
void unregister_early_suspend(struct early_suspend *handler)
{
mutex_lock(&early_suspend_lock);
list_del(&handler->link);
mutex_unlock(&early_suspend_lock);
}
EXPORT_SYMBOL(unregister_early_suspend);
static inline void early_suspend(void)
{
struct early_suspend *pos;
mutex_lock(&early_suspend_lock);
if (!already_early_suspend)
already_early_suspend = 1;
else
goto end_early_suspend;
pr_info("early_suspend: call handlers\n");
list_for_each_entry(pos, &early_suspend_handlers, link)
if (pos->suspend != NULL) {
pr_info("early_suspend: %pf\n", pos->suspend);
pos->suspend(pos);
}
pr_info("early_suspend: done\n");
end_early_suspend:
mutex_unlock(&early_suspend_lock);
}
static inline void late_resume(void)
{
struct early_suspend *pos;
mutex_lock(&early_suspend_lock);
if (already_early_suspend)
already_early_suspend = 0;
else
goto end_late_resume;
pr_info("late_resume: call handlers\n");
list_for_each_entry_reverse(pos, &early_suspend_handlers, link)
if (pos->resume != NULL) {
pr_info("late_resume: %pf\n", pos->resume);
pos->resume(pos);
}
pr_info("late_resume: done\n");
end_late_resume:
mutex_unlock(&early_suspend_lock);
}
static ssize_t early_suspend_trigger_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
unsigned int len;
len = sprintf(buf, "%d\n", early_suspend_state);
return len;
}
static ssize_t early_suspend_trigger_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int ret;
ret = kstrtouint(buf, 0, &early_suspend_state);
pr_info("early_suspend_state=%d\n", early_suspend_state);
if (ret)
return -EINVAL;
mutex_lock(&sysfs_trigger_lock);
sysfs_trigger = 1;
if (early_suspend_state == 0)
late_resume();
else if (early_suspend_state == 1)
early_suspend();
mutex_unlock(&sysfs_trigger_lock);
return n;
}
power_attr(early_suspend_trigger);
static struct attribute *g[] = {
&early_suspend_trigger_attr.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = g,
};
void lgcy_early_suspend(void)
{
mutex_lock(&sysfs_trigger_lock);
if (!sysfs_trigger)
early_suspend();
mutex_unlock(&sysfs_trigger_lock);
}
void lgcy_late_resume(void)
{
mutex_lock(&sysfs_trigger_lock);
if (!sysfs_trigger)
late_resume();
mutex_unlock(&sysfs_trigger_lock);
}
static int lgcy_early_suspend_notify(struct notifier_block *nb,
unsigned long event, void *dummy)
{
if (event == PM_SUSPEND_PREPARE)
lgcy_early_suspend();
if (event == PM_POST_SUSPEND)
lgcy_late_resume();
return NOTIFY_OK;
}
static struct notifier_block lgcy_early_suspend_notifier = {
.notifier_call = lgcy_early_suspend_notify,
};
unsigned int lgcy_early_suspend_init(void)
{
int ret;
ret = sysfs_create_group(power_kobj, &attr_group);
if (ret)
return ret;
ret = register_pm_notifier(&lgcy_early_suspend_notifier);
return ret;
}