Blame view

drivers/watchdog/it8712f_wdt.c 9.08 KB
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
1
2
3
4
5
6
7
8
9
  /*
   *	IT8712F "Smart Guardian" Watchdog support
   *
   *	Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net>
   *
   *	Based on info and code taken from:
   *
   *	drivers/char/watchdog/scx200_wdt.c
   *	drivers/hwmon/it87.c
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
10
11
   *	IT8712F EC-LPC I/O Preliminary Specification 0.8.2
   *	IT8712F EC-LPC I/O Preliminary Specification 0.9.3
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   *
   *	This program is free software; you can redistribute it and/or
   *	modify it under the terms of the GNU General Public License as
   *	published by the Free Software Foundation; either version 2 of the
   *	License, or (at your option) any later version.
   *
   *	The author(s) of this software shall not be held liable for damages
   *	of any nature resulting due to the use of this software. This
   *	software is provided AS-IS with no warranties.
   */
  
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/init.h>
  #include <linux/miscdevice.h>
  #include <linux/watchdog.h>
  #include <linux/notifier.h>
  #include <linux/reboot.h>
  #include <linux/fs.h>
  #include <linux/pci.h>
  #include <linux/spinlock.h>
d6547378d   Alan Cox   it8712f_wdt: Lock...
33
34
  #include <linux/uaccess.h>
  #include <linux/io.h>
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
35
36
37
38
39
40
41
  
  #define NAME "it8712f_wdt"
  
  MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
  MODULE_DESCRIPTION("IT8712F Watchdog Driver");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
42
  static int max_units = 255;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
43
44
45
46
47
48
49
  static int margin = 60;		/* in seconds */
  module_param(margin, int, 0);
  MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  
  static int nowayout = WATCHDOG_NOWAYOUT;
  module_param(nowayout, int, 0);
  MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
d6547378d   Alan Cox   it8712f_wdt: Lock...
50
  static unsigned long wdt_open;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
51
52
  static unsigned expect_close;
  static spinlock_t io_lock;
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
53
  static unsigned char revision;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  
  /* Dog Food address - We use the game port address */
  static unsigned short address;
  
  #define	REG		0x2e	/* The register to read/write */
  #define	VAL		0x2f	/* The value to read/write */
  
  #define	LDN		0x07	/* Register: Logical device select */
  #define	DEVID		0x20	/* Register: Device ID */
  #define	DEVREV		0x22	/* Register: Device Revision */
  #define ACT_REG		0x30	/* LDN Register: Activation */
  #define BASE_REG	0x60	/* LDN Register: Base address */
  
  #define IT8712F_DEVID	0x8712
  
  #define LDN_GPIO	0x07	/* GPIO and Watch Dog Timer */
  #define LDN_GAME 	0x09	/* Game Port */
  
  #define WDT_CONTROL	0x71	/* WDT Register: Control */
  #define WDT_CONFIG	0x72	/* WDT Register: Configuration */
  #define WDT_TIMEOUT	0x73	/* WDT Register: Timeout Value */
  
  #define WDT_RESET_GAME	0x10
  #define WDT_RESET_KBD	0x20
  #define WDT_RESET_MOUSE	0x40
  #define WDT_RESET_CIR	0x80
  
  #define WDT_UNIT_SEC	0x80	/* If 0 in MINUTES */
  
  #define WDT_OUT_PWROK	0x10
  #define WDT_OUT_KRST	0x40
d6547378d   Alan Cox   it8712f_wdt: Lock...
85
  static int superio_inb(int reg)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
86
87
88
89
  {
  	outb(reg, REG);
  	return inb(VAL);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
90
  static void superio_outb(int val, int reg)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
91
92
93
94
  {
  	outb(reg, REG);
  	outb(val, VAL);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
95
  static int superio_inw(int reg)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
96
97
98
99
100
101
102
103
  {
  	int val;
  	outb(reg++, REG);
  	val = inb(VAL) << 8;
  	outb(reg, REG);
  	val |= inb(VAL);
  	return val;
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
104
  static inline void superio_select(int ldn)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
105
106
107
108
  {
  	outb(LDN, REG);
  	outb(ldn, VAL);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
109
  static inline void superio_enter(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
110
111
112
113
114
115
116
  {
  	spin_lock(&io_lock);
  	outb(0x87, REG);
  	outb(0x01, REG);
  	outb(0x55, REG);
  	outb(0x55, REG);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
117
  static inline void superio_exit(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
118
119
120
121
122
  {
  	outb(0x02, REG);
  	outb(0x02, VAL);
  	spin_unlock(&io_lock);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
123
  static inline void it8712f_wdt_ping(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
124
125
126
  {
  	inb(address);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
127
  static void it8712f_wdt_update_margin(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
128
129
  {
  	int config = WDT_OUT_KRST | WDT_OUT_PWROK;
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  	int units = margin;
  
  	/* Switch to minutes precision if the configured margin
  	 * value does not fit within the register width.
  	 */
  	if (units <= max_units) {
  		config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */
  		printk(KERN_INFO NAME ": timer margin %d seconds
  ", units);
  	} else {
  		units /= 60;
  		printk(KERN_INFO NAME ": timer margin %d minutes
  ", units);
  	}
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
144
  	superio_outb(config, WDT_CONFIG);
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
145
  	if (revision >= 0x08)
0e45adb8f   Oliver Schuster   [WATCHDOG] Fix it...
146
147
  		superio_outb(units >> 8, WDT_TIMEOUT + 1);
  	superio_outb(units, WDT_TIMEOUT);
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
148
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
149
  static int it8712f_wdt_get_status(void)
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
150
151
152
153
154
  {
  	if (superio_inb(WDT_CONTROL) & 0x01)
  		return WDIOF_CARDRESET;
  	else
  		return 0;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
155
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
156
  static void it8712f_wdt_enable(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
  {
  	printk(KERN_DEBUG NAME ": enabling watchdog timer
  ");
  	superio_enter();
  	superio_select(LDN_GPIO);
  
  	superio_outb(WDT_RESET_GAME, WDT_CONTROL);
  
  	it8712f_wdt_update_margin();
  
  	superio_exit();
  
  	it8712f_wdt_ping();
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
171
  static void it8712f_wdt_disable(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
172
173
174
175
176
177
178
179
180
  {
  	printk(KERN_DEBUG NAME ": disabling watchdog timer
  ");
  
  	superio_enter();
  	superio_select(LDN_GPIO);
  
  	superio_outb(0, WDT_CONFIG);
  	superio_outb(0, WDT_CONTROL);
cc1020f15   Andrew Paprocki   [WATCHDOG] it8712...
181
182
  	if (revision >= 0x08)
  		superio_outb(0, WDT_TIMEOUT + 1);
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
183
184
185
186
  	superio_outb(0, WDT_TIMEOUT);
  
  	superio_exit();
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
187
  static int it8712f_wdt_notify(struct notifier_block *this,
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
188
189
190
191
192
193
194
195
196
197
198
199
  		    unsigned long code, void *unused)
  {
  	if (code == SYS_HALT || code == SYS_POWER_OFF)
  		if (!nowayout)
  			it8712f_wdt_disable();
  
  	return NOTIFY_DONE;
  }
  
  static struct notifier_block it8712f_wdt_notifier = {
  	.notifier_call = it8712f_wdt_notify,
  };
d6547378d   Alan Cox   it8712f_wdt: Lock...
200
201
  static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
  					size_t len, loff_t *ppos)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
202
203
204
205
206
207
208
209
210
211
  {
  	/* check for a magic close character */
  	if (len) {
  		size_t i;
  
  		it8712f_wdt_ping();
  
  		expect_close = 0;
  		for (i = 0; i < len; ++i) {
  			char c;
7944d3a5a   Wim Van Sebroeck   [WATCHDOG] more c...
212
  			if (get_user(c, data + i))
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
213
214
215
216
217
218
219
220
  				return -EFAULT;
  			if (c == 'V')
  				expect_close = 42;
  		}
  	}
  
  	return len;
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
221
222
  static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd,
  							unsigned long arg)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
223
224
225
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
42747d712   Wim Van Sebroeck   [WATCHDOG] watchd...
226
  	static const struct watchdog_info ident = {
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
227
228
  		.identity = "IT8712F Watchdog",
  		.firmware_version = 1,
e73a78027   Wim Van Sebroeck   [WATCHDOG] Correc...
229
230
  		.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  						WDIOF_MAGICCLOSE,
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
231
  	};
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
232
  	int value;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
233
234
  
  	switch (cmd) {
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
235
236
237
238
239
  	case WDIOC_GETSUPPORT:
  		if (copy_to_user(argp, &ident, sizeof(ident)))
  			return -EFAULT;
  		return 0;
  	case WDIOC_GETSTATUS:
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
240
241
242
243
244
245
246
247
  		superio_enter();
  		superio_select(LDN_GPIO);
  
  		value = it8712f_wdt_get_status();
  
  		superio_exit();
  
  		return put_user(value, p);
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
248
249
250
251
252
253
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, p);
  	case WDIOC_KEEPALIVE:
  		it8712f_wdt_ping();
  		return 0;
  	case WDIOC_SETTIMEOUT:
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
254
  		if (get_user(value, p))
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
255
  			return -EFAULT;
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
256
257
258
  		if (value < 1)
  			return -EINVAL;
  		if (value > (max_units * 60))
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
259
  			return -EINVAL;
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
260
  		margin = value;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
261
262
263
264
265
266
267
  		superio_enter();
  		superio_select(LDN_GPIO);
  
  		it8712f_wdt_update_margin();
  
  		superio_exit();
  		it8712f_wdt_ping();
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
268
  		/* Fall through */
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
269
270
271
272
  	case WDIOC_GETTIMEOUT:
  		if (put_user(margin, p))
  			return -EFAULT;
  		return 0;
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
273
274
  	default:
  		return -ENOTTY;
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
275
276
  	}
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
277
  static int it8712f_wdt_open(struct inode *inode, struct file *file)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
278
279
  {
  	/* only allow one at a time */
d6547378d   Alan Cox   it8712f_wdt: Lock...
280
  	if (test_and_set_bit(0, &wdt_open))
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
281
282
  		return -EBUSY;
  	it8712f_wdt_enable();
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
283
284
  	return nonseekable_open(inode, file);
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
285
  static int it8712f_wdt_release(struct inode *inode, struct file *file)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
286
287
288
289
290
291
292
293
294
295
  {
  	if (expect_close != 42) {
  		printk(KERN_WARNING NAME
  			": watchdog device closed unexpectedly, will not"
  			" disable the watchdog timer
  ");
  	} else if (!nowayout) {
  		it8712f_wdt_disable();
  	}
  	expect_close = 0;
d6547378d   Alan Cox   it8712f_wdt: Lock...
296
  	clear_bit(0, &wdt_open);
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
297
298
299
  
  	return 0;
  }
b47a166ed   Jan Engelhardt   [WATCHDOG] consti...
300
  static const struct file_operations it8712f_wdt_fops = {
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
301
302
303
  	.owner = THIS_MODULE,
  	.llseek = no_llseek,
  	.write = it8712f_wdt_write,
d6547378d   Alan Cox   it8712f_wdt: Lock...
304
  	.unlocked_ioctl = it8712f_wdt_ioctl,
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
305
306
307
308
309
310
311
312
313
  	.open = it8712f_wdt_open,
  	.release = it8712f_wdt_release,
  };
  
  static struct miscdevice it8712f_wdt_miscdev = {
  	.minor = WATCHDOG_MINOR,
  	.name = "watchdog",
  	.fops = &it8712f_wdt_fops,
  };
d6547378d   Alan Cox   it8712f_wdt: Lock...
314
  static int __init it8712f_wdt_find(unsigned short *address)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
  {
  	int err = -ENODEV;
  	int chip_type;
  
  	superio_enter();
  	chip_type = superio_inw(DEVID);
  	if (chip_type != IT8712F_DEVID)
  		goto exit;
  
  	superio_select(LDN_GAME);
  	superio_outb(1, ACT_REG);
  	if (!(superio_inb(ACT_REG) & 0x01)) {
  		printk(KERN_ERR NAME ": Device not activated, skipping
  ");
  		goto exit;
  	}
  
  	*address = superio_inw(BASE_REG);
  	if (*address == 0) {
  		printk(KERN_ERR NAME ": Base address not set, skipping
  ");
  		goto exit;
  	}
  
  	err = 0;
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
340
341
342
343
344
345
346
347
348
349
  	revision = superio_inb(DEVREV) & 0x0f;
  
  	/* Later revisions have 16-bit values per datasheet 0.9.1 */
  	if (revision >= 0x08)
  		max_units = 65535;
  
  	if (margin > (max_units * 60))
  		margin = (max_units * 60);
  
  	printk(KERN_INFO NAME ": Found IT%04xF chip revision %d - "
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
350
351
  		"using DogFood address 0x%x
  ",
5e6996086   Andrew Paprocki   [WATCHDOG] it8712...
352
  		chip_type, revision, *address);
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
353
354
355
356
357
  
  exit:
  	superio_exit();
  	return err;
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
358
  static int __init it8712f_wdt_init(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
  {
  	int err = 0;
  
  	spin_lock_init(&io_lock);
  
  	if (it8712f_wdt_find(&address))
  		return -ENODEV;
  
  	if (!request_region(address, 1, "IT8712F Watchdog")) {
  		printk(KERN_WARNING NAME ": watchdog I/O region busy
  ");
  		return -EBUSY;
  	}
  
  	it8712f_wdt_disable();
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
  	err = register_reboot_notifier(&it8712f_wdt_notifier);
  	if (err) {
  		printk(KERN_ERR NAME ": unable to register reboot notifier
  ");
  		goto out;
  	}
  
  	err = misc_register(&it8712f_wdt_miscdev);
  	if (err) {
  		printk(KERN_ERR NAME
  			": cannot register miscdev on minor=%d (err=%d)
  ",
  			WATCHDOG_MINOR, err);
  		goto reboot_out;
  	}
  
  	return 0;
  
  
  reboot_out:
  	unregister_reboot_notifier(&it8712f_wdt_notifier);
  out:
  	release_region(address, 1);
  	return err;
  }
d6547378d   Alan Cox   it8712f_wdt: Lock...
399
  static void __exit it8712f_wdt_exit(void)
38ff6fd2f   Jorge Boncompte [DTI2]   [WATCHDOG] IT8212...
400
401
402
403
404
405
406
407
  {
  	misc_deregister(&it8712f_wdt_miscdev);
  	unregister_reboot_notifier(&it8712f_wdt_notifier);
  	release_region(address, 1);
  }
  
  module_init(it8712f_wdt_init);
  module_exit(it8712f_wdt_exit);