/* * Copyright (C) 2017 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_TIMEOUT 60 /* * Software timer tick implemented in scfw side, support 10ms to 0xffffffff ms * in theory, but for normal case, 1s~128s is enough, you can change this max * value in case it's not enough. */ #define MAX_TIMEOUT 128 static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0000); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); static unsigned int timeout = DEFAULT_TIMEOUT; module_param(timeout, uint, 0000); MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds 1<= timeout <=" __MODULE_STRING(MAX_TIMEOUT) " (default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); static struct watchdog_device imx8_wdd; static int imx8_wdt_notify(struct notifier_block *nb, unsigned long event, void *group) { /* ignore other irqs */ if (!(event & SC_IRQ_WDOG && (*(sc_irq_group_t *)group == SC_IRQ_GROUP_WDOG))) return 0; watchdog_notify_pretimeout(&imx8_wdd); return 0; } static int imx8_wdt_ping(struct watchdog_device *wdog) { struct arm_smccc_res res; arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_PING_WDOG, 0, 0, 0, 0, 0, 0, &res); return res.a0; } static int imx8_wdt_start(struct watchdog_device *wdog) { struct arm_smccc_res res; /* no block */ arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_START_WDOG, 0, 0, 0, 0, 0, 0, &res); if (res.a0) return res.a0; dev_dbg(wdog->parent, "Watchdog started\n"); return 0; } static int imx8_wdt_stop(struct watchdog_device *wdog) { struct arm_smccc_res res; arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_STOP_WDOG, 0, 0, 0, 0, 0, 0, &res); dev_dbg(wdog->parent, "Watchdog stopped\n"); return res.a0; } static int imx8_wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) { struct arm_smccc_res res; wdog->timeout = timeout; arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_SET_TIMEOUT_WDOG, timeout * 1000, 0, 0, 0, 0, 0, &res); dev_dbg(wdog->parent, "Set timeout to %d seconds\n", timeout); return res.a0; } static int imx8_wdt_set_pretimeout(struct watchdog_device *wdog, unsigned int new_pretimeout) { struct arm_smccc_res res; /* * SCU firmware calculates pretimeout based on current time * stamp instead of watchdog timeout stamp, need to convert * the pretimeout to SCU firmware's timeout value. */ arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_SET_PRETIME_WDOG, (wdog->timeout - new_pretimeout) * 1000, 0, 0, 0, 0, 0, &res); dev_dbg(wdog->parent, "Set pretimeout to %d seconds\n", new_pretimeout); return res.a0; } static const struct watchdog_ops imx8_wdt_ops = { .owner = THIS_MODULE, .start = imx8_wdt_start, .stop = imx8_wdt_stop, .ping = imx8_wdt_ping, .set_timeout = imx8_wdt_set_timeout, .set_pretimeout = imx8_wdt_set_pretimeout, }; static const struct watchdog_info imx8_wdt_info = { .identity = "i.MX8 watchdog timer", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT, }; static struct notifier_block imx8_wdt_notifier = { .notifier_call = imx8_wdt_notify, }; #ifdef CONFIG_PM_SLEEP static int imx8_wdt_suspend(struct device *dev) { if (test_bit(WDOG_ACTIVE, &imx8_wdd.status)) imx8_wdt_stop(&imx8_wdd); return 0; } static int imx8_wdt_resume(struct device *dev) { if (test_bit(WDOG_ACTIVE, &imx8_wdd.status)) imx8_wdt_start(&imx8_wdd); return 0; } static const struct dev_pm_ops imx8_wdt_pm_ops = { .suspend = imx8_wdt_suspend, .resume = imx8_wdt_resume, }; #define IMX8_WDT_PM_OPS (&imx8_wdt_pm_ops) #else #define IMX8_WDT_PM_OPS NULL #endif static int imx8_wdt_probe(struct platform_device *pdev) { struct watchdog_device *wdt = &imx8_wdd; int err; platform_set_drvdata(pdev, wdt); /* init the wdd */ wdt->info = &imx8_wdt_info; wdt->ops = &imx8_wdt_ops; wdt->min_timeout = 1; wdt->max_timeout = MAX_TIMEOUT; wdt->parent = &pdev->dev; watchdog_set_drvdata(wdt, NULL); err = watchdog_init_timeout(wdt, timeout, &pdev->dev); if (err) { dev_err(&pdev->dev, "Failed to init watchdog timeout:%d\n", err); return err; } err = watchdog_register_device(wdt); if (err) { dev_err(&pdev->dev, "Failed to register watchdog device: %d\n", err); return err; } err = register_scu_notifier(&imx8_wdt_notifier); if (err) { dev_err(&pdev->dev, "Failed to register scu notifier: %d\n", err); goto scu_err; } dev_info(&pdev->dev, "initialized (timeout=%d sec, nowayout=%d)\n", timeout, nowayout); return 0; scu_err: watchdog_unregister_device(wdt); return err; } static int imx8_wdt_remove(struct platform_device *pdev) { struct watchdog_device *wdt = platform_get_drvdata(pdev); imx8_wdt_stop(wdt); unregister_scu_notifier(&imx8_wdt_notifier); watchdog_unregister_device(wdt); return 0; } static void imx8_wdt_shutdown(struct platform_device *pdev) { struct watchdog_device *wdt = platform_get_drvdata(pdev); if (watchdog_active(wdt)) imx8_wdt_stop(wdt); } static const struct of_device_id imx8_wdt_dt_ids[] = { { .compatible = "fsl,imx8-wdt", }, { /*sentinel */ } }; MODULE_DEVICE_TABLE(of, imx8_wdt_dt_ids); static struct platform_driver imx8_wdt_driver = { .probe = imx8_wdt_probe, .remove = imx8_wdt_remove, .shutdown = imx8_wdt_shutdown, .driver = { .name = "imx8-wdt", .of_match_table = imx8_wdt_dt_ids, .pm = IMX8_WDT_PM_OPS, }, }; module_platform_driver(imx8_wdt_driver); MODULE_AUTHOR("Robin Gong "); MODULE_DESCRIPTION("NXP i.MX8 watchdog driver"); MODULE_LICENSE("GPL v2");