]>
Commit | Line | Data |
---|---|---|
75d67a54 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
101ce87b AS |
2 | /* |
3 | * Intel Atom E6xx Watchdog driver | |
4 | * | |
5 | * Copyright (C) 2011 Alexander Stein | |
6 | * <alexander.stein@systec-electronic.com> | |
101ce87b AS |
7 | */ |
8 | ||
9 | #include <linux/module.h> | |
10 | #include <linux/moduleparam.h> | |
11 | #include <linux/platform_device.h> | |
491d9e2a | 12 | #include <linux/io.h> |
101ce87b AS |
13 | #include <linux/kernel.h> |
14 | #include <linux/types.h> | |
15 | #include <linux/watchdog.h> | |
101ce87b AS |
16 | #include <linux/seq_file.h> |
17 | #include <linux/debugfs.h> | |
18 | #include <linux/uaccess.h> | |
19 | #include <linux/spinlock.h> | |
20 | ||
21 | #define DRIVER_NAME "ie6xx_wdt" | |
22 | ||
23 | #define PV1 0x00 | |
24 | #define PV2 0x04 | |
25 | ||
26 | #define RR0 0x0c | |
27 | #define RR1 0x0d | |
28 | #define WDT_RELOAD 0x01 | |
29 | #define WDT_TOUT 0x02 | |
30 | ||
31 | #define WDTCR 0x10 | |
32 | #define WDT_PRE_SEL 0x04 | |
33 | #define WDT_RESET_SEL 0x08 | |
34 | #define WDT_RESET_EN 0x10 | |
35 | #define WDT_TOUT_EN 0x20 | |
36 | ||
37 | #define DCR 0x14 | |
38 | ||
39 | #define WDTLR 0x18 | |
40 | #define WDT_LOCK 0x01 | |
41 | #define WDT_ENABLE 0x02 | |
42 | #define WDT_TOUT_CNF 0x03 | |
43 | ||
44 | #define MIN_TIME 1 | |
45 | #define MAX_TIME (10 * 60) /* 10 minutes */ | |
46 | #define DEFAULT_TIME 60 | |
47 | ||
48 | static unsigned int timeout = DEFAULT_TIME; | |
49 | module_param(timeout, uint, 0); | |
50 | MODULE_PARM_DESC(timeout, | |
51 | "Default Watchdog timer setting (" | |
52 | __MODULE_STRING(DEFAULT_TIME) "s)." | |
53 | "The range is from 1 to 600"); | |
54 | ||
55 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
56 | module_param(nowayout, bool, 0); | |
57 | MODULE_PARM_DESC(nowayout, | |
58 | "Watchdog cannot be stopped once started (default=" | |
59 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
60 | ||
61 | static u8 resetmode = 0x10; | |
62 | module_param(resetmode, byte, 0); | |
63 | MODULE_PARM_DESC(resetmode, | |
64 | "Resetmode bits: 0x08 warm reset (cold reset otherwise), " | |
65 | "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)"); | |
66 | ||
67 | static struct { | |
68 | unsigned short sch_wdtba; | |
69 | struct spinlock unlock_sequence; | |
70 | #ifdef CONFIG_DEBUG_FS | |
71 | struct dentry *debugfs; | |
72 | #endif | |
73 | } ie6xx_wdt_data; | |
74 | ||
75 | /* | |
76 | * This is needed to write to preload and reload registers | |
77 | * struct ie6xx_wdt_data.unlock_sequence must be used | |
78 | * to prevent sequence interrupts | |
79 | */ | |
80 | static void ie6xx_wdt_unlock_registers(void) | |
81 | { | |
82 | outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0); | |
83 | outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0); | |
84 | } | |
85 | ||
86 | static int ie6xx_wdt_ping(struct watchdog_device *wdd) | |
87 | { | |
88 | spin_lock(&ie6xx_wdt_data.unlock_sequence); | |
89 | ie6xx_wdt_unlock_registers(); | |
90 | outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1); | |
91 | spin_unlock(&ie6xx_wdt_data.unlock_sequence); | |
92 | return 0; | |
93 | } | |
94 | ||
95 | static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) | |
96 | { | |
97 | u32 preload; | |
98 | u64 clock; | |
99 | u8 wdtcr; | |
100 | ||
101 | /* Watchdog clock is PCI Clock (33MHz) */ | |
102 | clock = 33000000; | |
103 | /* and the preload value is loaded into [34:15] of the down counter */ | |
104 | preload = (t * clock) >> 15; | |
105 | /* | |
106 | * Manual states preload must be one less. | |
107 | * Does not wrap as t is at least 1 | |
108 | */ | |
109 | preload -= 1; | |
110 | ||
111 | spin_lock(&ie6xx_wdt_data.unlock_sequence); | |
112 | ||
113 | /* Set ResetMode & Enable prescaler for range 10ms to 10 min */ | |
114 | wdtcr = resetmode & 0x38; | |
115 | outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR); | |
116 | ||
117 | ie6xx_wdt_unlock_registers(); | |
118 | outl(0, ie6xx_wdt_data.sch_wdtba + PV1); | |
119 | ||
120 | ie6xx_wdt_unlock_registers(); | |
121 | outl(preload, ie6xx_wdt_data.sch_wdtba + PV2); | |
122 | ||
123 | ie6xx_wdt_unlock_registers(); | |
124 | outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1); | |
125 | ||
126 | spin_unlock(&ie6xx_wdt_data.unlock_sequence); | |
127 | ||
128 | wdd->timeout = t; | |
129 | return 0; | |
130 | } | |
131 | ||
132 | static int ie6xx_wdt_start(struct watchdog_device *wdd) | |
133 | { | |
134 | ie6xx_wdt_set_timeout(wdd, wdd->timeout); | |
135 | ||
136 | /* Enable the watchdog timer */ | |
137 | spin_lock(&ie6xx_wdt_data.unlock_sequence); | |
138 | outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR); | |
139 | spin_unlock(&ie6xx_wdt_data.unlock_sequence); | |
140 | ||
141 | return 0; | |
142 | } | |
143 | ||
144 | static int ie6xx_wdt_stop(struct watchdog_device *wdd) | |
145 | { | |
146 | if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK) | |
147 | return -1; | |
148 | ||
149 | /* Disable the watchdog timer */ | |
150 | spin_lock(&ie6xx_wdt_data.unlock_sequence); | |
151 | outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR); | |
152 | spin_unlock(&ie6xx_wdt_data.unlock_sequence); | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
157 | static const struct watchdog_info ie6xx_wdt_info = { | |
158 | .identity = "Intel Atom E6xx Watchdog", | |
159 | .options = WDIOF_SETTIMEOUT | | |
160 | WDIOF_MAGICCLOSE | | |
161 | WDIOF_KEEPALIVEPING, | |
162 | }; | |
163 | ||
164 | static const struct watchdog_ops ie6xx_wdt_ops = { | |
165 | .owner = THIS_MODULE, | |
166 | .start = ie6xx_wdt_start, | |
167 | .stop = ie6xx_wdt_stop, | |
168 | .ping = ie6xx_wdt_ping, | |
169 | .set_timeout = ie6xx_wdt_set_timeout, | |
170 | }; | |
171 | ||
172 | static struct watchdog_device ie6xx_wdt_dev = { | |
173 | .info = &ie6xx_wdt_info, | |
174 | .ops = &ie6xx_wdt_ops, | |
175 | .min_timeout = MIN_TIME, | |
176 | .max_timeout = MAX_TIME, | |
177 | }; | |
178 | ||
179 | #ifdef CONFIG_DEBUG_FS | |
180 | ||
248e655b | 181 | static int ie6xx_wdt_show(struct seq_file *s, void *unused) |
101ce87b AS |
182 | { |
183 | seq_printf(s, "PV1 = 0x%08x\n", | |
184 | inl(ie6xx_wdt_data.sch_wdtba + PV1)); | |
185 | seq_printf(s, "PV2 = 0x%08x\n", | |
186 | inl(ie6xx_wdt_data.sch_wdtba + PV2)); | |
187 | seq_printf(s, "RR = 0x%08x\n", | |
188 | inw(ie6xx_wdt_data.sch_wdtba + RR0)); | |
189 | seq_printf(s, "WDTCR = 0x%08x\n", | |
190 | inw(ie6xx_wdt_data.sch_wdtba + WDTCR)); | |
191 | seq_printf(s, "DCR = 0x%08x\n", | |
192 | inl(ie6xx_wdt_data.sch_wdtba + DCR)); | |
193 | seq_printf(s, "WDTLR = 0x%08x\n", | |
194 | inw(ie6xx_wdt_data.sch_wdtba + WDTLR)); | |
195 | ||
196 | seq_printf(s, "\n"); | |
197 | return 0; | |
198 | } | |
199 | ||
248e655b | 200 | DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt); |
101ce87b | 201 | |
2d991a16 | 202 | static void ie6xx_wdt_debugfs_init(void) |
101ce87b AS |
203 | { |
204 | /* /sys/kernel/debug/ie6xx_wdt */ | |
205 | ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt", | |
248e655b | 206 | S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops); |
101ce87b AS |
207 | } |
208 | ||
0402450f | 209 | static void ie6xx_wdt_debugfs_exit(void) |
101ce87b AS |
210 | { |
211 | debugfs_remove(ie6xx_wdt_data.debugfs); | |
212 | } | |
213 | ||
214 | #else | |
2d991a16 | 215 | static void ie6xx_wdt_debugfs_init(void) |
101ce87b AS |
216 | { |
217 | } | |
218 | ||
0402450f | 219 | static void ie6xx_wdt_debugfs_exit(void) |
101ce87b AS |
220 | { |
221 | } | |
222 | #endif | |
223 | ||
2d991a16 | 224 | static int ie6xx_wdt_probe(struct platform_device *pdev) |
101ce87b AS |
225 | { |
226 | struct resource *res; | |
227 | u8 wdtlr; | |
228 | int ret; | |
229 | ||
230 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
231 | if (!res) | |
232 | return -ENODEV; | |
233 | ||
234 | if (!request_region(res->start, resource_size(res), pdev->name)) { | |
744f6f4d RD |
235 | dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n", |
236 | (u64)res->start); | |
101ce87b AS |
237 | return -EBUSY; |
238 | } | |
239 | ||
240 | ie6xx_wdt_data.sch_wdtba = res->start; | |
241 | dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba); | |
242 | ||
243 | ie6xx_wdt_dev.timeout = timeout; | |
244 | watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout); | |
6551881c | 245 | ie6xx_wdt_dev.parent = &pdev->dev; |
101ce87b AS |
246 | |
247 | spin_lock_init(&ie6xx_wdt_data.unlock_sequence); | |
248 | ||
249 | wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR); | |
250 | if (wdtlr & WDT_LOCK) | |
251 | dev_warn(&pdev->dev, | |
252 | "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr); | |
253 | ||
254 | ie6xx_wdt_debugfs_init(); | |
255 | ||
256 | ret = watchdog_register_device(&ie6xx_wdt_dev); | |
257 | if (ret) { | |
258 | dev_err(&pdev->dev, | |
259 | "Watchdog timer: cannot register device (err =%d)\n", | |
260 | ret); | |
261 | goto misc_register_error; | |
262 | } | |
263 | ||
264 | return 0; | |
265 | ||
266 | misc_register_error: | |
267 | ie6xx_wdt_debugfs_exit(); | |
268 | release_region(res->start, resource_size(res)); | |
269 | ie6xx_wdt_data.sch_wdtba = 0; | |
270 | return ret; | |
271 | } | |
272 | ||
4b12b896 | 273 | static int ie6xx_wdt_remove(struct platform_device *pdev) |
101ce87b AS |
274 | { |
275 | struct resource *res; | |
276 | ||
277 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
278 | ie6xx_wdt_stop(NULL); | |
279 | watchdog_unregister_device(&ie6xx_wdt_dev); | |
280 | ie6xx_wdt_debugfs_exit(); | |
281 | release_region(res->start, resource_size(res)); | |
282 | ie6xx_wdt_data.sch_wdtba = 0; | |
283 | ||
284 | return 0; | |
285 | } | |
286 | ||
287 | static struct platform_driver ie6xx_wdt_driver = { | |
288 | .probe = ie6xx_wdt_probe, | |
82268714 | 289 | .remove = ie6xx_wdt_remove, |
101ce87b AS |
290 | .driver = { |
291 | .name = DRIVER_NAME, | |
101ce87b AS |
292 | }, |
293 | }; | |
294 | ||
295 | static int __init ie6xx_wdt_init(void) | |
296 | { | |
297 | /* Check boot parameters to verify that their initial values */ | |
298 | /* are in range. */ | |
299 | if ((timeout < MIN_TIME) || | |
300 | (timeout > MAX_TIME)) { | |
301 | pr_err("Watchdog timer: value of timeout %d (dec) " | |
302 | "is out of range from %d to %d (dec)\n", | |
303 | timeout, MIN_TIME, MAX_TIME); | |
304 | return -EINVAL; | |
305 | } | |
306 | ||
307 | return platform_driver_register(&ie6xx_wdt_driver); | |
308 | } | |
309 | ||
310 | static void __exit ie6xx_wdt_exit(void) | |
311 | { | |
312 | platform_driver_unregister(&ie6xx_wdt_driver); | |
313 | } | |
314 | ||
315 | late_initcall(ie6xx_wdt_init); | |
316 | module_exit(ie6xx_wdt_exit); | |
317 | ||
318 | MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>"); | |
319 | MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver"); | |
320 | MODULE_LICENSE("GPL"); | |
101ce87b | 321 | MODULE_ALIAS("platform:" DRIVER_NAME); |