Blame view

drivers/watchdog/sbc_fitpc2_wdt.c 5.25 KB
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  /*
   * Watchdog driver for SBC-FITPC2 board
   *
   * Author: Denis Turischev <denis@compulab.co.il>
   *
   * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
   *
   * This file is licensed under  the terms of the GNU General Public
   * License version 2. This program is licensed "as is" without any
   * warranty of any kind, whether express or implied.
   */
  
  #define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
  
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/miscdevice.h>
  #include <linux/watchdog.h>
  #include <linux/ioport.h>
  #include <linux/delay.h>
  #include <linux/fs.h>
  #include <linux/init.h>
  #include <linux/moduleparam.h>
  #include <linux/dmi.h>
  #include <linux/io.h>
  #include <linux/uaccess.h>
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
27

86a1e1896   Wim Van Sebroeck   watchdog: nowayou...
28
  static bool nowayout = WATCHDOG_NOWAYOUT;
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
29
30
  static unsigned int margin = 60;	/* (secs) Default is 1 minute */
  static unsigned long wdt_status;
322af98c5   Denis Turischev   watchdog: sbc_fit...
31
  static DEFINE_MUTEX(wdt_lock);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
32
33
34
35
36
37
38
39
40
  
  #define WDT_IN_USE		0
  #define WDT_OK_TO_CLOSE		1
  
  #define COMMAND_PORT		0x4c
  #define DATA_PORT		0x48
  
  #define IFACE_ON_COMMAND	1
  #define REBOOT_COMMAND		2
5f3b27569   Wim Van Sebroeck   watchdog: cleanup...
41
  #define WATCHDOG_NAME		"SBC-FITPC2 Watchdog"
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
42
43
44
  
  static void wdt_send_data(unsigned char command, unsigned char data)
  {
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
45
  	outb(data, DATA_PORT);
ef39a1bf3   Denis Turischev   [WATCHDOG] sbc_fi...
46
  	msleep(200);
fcf1dd7e6   Denis Turischev   watchdog: sbc_fit...
47
48
  	outb(command, COMMAND_PORT);
  	msleep(100);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
49
50
51
52
  }
  
  static void wdt_enable(void)
  {
322af98c5   Denis Turischev   watchdog: sbc_fit...
53
  	mutex_lock(&wdt_lock);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
54
55
  	wdt_send_data(IFACE_ON_COMMAND, 1);
  	wdt_send_data(REBOOT_COMMAND, margin);
322af98c5   Denis Turischev   watchdog: sbc_fit...
56
  	mutex_unlock(&wdt_lock);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
57
58
59
60
  }
  
  static void wdt_disable(void)
  {
322af98c5   Denis Turischev   watchdog: sbc_fit...
61
  	mutex_lock(&wdt_lock);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
62
63
  	wdt_send_data(IFACE_ON_COMMAND, 0);
  	wdt_send_data(REBOOT_COMMAND, 0);
322af98c5   Denis Turischev   watchdog: sbc_fit...
64
  	mutex_unlock(&wdt_lock);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
  }
  
  static int fitpc2_wdt_open(struct inode *inode, struct file *file)
  {
  	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  		return -EBUSY;
  
  	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  
  	wdt_enable();
  
  	return nonseekable_open(inode, file);
  }
  
  static ssize_t fitpc2_wdt_write(struct file *file, const char *data,
  						size_t len, loff_t *ppos)
  {
  	size_t i;
  
  	if (!len)
  		return 0;
  
  	if (nowayout) {
  		len = 0;
  		goto out;
  	}
  
  	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  
  	for (i = 0; i != len; i++) {
  		char c;
  
  		if (get_user(c, data + i))
  			return -EFAULT;
  
  		if (c == 'V')
  			set_bit(WDT_OK_TO_CLOSE, &wdt_status);
  	}
  
  out:
  	wdt_enable();
  
  	return len;
  }
42747d712   Wim Van Sebroeck   [WATCHDOG] watchd...
109
  static const struct watchdog_info ident = {
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
  	.options	= WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
  				WDIOF_KEEPALIVEPING,
  	.identity	= WATCHDOG_NAME,
  };
  
  
  static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
  							unsigned long arg)
  {
  	int ret = -ENOTTY;
  	int time;
  
  	switch (cmd) {
  	case WDIOC_GETSUPPORT:
  		ret = copy_to_user((struct watchdog_info *)arg, &ident,
  				   sizeof(ident)) ? -EFAULT : 0;
  		break;
  
  	case WDIOC_GETSTATUS:
  		ret = put_user(0, (int *)arg);
  		break;
  
  	case WDIOC_GETBOOTSTATUS:
  		ret = put_user(0, (int *)arg);
  		break;
  
  	case WDIOC_KEEPALIVE:
  		wdt_enable();
  		ret = 0;
  		break;
  
  	case WDIOC_SETTIMEOUT:
  		ret = get_user(time, (int *)arg);
  		if (ret)
  			break;
  
  		if (time < 31 || time > 255) {
  			ret = -EINVAL;
  			break;
  		}
  
  		margin = time;
  		wdt_enable();
  		/* Fall through */
  
  	case WDIOC_GETTIMEOUT:
  		ret = put_user(margin, (int *)arg);
  		break;
  	}
  
  	return ret;
  }
  
  static int fitpc2_wdt_release(struct inode *inode, struct file *file)
  {
  	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
  		wdt_disable();
  		pr_info("Device disabled
  ");
  	} else {
27c766aaa   Joe Perches   watchdog: Use pr_...
170
171
  		pr_warn("Device closed unexpectedly - timer will not stop
  ");
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
  		wdt_enable();
  	}
  
  	clear_bit(WDT_IN_USE, &wdt_status);
  	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  
  	return 0;
  }
  
  
  static const struct file_operations fitpc2_wdt_fops = {
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.write		= fitpc2_wdt_write,
  	.unlocked_ioctl	= fitpc2_wdt_ioctl,
  	.open		= fitpc2_wdt_open,
  	.release	= fitpc2_wdt_release,
  };
  
  static struct miscdevice fitpc2_wdt_miscdev = {
  	.minor		= WATCHDOG_MINOR,
  	.name		= "watchdog",
  	.fops		= &fitpc2_wdt_fops,
  };
  
  static int __init fitpc2_wdt_init(void)
  {
  	int err;
d40657752   Jiri Slaby   watchdog: sbc_fit...
200
  	const char *brd_name;
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
201

d40657752   Jiri Slaby   watchdog: sbc_fit...
202
203
204
  	brd_name = dmi_get_system_info(DMI_BOARD_NAME);
  
  	if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
205
  		return -ENODEV;
ef39a1bf3   Denis Turischev   [WATCHDOG] sbc_fi...
206

d40657752   Jiri Slaby   watchdog: sbc_fit...
207
208
  	pr_info("%s found
  ", brd_name);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
  
  	if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
  		pr_err("I/O address 0x%04x already in use
  ", COMMAND_PORT);
  		return -EIO;
  	}
  
  	if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
  		pr_err("I/O address 0x%04x already in use
  ", DATA_PORT);
  		err = -EIO;
  		goto err_data_port;
  	}
  
  	if (margin < 31 || margin > 255) {
27c766aaa   Joe Perches   watchdog: Use pr_...
224
225
226
  		pr_err("margin must be in range 31 - 255 seconds, you tried to set %d
  ",
  		       margin);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
227
228
229
230
231
  		err = -EINVAL;
  		goto err_margin;
  	}
  
  	err = misc_register(&fitpc2_wdt_miscdev);
1efd374d7   Denis Turischev   [WATCHDOG] SBC-FI...
232
  	if (err) {
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
233
234
  		pr_err("cannot register miscdev on minor=%d (err=%d)
  ",
27c766aaa   Joe Perches   watchdog: Use pr_...
235
  		       WATCHDOG_MINOR, err);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
  		goto err_margin;
  	}
  
  	return 0;
  
  err_margin:
  	release_region(DATA_PORT, 1);
  err_data_port:
  	release_region(COMMAND_PORT, 1);
  
  	return err;
  }
  
  static void __exit fitpc2_wdt_exit(void)
  {
  	misc_deregister(&fitpc2_wdt_miscdev);
  	release_region(DATA_PORT, 1);
  	release_region(COMMAND_PORT, 1);
  }
  
  module_init(fitpc2_wdt_init);
  module_exit(fitpc2_wdt_exit);
  
  MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
  MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
  
  module_param(margin, int, 0);
  MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
86a1e1896   Wim Van Sebroeck   watchdog: nowayou...
264
  module_param(nowayout, bool, 0);
3a5f90002   Denis Turischev   [WATCHDOG] add SB...
265
266
267
  MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
  
  MODULE_LICENSE("GPL");