diff options
author | Kyle Roeschley <kyle.roeschley@ni.com> | 2016-02-25 12:28:00 -0500 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2016-03-01 10:25:39 -0500 |
commit | 70f3997667fb127333862977ba4fd3e855fbf617 (patch) | |
tree | 78e0f853571f83539f9620d68cd3bb34eb8b68e7 | |
parent | 4d8b229d5ea610affe672e919021e9d02cd877da (diff) |
watchdog: ni903x_wdt: Add NI 903x/913x watchdog driver
Add support for the watchdog timer on NI cRIO-903x and cDAQ-913x real-
time controllers.
Signed-off-by: Jeff Westfahl <jeff.westfahl@ni.com>
Signed-off-by: Kyle Roeschley <kyle.roeschley@ni.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
-rw-r--r-- | Documentation/watchdog/watchdog-parameters.txt | 5 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 11 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/ni903x_wdt.c | 270 |
4 files changed, 287 insertions, 0 deletions
diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 9f9ec9f76039..53dfc73e0171 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt | |||
@@ -200,6 +200,11 @@ mv64x60_wdt: | |||
200 | nowayout: Watchdog cannot be stopped once started | 200 | nowayout: Watchdog cannot be stopped once started |
201 | (default=kernel config parameter) | 201 | (default=kernel config parameter) |
202 | ------------------------------------------------- | 202 | ------------------------------------------------- |
203 | ni903x_wdt: | ||
204 | timeout: Initial watchdog timeout in seconds (0<timeout<516, default=60) | ||
205 | nowayout: Watchdog cannot be stopped once started | ||
206 | (default=kernel config parameter) | ||
207 | ------------------------------------------------- | ||
203 | nuc900_wdt: | 208 | nuc900_wdt: |
204 | heartbeat: Watchdog heartbeats in seconds. | 209 | heartbeat: Watchdog heartbeats in seconds. |
205 | (default = 15) | 210 | (default = 15) |
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 5f29f72f4691..6f9530318e14 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -1217,6 +1217,17 @@ config SBC_EPX_C3_WATCHDOG | |||
1217 | To compile this driver as a module, choose M here: the | 1217 | To compile this driver as a module, choose M here: the |
1218 | module will be called sbc_epx_c3. | 1218 | module will be called sbc_epx_c3. |
1219 | 1219 | ||
1220 | config NI903X_WDT | ||
1221 | tristate "NI 903x/913x Watchdog" | ||
1222 | depends on X86 && ACPI | ||
1223 | select WATCHDOG_CORE | ||
1224 | ---help--- | ||
1225 | This is the driver for the watchdog timer on the National Instruments | ||
1226 | 903x/913x real-time controllers. | ||
1227 | |||
1228 | To compile this driver as a module, choose M here: the module will be | ||
1229 | called ni903x_wdt. | ||
1230 | |||
1220 | # M32R Architecture | 1231 | # M32R Architecture |
1221 | 1232 | ||
1222 | # M68K Architecture | 1233 | # M68K Architecture |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f566753256ab..5a23529091a0 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -126,6 +126,7 @@ obj-$(CONFIG_MACHZ_WDT) += machzwd.o | |||
126 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | 126 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o |
127 | obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o | 127 | obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o |
128 | obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o | 128 | obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o |
129 | obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o | ||
129 | 130 | ||
130 | # M32R Architecture | 131 | # M32R Architecture |
131 | 132 | ||
diff --git a/drivers/watchdog/ni903x_wdt.c b/drivers/watchdog/ni903x_wdt.c new file mode 100644 index 000000000000..dc67742e9018 --- /dev/null +++ b/drivers/watchdog/ni903x_wdt.c | |||
@@ -0,0 +1,270 @@ | |||
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/device.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/watchdog.h> | ||
21 | |||
22 | #define NIWD_CONTROL 0x01 | ||
23 | #define NIWD_COUNTER2 0x02 | ||
24 | #define NIWD_COUNTER1 0x03 | ||
25 | #define NIWD_COUNTER0 0x04 | ||
26 | #define NIWD_SEED2 0x05 | ||
27 | #define NIWD_SEED1 0x06 | ||
28 | #define NIWD_SEED0 0x07 | ||
29 | |||
30 | #define NIWD_IO_SIZE 0x08 | ||
31 | |||
32 | #define NIWD_CONTROL_MODE 0x80 | ||
33 | #define NIWD_CONTROL_PROC_RESET 0x20 | ||
34 | #define NIWD_CONTROL_PET 0x10 | ||
35 | #define NIWD_CONTROL_RUNNING 0x08 | ||
36 | #define NIWD_CONTROL_CAPTURECOUNTER 0x04 | ||
37 | #define NIWD_CONTROL_RESET 0x02 | ||
38 | #define NIWD_CONTROL_ALARM 0x01 | ||
39 | |||
40 | #define NIWD_PERIOD_NS 30720 | ||
41 | #define NIWD_MIN_TIMEOUT 1 | ||
42 | #define NIWD_MAX_TIMEOUT 515 | ||
43 | #define NIWD_DEFAULT_TIMEOUT 60 | ||
44 | |||
45 | #define NIWD_NAME "ni903x_wdt" | ||
46 | |||
47 | struct ni903x_wdt { | ||
48 | struct device *dev; | ||
49 | u16 io_base; | ||
50 | struct watchdog_device wdd; | ||
51 | }; | ||
52 | |||
53 | static unsigned int timeout; | ||
54 | module_param(timeout, uint, 0); | ||
55 | MODULE_PARM_DESC(timeout, | ||
56 | "Watchdog timeout in seconds. (default=" | ||
57 | __MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")"); | ||
58 | |||
59 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
60 | module_param(nowayout, int, S_IRUGO); | ||
61 | MODULE_PARM_DESC(nowayout, | ||
62 | "Watchdog cannot be stopped once started (default=" | ||
63 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
64 | |||
65 | static void ni903x_start(struct ni903x_wdt *wdt) | ||
66 | { | ||
67 | u8 control = inb(wdt->io_base + NIWD_CONTROL); | ||
68 | |||
69 | outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL); | ||
70 | outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL); | ||
71 | } | ||
72 | |||
73 | static int ni903x_wdd_set_timeout(struct watchdog_device *wdd, | ||
74 | unsigned int timeout) | ||
75 | { | ||
76 | struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); | ||
77 | u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS); | ||
78 | |||
79 | outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2); | ||
80 | outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1); | ||
81 | outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0); | ||
82 | |||
83 | wdd->timeout = timeout; | ||
84 | |||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd) | ||
89 | { | ||
90 | struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); | ||
91 | u8 control, counter0, counter1, counter2; | ||
92 | u32 counter; | ||
93 | |||
94 | control = inb(wdt->io_base + NIWD_CONTROL); | ||
95 | control |= NIWD_CONTROL_CAPTURECOUNTER; | ||
96 | outb(control, wdt->io_base + NIWD_CONTROL); | ||
97 | |||
98 | counter2 = inb(wdt->io_base + NIWD_COUNTER2); | ||
99 | counter1 = inb(wdt->io_base + NIWD_COUNTER1); | ||
100 | counter0 = inb(wdt->io_base + NIWD_COUNTER0); | ||
101 | |||
102 | counter = (counter2 << 16) | (counter1 << 8) | counter0; | ||
103 | |||
104 | return counter / (1000000000 / NIWD_PERIOD_NS); | ||
105 | } | ||
106 | |||
107 | static int ni903x_wdd_ping(struct watchdog_device *wdd) | ||
108 | { | ||
109 | struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); | ||
110 | u8 control; | ||
111 | |||
112 | control = inb(wdt->io_base + NIWD_CONTROL); | ||
113 | outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL); | ||
114 | |||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static int ni903x_wdd_start(struct watchdog_device *wdd) | ||
119 | { | ||
120 | struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); | ||
121 | |||
122 | outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET, | ||
123 | wdt->io_base + NIWD_CONTROL); | ||
124 | |||
125 | ni903x_wdd_set_timeout(wdd, wdd->timeout); | ||
126 | ni903x_start(wdt); | ||
127 | |||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int ni903x_wdd_stop(struct watchdog_device *wdd) | ||
132 | { | ||
133 | struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); | ||
134 | |||
135 | outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL); | ||
136 | |||
137 | return 0; | ||
138 | } | ||
139 | |||
140 | static acpi_status ni903x_resources(struct acpi_resource *res, void *data) | ||
141 | { | ||
142 | struct ni903x_wdt *wdt = data; | ||
143 | u16 io_size; | ||
144 | |||
145 | switch (res->type) { | ||
146 | case ACPI_RESOURCE_TYPE_IO: | ||
147 | if (wdt->io_base != 0) { | ||
148 | dev_err(wdt->dev, "too many IO resources\n"); | ||
149 | return AE_ERROR; | ||
150 | } | ||
151 | |||
152 | wdt->io_base = res->data.io.minimum; | ||
153 | io_size = res->data.io.address_length; | ||
154 | |||
155 | if (io_size < NIWD_IO_SIZE) { | ||
156 | dev_err(wdt->dev, "memory region too small\n"); | ||
157 | return AE_ERROR; | ||
158 | } | ||
159 | |||
160 | if (!devm_request_region(wdt->dev, wdt->io_base, io_size, | ||
161 | NIWD_NAME)) { | ||
162 | dev_err(wdt->dev, "failed to get memory region\n"); | ||
163 | return AE_ERROR; | ||
164 | } | ||
165 | |||
166 | return AE_OK; | ||
167 | |||
168 | case ACPI_RESOURCE_TYPE_END_TAG: | ||
169 | default: | ||
170 | /* Ignore unsupported resources, e.g. IRQ */ | ||
171 | return AE_OK; | ||
172 | } | ||
173 | } | ||
174 | |||
175 | static const struct watchdog_info ni903x_wdd_info = { | ||
176 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
177 | .identity = "NI Watchdog", | ||
178 | }; | ||
179 | |||
180 | static const struct watchdog_ops ni903x_wdd_ops = { | ||
181 | .owner = THIS_MODULE, | ||
182 | .start = ni903x_wdd_start, | ||
183 | .stop = ni903x_wdd_stop, | ||
184 | .ping = ni903x_wdd_ping, | ||
185 | .set_timeout = ni903x_wdd_set_timeout, | ||
186 | .get_timeleft = ni903x_wdd_get_timeleft, | ||
187 | }; | ||
188 | |||
189 | static int ni903x_acpi_add(struct acpi_device *device) | ||
190 | { | ||
191 | struct device *dev = &device->dev; | ||
192 | struct watchdog_device *wdd; | ||
193 | struct ni903x_wdt *wdt; | ||
194 | acpi_status status; | ||
195 | int ret; | ||
196 | |||
197 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); | ||
198 | if (!wdt) | ||
199 | return -ENOMEM; | ||
200 | |||
201 | device->driver_data = wdt; | ||
202 | wdt->dev = dev; | ||
203 | |||
204 | status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, | ||
205 | ni903x_resources, wdt); | ||
206 | if (ACPI_FAILURE(status) || wdt->io_base == 0) { | ||
207 | dev_err(dev, "failed to get resources\n"); | ||
208 | return -ENODEV; | ||
209 | } | ||
210 | |||
211 | wdd = &wdt->wdd; | ||
212 | wdd->info = &ni903x_wdd_info; | ||
213 | wdd->ops = &ni903x_wdd_ops; | ||
214 | wdd->min_timeout = NIWD_MIN_TIMEOUT; | ||
215 | wdd->max_timeout = NIWD_MAX_TIMEOUT; | ||
216 | wdd->timeout = NIWD_DEFAULT_TIMEOUT; | ||
217 | wdd->parent = dev; | ||
218 | watchdog_set_drvdata(wdd, wdt); | ||
219 | watchdog_set_nowayout(wdd, nowayout); | ||
220 | ret = watchdog_init_timeout(wdd, timeout, dev); | ||
221 | if (ret) | ||
222 | dev_err(dev, "unable to set timeout value, using default\n"); | ||
223 | |||
224 | ret = watchdog_register_device(wdd); | ||
225 | if (ret) { | ||
226 | dev_err(dev, "failed to register watchdog\n"); | ||
227 | return ret; | ||
228 | } | ||
229 | |||
230 | /* Switch from boot mode to user mode */ | ||
231 | outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE, | ||
232 | wdt->io_base + NIWD_CONTROL); | ||
233 | |||
234 | dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n", | ||
235 | wdt->io_base, timeout, nowayout); | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static int ni903x_acpi_remove(struct acpi_device *device) | ||
241 | { | ||
242 | struct ni903x_wdt *wdt = acpi_driver_data(device); | ||
243 | |||
244 | ni903x_wdd_stop(&wdt->wdd); | ||
245 | watchdog_unregister_device(&wdt->wdd); | ||
246 | |||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | static const struct acpi_device_id ni903x_device_ids[] = { | ||
251 | {"NIC775C", 0}, | ||
252 | {"", 0}, | ||
253 | }; | ||
254 | MODULE_DEVICE_TABLE(acpi, ni903x_device_ids); | ||
255 | |||
256 | static struct acpi_driver ni903x_acpi_driver = { | ||
257 | .name = NIWD_NAME, | ||
258 | .ids = ni903x_device_ids, | ||
259 | .ops = { | ||
260 | .add = ni903x_acpi_add, | ||
261 | .remove = ni903x_acpi_remove, | ||
262 | }, | ||
263 | }; | ||
264 | |||
265 | module_acpi_driver(ni903x_acpi_driver); | ||
266 | |||
267 | MODULE_DESCRIPTION("NI 903x Watchdog"); | ||
268 | MODULE_AUTHOR("Jeff Westfahl <jeff.westfahl@ni.com>"); | ||
269 | MODULE_AUTHOR("Kyle Roeschley <kyle.roeschley@ni.com>"); | ||
270 | MODULE_LICENSE("GPL"); | ||