blob: e760379cdf85c32d6146dc8882173e3463043112 [file] [log] [blame]
/*
* 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");