blob: fddba48d19c920231e25da86e979370353053526 [file] [log] [blame]
/*
* drivers/amlogic/pm/gx_pm.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 <linux/psci.h>
#include <asm/compiler.h>
#include <linux/errno.h>
#include <linux/suspend.h>
#include <asm/suspend.h>
#include <linux/of_address.h>
#include <linux/input.h>
#include <linux/cpuidle.h>
#include <asm/cpuidle.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>
#include <linux/amlogic/scpi_protocol.h>
#include "vad_power.h"
#include <linux/syscore_ops.h>
typedef unsigned long (psci_fn)(unsigned long, unsigned long,
unsigned long, unsigned long);
static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
unsigned long arg0, unsigned long arg1,
unsigned long arg2)
{
struct arm_smccc_res res;
arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
return res.a0;
}
static u32 psci_get_version(void)
{
return __invoke_psci_fn_smc(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
}
#undef pr_fmt
#define pr_fmt(fmt) "gxbb_pm: " fmt
static void __iomem *debug_reg;
static void __iomem *exit_reg;
static int max_idle_lvl;
static suspend_state_t pm_state;
static unsigned int resume_reason;
/*
*0x10000 : bit[16]=1:control cpu suspend to power down
*cpu_suspend(0, meson_system_suspend);
*/
static void meson_gx_suspend(void)
{
pr_info("enter meson_pm_suspend!\n");
arm_cpuidle_suspend(max_idle_lvl);
/* cpu_suspend(0, meson_system_suspend);
*/
pr_info("... wake up\n");
}
static int meson_pm_prepare(void)
{
pr_info("enter meson_pm_prepare!\n");
return 0;
}
static int meson_gx_enter(suspend_state_t state)
{
int ret = 0;
switch (state) {
case PM_SUSPEND_STANDBY:
case PM_SUSPEND_MEM:
meson_gx_suspend();
break;
default:
ret = -EINVAL;
}
return ret;
}
static void meson_pm_finish(void)
{
pr_info("enter meson_pm_finish!\n");
}
/*
* get_resume_reason always return last resume reason.
*/
unsigned int get_resume_reason(void)
{
unsigned int val = 0;
if (exit_reg)
val = (readl(exit_reg) >> 28) & 0xf;
return val;
}
EXPORT_SYMBOL_GPL(get_resume_reason);
/*
* get_resume_method return last resume reason.
* It can be cleared by clr_resume_method().
*/
unsigned int get_resume_method(void)
{
return resume_reason;
}
EXPORT_SYMBOL_GPL(get_resume_method);
static void set_resume_method(unsigned int val)
{
resume_reason = val;
}
static int clr_suspend_notify(struct notifier_block *nb,
unsigned long event, void *dummy)
{
if (event == PM_SUSPEND_PREPARE)
set_resume_method(UDEFINED_WAKEUP);
return NOTIFY_OK;
}
static struct notifier_block clr_suspend_notifier = {
.notifier_call = clr_suspend_notify,
};
static const struct platform_suspend_ops meson_gx_ops = {
.enter = meson_gx_enter,
.prepare = meson_pm_prepare,
.finish = meson_pm_finish,
.valid = suspend_valid_only_mem,
};
unsigned int is_pm_freeze_mode(void)
{
if (pm_state == PM_SUSPEND_FREEZE)
return 1;
else
return 0;
}
EXPORT_SYMBOL(is_pm_freeze_mode);
static int frz_begin(void)
{
pm_state = PM_SUSPEND_FREEZE;
return 0;
}
static void frz_end(void)
{
pm_state = PM_SUSPEND_ON;
}
static const struct platform_freeze_ops meson_gx_frz_ops = {
.begin = frz_begin,
.end = frz_end,
};
static unsigned int suspend_reason;
ssize_t suspend_reason_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
unsigned int len;
if (scpi_get_wakeup_reason(&suspend_reason))
return -EPERM;
len = sprintf(buf, "%d\n", suspend_reason);
return len;
}
ssize_t suspend_reason_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 0, &suspend_reason);
switch (ret) {
case 1:
__invoke_psci_fn_smc(0x82000042, suspend_reason, 0, 0);
break;
default:
return -EINVAL;
}
return count;
}
DEVICE_ATTR(suspend_reason, 0664, suspend_reason_show, suspend_reason_store);
ssize_t time_out_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
unsigned int val = 0, len;
val = readl(debug_reg);
len = sprintf(buf, "%d\n", val);
return len;
}
static int sys_time_out;
ssize_t time_out_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int time_out;
int ret;
ret = kstrtouint(buf, 10, &time_out);
switch (ret) {
case 0:
sys_time_out = time_out;
writel(time_out, debug_reg);
break;
default:
return -EINVAL;
}
return count;
}
DEVICE_ATTR(time_out, 0664, time_out_show, time_out_store);
int gx_pm_syscore_suspend(void)
{
if (sys_time_out)
writel_relaxed(sys_time_out, debug_reg);
return 0;
}
void gx_pm_syscore_resume(void)
{
sys_time_out = 0;
set_resume_method(get_resume_reason());
}
static struct syscore_ops gx_pm_syscore_ops = {
.suspend = gx_pm_syscore_suspend,
.resume = gx_pm_syscore_resume,
};
static int __init gx_pm_init_ops(void)
{
register_syscore_ops(&gx_pm_syscore_ops);
return 0;
}
static int meson_pm_probe(struct platform_device *pdev)
{
struct device_node *cpu_node;
struct device_node *state_node;
struct pm_data *p_data;
struct device *dev = &pdev->dev;
int count = 0, ret;
u32 ver = psci_get_version();
u32 paddr = 0;
pr_info("enter meson_pm_probe!\n");
if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) == 2) {
cpu_node = of_get_cpu_node(0, NULL);
if (!cpu_node) {
pr_info("cpu node get fail!\n");
return -1;
}
while ((state_node = of_parse_phandle(cpu_node,
"cpu-idle-states", count))) {
count++;
of_node_put(state_node);
}
if (count)
max_idle_lvl = count;
else {
max_idle_lvl = -1;
pr_info("get system suspend level fail!\n");
return -1;
}
pr_info("system suspend level: %d\n", max_idle_lvl);
suspend_set_ops(&meson_gx_ops);
}
p_data = devm_kzalloc(&pdev->dev, sizeof(struct pm_data), GFP_KERNEL);
if (!p_data)
return -ENOMEM;
p_data->dev = dev;
dev_set_drvdata(dev, p_data);
vad_wakeup_power_init(pdev, p_data);
ret = of_property_read_u32(pdev->dev.of_node,
"debug_reg", &paddr);
if (!ret) {
pr_debug("debug_reg: 0x%x\n", paddr);
debug_reg = ioremap(paddr, 0x4);
if (IS_ERR_OR_NULL(debug_reg))
goto uniomap;
}
ret = of_property_read_u32(pdev->dev.of_node,
"exit_reg", &paddr);
if (!ret) {
pr_debug("exit_reg: 0x%x\n", paddr);
exit_reg = ioremap(paddr, 0x4);
if (IS_ERR_OR_NULL(exit_reg))
goto uniomap;
}
device_create_file(&pdev->dev, &dev_attr_suspend_reason);
device_create_file(&pdev->dev, &dev_attr_time_out);
dev->power.wakeup = devm_kzalloc(&pdev->dev,
sizeof(struct wakeup_source), GFP_KERNEL);
wakeup_source_init(dev->power.wakeup, "ddrwakesrc");
pm_stay_awake(dev);
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
if (lgcy_early_suspend_init())
return -1;
#endif
freeze_set_ops(&meson_gx_frz_ops);
gx_pm_init_ops();
ret = register_pm_notifier(&clr_suspend_notifier);
if (unlikely(ret))
return ret;
pr_info("meson_pm_probe done\n");
return 0;
uniomap:
if (debug_reg)
iounmap(debug_reg);
return -ENXIO;
}
int pm_suspend_noirq(struct device *dev)
{
vad_wakeup_power_suspend(dev);
return 0;
}
int pm_resume_noirq(struct device *dev)
{
vad_wakeup_power_resume(dev);
return 0;
}
static const struct dev_pm_ops meson_pm_noirq_ops = {
.suspend_noirq = pm_suspend_noirq,
.resume_noirq = pm_resume_noirq,
};
static int meson_pm_remove(struct platform_device *pdev)
{
if (pdev->dev.power.wakeup)
pm_relax(&pdev->dev);
return 0;
}
static const struct of_device_id amlogic_pm_dt_match[] = {
{.compatible = "amlogic, pm",
},
{}
};
static struct platform_driver meson_pm_driver = {
.driver = {
.name = "pm-meson",
.owner = THIS_MODULE,
.of_match_table = amlogic_pm_dt_match,
.pm = &meson_pm_noirq_ops,
},
.probe = meson_pm_probe,
.remove = meson_pm_remove,
};
static int __init meson_pm_init(void)
{
return platform_driver_register(&meson_pm_driver);
}
late_initcall(meson_pm_init);