diff options
Diffstat (limited to 'drivers/watchdog/imx8_wdt.c')
-rw-r--r-- | drivers/watchdog/imx8_wdt.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/drivers/watchdog/imx8_wdt.c b/drivers/watchdog/imx8_wdt.c new file mode 100644 index 000000000000..955f1295d591 --- /dev/null +++ b/drivers/watchdog/imx8_wdt.c @@ -0,0 +1,235 @@ +/* + * 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 <linux/arm-smccc.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/watchdog.h> +#include <soc/imx/fsl_sip.h> +#include <soc/imx8/sc/sci.h> +#include <soc/imx8/sc/svc/irq/api.h> + +#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 (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; + /* TODO: change to SC_TIMER_WDOG_ACTION_PARTITION after SCFW support */ + arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_SET_WDOG_ACT, + SC_TIMER_WDOG_ACTION_BOARD, 0, 0, 0, 0, 0, &res); + return res.a0; +} + +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); + + 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); + + return res.a0; +} + +static int imx8_wdt_set_pretimeout(struct watchdog_device *wdog, + unsigned int new_pretimeout) +{ + struct arm_smccc_res res; + + arm_smccc_smc(FSL_SIP_SRTC, FSL_SIP_SRTC_SET_PRETIME_WDOG, + new_pretimeout * 1000, 0, 0, 0, 0, 0, + &res); + + 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, DEFAULT_TIMEOUT, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Failed to init the wdog timeout:%d\n", + err); + return err; + } + + err = watchdog_register_device(wdt); + if (err) { + dev_err(&pdev->dev, "Failed to register watchdog device\n"); + return err; + } + + return register_scu_notifier(&imx8_wdt_notifier); +} + +static int imx8_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdt = platform_get_drvdata(pdev); + + imx8_wdt_stop(wdt); + + 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 <yibin.gong@nxp.com>"); +MODULE_DESCRIPTION("NXP i.MX8 watchdog driver"); +MODULE_LICENSE("GPL v2"); |