mirror of
https://github.com/brain-hackers/linux-brain.git
synced 2024-06-09 23:36:23 +09:00
6f233c715c
Add i.MX7ULP suspend/resume support, including standby mode and mem mode, mapped to VLPS and VLLS mode. Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
354 lines
7.5 KiB
C
354 lines
7.5 KiB
C
/*
|
|
* Copyright 2017-2018 NXP
|
|
*
|
|
* 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/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/imx_rpmsg.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/virtio.h>
|
|
#include "common.h"
|
|
|
|
#define RPMSG_TIMEOUT 1000
|
|
|
|
#define PM_RPMSG_TYPE 0
|
|
#define HEATBEAT_RPMSG_TYPE 2
|
|
|
|
enum pm_rpmsg_cmd {
|
|
PM_RPMSG_MODE,
|
|
PM_RPMSG_HEART_BEAT,
|
|
PM_RPMSG_HEART_BEAT_OFF,
|
|
};
|
|
|
|
enum pm_rpmsg_power_mode {
|
|
PM_RPMSG_HSRUN,
|
|
PM_RPMSG_RUN,
|
|
PM_RPMSG_VLPR,
|
|
PM_RPMSG_WAIT,
|
|
PM_RPMSG_VLPS,
|
|
PM_RPMSG_VLLS,
|
|
PM_RPMSG_REBOOT,
|
|
PM_RPMSG_SHUTDOWN,
|
|
};
|
|
|
|
struct pm_rpmsg_info {
|
|
struct rpmsg_device *rpdev;
|
|
struct device *dev;
|
|
struct pm_rpmsg_data *msg;
|
|
struct pm_qos_request pm_qos_req;
|
|
struct notifier_block restart_handler;
|
|
struct completion cmd_complete;
|
|
bool first_flag;
|
|
struct mutex lock;
|
|
};
|
|
|
|
static struct pm_rpmsg_info pm_rpmsg;
|
|
|
|
static struct delayed_work heart_beat_work;
|
|
|
|
static bool heartbeat_off;
|
|
|
|
struct pm_rpmsg_data {
|
|
struct imx_rpmsg_head header;
|
|
u8 data;
|
|
} __attribute__ ((packed));
|
|
|
|
static int pm_send_message(struct pm_rpmsg_data *msg,
|
|
struct pm_rpmsg_info *info, bool ack)
|
|
{
|
|
int err;
|
|
|
|
if (!info->rpdev) {
|
|
dev_dbg(info->dev,
|
|
"rpmsg channel not ready, m4 image ready?\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&info->lock);
|
|
pm_qos_add_request(&info->pm_qos_req,
|
|
PM_QOS_CPU_DMA_LATENCY, 0);
|
|
|
|
reinit_completion(&info->cmd_complete);
|
|
|
|
err = rpmsg_send(info->rpdev->ept, (void *)msg,
|
|
sizeof(struct pm_rpmsg_data));
|
|
|
|
if (err) {
|
|
dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
|
|
goto err_out;
|
|
}
|
|
|
|
if (ack) {
|
|
err = wait_for_completion_timeout(&info->cmd_complete,
|
|
msecs_to_jiffies(RPMSG_TIMEOUT));
|
|
if (!err) {
|
|
dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n");
|
|
err = -ETIMEDOUT;
|
|
goto err_out;
|
|
}
|
|
|
|
if (info->msg->data != 0) {
|
|
dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n",
|
|
info->msg->data);
|
|
err = -EINVAL;
|
|
goto err_out;
|
|
}
|
|
|
|
err = 0;
|
|
}
|
|
|
|
err_out:
|
|
pm_qos_remove_request(&info->pm_qos_req);
|
|
mutex_unlock(&info->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int pm_vlls_notify_m4(bool enter)
|
|
{
|
|
struct pm_rpmsg_data msg;
|
|
|
|
msg.header.cate = IMX_RMPSG_LIFECYCLE;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = PM_RPMSG_TYPE;
|
|
msg.header.cmd = PM_RPMSG_MODE;
|
|
msg.data = enter ? PM_RPMSG_VLLS : PM_RPMSG_RUN;
|
|
|
|
return pm_send_message(&msg, &pm_rpmsg, true);
|
|
}
|
|
|
|
void pm_shutdown_notify_m4(void)
|
|
{
|
|
struct pm_rpmsg_data msg;
|
|
|
|
msg.header.cate = IMX_RMPSG_LIFECYCLE;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = PM_RPMSG_TYPE;
|
|
msg.header.cmd = PM_RPMSG_MODE;
|
|
msg.data = PM_RPMSG_SHUTDOWN;
|
|
/* No ACK from M4 */
|
|
pm_send_message(&msg, &pm_rpmsg, false);
|
|
imx7ulp_poweroff();
|
|
}
|
|
|
|
void pm_reboot_notify_m4(void)
|
|
{
|
|
struct pm_rpmsg_data msg;
|
|
|
|
msg.header.cate = IMX_RMPSG_LIFECYCLE;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = PM_RPMSG_TYPE;
|
|
msg.header.cmd = PM_RPMSG_MODE;
|
|
msg.data = PM_RPMSG_REBOOT;
|
|
|
|
pm_send_message(&msg, &pm_rpmsg, true);
|
|
|
|
}
|
|
|
|
void pm_heartbeat_off_notify_m4(bool enter)
|
|
{
|
|
struct pm_rpmsg_data msg;
|
|
|
|
msg.header.cate = IMX_RMPSG_LIFECYCLE;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = PM_RPMSG_TYPE;
|
|
msg.header.cmd = PM_RPMSG_HEART_BEAT_OFF;
|
|
msg.data = enter ? 0 : 1;
|
|
|
|
pm_send_message(&msg, &pm_rpmsg, true);
|
|
}
|
|
|
|
static void pm_heart_beat_work_handler(struct work_struct *work)
|
|
{
|
|
struct pm_rpmsg_data msg;
|
|
|
|
/* Notify M4 side A7 in RUN mode at boot time */
|
|
if (pm_rpmsg.first_flag) {
|
|
pm_vlls_notify_m4(false);
|
|
|
|
pm_heartbeat_off_notify_m4(heartbeat_off);
|
|
|
|
pm_rpmsg.first_flag = false;
|
|
}
|
|
|
|
if (!heartbeat_off) {
|
|
msg.header.cate = IMX_RMPSG_LIFECYCLE;
|
|
msg.header.major = IMX_RMPSG_MAJOR;
|
|
msg.header.minor = IMX_RMPSG_MINOR;
|
|
msg.header.type = HEATBEAT_RPMSG_TYPE;
|
|
msg.header.cmd = PM_RPMSG_HEART_BEAT;
|
|
msg.data = 0;
|
|
pm_send_message(&msg, &pm_rpmsg, false);
|
|
|
|
schedule_delayed_work(&heart_beat_work,
|
|
msecs_to_jiffies(30000));
|
|
}
|
|
}
|
|
|
|
static void pm_poweroff_rpmsg(void)
|
|
{
|
|
pm_shutdown_notify_m4();
|
|
pr_emerg("Unable to poweroff system\n");
|
|
}
|
|
|
|
static int pm_restart_handler(struct notifier_block *this, unsigned long mode,
|
|
void *cmd)
|
|
{
|
|
pm_reboot_notify_m4();
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int pm_rpmsg_probe(struct rpmsg_device *rpdev)
|
|
{
|
|
int ret;
|
|
|
|
pm_rpmsg.rpdev = rpdev;
|
|
|
|
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
|
|
rpdev->src, rpdev->dst);
|
|
|
|
init_completion(&pm_rpmsg.cmd_complete);
|
|
mutex_init(&pm_rpmsg.lock);
|
|
|
|
INIT_DELAYED_WORK(&heart_beat_work,
|
|
pm_heart_beat_work_handler);
|
|
|
|
pm_rpmsg.first_flag = true;
|
|
schedule_delayed_work(&heart_beat_work, 0);
|
|
|
|
pm_rpmsg.restart_handler.notifier_call = pm_restart_handler;
|
|
pm_rpmsg.restart_handler.priority = 128;
|
|
ret = register_restart_handler(&pm_rpmsg.restart_handler);
|
|
if (ret)
|
|
dev_err(&rpdev->dev, "cannot register restart handler\n");
|
|
|
|
pm_power_off = pm_poweroff_rpmsg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pm_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
|
|
void *priv, u32 src)
|
|
{
|
|
struct pm_rpmsg_data *msg = (struct pm_rpmsg_data *)data;
|
|
|
|
pm_rpmsg.msg = msg;
|
|
|
|
complete(&pm_rpmsg.cmd_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pm_rpmsg_remove(struct rpmsg_device *rpdev)
|
|
{
|
|
dev_info(&rpdev->dev, "pm rpmsg driver is removed\n");
|
|
}
|
|
|
|
static struct rpmsg_device_id pm_rpmsg_id_table[] = {
|
|
{ .name = "rpmsg-life-cycle-channel" },
|
|
{ },
|
|
};
|
|
|
|
static struct rpmsg_driver pm_rpmsg_driver = {
|
|
.drv.name = "pm_rpmsg",
|
|
.drv.owner = THIS_MODULE,
|
|
.id_table = pm_rpmsg_id_table,
|
|
.probe = pm_rpmsg_probe,
|
|
.callback = pm_rpmsg_cb,
|
|
.remove = pm_rpmsg_remove,
|
|
};
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int pm_heartbeat_suspend(struct device *dev)
|
|
{
|
|
int err;
|
|
|
|
err = pm_vlls_notify_m4(true);
|
|
if (err)
|
|
return err;
|
|
|
|
cancel_delayed_work_sync(&heart_beat_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pm_heartbeat_resume(struct device *dev)
|
|
{
|
|
int err;
|
|
|
|
err = pm_vlls_notify_m4(false);
|
|
if (err)
|
|
return err;
|
|
|
|
schedule_delayed_work(&heart_beat_work,
|
|
msecs_to_jiffies(10000));
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int pm_heartbeat_probe(struct platform_device *pdev)
|
|
{
|
|
platform_set_drvdata(pdev, &pm_rpmsg);
|
|
|
|
return register_rpmsg_driver(&pm_rpmsg_driver);
|
|
}
|
|
|
|
static const struct of_device_id pm_heartbeat_id[] = {
|
|
{"fsl,heartbeat-rpmsg",},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, pm_heartbeat_id);
|
|
|
|
static const struct dev_pm_ops pm_heartbeat_ops = {
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_heartbeat_suspend,
|
|
pm_heartbeat_resume)
|
|
};
|
|
|
|
static struct platform_driver pm_heartbeat_driver = {
|
|
.driver = {
|
|
.name = "heartbeat-rpmsg",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = pm_heartbeat_id,
|
|
.pm = &pm_heartbeat_ops,
|
|
},
|
|
.probe = pm_heartbeat_probe,
|
|
};
|
|
|
|
static int __init setup_heartbeat(char *str)
|
|
{
|
|
heartbeat_off = true;
|
|
|
|
return 1;
|
|
};
|
|
__setup("heartbeat_off", setup_heartbeat);
|
|
|
|
module_platform_driver(pm_heartbeat_driver);
|
|
|
|
MODULE_DESCRIPTION("Freescale PM rpmsg driver");
|
|
MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
|
|
MODULE_LICENSE("GPL");
|