xref: /linux/drivers/platform/cznic/turris-omnia-mcu-watchdog.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * CZ.NIC's Turris Omnia MCU watchdog driver
4  *
5  * 2024 by Marek Behún <kabel@kernel.org>
6  */
7 
8 #include <linux/bitops.h>
9 #include <linux/device.h>
10 #include <linux/i2c.h>
11 #include <linux/moduleparam.h>
12 #include <linux/types.h>
13 #include <linux/units.h>
14 #include <linux/watchdog.h>
15 
16 #include <linux/turris-omnia-mcu-interface.h>
17 #include "turris-omnia-mcu.h"
18 
19 #define WATCHDOG_TIMEOUT		120
20 
21 static unsigned int timeout;
22 module_param(timeout, int, 0);
23 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
24 
25 static bool nowayout = WATCHDOG_NOWAYOUT;
26 module_param(nowayout, bool, 0);
27 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
28 			   __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
29 
omnia_wdt_start(struct watchdog_device * wdt)30 static int omnia_wdt_start(struct watchdog_device *wdt)
31 {
32 	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
33 
34 	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
35 }
36 
omnia_wdt_stop(struct watchdog_device * wdt)37 static int omnia_wdt_stop(struct watchdog_device *wdt)
38 {
39 	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
40 
41 	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 0);
42 }
43 
omnia_wdt_ping(struct watchdog_device * wdt)44 static int omnia_wdt_ping(struct watchdog_device *wdt)
45 {
46 	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
47 
48 	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
49 }
50 
omnia_wdt_set_timeout(struct watchdog_device * wdt,unsigned int timeout)51 static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
52 				 unsigned int timeout)
53 {
54 	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
55 
56 	return omnia_cmd_write_u16(mcu->client, OMNIA_CMD_SET_WDT_TIMEOUT,
57 				   timeout * DECI);
58 }
59 
omnia_wdt_get_timeleft(struct watchdog_device * wdt)60 static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
61 {
62 	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
63 	u16 timeleft;
64 	int err;
65 
66 	err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_WDT_TIMELEFT,
67 				 &timeleft);
68 	if (err) {
69 		dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
70 			err);
71 		return 0;
72 	}
73 
74 	return timeleft / DECI;
75 }
76 
77 static const struct watchdog_info omnia_wdt_info = {
78 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
79 	.identity = "Turris Omnia MCU Watchdog",
80 };
81 
82 static const struct watchdog_ops omnia_wdt_ops = {
83 	.owner		= THIS_MODULE,
84 	.start		= omnia_wdt_start,
85 	.stop		= omnia_wdt_stop,
86 	.ping		= omnia_wdt_ping,
87 	.set_timeout	= omnia_wdt_set_timeout,
88 	.get_timeleft	= omnia_wdt_get_timeleft,
89 };
90 
omnia_mcu_register_watchdog(struct omnia_mcu * mcu)91 int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
92 {
93 	struct device *dev = &mcu->client->dev;
94 	u8 state;
95 	int err;
96 
97 	if (!(mcu->features & OMNIA_FEAT_WDT_PING))
98 		return 0;
99 
100 	mcu->wdt.info = &omnia_wdt_info;
101 	mcu->wdt.ops = &omnia_wdt_ops;
102 	mcu->wdt.parent = dev;
103 	mcu->wdt.min_timeout = 1;
104 	mcu->wdt.max_timeout = 65535 / DECI;
105 
106 	mcu->wdt.timeout = WATCHDOG_TIMEOUT;
107 	watchdog_init_timeout(&mcu->wdt, timeout, dev);
108 
109 	watchdog_set_drvdata(&mcu->wdt, mcu);
110 
111 	omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);
112 
113 	err = omnia_cmd_read_u8(mcu->client, OMNIA_CMD_GET_WATCHDOG_STATE,
114 				&state);
115 	if (err)
116 		return dev_err_probe(dev, err,
117 				     "Cannot get MCU watchdog state\n");
118 
119 	if (state)
120 		set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);
121 
122 	watchdog_set_nowayout(&mcu->wdt, nowayout);
123 	watchdog_stop_on_reboot(&mcu->wdt);
124 	err = devm_watchdog_register_device(dev, &mcu->wdt);
125 	if (err)
126 		return dev_err_probe(dev, err,
127 				     "Cannot register MCU watchdog\n");
128 
129 	return 0;
130 }
131