pm-rpmsg.c 7.49 KB
/*
 * 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);
	cpu_latency_qos_add_request(&info->pm_qos_req,
			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:
	cpu_latency_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");