diff options
-rw-r--r-- | Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt | 7 | ||||
-rw-r--r-- | drivers/rtc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-imx-rpmsg.c | 378 | ||||
-rw-r--r-- | drivers/rtc/rtc-imx-sc.c | 81 |
5 files changed, 469 insertions, 5 deletions
diff --git a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt index eeb6c891647d..f8a15c8eb86e 100644 --- a/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt +++ b/Documentation/devicetree/bindings/arm/freescale/fsl,scu.txt @@ -138,6 +138,13 @@ Required properties: "fsl,imx8dxl-sc-rtc"; "fsl,imx8qxp-sc-rtc"; +Optional Child nodes: + +- read-only: + For some use cases, like cockpit, one cockpit domain owns it + while other domain is just a reader. Its needed to provide + /dev/rtc funtion for applications. + OCOTP bindings based on SCU Message Protocol ------------------------------------------------------------ Required properties: diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index e1bc5214494e..4b6b0f187dc5 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1781,6 +1781,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_ST_LPC tristate "STMicroelectronics LPC RTC" depends on ARCH_STI diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 5ceeafe4d5b2..958cadb14d1b 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -75,6 +75,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..703e4bc2926d --- /dev/null +++ b/drivers/rtc/rtc-imx-rpmsg.c @@ -0,0 +1,378 @@ +/* + * 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 <linux/init.h> +#include <linux/io.h> +#include <linux/imx_rpmsg.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_qos.h> +#include <linux/rpmsg.h> +#include <linux/rtc.h> + +#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); + 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 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: + cpu_latency_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_time64_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; + + time = rtc_tm_to_time64(tm); + + 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_time64_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; + + time = rtc_tm_to_time64(&alrm->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 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); + } + + return ret; +} + +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 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"); +} + +#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 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, +}; + +static int rtc_rpmsg_probe(struct rpmsg_device *rpdev) +{ + 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 platform_driver_register(&imx_rpmsg_rtc_driver); +} + +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 __init rtc_imx_rpmsg_init(void) +{ + return register_rpmsg_driver(&rtc_rpmsg_driver); +} +late_initcall(rtc_imx_rpmsg_init); + +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); +MODULE_DESCRIPTION("NXP i.MX RPMSG RTC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-imx-sc.c b/drivers/rtc/rtc-imx-sc.c index 814d516645e2..7239e7609a06 100644 --- a/drivers/rtc/rtc-imx-sc.c +++ b/drivers/rtc/rtc-imx-sc.c @@ -10,6 +10,7 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/rtc.h> +#include <linux/suspend.h> #define IMX_SC_TIMER_FUNC_GET_RTC_SEC1970 9 #define IMX_SC_TIMER_FUNC_SET_RTC_ALARM 8 @@ -18,11 +19,9 @@ #define IMX_SIP_SRTC 0xC2000002 #define IMX_SIP_SRTC_SET_TIME 0x0 -#define SC_IRQ_GROUP_RTC 2 -#define SC_IRQ_RTC 1 - static struct imx_sc_ipc *rtc_ipc_handle; static struct rtc_device *imx_sc_rtc; +static bool readonly; /* true if not authorised to set time */ struct imx_sc_msg_timer_get_rtc_time { struct imx_sc_rpc_msg hdr; @@ -39,6 +38,11 @@ struct imx_sc_msg_timer_rtc_set_alarm { u8 sec; } __packed __aligned(4); +#define RTC_NORMAL_MODE 0 +#define RTC_IN_SUSPEND 1 +#define RTC_ABORT_SUSPEND 2 +static int rtc_state = RTC_NORMAL_MODE; + static int imx_sc_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct imx_sc_msg_timer_get_rtc_time msg; @@ -65,6 +69,9 @@ static int imx_sc_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct arm_smccc_res res; + if (readonly) + return 0; + /* pack 2 time parameters into 1 register, 16 bits for each */ arm_smccc_smc(IMX_SIP_SRTC, IMX_SIP_SRTC_SET_TIME, ((tm->tm_year + 1900) << 16) | (tm->tm_mon + 1), @@ -77,7 +84,7 @@ static int imx_sc_rtc_set_time(struct device *dev, struct rtc_time *tm) static int imx_sc_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) { - return imx_scu_irq_group_enable(SC_IRQ_GROUP_RTC, SC_IRQ_RTC, enable); + return imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_RTC, IMX_SC_IRQ_RTC, enable); } static int imx_sc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) @@ -99,6 +106,8 @@ static int imx_sc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) msg.min = alrm_tm->tm_min; msg.sec = alrm_tm->tm_sec; + rtc_state = RTC_NORMAL_MODE; + ret = imx_scu_call_rpc(rtc_ipc_handle, &msg, true); if (ret) { dev_err(dev, "set rtc alarm failed, ret %d\n", ret); @@ -121,13 +130,56 @@ static const struct rtc_class_ops imx_sc_rtc_ops = { .alarm_irq_enable = imx_sc_rtc_alarm_irq_enable, }; +static int imx_sc_rtc_suspend(struct device *dev) +{ + int err = 0; + + mutex_lock(&imx_sc_rtc->ops_lock); + /* Abort suspend if the RTC wakeup interrupt triggered during suspend. */ + if (rtc_state == RTC_ABORT_SUSPEND) + err = -EBUSY; + + rtc_state = RTC_NORMAL_MODE; + mutex_unlock(&imx_sc_rtc->ops_lock); + + return err; +} + +static int imx_sc_rtc_resume(struct device *dev) +{ + return 0; +} + +static int imx_sc_rtc_pm_notify(struct notifier_block *nb, + unsigned long event, void *group) +{ + mutex_lock(&imx_sc_rtc->ops_lock); + switch (event) { + case PM_SUSPEND_PREPARE: + rtc_state = RTC_IN_SUSPEND; + break; + case PM_POST_SUSPEND: + rtc_state = RTC_NORMAL_MODE; + break; + default: + break; + } + mutex_unlock(&imx_sc_rtc->ops_lock); + return 0; +} + static int imx_sc_rtc_alarm_notify(struct notifier_block *nb, unsigned long event, void *group) { /* ignore non-rtc irq */ - if (!((event & SC_IRQ_RTC) && (*(u8 *)group == SC_IRQ_GROUP_RTC))) + if (!((event & IMX_SC_IRQ_RTC) && (*(u8 *)group == IMX_SC_IRQ_GROUP_RTC))) return 0; + /* Abort the suspend process if the alarm expired during suspend. */ + mutex_lock(&imx_sc_rtc->ops_lock); + if (rtc_state == RTC_IN_SUSPEND) + rtc_state = RTC_ABORT_SUSPEND; + mutex_unlock(&imx_sc_rtc->ops_lock); rtc_update_irq(imx_sc_rtc, 1, RTC_IRQF | RTC_AF); return 0; @@ -137,6 +189,10 @@ static struct notifier_block imx_sc_rtc_alarm_sc_notifier = { .notifier_call = imx_sc_rtc_alarm_notify, }; +static struct notifier_block imx_sc_rtc_pm_notifier = { + .notifier_call = imx_sc_rtc_pm_notify, +}; + static int imx_sc_rtc_probe(struct platform_device *pdev) { int ret; @@ -160,12 +216,26 @@ static int imx_sc_rtc_probe(struct platform_device *pdev) return ret; imx_scu_irq_register_notifier(&imx_sc_rtc_alarm_sc_notifier); + /* Register for PM calls. */ + ret = register_pm_notifier(&imx_sc_rtc_pm_notifier); + if(ret) + pr_warn("iMX_SC_RTC: Cannot register suspend notifier, ret = %d\n", ret); + + if (of_property_read_bool(pdev->dev.of_node, "read-only")) { + readonly = true; + dev_info(&pdev->dev, "not allowed to change time\n"); + } else { + readonly = false; + } return 0; } +static SIMPLE_DEV_PM_OPS(imx_sc_rtc_pm_ops, imx_sc_rtc_suspend, imx_sc_rtc_resume); + static const struct of_device_id imx_sc_dt_ids[] = { { .compatible = "fsl,imx8qxp-sc-rtc", }, + { .compatible = "fsl,imx8qm-sc-rtc", }, {} }; MODULE_DEVICE_TABLE(of, imx_sc_dt_ids); @@ -174,6 +244,7 @@ static struct platform_driver imx_sc_rtc_driver = { .driver = { .name = "imx-sc-rtc", .of_match_table = imx_sc_dt_ids, + .pm = &imx_sc_rtc_pm_ops, }, .probe = imx_sc_rtc_probe, }; |