ARM: mach-imx: add heartbeat driver for i.mx7ulp
Add heartbeat driver for i.mx7ulp. Signed-off-by: Robin Gong <yibin.gong@nxp.com>5.4-rM2-2.2.x-imx-squashed
parent
7d4e79f327
commit
a9796ede8a
|
@ -34,7 +34,7 @@ AFLAGS_imx6sx_low_power_idle.o :=-Wa,-march=armv7-a
|
|||
AFLAGS_imx6ul_low_power_idle.o :=-Wa,-march=armv7-a
|
||||
AFLAGS_imx6ull_low_power_idle.o :=-Wa,-march=armv7-a
|
||||
obj-$(CONFIG_SOC_IMX6UL) += cpuidle-imx6ul.o imx6ul_low_power_idle.o imx6ull_low_power_idle.o
|
||||
obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o
|
||||
obj-$(CONFIG_SOC_IMX7ULP) += cpuidle-imx7ulp.o pm-rpmsg.o
|
||||
AFLAGS_imx7d_low_power_idle.o :=-Wa,-march=armv7-a
|
||||
obj-$(CONFIG_SOC_IMX7D_CA7) += cpuidle-imx7d.o imx7d_low_power_idle.o
|
||||
endif
|
||||
|
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
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");
|
Loading…
Reference in New Issue