xref: /linux/drivers/watchdog/rdc321x_wdt.c (revision 0d456bad36d42d16022be045c8a53ddbb59ee478)
1 /*
2  * RDC321x watchdog driver
3  *
4  * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
5  *
6  * This driver is highly inspired from the cpu5_wdt driver
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  */
23 
24 #include <linux/module.h>
25 #include <linux/moduleparam.h>
26 #include <linux/types.h>
27 #include <linux/errno.h>
28 #include <linux/miscdevice.h>
29 #include <linux/fs.h>
30 #include <linux/init.h>
31 #include <linux/ioport.h>
32 #include <linux/timer.h>
33 #include <linux/completion.h>
34 #include <linux/jiffies.h>
35 #include <linux/platform_device.h>
36 #include <linux/watchdog.h>
37 #include <linux/io.h>
38 #include <linux/uaccess.h>
39 #include <linux/mfd/rdc321x.h>
40 
41 #define RDC_WDT_MASK	0x80000000 /* Mask */
42 #define RDC_WDT_EN	0x00800000 /* Enable bit */
43 #define RDC_WDT_WTI	0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
44 #define RDC_WDT_RST	0x00100000 /* Reset bit */
45 #define RDC_WDT_WIF	0x00040000 /* WDT IRQ Flag */
46 #define RDC_WDT_IRT	0x00000100 /* IRQ Routing table */
47 #define RDC_WDT_CNT	0x00000001 /* WDT count */
48 
49 #define RDC_CLS_TMR	0x80003844 /* Clear timer */
50 
51 #define RDC_WDT_INTERVAL	(HZ/10+1)
52 
53 static int ticks = 1000;
54 
55 /* some device data */
56 
57 static struct {
58 	struct completion stop;
59 	int running;
60 	struct timer_list timer;
61 	int queue;
62 	int default_ticks;
63 	unsigned long inuse;
64 	spinlock_t lock;
65 	struct pci_dev *sb_pdev;
66 	int base_reg;
67 } rdc321x_wdt_device;
68 
69 /* generic helper functions */
70 
71 static void rdc321x_wdt_trigger(unsigned long unused)
72 {
73 	unsigned long flags;
74 	u32 val;
75 
76 	if (rdc321x_wdt_device.running)
77 		ticks--;
78 
79 	/* keep watchdog alive */
80 	spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
81 	pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
82 					rdc321x_wdt_device.base_reg, &val);
83 	val |= RDC_WDT_EN;
84 	pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
85 					rdc321x_wdt_device.base_reg, val);
86 	spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
87 
88 	/* requeue?? */
89 	if (rdc321x_wdt_device.queue && ticks)
90 		mod_timer(&rdc321x_wdt_device.timer,
91 				jiffies + RDC_WDT_INTERVAL);
92 	else {
93 		/* ticks doesn't matter anyway */
94 		complete(&rdc321x_wdt_device.stop);
95 	}
96 
97 }
98 
99 static void rdc321x_wdt_reset(void)
100 {
101 	ticks = rdc321x_wdt_device.default_ticks;
102 }
103 
104 static void rdc321x_wdt_start(void)
105 {
106 	unsigned long flags;
107 
108 	if (!rdc321x_wdt_device.queue) {
109 		rdc321x_wdt_device.queue = 1;
110 
111 		/* Clear the timer */
112 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
113 		pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
114 				rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
115 
116 		/* Enable watchdog and set the timeout to 81.92 us */
117 		pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
118 					rdc321x_wdt_device.base_reg,
119 					RDC_WDT_EN | RDC_WDT_CNT);
120 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
121 
122 		mod_timer(&rdc321x_wdt_device.timer,
123 				jiffies + RDC_WDT_INTERVAL);
124 	}
125 
126 	/* if process dies, counter is not decremented */
127 	rdc321x_wdt_device.running++;
128 }
129 
130 static int rdc321x_wdt_stop(void)
131 {
132 	if (rdc321x_wdt_device.running)
133 		rdc321x_wdt_device.running = 0;
134 
135 	ticks = rdc321x_wdt_device.default_ticks;
136 
137 	return -EIO;
138 }
139 
140 /* filesystem operations */
141 static int rdc321x_wdt_open(struct inode *inode, struct file *file)
142 {
143 	if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
144 		return -EBUSY;
145 
146 	return nonseekable_open(inode, file);
147 }
148 
149 static int rdc321x_wdt_release(struct inode *inode, struct file *file)
150 {
151 	clear_bit(0, &rdc321x_wdt_device.inuse);
152 	return 0;
153 }
154 
155 static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
156 				unsigned long arg)
157 {
158 	void __user *argp = (void __user *)arg;
159 	u32 value;
160 	static const struct watchdog_info ident = {
161 		.options = WDIOF_CARDRESET,
162 		.identity = "RDC321x WDT",
163 	};
164 	unsigned long flags;
165 
166 	switch (cmd) {
167 	case WDIOC_KEEPALIVE:
168 		rdc321x_wdt_reset();
169 		break;
170 	case WDIOC_GETSTATUS:
171 		/* Read the value from the DATA register */
172 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
173 		pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
174 					rdc321x_wdt_device.base_reg, &value);
175 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
176 		if (copy_to_user(argp, &value, sizeof(u32)))
177 			return -EFAULT;
178 		break;
179 	case WDIOC_GETSUPPORT:
180 		if (copy_to_user(argp, &ident, sizeof(ident)))
181 			return -EFAULT;
182 		break;
183 	case WDIOC_SETOPTIONS:
184 		if (copy_from_user(&value, argp, sizeof(int)))
185 			return -EFAULT;
186 		switch (value) {
187 		case WDIOS_ENABLECARD:
188 			rdc321x_wdt_start();
189 			break;
190 		case WDIOS_DISABLECARD:
191 			return rdc321x_wdt_stop();
192 		default:
193 			return -EINVAL;
194 		}
195 		break;
196 	default:
197 		return -ENOTTY;
198 	}
199 	return 0;
200 }
201 
202 static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
203 				size_t count, loff_t *ppos)
204 {
205 	if (!count)
206 		return -EIO;
207 
208 	rdc321x_wdt_reset();
209 
210 	return count;
211 }
212 
213 static const struct file_operations rdc321x_wdt_fops = {
214 	.owner		= THIS_MODULE,
215 	.llseek		= no_llseek,
216 	.unlocked_ioctl	= rdc321x_wdt_ioctl,
217 	.open		= rdc321x_wdt_open,
218 	.write		= rdc321x_wdt_write,
219 	.release	= rdc321x_wdt_release,
220 };
221 
222 static struct miscdevice rdc321x_wdt_misc = {
223 	.minor	= WATCHDOG_MINOR,
224 	.name	= "watchdog",
225 	.fops	= &rdc321x_wdt_fops,
226 };
227 
228 static int rdc321x_wdt_probe(struct platform_device *pdev)
229 {
230 	int err;
231 	struct resource *r;
232 	struct rdc321x_wdt_pdata *pdata;
233 
234 	pdata = pdev->dev.platform_data;
235 	if (!pdata) {
236 		dev_err(&pdev->dev, "no platform data supplied\n");
237 		return -ENODEV;
238 	}
239 
240 	r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");
241 	if (!r) {
242 		dev_err(&pdev->dev, "failed to get wdt-reg resource\n");
243 		return -ENODEV;
244 	}
245 
246 	rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
247 	rdc321x_wdt_device.base_reg = r->start;
248 
249 	err = misc_register(&rdc321x_wdt_misc);
250 	if (err < 0) {
251 		dev_err(&pdev->dev, "misc_register failed\n");
252 		return err;
253 	}
254 
255 	spin_lock_init(&rdc321x_wdt_device.lock);
256 
257 	/* Reset the watchdog */
258 	pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
259 				rdc321x_wdt_device.base_reg, RDC_WDT_RST);
260 
261 	init_completion(&rdc321x_wdt_device.stop);
262 	rdc321x_wdt_device.queue = 0;
263 
264 	clear_bit(0, &rdc321x_wdt_device.inuse);
265 
266 	setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
267 
268 	rdc321x_wdt_device.default_ticks = ticks;
269 
270 	dev_info(&pdev->dev, "watchdog init success\n");
271 
272 	return 0;
273 }
274 
275 static int rdc321x_wdt_remove(struct platform_device *pdev)
276 {
277 	if (rdc321x_wdt_device.queue) {
278 		rdc321x_wdt_device.queue = 0;
279 		wait_for_completion(&rdc321x_wdt_device.stop);
280 	}
281 
282 	misc_deregister(&rdc321x_wdt_misc);
283 
284 	return 0;
285 }
286 
287 static struct platform_driver rdc321x_wdt_driver = {
288 	.probe = rdc321x_wdt_probe,
289 	.remove = rdc321x_wdt_remove,
290 	.driver = {
291 		.owner = THIS_MODULE,
292 		.name = "rdc321x-wdt",
293 	},
294 };
295 
296 module_platform_driver(rdc321x_wdt_driver);
297 
298 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
299 MODULE_DESCRIPTION("RDC321x watchdog driver");
300 MODULE_LICENSE("GPL");
301 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
302