1986857acSAnson Huang // SPDX-License-Identifier: GPL-2.0 2986857acSAnson Huang /* 3986857acSAnson Huang * Copyright 2018-2019 NXP. 4986857acSAnson Huang */ 5986857acSAnson Huang 6986857acSAnson Huang #include <linux/arm-smccc.h> 715f7d7fcSAnson Huang #include <linux/firmware/imx/sci.h> 8986857acSAnson Huang #include <linux/io.h> 9986857acSAnson Huang #include <linux/kernel.h> 10986857acSAnson Huang #include <linux/module.h> 11986857acSAnson Huang #include <linux/moduleparam.h> 12986857acSAnson Huang #include <linux/of.h> 13986857acSAnson Huang #include <linux/platform_device.h> 14986857acSAnson Huang #include <linux/watchdog.h> 15986857acSAnson Huang 16986857acSAnson Huang #define DEFAULT_TIMEOUT 60 17986857acSAnson Huang /* 18986857acSAnson Huang * Software timer tick implemented in scfw side, support 10ms to 0xffffffff ms 19986857acSAnson Huang * in theory, but for normal case, 1s~128s is enough, you can change this max 20986857acSAnson Huang * value in case it's not enough. 21986857acSAnson Huang */ 22986857acSAnson Huang #define MAX_TIMEOUT 128 23986857acSAnson Huang 24986857acSAnson Huang #define IMX_SIP_TIMER 0xC2000002 25986857acSAnson Huang #define IMX_SIP_TIMER_START_WDOG 0x01 26986857acSAnson Huang #define IMX_SIP_TIMER_STOP_WDOG 0x02 27986857acSAnson Huang #define IMX_SIP_TIMER_SET_WDOG_ACT 0x03 28986857acSAnson Huang #define IMX_SIP_TIMER_PING_WDOG 0x04 29986857acSAnson Huang #define IMX_SIP_TIMER_SET_TIMEOUT_WDOG 0x05 30986857acSAnson Huang #define IMX_SIP_TIMER_GET_WDOG_STAT 0x06 31986857acSAnson Huang #define IMX_SIP_TIMER_SET_PRETIME_WDOG 0x07 32986857acSAnson Huang 33986857acSAnson Huang #define SC_TIMER_WDOG_ACTION_PARTITION 0 34986857acSAnson Huang 3515f7d7fcSAnson Huang #define SC_IRQ_WDOG 1 3615f7d7fcSAnson Huang #define SC_IRQ_GROUP_WDOG 1 3715f7d7fcSAnson Huang 38986857acSAnson Huang static bool nowayout = WATCHDOG_NOWAYOUT; 39986857acSAnson Huang module_param(nowayout, bool, 0000); 40986857acSAnson Huang MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 41986857acSAnson Huang __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 42986857acSAnson Huang 4315f7d7fcSAnson Huang struct imx_sc_wdt_device { 4415f7d7fcSAnson Huang struct watchdog_device wdd; 4515f7d7fcSAnson Huang struct notifier_block wdt_notifier; 4615f7d7fcSAnson Huang }; 4715f7d7fcSAnson Huang 48986857acSAnson Huang static int imx_sc_wdt_ping(struct watchdog_device *wdog) 49986857acSAnson Huang { 50986857acSAnson Huang struct arm_smccc_res res; 51986857acSAnson Huang 52986857acSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_PING_WDOG, 53986857acSAnson Huang 0, 0, 0, 0, 0, 0, &res); 54986857acSAnson Huang 55986857acSAnson Huang return 0; 56986857acSAnson Huang } 57986857acSAnson Huang 58986857acSAnson Huang static int imx_sc_wdt_start(struct watchdog_device *wdog) 59986857acSAnson Huang { 60986857acSAnson Huang struct arm_smccc_res res; 61986857acSAnson Huang 62986857acSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_START_WDOG, 63986857acSAnson Huang 0, 0, 0, 0, 0, 0, &res); 64986857acSAnson Huang if (res.a0) 65986857acSAnson Huang return -EACCES; 66986857acSAnson Huang 67986857acSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_WDOG_ACT, 68986857acSAnson Huang SC_TIMER_WDOG_ACTION_PARTITION, 69986857acSAnson Huang 0, 0, 0, 0, 0, &res); 70986857acSAnson Huang return res.a0 ? -EACCES : 0; 71986857acSAnson Huang } 72986857acSAnson Huang 73986857acSAnson Huang static int imx_sc_wdt_stop(struct watchdog_device *wdog) 74986857acSAnson Huang { 75986857acSAnson Huang struct arm_smccc_res res; 76986857acSAnson Huang 77986857acSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_STOP_WDOG, 78986857acSAnson Huang 0, 0, 0, 0, 0, 0, &res); 79986857acSAnson Huang 80986857acSAnson Huang return res.a0 ? -EACCES : 0; 81986857acSAnson Huang } 82986857acSAnson Huang 83986857acSAnson Huang static int imx_sc_wdt_set_timeout(struct watchdog_device *wdog, 84986857acSAnson Huang unsigned int timeout) 85986857acSAnson Huang { 86986857acSAnson Huang struct arm_smccc_res res; 87986857acSAnson Huang 88986857acSAnson Huang wdog->timeout = timeout; 89986857acSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_TIMEOUT_WDOG, 90986857acSAnson Huang timeout * 1000, 0, 0, 0, 0, 0, &res); 91986857acSAnson Huang 92986857acSAnson Huang return res.a0 ? -EACCES : 0; 93986857acSAnson Huang } 94986857acSAnson Huang 9515f7d7fcSAnson Huang static int imx_sc_wdt_set_pretimeout(struct watchdog_device *wdog, 9615f7d7fcSAnson Huang unsigned int pretimeout) 9715f7d7fcSAnson Huang { 9815f7d7fcSAnson Huang struct arm_smccc_res res; 9915f7d7fcSAnson Huang 1002c50a6b8SAnson Huang /* 1012c50a6b8SAnson Huang * SCU firmware calculates pretimeout based on current time 1022c50a6b8SAnson Huang * stamp instead of watchdog timeout stamp, need to convert 1032c50a6b8SAnson Huang * the pretimeout to SCU firmware's timeout value. 1042c50a6b8SAnson Huang */ 10515f7d7fcSAnson Huang arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_PRETIME_WDOG, 1062c50a6b8SAnson Huang (wdog->timeout - pretimeout) * 1000, 0, 0, 0, 1072c50a6b8SAnson Huang 0, 0, &res); 10815f7d7fcSAnson Huang if (res.a0) 10915f7d7fcSAnson Huang return -EACCES; 11015f7d7fcSAnson Huang 11115f7d7fcSAnson Huang wdog->pretimeout = pretimeout; 11215f7d7fcSAnson Huang 11315f7d7fcSAnson Huang return 0; 11415f7d7fcSAnson Huang } 11515f7d7fcSAnson Huang 11615f7d7fcSAnson Huang static int imx_sc_wdt_notify(struct notifier_block *nb, 11715f7d7fcSAnson Huang unsigned long event, void *group) 11815f7d7fcSAnson Huang { 11915f7d7fcSAnson Huang struct imx_sc_wdt_device *imx_sc_wdd = 12015f7d7fcSAnson Huang container_of(nb, 12115f7d7fcSAnson Huang struct imx_sc_wdt_device, 12215f7d7fcSAnson Huang wdt_notifier); 12315f7d7fcSAnson Huang 12415f7d7fcSAnson Huang if (event & SC_IRQ_WDOG && 12515f7d7fcSAnson Huang *(u8 *)group == SC_IRQ_GROUP_WDOG) 12615f7d7fcSAnson Huang watchdog_notify_pretimeout(&imx_sc_wdd->wdd); 12715f7d7fcSAnson Huang 12815f7d7fcSAnson Huang return 0; 12915f7d7fcSAnson Huang } 13015f7d7fcSAnson Huang 13115f7d7fcSAnson Huang static void imx_sc_wdt_action(void *data) 13215f7d7fcSAnson Huang { 13315f7d7fcSAnson Huang struct notifier_block *wdt_notifier = data; 13415f7d7fcSAnson Huang 13515f7d7fcSAnson Huang imx_scu_irq_unregister_notifier(wdt_notifier); 13615f7d7fcSAnson Huang imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG, 13715f7d7fcSAnson Huang SC_IRQ_WDOG, 13815f7d7fcSAnson Huang false); 13915f7d7fcSAnson Huang } 14015f7d7fcSAnson Huang 141986857acSAnson Huang static const struct watchdog_ops imx_sc_wdt_ops = { 142986857acSAnson Huang .owner = THIS_MODULE, 143986857acSAnson Huang .start = imx_sc_wdt_start, 144986857acSAnson Huang .stop = imx_sc_wdt_stop, 145986857acSAnson Huang .ping = imx_sc_wdt_ping, 146986857acSAnson Huang .set_timeout = imx_sc_wdt_set_timeout, 14715f7d7fcSAnson Huang .set_pretimeout = imx_sc_wdt_set_pretimeout, 148986857acSAnson Huang }; 149986857acSAnson Huang 15015f7d7fcSAnson Huang static struct watchdog_info imx_sc_wdt_info = { 151986857acSAnson Huang .identity = "i.MX SC watchdog timer", 152986857acSAnson Huang .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 15315f7d7fcSAnson Huang WDIOF_MAGICCLOSE, 154986857acSAnson Huang }; 155986857acSAnson Huang 156986857acSAnson Huang static int imx_sc_wdt_probe(struct platform_device *pdev) 157986857acSAnson Huang { 15815f7d7fcSAnson Huang struct imx_sc_wdt_device *imx_sc_wdd; 15915f7d7fcSAnson Huang struct watchdog_device *wdog; 16001022e33SGuenter Roeck struct device *dev = &pdev->dev; 161986857acSAnson Huang int ret; 162986857acSAnson Huang 16301022e33SGuenter Roeck imx_sc_wdd = devm_kzalloc(dev, sizeof(*imx_sc_wdd), GFP_KERNEL); 164986857acSAnson Huang if (!imx_sc_wdd) 165986857acSAnson Huang return -ENOMEM; 166986857acSAnson Huang 167986857acSAnson Huang platform_set_drvdata(pdev, imx_sc_wdd); 168986857acSAnson Huang 16915f7d7fcSAnson Huang wdog = &imx_sc_wdd->wdd; 17015f7d7fcSAnson Huang wdog->info = &imx_sc_wdt_info; 17115f7d7fcSAnson Huang wdog->ops = &imx_sc_wdt_ops; 17215f7d7fcSAnson Huang wdog->min_timeout = 1; 17315f7d7fcSAnson Huang wdog->max_timeout = MAX_TIMEOUT; 17415f7d7fcSAnson Huang wdog->parent = dev; 17515f7d7fcSAnson Huang wdog->timeout = DEFAULT_TIMEOUT; 176986857acSAnson Huang 17715f7d7fcSAnson Huang watchdog_init_timeout(wdog, 0, dev); 178e56d48e9SFabio Estevam 179e56d48e9SFabio Estevam ret = imx_sc_wdt_set_timeout(wdog, wdog->timeout); 180e56d48e9SFabio Estevam if (ret) 181e56d48e9SFabio Estevam return ret; 182e56d48e9SFabio Estevam 18315f7d7fcSAnson Huang watchdog_stop_on_reboot(wdog); 18415f7d7fcSAnson Huang watchdog_stop_on_unregister(wdog); 185986857acSAnson Huang 18615f7d7fcSAnson Huang ret = imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG, 18715f7d7fcSAnson Huang SC_IRQ_WDOG, 18815f7d7fcSAnson Huang true); 18915f7d7fcSAnson Huang if (ret) { 19015f7d7fcSAnson Huang dev_warn(dev, "Enable irq failed, pretimeout NOT supported\n"); 191*854478a3SStefan Eichenberger goto register_device; 19215f7d7fcSAnson Huang } 19315f7d7fcSAnson Huang 19415f7d7fcSAnson Huang imx_sc_wdd->wdt_notifier.notifier_call = imx_sc_wdt_notify; 19515f7d7fcSAnson Huang ret = imx_scu_irq_register_notifier(&imx_sc_wdd->wdt_notifier); 19615f7d7fcSAnson Huang if (ret) { 19715f7d7fcSAnson Huang imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG, 19815f7d7fcSAnson Huang SC_IRQ_WDOG, 19915f7d7fcSAnson Huang false); 20015f7d7fcSAnson Huang dev_warn(dev, 20115f7d7fcSAnson Huang "Register irq notifier failed, pretimeout NOT supported\n"); 202*854478a3SStefan Eichenberger goto register_device; 20315f7d7fcSAnson Huang } 20415f7d7fcSAnson Huang 20515f7d7fcSAnson Huang ret = devm_add_action_or_reset(dev, imx_sc_wdt_action, 20615f7d7fcSAnson Huang &imx_sc_wdd->wdt_notifier); 20715f7d7fcSAnson Huang if (!ret) 20815f7d7fcSAnson Huang imx_sc_wdt_info.options |= WDIOF_PRETIMEOUT; 20915f7d7fcSAnson Huang else 21015f7d7fcSAnson Huang dev_warn(dev, "Add action failed, pretimeout NOT supported\n"); 21115f7d7fcSAnson Huang 212*854478a3SStefan Eichenberger register_device: 213*854478a3SStefan Eichenberger return devm_watchdog_register_device(dev, wdog); 214986857acSAnson Huang } 215986857acSAnson Huang 216986857acSAnson Huang static int __maybe_unused imx_sc_wdt_suspend(struct device *dev) 217986857acSAnson Huang { 21815f7d7fcSAnson Huang struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev); 219986857acSAnson Huang 22015f7d7fcSAnson Huang if (watchdog_active(&imx_sc_wdd->wdd)) 22115f7d7fcSAnson Huang imx_sc_wdt_stop(&imx_sc_wdd->wdd); 222986857acSAnson Huang 223986857acSAnson Huang return 0; 224986857acSAnson Huang } 225986857acSAnson Huang 226986857acSAnson Huang static int __maybe_unused imx_sc_wdt_resume(struct device *dev) 227986857acSAnson Huang { 22815f7d7fcSAnson Huang struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev); 229986857acSAnson Huang 23015f7d7fcSAnson Huang if (watchdog_active(&imx_sc_wdd->wdd)) 23115f7d7fcSAnson Huang imx_sc_wdt_start(&imx_sc_wdd->wdd); 232986857acSAnson Huang 233986857acSAnson Huang return 0; 234986857acSAnson Huang } 235986857acSAnson Huang 236986857acSAnson Huang static SIMPLE_DEV_PM_OPS(imx_sc_wdt_pm_ops, 237986857acSAnson Huang imx_sc_wdt_suspend, imx_sc_wdt_resume); 238986857acSAnson Huang 239986857acSAnson Huang static const struct of_device_id imx_sc_wdt_dt_ids[] = { 240986857acSAnson Huang { .compatible = "fsl,imx-sc-wdt", }, 241986857acSAnson Huang { /* sentinel */ } 242986857acSAnson Huang }; 243986857acSAnson Huang MODULE_DEVICE_TABLE(of, imx_sc_wdt_dt_ids); 244986857acSAnson Huang 245986857acSAnson Huang static struct platform_driver imx_sc_wdt_driver = { 246986857acSAnson Huang .probe = imx_sc_wdt_probe, 247986857acSAnson Huang .driver = { 248986857acSAnson Huang .name = "imx-sc-wdt", 249986857acSAnson Huang .of_match_table = imx_sc_wdt_dt_ids, 250986857acSAnson Huang .pm = &imx_sc_wdt_pm_ops, 251986857acSAnson Huang }, 252986857acSAnson Huang }; 253986857acSAnson Huang module_platform_driver(imx_sc_wdt_driver); 254986857acSAnson Huang 255986857acSAnson Huang MODULE_AUTHOR("Robin Gong <yibin.gong@nxp.com>"); 256986857acSAnson Huang MODULE_DESCRIPTION("NXP i.MX system controller watchdog driver"); 257986857acSAnson Huang MODULE_LICENSE("GPL v2"); 258