/*
* arch/arm/mach-tegra/tegra_wakeup_monitor.c
*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "pm-irq.h"
struct tegra_wakeup_monitor {
struct tegra_wakeup_monitor_platform_data *pdata;
struct notifier_block pm_notifier;
struct platform_device *pdev;
bool wow_enabled;
bool monitor_enable;
int wakeup_source;
struct semaphore suspend_prepare_sem;
};
static ssize_t show_monitor_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", twm->monitor_enable ? 1 : 0);
}
static ssize_t store_monitor_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int enable;
struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev);
if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1)
return -EINVAL;
dev_info(dev, "wakeup moniter: monitor enable = %d\n", enable);
twm->monitor_enable = enable;
return count;
}
static DEVICE_ATTR(monitor_enable, S_IRUSR | S_IWUSR, show_monitor_enable,
store_monitor_enable);
static int set_wow(struct tegra_wakeup_monitor *twm, bool enable)
{
char *envp[2];
if (enable)
envp[0] = TEGRA_WOW_WAKEUP_ENABLE;
else
envp[0] = TEGRA_WOW_WAKEUP_DISABLE;
envp[1] = NULL;
/* Sent out a uevent to broadcast wow enable change*/
kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE, envp);
dev_info(&twm->pdev->dev,
"wakeup moniter: set_wow = %d\n", (int)enable);
return 0;
}
static ssize_t show_wow_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", twm->wow_enabled ? 1 : 0);
}
static ssize_t store_wow_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int enable;
struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev);
if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1)
return -EINVAL;
dev_info(dev, "wakeup moniter: wow_enable = %d\n", enable);
set_wow(twm, enable);
twm->wow_enabled = enable;
return count;
}
static DEVICE_ATTR(wow_enable, S_IRUSR | S_IWUSR, show_wow_enable,
store_wow_enable);
/* if the wakeup monitor is enabled, it will receive a command before suspend */
static ssize_t store_cmd(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev);
if (strncmp(buf, "0", 1))
return -EINVAL;
dev_info(dev, "wakeup moniter: get done cmd\n");
up(&twm->suspend_prepare_sem);
return count;
}
static DEVICE_ATTR(cmd, S_IWUSR, NULL, store_cmd);
static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
{
struct tegra_wakeup_monitor *twm =
container_of(notifier, struct tegra_wakeup_monitor, pm_notifier);
char *envp[2];
unsigned long const timeout =
msecs_to_jiffies(TEGRA_WAKEUP_MONITOR_CMD_TIMEOUT_MS);
envp[1] = NULL;
switch (pm_event) {
case PM_SUSPEND_PREPARE:
if (twm->monitor_enable) {
dev_info(&twm->pdev->dev, "enter suspend_prepare\n");
switch (twm->wakeup_source) {
case TEGRA_WAKEUP_SOURCE_WIFI:
envp[0] = TEGRA_SUSPEND_PREPARE_UEVENT_WIFI;
break;
default:
envp[0] = TEGRA_SUSPEND_PREPARE_UEVENT_OTHERS;
}
/* send out a uevent to boardcast suspend prepare */
kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE,
envp);
/* clean the wakeup source flag */
twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS;
/* waiting for cmd feedback */
if (down_timeout(&twm->suspend_prepare_sem,
timeout) != 0)
dev_err(&twm->pdev->dev, "wakeup monitor: cmd time out\n");
}
return NOTIFY_OK;
case PM_POST_SUSPEND:
if (twm->monitor_enable) {
dev_info(&twm->pdev->dev, "enter post_suspend\n");
if (twm->wow_enabled == false) {
set_wow(twm, true);
twm->wow_enabled = true;
}
envp[0] = TEGRA_POST_SUSPEND_UEVENT;
/* send out a uevent to boardcast post suspend*/
kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE,
envp);
}
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static inline int twm_init(struct tegra_wakeup_monitor *twm,
struct platform_device *pdev)
{
struct tegra_wakeup_monitor_platform_data *pdata =
pdev->dev.platform_data;
int ret = 0;
twm->pdata = pdata;
twm->pdev = pdev;
/* create sysfs node */
ret = device_create_file(&pdev->dev, &dev_attr_monitor_enable);
if (ret)
goto error;
ret = device_create_file(&pdev->dev, &dev_attr_wow_enable);
if (ret)
goto error;
ret = device_create_file(&pdev->dev, &dev_attr_cmd);
if (ret)
goto error;
twm->monitor_enable = false;
twm->wow_enabled = true;
twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS;
twm->pm_notifier.notifier_call = tegra_wakeup_monitor_pm_notifier;
sema_init(&(twm->suspend_prepare_sem), 1);
ret = register_pm_notifier(&twm->pm_notifier);
return ret;
error:
device_remove_file(&pdev->dev, &dev_attr_cmd);
device_remove_file(&pdev->dev, &dev_attr_wow_enable);
device_remove_file(&pdev->dev, &dev_attr_monitor_enable);
return ret;
}
static int tegra_wakeup_monitor_probe(struct platform_device *pdev)
{
struct tegra_wakeup_monitor_platform_data *pdata =
pdev->dev.platform_data;
struct tegra_wakeup_monitor *twm;
int ret = 0;
if (!pdata) {
dev_dbg(&pdev->dev, "platform_data not available\n");
return -EINVAL;
}
twm = devm_kzalloc(&pdev->dev,
sizeof(struct tegra_wakeup_monitor), GFP_KERNEL);
if (!twm) {
dev_dbg(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
twm_init(twm, pdev);
dev_set_drvdata(&pdev->dev, twm);
return ret;
}
static int __exit tegra_wakeup_monitor_remove(struct platform_device *pdev)
{
struct tegra_wakeup_monitor *twm = platform_get_drvdata(pdev);
unregister_pm_notifier(&twm->pm_notifier);
device_remove_file(&pdev->dev, &dev_attr_monitor_enable);
device_remove_file(&pdev->dev, &dev_attr_wow_enable);
device_remove_file(&pdev->dev, &dev_attr_cmd);
kfree(twm);
return 0;
}
static int tegra_wakeup_monitor_resume(struct platform_device *pdev)
{
struct tegra_wakeup_monitor *twm = platform_get_drvdata(pdev);
/* read and save wake status */
u64 wake_status = tegra_read_pmc_wake_status();
if (twm->pdata->wifi_wakeup_source != -1 &&
(wake_status & BIT(twm->pdata->wifi_wakeup_source)))
twm->wakeup_source = TEGRA_WAKEUP_SOURCE_WIFI;
else
twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS;
pr_info("wakeup monitor: wakeup source =%d\n", twm->wakeup_source);
return 0;
}
static struct platform_driver tegra_wakeup_monitor_driver = {
.driver = {
.name = "tegra_wakeup_monitor",
.owner = THIS_MODULE,
},
.probe = tegra_wakeup_monitor_probe,
.remove = __exit_p(tegra_wakeup_monitor_remove),
#ifdef CONFIG_PM
.resume = tegra_wakeup_monitor_resume,
#endif
};
static int __init tegra_wakeup_monitor_init(void)
{
return platform_driver_register(&tegra_wakeup_monitor_driver);
}
subsys_initcall(tegra_wakeup_monitor_init);
static void __exit tegra_wakeup_monitor_exit(void)
{
platform_driver_unregister(&tegra_wakeup_monitor_driver);
}
module_exit(tegra_wakeup_monitor_exit);
MODULE_DESCRIPTION("Tegra Wakeup Monitor driver");
MODULE_LICENSE("GPL");