diff options
Diffstat (limited to 'arch/x86/kernel/mfgpt_32.c')
-rw-r--r-- | arch/x86/kernel/mfgpt_32.c | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/arch/x86/kernel/mfgpt_32.c b/arch/x86/kernel/mfgpt_32.c new file mode 100644 index 000000000000..3a63a2dd8d27 --- /dev/null +++ b/arch/x86/kernel/mfgpt_32.c | |||
@@ -0,0 +1,197 @@ | |||
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 | |||