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