aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/mfgpt_32.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/kernel/mfgpt_32.c')
-rw-r--r--arch/x86/kernel/mfgpt_32.c197
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
39static 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 */
52static int disable;
53static 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 */
67int __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
88int 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
136int 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
162static 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
170int 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