/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_LICENSE("GPL");