From 31a37c0612f69b9684cadd57ed978880ce050d4e Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Tue, 6 Aug 2019 09:44:07 +0800 Subject: [PATCH] rtc: imx-rpmsg: Add i.MX RPMSG RTC support Add i.MX RPMSG RTC support for i.MX7ULP. Signed-off-by: Anson Huang --- drivers/rtc/Kconfig | 7 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-imx-rpmsg.c | 383 ++++++++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+) create mode 100644 drivers/rtc/rtc-imx-rpmsg.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 1adf9f815652..0c95f79d8f71 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1770,6 +1770,13 @@ config RTC_DRV_IMX_SC If you say yes here you get support for the NXP i.MX System Controller RTC module. +config RTC_DRV_IMX_RPMSG + tristate "NXP RPMSG RTC support" + depends on OF + help + If you say yes here you get support for the NXP RPMSG + RTC module. + config RTC_DRV_SIRFSOC tristate "SiRFSOC RTC" depends on ARCH_SIRF diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 4ac8f19fb631..fab66205bce0 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -81,6 +81,7 @@ obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o obj-$(CONFIG_RTC_DRV_IMX_SC) += rtc-imx-sc.o +obj-$(CONFIG_RTC_DRV_IMX_RPMSG) += rtc-imx-rpmsg.o obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_ISL12026) += rtc-isl12026.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o diff --git a/drivers/rtc/rtc-imx-rpmsg.c b/drivers/rtc/rtc-imx-rpmsg.c new file mode 100644 index 000000000000..dc74891be861 --- /dev/null +++ b/drivers/rtc/rtc-imx-rpmsg.c @@ -0,0 +1,383 @@ +/* + * Copyright 2017-2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RPMSG_TIMEOUT 1000 + +#define RTC_RPMSG_SEND 0x0 +#define RTC_RPMSG_RECEIVE 0x1 +#define RTC_RPMSG_NOTIFY 0x2 + +enum rtc_rpmsg_cmd { + RTC_RPMSG_SET_TIME, + RTC_RPMSG_GET_TIME, + RTC_RPMSG_SET_ALARM, + RTC_RPMSG_GET_ALARM, + RTC_RPMSG_ENABLE_ALARM, +}; + +struct rtc_rpmsg_data { + struct imx_rpmsg_head header; + u8 reserved0; + union { + u8 reserved1; + u8 ret; + }; + union { + u32 reserved2; + u32 sec; + }; + union { + u8 enable; + u8 reserved3; + }; + union { + u8 pending; + u8 reserved4; + }; +} __attribute__ ((packed)); + +struct rtc_rpmsg_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct rtc_rpmsg_data *msg; + struct pm_qos_request pm_qos_req; + struct completion cmd_complete; + struct mutex lock; +}; + +static struct rtc_rpmsg_info rtc_rpmsg; + +struct imx_rpmsg_rtc_data { + struct rtc_device *rtc; + spinlock_t lock; +}; + +struct imx_rpmsg_rtc_data *data; + +static int rtc_send_message(struct rtc_rpmsg_data *msg, + struct rtc_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 rtc_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->ret != 0) { + dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n", + info->msg->ret); + 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 imx_rpmsg_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_rpmsg_data msg; + int ret; + + msg.header.cate = IMX_RPMSG_RTC; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = RTC_RPMSG_SEND; + msg.header.cmd = RTC_RPMSG_GET_TIME; + + ret = rtc_send_message(&msg, &rtc_rpmsg, true); + if (ret) + return ret; + + rtc_time_to_tm(rtc_rpmsg.msg->sec, tm); + + return 0; +} + +static int imx_rpmsg_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_rpmsg_data msg; + unsigned long time; + int ret; + + rtc_tm_to_time(tm, &time); + + msg.header.cate = IMX_RPMSG_RTC; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = RTC_RPMSG_SEND; + msg.header.cmd = RTC_RPMSG_SET_TIME; + msg.sec = time; + + ret = rtc_send_message(&msg, &rtc_rpmsg, true); + if (ret) + return ret; + + return rtc_rpmsg.msg->ret; +} + +static int imx_rpmsg_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_rpmsg_data msg; + int ret; + + msg.header.cate = IMX_RPMSG_RTC; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = RTC_RPMSG_SEND; + msg.header.cmd = RTC_RPMSG_GET_ALARM; + + ret = rtc_send_message(&msg, &rtc_rpmsg, true); + if (ret) + return ret; + + rtc_time_to_tm(rtc_rpmsg.msg->sec, &alrm->time); + alrm->pending = rtc_rpmsg.msg->pending; + + return rtc_rpmsg.msg->ret; +} + +static int imx_rpmsg_rtc_alarm_irq_enable(struct device *dev, + unsigned int enable) +{ + struct rtc_rpmsg_data msg; + int ret; + + msg.header.cate = IMX_RPMSG_RTC; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = RTC_RPMSG_SEND; + msg.header.cmd = RTC_RPMSG_ENABLE_ALARM; + msg.enable = enable; + + ret = rtc_send_message(&msg, &rtc_rpmsg, true); + if (ret) + return ret; + + return rtc_rpmsg.msg->ret; +} + +static int imx_rpmsg_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_rpmsg_data msg; + unsigned long time; + int ret; + + rtc_tm_to_time(&alrm->time, &time); + + msg.header.cate = IMX_RPMSG_RTC; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = RTC_RPMSG_SEND; + msg.header.cmd = RTC_RPMSG_SET_ALARM; + msg.sec = time; + msg.enable = alrm->enabled; + + ret = rtc_send_message(&msg, &rtc_rpmsg, true); + if (ret) + return ret; + + return rtc_rpmsg.msg->ret; +} + +static int imx_rpmsg_rtc_alarm_irq_update(void) +{ + rtc_update_irq(data->rtc, 1, RTC_IRQF); + + return 0; +} + +static const struct rtc_class_ops imx_rpmsg_rtc_ops = { + .read_time = imx_rpmsg_rtc_read_time, + .set_time = imx_rpmsg_rtc_set_time, + .read_alarm = imx_rpmsg_rtc_read_alarm, + .set_alarm = imx_rpmsg_rtc_set_alarm, + .alarm_irq_enable = imx_rpmsg_rtc_alarm_irq_enable, +}; + +static struct rpmsg_device_id rtc_rpmsg_id_table[] = { + { .name = "rpmsg-rtc-channel" }, + { }, +}; + +static int rtc_rpmsg_probe(struct rpmsg_device *rpdev) +{ + int ret = 0; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + rtc_rpmsg.rpdev = rpdev; + mutex_init(&rtc_rpmsg.lock); + + init_completion(&rtc_rpmsg.cmd_complete); + + return ret; +} + +static int rtc_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct rtc_rpmsg_data *msg = (struct rtc_rpmsg_data *)data; + + if (msg->header.type == RTC_RPMSG_RECEIVE) { + rtc_rpmsg.msg = msg; + complete(&rtc_rpmsg.cmd_complete); + return 0; + } else if (msg->header.type == RTC_RPMSG_NOTIFY) { + rtc_rpmsg.msg = msg; + imx_rpmsg_rtc_alarm_irq_update(); + } else + dev_err(&rtc_rpmsg.rpdev->dev, "wrong command type!\n"); + + return 0; +} + +static void rtc_rpmsg_remove(struct rpmsg_device *rpdev) +{ + dev_info(&rpdev->dev, "rtc rpmsg driver is removed\n"); +} + +static struct rpmsg_driver rtc_rpmsg_driver = { + .drv.name = "rtc_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = rtc_rpmsg_id_table, + .probe = rtc_rpmsg_probe, + .callback = rtc_rpmsg_cb, + .remove = rtc_rpmsg_remove, +}; + +static int imx_rpmsg_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + device_init_wakeup(&pdev->dev, true); + + data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, + &imx_rpmsg_rtc_ops, THIS_MODULE); + if (IS_ERR(data->rtc)) { + ret = PTR_ERR(data->rtc); + dev_err(&pdev->dev, "failed to register rtc: %d\n", ret); + goto error_rtc_device_register; + } + + ret = register_rpmsg_driver(&rtc_rpmsg_driver); + if (ret) + dev_err(&pdev->dev, "failed to register rpmsg for rtc: %d\n", + ret); + +error_rtc_device_register: + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_rpmsg_rtc_suspend(struct device *dev) +{ + return 0; +} + +static int imx_rpmsg_rtc_suspend_noirq(struct device *dev) +{ + return 0; +} + +static int imx_rpmsg_rtc_resume(struct device *dev) +{ + return 0; +} + +static int imx_rpmsg_rtc_resume_noirq(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops imx_rpmsg_rtc_pm_ops = { + .suspend = imx_rpmsg_rtc_suspend, + .suspend_noirq = imx_rpmsg_rtc_suspend_noirq, + .resume = imx_rpmsg_rtc_resume, + .resume_noirq = imx_rpmsg_rtc_resume_noirq, +}; + +#define IMX_RPMSG_RTC_PM_OPS (&imx_rpmsg_rtc_pm_ops) + +#else + +#define IMX_RPMSG_RTC_PM_OPS NULL + +#endif + +static const struct of_device_id imx_rpmsg_rtc_dt_ids[] = { + { .compatible = "fsl,imx-rpmsg-rtc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_rpmsg_rtc_dt_ids); + +static struct platform_driver imx_rpmsg_rtc_driver = { + .driver = { + .name = "imx_rpmsg_rtc", + .pm = IMX_RPMSG_RTC_PM_OPS, + .of_match_table = imx_rpmsg_rtc_dt_ids, + }, + .probe = imx_rpmsg_rtc_probe, +}; +module_platform_driver(imx_rpmsg_rtc_driver); + +MODULE_AUTHOR("Anson Huang "); +MODULE_DESCRIPTION("NXP i.MX RPMSG RTC Driver"); +MODULE_LICENSE("GPL");