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