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