xref: /linux/drivers/watchdog/stmp3xxx_rtc_wdt.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
12671a3a3SFabio Estevam // SPDX-License-Identifier: GPL-2.0
2de6303abSWolfram Sang /*
3de6303abSWolfram Sang  * Watchdog driver for the RTC based watchdog in STMP3xxx and i.MX23/28
4de6303abSWolfram Sang  *
5cf82f52dSWolfram Sang  * Author: Wolfram Sang <kernel@pengutronix.de>
6de6303abSWolfram Sang  *
7de6303abSWolfram Sang  * Copyright (C) 2011-12 Wolfram Sang, Pengutronix
8de6303abSWolfram Sang  */
9de6303abSWolfram Sang #include <linux/kernel.h>
10de6303abSWolfram Sang #include <linux/module.h>
11de6303abSWolfram Sang #include <linux/watchdog.h>
12de6303abSWolfram Sang #include <linux/platform_device.h>
13de6303abSWolfram Sang #include <linux/stmp3xxx_rtc_wdt.h>
148d2fa171SHarald Geyer #include <linux/notifier.h>
158d2fa171SHarald Geyer #include <linux/reboot.h>
16de6303abSWolfram Sang 
17de6303abSWolfram Sang #define WDOG_TICK_RATE 1000 /* 1 kHz clock */
18de6303abSWolfram Sang #define STMP3XXX_DEFAULT_TIMEOUT 19
19de6303abSWolfram Sang #define STMP3XXX_MAX_TIMEOUT (UINT_MAX / WDOG_TICK_RATE)
20de6303abSWolfram Sang 
21de6303abSWolfram Sang static int heartbeat = STMP3XXX_DEFAULT_TIMEOUT;
22de6303abSWolfram Sang module_param(heartbeat, uint, 0);
23de6303abSWolfram Sang MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds from 1 to "
24de6303abSWolfram Sang 		 __MODULE_STRING(STMP3XXX_MAX_TIMEOUT) ", default "
25de6303abSWolfram Sang 		 __MODULE_STRING(STMP3XXX_DEFAULT_TIMEOUT));
26de6303abSWolfram Sang 
wdt_start(struct watchdog_device * wdd)27de6303abSWolfram Sang static int wdt_start(struct watchdog_device *wdd)
28de6303abSWolfram Sang {
29de6303abSWolfram Sang 	struct device *dev = watchdog_get_drvdata(wdd);
30bc8fdfbeSJingoo Han 	struct stmp3xxx_wdt_pdata *pdata = dev_get_platdata(dev);
31de6303abSWolfram Sang 
32de6303abSWolfram Sang 	pdata->wdt_set_timeout(dev->parent, wdd->timeout * WDOG_TICK_RATE);
33de6303abSWolfram Sang 	return 0;
34de6303abSWolfram Sang }
35de6303abSWolfram Sang 
wdt_stop(struct watchdog_device * wdd)36de6303abSWolfram Sang static int wdt_stop(struct watchdog_device *wdd)
37de6303abSWolfram Sang {
38de6303abSWolfram Sang 	struct device *dev = watchdog_get_drvdata(wdd);
39bc8fdfbeSJingoo Han 	struct stmp3xxx_wdt_pdata *pdata = dev_get_platdata(dev);
40de6303abSWolfram Sang 
41de6303abSWolfram Sang 	pdata->wdt_set_timeout(dev->parent, 0);
42de6303abSWolfram Sang 	return 0;
43de6303abSWolfram Sang }
44de6303abSWolfram Sang 
wdt_set_timeout(struct watchdog_device * wdd,unsigned new_timeout)45de6303abSWolfram Sang static int wdt_set_timeout(struct watchdog_device *wdd, unsigned new_timeout)
46de6303abSWolfram Sang {
47de6303abSWolfram Sang 	wdd->timeout = new_timeout;
48de6303abSWolfram Sang 	return wdt_start(wdd);
49de6303abSWolfram Sang }
50de6303abSWolfram Sang 
51de6303abSWolfram Sang static const struct watchdog_info stmp3xxx_wdt_ident = {
52de6303abSWolfram Sang 	.options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
53de6303abSWolfram Sang 	.identity = "STMP3XXX RTC Watchdog",
54de6303abSWolfram Sang };
55de6303abSWolfram Sang 
56de6303abSWolfram Sang static const struct watchdog_ops stmp3xxx_wdt_ops = {
57de6303abSWolfram Sang 	.owner = THIS_MODULE,
58de6303abSWolfram Sang 	.start = wdt_start,
59de6303abSWolfram Sang 	.stop = wdt_stop,
60de6303abSWolfram Sang 	.set_timeout = wdt_set_timeout,
61de6303abSWolfram Sang };
62de6303abSWolfram Sang 
63de6303abSWolfram Sang static struct watchdog_device stmp3xxx_wdd = {
64de6303abSWolfram Sang 	.info = &stmp3xxx_wdt_ident,
65de6303abSWolfram Sang 	.ops = &stmp3xxx_wdt_ops,
66de6303abSWolfram Sang 	.min_timeout = 1,
67de6303abSWolfram Sang 	.max_timeout = STMP3XXX_MAX_TIMEOUT,
68de6303abSWolfram Sang 	.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
69de6303abSWolfram Sang };
70de6303abSWolfram Sang 
wdt_notify_sys(struct notifier_block * nb,unsigned long code,void * unused)718d2fa171SHarald Geyer static int wdt_notify_sys(struct notifier_block *nb, unsigned long code,
728d2fa171SHarald Geyer 			  void *unused)
738d2fa171SHarald Geyer {
748d2fa171SHarald Geyer 	switch (code) {
758d2fa171SHarald Geyer 	case SYS_DOWN:	/* keep enabled, system might crash while going down */
768d2fa171SHarald Geyer 		break;
778d2fa171SHarald Geyer 	case SYS_HALT:	/* allow the system to actually halt */
788d2fa171SHarald Geyer 	case SYS_POWER_OFF:
798d2fa171SHarald Geyer 		wdt_stop(&stmp3xxx_wdd);
808d2fa171SHarald Geyer 		break;
818d2fa171SHarald Geyer 	}
828d2fa171SHarald Geyer 
838d2fa171SHarald Geyer 	return NOTIFY_DONE;
848d2fa171SHarald Geyer }
858d2fa171SHarald Geyer 
868d2fa171SHarald Geyer static struct notifier_block wdt_notifier = {
878d2fa171SHarald Geyer 	.notifier_call = wdt_notify_sys,
888d2fa171SHarald Geyer };
898d2fa171SHarald Geyer 
stmp3xxx_wdt_probe(struct platform_device * pdev)90de6303abSWolfram Sang static int stmp3xxx_wdt_probe(struct platform_device *pdev)
91de6303abSWolfram Sang {
9255082c03SGuenter Roeck 	struct device *dev = &pdev->dev;
93de6303abSWolfram Sang 	int ret;
94de6303abSWolfram Sang 
9555082c03SGuenter Roeck 	watchdog_set_drvdata(&stmp3xxx_wdd, dev);
96de6303abSWolfram Sang 
97de6303abSWolfram Sang 	stmp3xxx_wdd.timeout = clamp_t(unsigned, heartbeat, 1, STMP3XXX_MAX_TIMEOUT);
9855082c03SGuenter Roeck 	stmp3xxx_wdd.parent = dev;
99de6303abSWolfram Sang 
10055082c03SGuenter Roeck 	ret = devm_watchdog_register_device(dev, &stmp3xxx_wdd);
101913b187dSWolfram Sang 	if (ret < 0)
102de6303abSWolfram Sang 		return ret;
103de6303abSWolfram Sang 
1048d2fa171SHarald Geyer 	if (register_reboot_notifier(&wdt_notifier))
10555082c03SGuenter Roeck 		dev_warn(dev, "cannot register reboot notifier\n");
1068d2fa171SHarald Geyer 
10755082c03SGuenter Roeck 	dev_info(dev, "initialized watchdog with heartbeat %ds\n",
108de6303abSWolfram Sang 		 stmp3xxx_wdd.timeout);
109de6303abSWolfram Sang 	return 0;
110de6303abSWolfram Sang }
111de6303abSWolfram Sang 
stmp3xxx_wdt_remove(struct platform_device * pdev)112*96c6e56dSUwe Kleine-König static void stmp3xxx_wdt_remove(struct platform_device *pdev)
113de6303abSWolfram Sang {
1148d2fa171SHarald Geyer 	unregister_reboot_notifier(&wdt_notifier);
115de6303abSWolfram Sang }
116de6303abSWolfram Sang 
stmp3xxx_wdt_suspend(struct device * dev)1173281b85cSJanusz Uzycki static int __maybe_unused stmp3xxx_wdt_suspend(struct device *dev)
1183281b85cSJanusz Uzycki {
1193281b85cSJanusz Uzycki 	struct watchdog_device *wdd = &stmp3xxx_wdd;
1203281b85cSJanusz Uzycki 
1213281b85cSJanusz Uzycki 	if (watchdog_active(wdd))
1223281b85cSJanusz Uzycki 		return wdt_stop(wdd);
1233281b85cSJanusz Uzycki 
1243281b85cSJanusz Uzycki 	return 0;
1253281b85cSJanusz Uzycki }
1263281b85cSJanusz Uzycki 
stmp3xxx_wdt_resume(struct device * dev)1273281b85cSJanusz Uzycki static int __maybe_unused stmp3xxx_wdt_resume(struct device *dev)
1283281b85cSJanusz Uzycki {
1293281b85cSJanusz Uzycki 	struct watchdog_device *wdd = &stmp3xxx_wdd;
1303281b85cSJanusz Uzycki 
1313281b85cSJanusz Uzycki 	if (watchdog_active(wdd))
1323281b85cSJanusz Uzycki 		return wdt_start(wdd);
1333281b85cSJanusz Uzycki 
1343281b85cSJanusz Uzycki 	return 0;
1353281b85cSJanusz Uzycki }
1363281b85cSJanusz Uzycki 
1373281b85cSJanusz Uzycki static SIMPLE_DEV_PM_OPS(stmp3xxx_wdt_pm_ops,
1383281b85cSJanusz Uzycki 			 stmp3xxx_wdt_suspend, stmp3xxx_wdt_resume);
1393281b85cSJanusz Uzycki 
140de6303abSWolfram Sang static struct platform_driver stmp3xxx_wdt_driver = {
141de6303abSWolfram Sang 	.driver = {
142de6303abSWolfram Sang 		.name = "stmp3xxx_rtc_wdt",
1433281b85cSJanusz Uzycki 		.pm = &stmp3xxx_wdt_pm_ops,
144de6303abSWolfram Sang 	},
145de6303abSWolfram Sang 	.probe = stmp3xxx_wdt_probe,
146*96c6e56dSUwe Kleine-König 	.remove_new = stmp3xxx_wdt_remove,
147de6303abSWolfram Sang };
148de6303abSWolfram Sang module_platform_driver(stmp3xxx_wdt_driver);
149de6303abSWolfram Sang 
150de6303abSWolfram Sang MODULE_DESCRIPTION("STMP3XXX RTC Watchdog Driver");
151de6303abSWolfram Sang MODULE_LICENSE("GPL v2");
152cf82f52dSWolfram Sang MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>");
153