OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [char/] [sc520_wdt.c] - Blame information for rev 1774

Go to most recent revision | Details | Compare with Previous | View Log

Line No. Rev Author Line
1 1275 phoenix
/*
2
 *      AMD Elan SC520 processor Watchdog Timer driver for Linux 2.4.x
3
 *
4
 *      Based on acquirewdt.c by Alan Cox,
5
 *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@ostenfeld.dk>
6
 *
7
 *      This program is free software; you can redistribute it and/or
8
 *      modify it under the terms of the GNU General Public License
9
 *      as published by the Free Software Foundation; either version
10
 *      2 of the License, or (at your option) any later version.
11
 *
12
 *      The authors do NOT admit liability nor provide warranty for
13
 *      any of this software. This material is provided "AS-IS" in
14
 *      the hope that it may be useful for others.
15
 *
16
 *      (c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
17
 *           9/27 - 2001      [Initial release]
18
 *
19
 *      Additional fixes Alan Cox
20
 *      -       Fixed formatting
21
 *      -       Removed debug printks
22
 *      -       Fixed SMP built kernel deadlock
23
 *      -       Switched to private locks not lock_kernel
24
 *      -       Used ioremap/writew/readw
25
 *      -       Added NOWAYOUT support
26
 *
27
 *  Theory of operation:
28
 *  A Watchdog Timer (WDT) is a hardware circuit that can
29
 *  reset the computer system in case of a software fault.
30
 *  You probably knew that already.
31
 *
32
 *  Usually a userspace daemon will notify the kernel WDT driver
33
 *  via the /proc/watchdog special device file that userspace is
34
 *  still alive, at regular intervals.  When such a notification
35
 *  occurs, the driver will usually tell the hardware watchdog
36
 *  that everything is in order, and that the watchdog should wait
37
 *  for yet another little while to reset the system.
38
 *  If userspace fails (RAM error, kernel bug, whatever), the
39
 *  notifications cease to occur, and the hardware watchdog will
40
 *  reset the system (causing a reboot) after the timeout occurs.
41
 *
42
 *  This WDT driver is different from most other Linux WDT
43
 *  drivers in that the driver will ping the watchdog by itself,
44
 *  because this particular WDT has a very short timeout (1.6
45
 *  seconds) and it would be insane to count on any userspace
46
 *  daemon always getting scheduled within that time frame.
47
 *
48
 *  This driver uses memory mapped IO, and spinlock.
49
 */
50
 
51
#include <linux/module.h>
52
#include <linux/version.h>
53
#include <linux/types.h>
54
#include <linux/errno.h>
55
#include <linux/kernel.h>
56
#include <linux/timer.h>
57
#include <linux/sched.h>
58
#include <linux/miscdevice.h>
59
#include <linux/watchdog.h>
60
#include <linux/slab.h>
61
#include <linux/ioport.h>
62
#include <linux/fcntl.h>
63
#include <linux/smp_lock.h>
64
#include <asm/io.h>
65
#include <asm/uaccess.h>
66
#include <asm/system.h>
67
#include <linux/notifier.h>
68
#include <linux/reboot.h>
69
#include <linux/init.h>
70
 
71
/*
72
 * The SC520 can timeout anywhere from 492us to 32.21s.
73
 * If we reset the watchdog every ~250ms we should be safe.
74
 */
75
 
76
#define WDT_INTERVAL (HZ/4+1)
77
 
78
/*
79
 * We must not require too good response from the userspace daemon.
80
 * Here we require the userspace daemon to send us a heartbeat
81
 * char to /dev/watchdog every 30 seconds.
82
 */
83
 
84
#define WDT_HEARTBEAT (HZ * 30)
85
 
86
/*
87
 * AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
88
 *
89
 *   0: 492us    2: 1.01s    4: 4.03s   6: 16.22s
90
 *   1: 503ms    3: 2.01s    5: 8.05s   7: 32.21s
91
 */
92
 
93
#define TIMEOUT_EXPONENT ( 1 << 3 )  /* 0x08 = 2.01s */
94
 
95
/* #define MMCR_BASE_DEFAULT 0xfffef000 */
96
#define MMCR_BASE_DEFAULT ((__u16 *)0xffffe)
97
#define OFFS_WDTMRCTL ((unsigned int)0xcb0)
98
#define WDT_ENB 0x8000          /* [15] Watchdog Timer Enable */
99
#define WDT_WRST_ENB 0x4000     /* [14] Watchdog Timer Reset Enable */
100
 
101
#define OUR_NAME "sc520_wdt"
102
 
103
#define WRT_DOG(data) *wdtmrctl=data
104
 
105
static __u16 *wdtmrctl;
106
 
107
static void wdt_timer_ping(unsigned long);
108
static struct timer_list timer;
109
static unsigned long next_heartbeat;
110
static unsigned long wdt_is_open;
111
static int wdt_expect_close;
112
 
113
#ifdef CONFIG_WATCHDOG_NOWAYOUT
114
static int nowayout = 1;
115
#else
116
static int nowayout = 0;
117
#endif
118
 
119
MODULE_PARM(nowayout,"i");
120
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
121
 
122
static spinlock_t wdt_spinlock;
123
/*
124
 *      Whack the dog
125
 */
126
 
127
static void wdt_timer_ping(unsigned long data)
128
{
129
        /* If we got a heartbeat pulse within the WDT_US_INTERVAL
130
         * we agree to ping the WDT
131
         */
132
        if(time_before(jiffies, next_heartbeat))
133
        {
134
                /* Ping the WDT */
135
                spin_lock(&wdt_spinlock);
136
                writew(0xAAAA, wdtmrctl);
137
                writew(0x5555, wdtmrctl);
138
                spin_unlock(&wdt_spinlock);
139
 
140
                /* Re-set the timer interval */
141
                timer.expires = jiffies + WDT_INTERVAL;
142
                add_timer(&timer);
143
        } else {
144
                printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n");
145
        }
146
}
147
 
148
/*
149
 * Utility routines
150
 */
151
 
152
static void wdt_config(int writeval)
153
{
154
        __u16 dummy;
155
        unsigned long flags;
156
 
157
        /* buy some time (ping) */
158
        spin_lock_irqsave(&wdt_spinlock, flags);
159
        dummy=readw(wdtmrctl);  /* ensure write synchronization */
160
        writew(0xAAAA, wdtmrctl);
161
        writew(0x5555, wdtmrctl);
162
        /* make WDT configuration register writable one time */
163
        writew(0x3333, wdtmrctl);
164
        writew(0xCCCC, wdtmrctl);
165
        /* write WDT configuration register */
166
        writew(writeval, wdtmrctl);
167
        spin_unlock_irqrestore(&wdt_spinlock, flags);
168
}
169
 
170
static void wdt_startup(void)
171
{
172
        next_heartbeat = jiffies + WDT_HEARTBEAT;
173
 
174
        /* Start the timer */
175
        timer.expires = jiffies + WDT_INTERVAL;
176
        add_timer(&timer);
177
 
178
        wdt_config(WDT_ENB | WDT_WRST_ENB | TIMEOUT_EXPONENT);
179
        printk(OUR_NAME ": Watchdog timer is now enabled.\n");
180
}
181
 
182
static void wdt_turnoff(void)
183
{
184
        if (!nowayout) {
185
                /* Stop the timer */
186
                del_timer(&timer);
187
                wdt_config(0);
188
                printk(OUR_NAME ": Watchdog timer is now disabled...\n");
189
        }
190
}
191
 
192
 
193
/*
194
 * /dev/watchdog handling
195
 */
196
 
197
static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
198
{
199
        /* We can't seek */
200
        if(ppos != &file->f_pos)
201
                return -ESPIPE;
202
 
203
        /* See if we got the magic character */
204
        if(count)
205
        {
206
                size_t ofs;
207
 
208
                /* note: just in case someone wrote the magic character
209
                 * five months ago... */
210
                wdt_expect_close = 0;
211
 
212
                /* now scan */
213
                for(ofs = 0; ofs != count; ofs++) {
214
                        char c;
215
                        if (get_user(c, buf + ofs))
216
                                return -EFAULT;
217
                        if(c == 'V')
218
                                wdt_expect_close = 1;
219
                }
220
 
221
                /* Well, anyhow someone wrote to us, we should return that favour */
222
                next_heartbeat = jiffies + WDT_HEARTBEAT;
223
                return 1;
224
        }
225
        return 0;
226
}
227
 
228
static int fop_open(struct inode * inode, struct file * file)
229
{
230
        switch(MINOR(inode->i_rdev))
231
        {
232
                case WATCHDOG_MINOR:
233
                        /* Just in case we're already talking to someone... */
234
                        if(test_and_set_bit(0, &wdt_is_open))
235
                                return -EBUSY;
236
                        /* Good, fire up the show */
237
                        wdt_startup();
238
                        if (nowayout) {
239
                                MOD_INC_USE_COUNT;
240
                        }
241
                        return 0;
242
                default:
243
                        return -ENODEV;
244
        }
245
}
246
 
247
static int fop_close(struct inode * inode, struct file * file)
248
{
249
        if(MINOR(inode->i_rdev) == WATCHDOG_MINOR)
250
        {
251
                if(wdt_expect_close)
252
                        wdt_turnoff();
253
                else {
254
                        del_timer(&timer);
255
                        printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n");
256
                }
257
        }
258
        clear_bit(0, &wdt_is_open);
259
        return 0;
260
}
261
 
262
static long long fop_llseek(struct file *file, long long offset, int origin)
263
{
264
        return -ESPIPE;
265
}
266
 
267
static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
268
        unsigned long arg)
269
{
270
        static struct watchdog_info ident=
271
        {
272
                WDIOF_MAGICCLOSE,
273
                1,
274
                "SC520"
275
        };
276
 
277
        switch(cmd)
278
        {
279
                default:
280
                        return -ENOIOCTLCMD;
281
                case WDIOC_GETSUPPORT:
282
                        return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
283
                case WDIOC_KEEPALIVE:
284
                        next_heartbeat = jiffies + WDT_HEARTBEAT;
285
                        return 0;
286
        }
287
}
288
 
289
static struct file_operations wdt_fops = {
290
        owner:          THIS_MODULE,
291
        llseek:         fop_llseek,
292
        write:          fop_write,
293
        open:           fop_open,
294
        release:        fop_close,
295
        ioctl:          fop_ioctl
296
};
297
 
298
static struct miscdevice wdt_miscdev = {
299
        WATCHDOG_MINOR,
300
        "watchdog",
301
        &wdt_fops
302
};
303
 
304
/*
305
 *      Notifier for system down
306
 */
307
 
308
static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
309
        void *unused)
310
{
311
        if(code==SYS_DOWN || code==SYS_HALT)
312
                wdt_turnoff();
313
        return NOTIFY_DONE;
314
}
315
 
316
/*
317
 *      The WDT needs to learn about soft shutdowns in order to
318
 *      turn the timebomb registers off.
319
 */
320
 
321
static struct notifier_block wdt_notifier=
322
{
323
        wdt_notify_sys,
324
        0,
325
 
326
};
327
 
328
static void __exit sc520_wdt_unload(void)
329
{
330
        wdt_turnoff();
331
 
332
        /* Deregister */
333
        misc_deregister(&wdt_miscdev);
334
        iounmap(wdtmrctl);
335
        unregister_reboot_notifier(&wdt_notifier);
336
}
337
 
338
static int __init sc520_wdt_init(void)
339
{
340
        int rc = -EBUSY;
341
        unsigned long cbar;
342
 
343
        spin_lock_init(&wdt_spinlock);
344
 
345
        init_timer(&timer);
346
        timer.function = wdt_timer_ping;
347
        timer.data = 0;
348
 
349
        rc = misc_register(&wdt_miscdev);
350
        if (rc)
351
                goto err_out_region2;
352
 
353
        rc = register_reboot_notifier(&wdt_notifier);
354
        if (rc)
355
                goto err_out_miscdev;
356
 
357
        /* get the Base Address Register */
358
        cbar = inl_p(0xfffc);
359
        printk(OUR_NAME ": CBAR: 0x%08lx\n", cbar);
360
        /* check if MMCR aliasing bit is set */
361
        if (cbar & 0x80000000) {
362
                printk(OUR_NAME ": MMCR Aliasing enabled.\n");
363
                wdtmrctl = (__u16 *)(cbar & 0x3fffffff);
364
        } else {
365
                printk(OUR_NAME "!!! WARNING !!!\n"
366
                  "\t MMCR Aliasing found NOT enabled!\n"
367
                  "\t Using default value of: %p\n"
368
                  "\t This has not been tested!\n"
369
                  "\t Please email Scott Jennings <smj@oro.net>\n"
370
                  "\t  and Bill Jennings <bj@oro.net> if it works!\n"
371
                  , MMCR_BASE_DEFAULT
372
                  );
373
          wdtmrctl = MMCR_BASE_DEFAULT;
374
        }
375
 
376
        wdtmrctl = (__u16 *)((char *)wdtmrctl + OFFS_WDTMRCTL);
377
        wdtmrctl = ioremap((unsigned long)wdtmrctl, 2);
378
        printk(KERN_INFO OUR_NAME ": WDT driver for SC520 initialised.\n");
379
 
380
        return 0;
381
 
382
err_out_miscdev:
383
        misc_deregister(&wdt_miscdev);
384
err_out_region2:
385
        return rc;
386
}
387
 
388
module_init(sc520_wdt_init);
389
module_exit(sc520_wdt_unload);
390
 
391
MODULE_AUTHOR("Scott and Bill Jennings");
392
MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
393
MODULE_LICENSE("GPL");
394
EXPORT_NO_SYMBOLS;

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.