1 /*
2  * drivers/char/watchdog/ixp2000_wdt.c
3  *
4  * Watchdog driver for Intel IXP2000 network processors
5  *
6  * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek.
7  * The original version carries these notices:
8  *
9  * Author: Deepak Saxena <dsaxena@plexity.net>
10  *
11  * Copyright 2004 (c) MontaVista, Software, Inc.
12  * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
13  *
14  * This file is licensed under  the terms of the GNU General Public
15  * License version 2. This program is licensed "as is" without any
16  * warranty of any kind, whether express or implied.
17  */
18 
19 #include <linux/module.h>
20 #include <linux/moduleparam.h>
21 #include <linux/types.h>
22 #include <linux/timer.h>
23 #include <linux/kernel.h>
24 #include <linux/fs.h>
25 #include <linux/miscdevice.h>
26 #include <linux/watchdog.h>
27 #include <linux/init.h>
28 #include <linux/bitops.h>
29 #include <linux/uaccess.h>
30 #include <mach/hardware.h>
31 
32 static int nowayout = WATCHDOG_NOWAYOUT;
33 static unsigned int heartbeat = 60;	/* (secs) Default is 1 minute */
34 static unsigned long wdt_status;
35 static DEFINE_SPINLOCK(wdt_lock);
36 
37 #define	WDT_IN_USE		0
38 #define	WDT_OK_TO_CLOSE		1
39 
40 static unsigned long wdt_tick_rate;
41 
wdt_enable(void)42 static void wdt_enable(void)
43 {
44 	spin_lock(&wdt_lock);
45 	ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE);
46 	ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE);
47 	ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
48 	ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE);
49 	spin_unlock(&wdt_lock);
50 }
51 
wdt_disable(void)52 static void wdt_disable(void)
53 {
54 	spin_lock(&wdt_lock);
55 	ixp2000_reg_write(IXP2000_T4_CTL, 0);
56 	spin_unlock(&wdt_lock);
57 }
58 
wdt_keepalive(void)59 static void wdt_keepalive(void)
60 {
61 	spin_lock(&wdt_lock);
62 	ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
63 	spin_unlock(&wdt_lock);
64 }
65 
ixp2000_wdt_open(struct inode * inode,struct file * file)66 static int ixp2000_wdt_open(struct inode *inode, struct file *file)
67 {
68 	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
69 		return -EBUSY;
70 
71 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
72 
73 	wdt_enable();
74 
75 	return nonseekable_open(inode, file);
76 }
77 
ixp2000_wdt_write(struct file * file,const char * data,size_t len,loff_t * ppos)78 static ssize_t ixp2000_wdt_write(struct file *file, const char *data,
79 						size_t len, loff_t *ppos)
80 {
81 	if (len) {
82 		if (!nowayout) {
83 			size_t i;
84 
85 			clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
86 
87 			for (i = 0; i != len; i++) {
88 				char c;
89 
90 				if (get_user(c, data + i))
91 					return -EFAULT;
92 				if (c == 'V')
93 					set_bit(WDT_OK_TO_CLOSE, &wdt_status);
94 			}
95 		}
96 		wdt_keepalive();
97 	}
98 
99 	return len;
100 }
101 
102 
103 static const struct watchdog_info ident = {
104 	.options	= WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
105 				WDIOF_KEEPALIVEPING,
106 	.identity	= "IXP2000 Watchdog",
107 };
108 
ixp2000_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)109 static long ixp2000_wdt_ioctl(struct file *file, unsigned int cmd,
110 							unsigned long arg)
111 {
112 	int ret = -ENOTTY;
113 	int time;
114 
115 	switch (cmd) {
116 	case WDIOC_GETSUPPORT:
117 		ret = copy_to_user((struct watchdog_info *)arg, &ident,
118 				   sizeof(ident)) ? -EFAULT : 0;
119 		break;
120 
121 	case WDIOC_GETSTATUS:
122 		ret = put_user(0, (int *)arg);
123 		break;
124 
125 	case WDIOC_GETBOOTSTATUS:
126 		ret = put_user(0, (int *)arg);
127 		break;
128 
129 	case WDIOC_KEEPALIVE:
130 		wdt_enable();
131 		ret = 0;
132 		break;
133 
134 	case WDIOC_SETTIMEOUT:
135 		ret = get_user(time, (int *)arg);
136 		if (ret)
137 			break;
138 
139 		if (time <= 0 || time > 60) {
140 			ret = -EINVAL;
141 			break;
142 		}
143 
144 		heartbeat = time;
145 		wdt_keepalive();
146 		/* Fall through */
147 
148 	case WDIOC_GETTIMEOUT:
149 		ret = put_user(heartbeat, (int *)arg);
150 		break;
151 	}
152 
153 	return ret;
154 }
155 
ixp2000_wdt_release(struct inode * inode,struct file * file)156 static int ixp2000_wdt_release(struct inode *inode, struct file *file)
157 {
158 	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
159 		wdt_disable();
160 	else
161 		printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
162 					"timer will not stop\n");
163 	clear_bit(WDT_IN_USE, &wdt_status);
164 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
165 
166 	return 0;
167 }
168 
169 
170 static const struct file_operations ixp2000_wdt_fops = {
171 	.owner		= THIS_MODULE,
172 	.llseek		= no_llseek,
173 	.write		= ixp2000_wdt_write,
174 	.unlocked_ioctl	= ixp2000_wdt_ioctl,
175 	.open		= ixp2000_wdt_open,
176 	.release	= ixp2000_wdt_release,
177 };
178 
179 static struct miscdevice ixp2000_wdt_miscdev = {
180 	.minor		= WATCHDOG_MINOR,
181 	.name		= "watchdog",
182 	.fops		= &ixp2000_wdt_fops,
183 };
184 
ixp2000_wdt_init(void)185 static int __init ixp2000_wdt_init(void)
186 {
187 	if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) {
188 		printk(KERN_INFO "Unable to use IXP2000 watchdog due to IXP2800 erratum #25.\n");
189 		return -EIO;
190 	}
191 	wdt_tick_rate = (*IXP2000_T1_CLD * HZ) / 256;
192 	return misc_register(&ixp2000_wdt_miscdev);
193 }
194 
ixp2000_wdt_exit(void)195 static void __exit ixp2000_wdt_exit(void)
196 {
197 	misc_deregister(&ixp2000_wdt_miscdev);
198 }
199 
200 module_init(ixp2000_wdt_init);
201 module_exit(ixp2000_wdt_exit);
202 
203 MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
204 MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog");
205 
206 module_param(heartbeat, int, 0);
207 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
208 
209 module_param(nowayout, int, 0);
210 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
211 
212 MODULE_LICENSE("GPL");
213 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
214 
215