import of ftp.dlink.com/GPL/DSMG-600_reB/ppclinux.tar.gz
[linux-2.4.21-pre4.git] / drivers / char / amd7xx_tco.c
1 /*
2  *      AMD 766/768 TCO Timer Driver
3  *      (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
4  *      All Rights Reserved.
5  *
6  *      Parts from;
7  *      Hardware driver for the AMD 768 Random Number Generator (RNG)
8  *      (c) Copyright 2001 Red Hat Inc <alan@redhat.com>
9  *
10  *      This program is free software; you can redistribute it and/or
11  *      modify it under the terms of the GNU General Public License version 2
12  *      as published by the Free Software Foundation.
13  *
14  *      The author(s) of this software shall not be held liable for damages
15  *      of any nature resulting due to the use of this software. This
16  *      software is provided AS-IS with no warranties.
17  *
18  */
19
20 #include <linux/config.h>
21 #include <linux/module.h>
22 #include <linux/version.h>
23 #include <linux/kernel.h>
24 #include <linux/miscdevice.h>
25 #include <linux/watchdog.h>
26 #include <linux/ioport.h>
27 #include <linux/spinlock.h>
28 #include <linux/ioport.h>
29 #include <asm/semaphore.h>
30 #include <asm/io.h>
31 #include <asm/uaccess.h>
32 #include <linux/notifier.h>
33 #include <linux/reboot.h>
34 #include <linux/init.h>
35 #include <linux/pci.h>
36
37 #define AMDTCO_MODULE_VER       "build 20020601"
38 #define AMDTCO_MODULE_NAME      "amd7xx_tco"
39 #define PFX                     AMDTCO_MODULE_NAME ": "
40
41 #define MAX_TIMEOUT     38      /* max of 38 seconds */
42
43 /* pmbase registers */
44 #define GLOBAL_SMI_REG  0x2a
45 #define TCO_EN          (1 << 1)        /* bit 1 in global SMI register */
46 #define TCO_RELOAD_REG  0x40            /* bits 0-5 are current count, 6-7 are reserved */
47 #define TCO_INITVAL_REG 0x41            /* bits 0-5 are value to load, 6-7 are reserved */
48 #define TCO_TIMEOUT_MASK        0x3f
49 #define TCO_STATUS2_REG 0x46
50 #define NDTO_STS2       (1 << 1)        /* we're interested in the second timeout */ 
51 #define BOOT_STS        (1 << 2)        /* will be set if NDTO_STS2 was set before reboot */
52 #define TCO_CTRL1_REG   0x48
53 #define TCO_HALT        (1 << 11)
54
55 static char banner[] __initdata = KERN_INFO PFX AMDTCO_MODULE_VER;
56 static int timeout = 38;
57 static u32 pmbase;              /* PMxx I/O base */
58 static struct pci_dev *dev;
59 static struct semaphore open_sem;
60 spinlock_t amdtco_lock; /* only for device access */
61 static int expect_close = 0;
62
63 MODULE_PARM(timeout, "i");
64 MODULE_PARM_DESC(timeout, "range is 0-38 seconds, default is 38");
65
66 static inline int amdtco_status(void)
67 {
68         u16 reg;
69         int status = 0;
70
71         reg = inb(pmbase+TCO_CTRL1_REG);
72         if ((reg & TCO_HALT) == 0)
73                 status |= WDIOF_KEEPALIVEPING;
74
75         reg = inb(pmbase+TCO_STATUS2_REG);
76         if (reg & BOOT_STS)
77                 status |= WDIOF_CARDRESET;
78
79         return status;
80 }
81
82 static inline void amdtco_ping(void)
83 {
84         u8 reg;
85
86         spin_lock(&amdtco_lock);
87         reg = inb(pmbase+TCO_RELOAD_REG);
88         outb(1 | reg, pmbase+TCO_RELOAD_REG);
89         spin_unlock(&amdtco_lock);
90 }
91
92 static inline int amdtco_gettimeout(void)
93 {
94         return inb(TCO_RELOAD_REG) & TCO_TIMEOUT_MASK;
95 }
96
97 static inline void amdtco_settimeout(unsigned int timeout)
98 {
99         u8 reg;
100
101         spin_lock(&amdtco_lock);
102         reg = inb(pmbase+TCO_INITVAL_REG);
103         reg |= timeout & TCO_TIMEOUT_MASK;
104         outb(reg, pmbase+TCO_INITVAL_REG);
105         spin_unlock(&amdtco_lock);
106 }
107
108 static inline void amdtco_global_enable(void)
109 {
110         u16 reg;
111
112         spin_lock(&amdtco_lock);
113         reg = inw(pmbase+GLOBAL_SMI_REG);
114         reg |= TCO_EN;
115         outw(reg, pmbase+GLOBAL_SMI_REG);
116         spin_unlock(&amdtco_lock);
117 }
118
119 static inline void amdtco_enable(void)
120 {
121         u16 reg;
122         
123         spin_lock(&amdtco_lock);
124         reg = inw(pmbase+TCO_CTRL1_REG);
125         reg &= ~TCO_HALT;
126         outw(reg, pmbase+TCO_CTRL1_REG);
127         spin_unlock(&amdtco_lock);
128 }
129
130 static inline void amdtco_disable(void)
131 {
132         u16 reg;
133
134         spin_lock(&amdtco_lock);
135         reg = inw(pmbase+TCO_CTRL1_REG);
136         reg |= TCO_HALT;
137         outw(reg, pmbase+TCO_CTRL1_REG);
138         spin_unlock(&amdtco_lock);
139 }
140
141 static int amdtco_fop_open(struct inode *inode, struct file *file)
142 {
143         if (down_trylock(&open_sem))
144                 return -EBUSY;
145
146 #ifdef CONFIG_WATCHDOG_NOWAYOUT 
147         MOD_INC_USE_COUNT;
148 #endif
149
150         if (timeout > MAX_TIMEOUT)
151                 timeout = MAX_TIMEOUT;
152
153         amdtco_settimeout(timeout);     
154         amdtco_global_enable();
155         amdtco_ping();
156         printk(KERN_INFO PFX "Watchdog enabled, timeout = %d/%d seconds",
157                 amdtco_gettimeout(), timeout);
158         
159         return 0;
160 }
161
162
163 static int amdtco_fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
164 {
165         int new_timeout;
166         int tmp;
167
168         static struct watchdog_info ident = {
169                 options:        WDIOF_SETTIMEOUT | WDIOF_CARDRESET,
170                 identity:       "AMD 766/768"
171         };
172
173         switch (cmd) {
174                 default:
175                         return -ENOTTY; 
176
177                 case WDIOC_GETSUPPORT:
178                         if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof ident))
179                                 return -EFAULT;
180                         return 0;
181
182                 case WDIOC_GETSTATUS:
183                         return put_user(amdtco_status(), (int *)arg);
184         
185                 case WDIOC_KEEPALIVE:
186                         amdtco_ping();
187                         return 0;
188
189                 case WDIOC_SETTIMEOUT:
190                         if (get_user(new_timeout, (int *)arg))
191                                 return -EFAULT;
192                         
193                         if (new_timeout < 0)
194                                 return -EINVAL;
195                 
196                         if (new_timeout > MAX_TIMEOUT)
197                                 new_timeout = MAX_TIMEOUT;
198
199                         timeout = new_timeout;
200                         amdtco_settimeout(timeout);
201                         /* fall through and return the new timeout */
202
203                 case WDIOC_GETTIMEOUT:
204                         return put_user(amdtco_gettimeout(), (int *)arg);
205         
206                 case WDIOC_SETOPTIONS:
207                         if (copy_from_user(&tmp, (int *)arg, sizeof tmp))
208                                 return -EFAULT;
209
210                         if (tmp & WDIOS_DISABLECARD)
211                                 amdtco_disable();
212
213                         if (tmp & WDIOS_ENABLECARD)
214                                 amdtco_enable();
215                         
216                         return 0;
217         }
218 }
219
220
221 static int amdtco_fop_release(struct inode *inode, struct file *file)
222 {
223         if (expect_close) {
224                 amdtco_disable();       
225                 printk(KERN_INFO PFX "Watchdog disabled\n");
226         } else {
227                 amdtco_ping();
228                 printk(KERN_CRIT PFX "Unexpected close!, timeout in %d seconds)\n", timeout);
229         }       
230         
231         up(&open_sem);
232         return 0;
233 }
234
235
236 static ssize_t amdtco_fop_write(struct file *file, const char *data, size_t len, loff_t *ppos)
237 {
238         if (ppos != &file->f_pos)
239                 return -ESPIPE;
240         
241         if (len) {
242 #ifndef CONFIG_WATCHDOG_NOWAYOUT
243                 size_t i;
244                 char c;
245                 expect_close = 0;
246                 
247                 for (i = 0; i != len; i++) {
248                         if (get_user(c, data + i))
249                                 return -EFAULT;
250
251                         if (c == 'V')
252                                 expect_close = 1;
253                 }
254 #endif
255                 amdtco_ping();
256                 return len;
257         }
258
259         return 0;
260 }
261
262
263 static int amdtco_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
264 {
265         if (code == SYS_DOWN || code == SYS_HALT)
266                 amdtco_disable();
267
268         return NOTIFY_DONE;
269 }
270
271
272 static struct notifier_block amdtco_notifier =
273 {
274         notifier_call:  amdtco_notify_sys
275 };
276
277 static struct file_operations amdtco_fops =
278 {
279         owner:          THIS_MODULE,
280         write:          amdtco_fop_write,
281         ioctl:          amdtco_fop_ioctl,
282         open:           amdtco_fop_open,
283         release:        amdtco_fop_release
284 };
285
286 static struct miscdevice amdtco_miscdev =
287 {
288         minor:          WATCHDOG_MINOR,
289         name:           "watchdog",
290         fops:           &amdtco_fops
291 };
292
293 static struct pci_device_id amdtco_pci_tbl[] __initdata = {
294         /* AMD 766 PCI_IDs here */
295         { 0x1022, 0x7443, PCI_ANY_ID, PCI_ANY_ID, },
296         { 0, }
297 };
298
299 MODULE_DEVICE_TABLE (pci, amdtco_pci_tbl);
300
301 static int __init amdtco_init(void)
302 {
303         int ret;
304
305         sema_init(&open_sem, 1);
306         spin_lock_init(&amdtco_lock);
307
308         pci_for_each_dev(dev) {
309                 if (pci_match_device (amdtco_pci_tbl, dev) != NULL)
310                         goto found_one;
311         }
312
313         return -ENODEV;
314
315 found_one:
316         
317         if ((ret = register_reboot_notifier(&amdtco_notifier))) {
318                 printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
319                 goto out_clean;
320         }
321
322         if ((ret = misc_register(&amdtco_miscdev))) {
323                 printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
324                 goto out_unreg_reboot;
325         }
326
327         pci_read_config_dword(dev, 0x58, &pmbase);
328         pmbase &= 0x0000FF00;
329
330         if (pmbase == 0) {
331                 printk (KERN_ERR PFX "power management base not set\n");
332                 ret = -EIO;
333                 goto out_unreg_misc;
334         }
335
336         /* ret = 0; */
337         printk(banner);
338         goto out_clean;
339
340 out_unreg_misc:
341         misc_deregister(&amdtco_miscdev);
342 out_unreg_reboot:
343         unregister_reboot_notifier(&amdtco_notifier);
344 out_clean:
345         return ret;
346 }
347
348 static void __exit amdtco_exit(void)
349 {
350         misc_deregister(&amdtco_miscdev);
351         unregister_reboot_notifier(&amdtco_notifier);
352 }
353
354
355 #ifndef MODULE
356 static int __init amdtco_setup(char *str)
357 {
358         int ints[4];
359
360         str = get_options (str, ARRAY_SIZE(ints), ints);
361         if (ints[0] > 0)
362                 timeout = ints[1];
363
364         return 1;
365 }
366
367 __setup("amd7xx_tco=", amdtco_setup);
368 #endif
369
370 module_init(amdtco_init);
371 module_exit(amdtco_exit);
372
373 MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
374 MODULE_DESCRIPTION("AMD 766/768 TCO Timer Driver");
375 MODULE_LICENSE("GPL");
376 EXPORT_NO_SYMBOLS;
377