1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
42#define RDC_WDT_EN 0x00800000
43#define RDC_WDT_WTI 0x00200000
44#define RDC_WDT_RST 0x00100000
45#define RDC_WDT_WIF 0x00040000
46#define RDC_WDT_IRT 0x00000100
47#define RDC_WDT_CNT 0x00000001
48
49#define RDC_CLS_TMR 0x80003844
50
51#define RDC_WDT_INTERVAL (HZ/10+1)
52
53static int ticks = 1000;
54
55
56
57static 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
70
71static 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
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
89 if (rdc321x_wdt_device.queue && ticks)
90 mod_timer(&rdc321x_wdt_device.timer,
91 jiffies + RDC_WDT_INTERVAL);
92 else {
93
94 complete(&rdc321x_wdt_device.stop);
95 }
96
97}
98
99static void rdc321x_wdt_reset(void)
100{
101 ticks = rdc321x_wdt_device.default_ticks;
102}
103
104static 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
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
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
127 rdc321x_wdt_device.running++;
128}
129
130static 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
141static 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
149static int rdc321x_wdt_release(struct inode *inode, struct file *file)
150{
151 clear_bit(0, &rdc321x_wdt_device.inuse);
152 return 0;
153}
154
155static 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
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
202static 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
213static 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
222static struct miscdevice rdc321x_wdt_misc = {
223 .minor = WATCHDOG_MINOR,
224 .name = "watchdog",
225 .fops = &rdc321x_wdt_fops,
226};
227
228static int __devinit rdc321x_wdt_probe(struct platform_device *pdev)
229{
230 int err;
231 struct resource *r;
232 struct rdc321x_wdt_pdata *pdata;
233
234 pdata = platform_get_drvdata(pdev);
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
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
275static int __devexit 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
287static struct platform_driver rdc321x_wdt_driver = {
288 .probe = rdc321x_wdt_probe,
289 .remove = __devexit_p(rdc321x_wdt_remove),
290 .driver = {
291 .owner = THIS_MODULE,
292 .name = "rdc321x-wdt",
293 },
294};
295
296static int __init rdc321x_wdt_init(void)
297{
298 return platform_driver_register(&rdc321x_wdt_driver);
299}
300
301static void __exit rdc321x_wdt_exit(void)
302{
303 platform_driver_unregister(&rdc321x_wdt_driver);
304}
305
306module_init(rdc321x_wdt_init);
307module_exit(rdc321x_wdt_exit);
308
309MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
310MODULE_DESCRIPTION("RDC321x watchdog driver");
311MODULE_LICENSE("GPL");
312MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
313