]> err.no Git - linux-2.6/blob - arch/x86/kernel/mfgpt_32.c
x86: Geode Multi-Function General Purpose Timers support
[linux-2.6] / arch / x86 / kernel / mfgpt_32.c
1 /*
2  * Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT)
3  *
4  * Copyright (C) 2006, Advanced Micro Devices, Inc.
5  * Copyright (C) 2007, Andres Salomon <dilinger@debian.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU General Public License
9  * as published by the Free Software Foundation.
10  *
11  * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
12  */
13
14 /*
15  * We are using the 32Khz input clock - its the only one that has the
16  * ranges we find desirable.  The following table lists the suitable
17  * divisors and the associated hz, minimum interval
18  * and the maximum interval:
19  *
20  *  Divisor   Hz      Min Delta (S) Max Delta (S)
21  *   1        32000     .0005          2.048
22  *   2        16000      .001          4.096
23  *   4         8000      .002          8.192
24  *   8         4000      .004         16.384
25  *   16        2000      .008         32.768
26  *   32        1000      .016         65.536
27  *   64         500      .032        131.072
28  *  128         250      .064        262.144
29  *  256         125      .128        524.288
30  */
31
32 #include <linux/kernel.h>
33 #include <linux/interrupt.h>
34 #include <linux/module.h>
35 #include <asm/geode.h>
36
37 #define F_AVAIL    0x01
38
39 static struct mfgpt_timer_t {
40         int flags;
41         struct module *owner;
42 } mfgpt_timers[MFGPT_MAX_TIMERS];
43
44 /* Selected from the table above */
45
46 #define MFGPT_DIVISOR 16
47 #define MFGPT_SCALE  4     /* divisor = 2^(scale) */
48 #define MFGPT_HZ  (32000 / MFGPT_DIVISOR)
49 #define MFGPT_PERIODIC (MFGPT_HZ / HZ)
50
51 /* Allow for disabling of MFGPTs */
52 static int disable;
53 static int __init mfgpt_disable(char *s)
54 {
55         disable = 1;
56         return 1;
57 }
58 __setup("nomfgpt", mfgpt_disable);
59
60 /*
61  * Check whether any MFGPTs are available for the kernel to use.  In most
62  * cases, firmware that uses AMD's VSA code will claim all timers during
63  * bootup; we certainly don't want to take them if they're already in use.
64  * In other cases (such as with VSAless OpenFirmware), the system firmware
65  * leaves timers available for us to use.
66  */
67 int __init geode_mfgpt_detect(void)
68 {
69         int count = 0, i;
70         u16 val;
71
72         if (disable) {
73                 printk(KERN_INFO "geode-mfgpt:  Skipping MFGPT setup\n");
74                 return 0;
75         }
76
77         for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
78                 val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
79                 if (!(val & MFGPT_SETUP_SETUP)) {
80                         mfgpt_timers[i].flags = F_AVAIL;
81                         count++;
82                 }
83         }
84
85         return count;
86 }
87
88 int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable)
89 {
90         u32 msr, mask, value, dummy;
91         int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
92
93         if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
94                 return -EIO;
95
96         /*
97          * The register maps for these are described in sections 6.17.1.x of
98          * the AMD Geode CS5536 Companion Device Data Book.
99          */
100         switch (event) {
101         case MFGPT_EVENT_RESET:
102                 /*
103                  * XXX: According to the docs, we cannot reset timers above
104                  * 6; that is, resets for 7 and 8 will be ignored.  Is this
105                  * a problem?   -dilinger
106                  */
107                 msr = MFGPT_NR_MSR;
108                 mask = 1 << (timer + 24);
109                 break;
110
111         case MFGPT_EVENT_NMI:
112                 msr = MFGPT_NR_MSR;
113                 mask = 1 << (timer + shift);
114                 break;
115
116         case MFGPT_EVENT_IRQ:
117                 msr = MFGPT_IRQ_MSR;
118                 mask = 1 << (timer + shift);
119                 break;
120
121         default:
122                 return -EIO;
123         }
124
125         rdmsr(msr, value, dummy);
126
127         if (enable)
128                 value |= mask;
129         else
130                 value &= ~mask;
131
132         wrmsr(msr, value, dummy);
133         return 0;
134 }
135
136 int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable)
137 {
138         u32 val, dummy;
139         int offset;
140
141         if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
142                 return -EIO;
143
144         if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
145                 return -EIO;
146
147         rdmsr(MSR_PIC_ZSEL_LOW, val, dummy);
148
149         offset = (timer % 4) * 4;
150
151         val &= ~((0xF << offset) | (0xF << (offset + 16)));
152
153         if (enable) {
154                 val |= (irq & 0x0F) << (offset);
155                 val |= (irq & 0x0F) << (offset + 16);
156         }
157
158         wrmsr(MSR_PIC_ZSEL_LOW, val, dummy);
159         return 0;
160 }
161
162 static int mfgpt_get(int timer, struct module *owner)
163 {
164         mfgpt_timers[timer].flags &= ~F_AVAIL;
165         mfgpt_timers[timer].owner = owner;
166         printk(KERN_INFO "geode-mfgpt:  Registered timer %d\n", timer);
167         return timer;
168 }
169
170 int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner)
171 {
172         int i;
173
174         if (!geode_get_dev_base(GEODE_DEV_MFGPT))
175                 return -ENODEV;
176         if (timer >= MFGPT_MAX_TIMERS)
177                 return -EIO;
178
179         if (timer < 0) {
180                 /* Try to find an available timer */
181                 for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
182                         if (mfgpt_timers[i].flags & F_AVAIL)
183                                 return mfgpt_get(i, owner);
184
185                         if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
186                                 break;
187                 }
188         } else {
189                 /* If they requested a specific timer, try to honor that */
190                 if (mfgpt_timers[timer].flags & F_AVAIL)
191                         return mfgpt_get(timer, owner);
192         }
193
194         /* No timers available - too bad */
195         return -1;
196 }
197