1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2016 National Instruments Corp.
4  */
5 
6 #include <linux/bitops.h>
7 #include <linux/device.h>
8 #include <linux/io.h>
9 #include <linux/mod_devicetable.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/types.h>
13 #include <linux/watchdog.h>
14 
15 #define LOCK			0xA5
16 #define UNLOCK			0x5A
17 
18 #define WDT_CTRL_RESET_EN	BIT(7)
19 #define WDT_RELOAD_PORT_EN	BIT(7)
20 
21 #define WDT_CTRL		1
22 #define WDT_RELOAD_CTRL		2
23 #define WDT_PRESET_PRESCALE	4
24 #define WDT_REG_LOCK		5
25 #define WDT_COUNT		6
26 #define WDT_RELOAD_PORT		7
27 
28 #define WDT_MIN_TIMEOUT		1
29 #define WDT_MAX_TIMEOUT		464
30 #define WDT_DEFAULT_TIMEOUT	80
31 
32 #define WDT_MAX_COUNTER		15
33 
34 static unsigned int timeout;
35 module_param(timeout, uint, 0);
36 MODULE_PARM_DESC(timeout,
37 		 "Watchdog timeout in seconds. (default="
38 		 __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
39 
40 static bool nowayout = WATCHDOG_NOWAYOUT;
41 module_param(nowayout, bool, 0);
42 MODULE_PARM_DESC(nowayout,
43 		 "Watchdog cannot be stopped once started. (default="
44 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
45 
46 struct nic7018_wdt {
47 	u16 io_base;
48 	u32 period;
49 	struct watchdog_device wdd;
50 };
51 
52 struct nic7018_config {
53 	u32 period;
54 	u8 divider;
55 };
56 
57 static const struct nic7018_config nic7018_configs[] = {
58 	{  2, 4 },
59 	{ 32, 5 },
60 };
61 
nic7018_timeout(u32 period,u8 counter)62 static inline u32 nic7018_timeout(u32 period, u8 counter)
63 {
64 	return period * counter - period / 2;
65 }
66 
nic7018_get_config(u32 timeout,u8 * counter)67 static const struct nic7018_config *nic7018_get_config(u32 timeout,
68 						       u8 *counter)
69 {
70 	const struct nic7018_config *config;
71 	u8 count;
72 
73 	if (timeout < 30 && timeout != 16) {
74 		config = &nic7018_configs[0];
75 		count = timeout / 2 + 1;
76 	} else {
77 		config = &nic7018_configs[1];
78 		count = DIV_ROUND_UP(timeout + 16, 32);
79 
80 		if (count > WDT_MAX_COUNTER)
81 			count = WDT_MAX_COUNTER;
82 	}
83 	*counter = count;
84 	return config;
85 }
86 
nic7018_set_timeout(struct watchdog_device * wdd,unsigned int timeout)87 static int nic7018_set_timeout(struct watchdog_device *wdd,
88 			       unsigned int timeout)
89 {
90 	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
91 	const struct nic7018_config *config;
92 	u8 counter;
93 
94 	config = nic7018_get_config(timeout, &counter);
95 
96 	outb(counter << 4 | config->divider,
97 	     wdt->io_base + WDT_PRESET_PRESCALE);
98 
99 	wdd->timeout = nic7018_timeout(config->period, counter);
100 	wdt->period = config->period;
101 
102 	return 0;
103 }
104 
nic7018_start(struct watchdog_device * wdd)105 static int nic7018_start(struct watchdog_device *wdd)
106 {
107 	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
108 	u8 control;
109 
110 	nic7018_set_timeout(wdd, wdd->timeout);
111 
112 	control = inb(wdt->io_base + WDT_RELOAD_CTRL);
113 	outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL);
114 
115 	outb(1, wdt->io_base + WDT_RELOAD_PORT);
116 
117 	control = inb(wdt->io_base + WDT_CTRL);
118 	outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL);
119 
120 	return 0;
121 }
122 
nic7018_stop(struct watchdog_device * wdd)123 static int nic7018_stop(struct watchdog_device *wdd)
124 {
125 	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
126 
127 	outb(0, wdt->io_base + WDT_CTRL);
128 	outb(0, wdt->io_base + WDT_RELOAD_CTRL);
129 	outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE);
130 
131 	return 0;
132 }
133 
nic7018_ping(struct watchdog_device * wdd)134 static int nic7018_ping(struct watchdog_device *wdd)
135 {
136 	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
137 
138 	outb(1, wdt->io_base + WDT_RELOAD_PORT);
139 
140 	return 0;
141 }
142 
nic7018_get_timeleft(struct watchdog_device * wdd)143 static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd)
144 {
145 	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
146 	u8 count;
147 
148 	count = inb(wdt->io_base + WDT_COUNT) & 0xF;
149 	if (!count)
150 		return 0;
151 
152 	return nic7018_timeout(wdt->period, count);
153 }
154 
155 static const struct watchdog_info nic7018_wdd_info = {
156 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
157 	.identity = "NIC7018 Watchdog",
158 };
159 
160 static const struct watchdog_ops nic7018_wdd_ops = {
161 	.owner = THIS_MODULE,
162 	.start = nic7018_start,
163 	.stop = nic7018_stop,
164 	.ping = nic7018_ping,
165 	.set_timeout = nic7018_set_timeout,
166 	.get_timeleft = nic7018_get_timeleft,
167 };
168 
nic7018_probe(struct platform_device * pdev)169 static int nic7018_probe(struct platform_device *pdev)
170 {
171 	struct device *dev = &pdev->dev;
172 	struct watchdog_device *wdd;
173 	struct nic7018_wdt *wdt;
174 	struct resource *io_rc;
175 	int ret;
176 
177 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
178 	if (!wdt)
179 		return -ENOMEM;
180 
181 	platform_set_drvdata(pdev, wdt);
182 
183 	io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
184 	if (!io_rc) {
185 		dev_err(dev, "missing IO resources\n");
186 		return -EINVAL;
187 	}
188 
189 	if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
190 				 KBUILD_MODNAME)) {
191 		dev_err(dev, "failed to get IO region\n");
192 		return -EBUSY;
193 	}
194 
195 	wdt->io_base = io_rc->start;
196 	wdd = &wdt->wdd;
197 	wdd->info = &nic7018_wdd_info;
198 	wdd->ops = &nic7018_wdd_ops;
199 	wdd->min_timeout = WDT_MIN_TIMEOUT;
200 	wdd->max_timeout = WDT_MAX_TIMEOUT;
201 	wdd->timeout = WDT_DEFAULT_TIMEOUT;
202 	wdd->parent = dev;
203 
204 	watchdog_set_drvdata(wdd, wdt);
205 	watchdog_set_nowayout(wdd, nowayout);
206 	watchdog_init_timeout(wdd, timeout, dev);
207 
208 	/* Unlock WDT register */
209 	outb(UNLOCK, wdt->io_base + WDT_REG_LOCK);
210 
211 	ret = watchdog_register_device(wdd);
212 	if (ret) {
213 		outb(LOCK, wdt->io_base + WDT_REG_LOCK);
214 		return ret;
215 	}
216 
217 	dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
218 		wdt->io_base, timeout, nowayout);
219 	return 0;
220 }
221 
nic7018_remove(struct platform_device * pdev)222 static void nic7018_remove(struct platform_device *pdev)
223 {
224 	struct nic7018_wdt *wdt = platform_get_drvdata(pdev);
225 
226 	watchdog_unregister_device(&wdt->wdd);
227 
228 	/* Lock WDT register */
229 	outb(LOCK, wdt->io_base + WDT_REG_LOCK);
230 }
231 
232 static const struct acpi_device_id nic7018_device_ids[] = {
233 	{ "NIC7018" },
234 	{ }
235 };
236 MODULE_DEVICE_TABLE(acpi, nic7018_device_ids);
237 
238 static struct platform_driver watchdog_driver = {
239 	.probe = nic7018_probe,
240 	.remove = nic7018_remove,
241 	.driver = {
242 		.name = KBUILD_MODNAME,
243 		.acpi_match_table = nic7018_device_ids,
244 	},
245 };
246 
247 module_platform_driver(watchdog_driver);
248 
249 MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver");
250 MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
251 MODULE_LICENSE("GPL");
252