diff options
-rw-r--r-- | Documentation/kernel-parameters.txt | 3 | ||||
-rw-r--r-- | arch/x86/kernel/Makefile_32 | 2 | ||||
-rw-r--r-- | arch/x86/kernel/geode_32.c | 4 | ||||
-rw-r--r-- | arch/x86/kernel/mfgpt_32.c | 197 | ||||
-rw-r--r-- | include/asm-x86/geode.h | 50 |
5 files changed, 255 insertions, 1 deletions
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index a57c1f216b21..2128de6d80ea 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
@@ -1160,6 +1160,9 @@ and is between 256 and 4096 characters. It is defined in the file | |||
1160 | 1160 | ||
1161 | nomce [X86-32] Machine Check Exception | 1161 | nomce [X86-32] Machine Check Exception |
1162 | 1162 | ||
1163 | nomfgpt [X86-32] Disable Multi-Function General Purpose | ||
1164 | Timer usage (for AMD Geode machines). | ||
1165 | |||
1163 | noreplace-paravirt [X86-32,PV_OPS] Don't patch paravirt_ops | 1166 | noreplace-paravirt [X86-32,PV_OPS] Don't patch paravirt_ops |
1164 | 1167 | ||
1165 | noreplace-smp [X86-32,SMP] Don't replace SMP instructions | 1168 | noreplace-smp [X86-32,SMP] Don't replace SMP instructions |
diff --git a/arch/x86/kernel/Makefile_32 b/arch/x86/kernel/Makefile_32 index c624193740fd..10356443998e 100644 --- a/arch/x86/kernel/Makefile_32 +++ b/arch/x86/kernel/Makefile_32 | |||
@@ -39,7 +39,7 @@ obj-$(CONFIG_VM86) += vm86_32.o | |||
39 | obj-$(CONFIG_EARLY_PRINTK) += early_printk.o | 39 | obj-$(CONFIG_EARLY_PRINTK) += early_printk.o |
40 | obj-$(CONFIG_HPET_TIMER) += hpet_32.o | 40 | obj-$(CONFIG_HPET_TIMER) += hpet_32.o |
41 | obj-$(CONFIG_K8_NB) += k8.o | 41 | obj-$(CONFIG_K8_NB) += k8.o |
42 | obj-$(CONFIG_MGEODE_LX) += geode_32.o | 42 | obj-$(CONFIG_MGEODE_LX) += geode_32.o mfgpt_32.o |
43 | 43 | ||
44 | obj-$(CONFIG_VMI) += vmi_32.o vmiclock_32.o | 44 | obj-$(CONFIG_VMI) += vmi_32.o vmiclock_32.o |
45 | obj-$(CONFIG_PARAVIRT) += paravirt_32.o | 45 | obj-$(CONFIG_PARAVIRT) += paravirt_32.o |
diff --git a/arch/x86/kernel/geode_32.c b/arch/x86/kernel/geode_32.c index 41e8aec4c61d..f12d8c5d9809 100644 --- a/arch/x86/kernel/geode_32.c +++ b/arch/x86/kernel/geode_32.c | |||
@@ -145,10 +145,14 @@ EXPORT_SYMBOL_GPL(geode_gpio_setup_event); | |||
145 | 145 | ||
146 | static int __init geode_southbridge_init(void) | 146 | static int __init geode_southbridge_init(void) |
147 | { | 147 | { |
148 | int timers; | ||
149 | |||
148 | if (!is_geode()) | 150 | if (!is_geode()) |
149 | return -ENODEV; | 151 | return -ENODEV; |
150 | 152 | ||
151 | init_lbars(); | 153 | init_lbars(); |
154 | timers = geode_mfgpt_detect(); | ||
155 | printk(KERN_INFO "geode: %d MFGPT timers available.\n", timers); | ||
152 | return 0; | 156 | return 0; |
153 | } | 157 | } |
154 | 158 | ||
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 | |||
diff --git a/include/asm-x86/geode.h b/include/asm-x86/geode.h index 6da4bbbea3dc..d94898831bac 100644 --- a/include/asm-x86/geode.h +++ b/include/asm-x86/geode.h | |||
@@ -156,4 +156,54 @@ static inline int is_geode(void) | |||
156 | return (is_geode_gx() || is_geode_lx()); | 156 | return (is_geode_gx() || is_geode_lx()); |
157 | } | 157 | } |
158 | 158 | ||
159 | /* MFGPTs */ | ||
160 | |||
161 | #define MFGPT_MAX_TIMERS 8 | ||
162 | #define MFGPT_TIMER_ANY -1 | ||
163 | |||
164 | #define MFGPT_DOMAIN_WORKING 1 | ||
165 | #define MFGPT_DOMAIN_STANDBY 2 | ||
166 | #define MFGPT_DOMAIN_ANY (MFGPT_DOMAIN_WORKING | MFGPT_DOMAIN_STANDBY) | ||
167 | |||
168 | #define MFGPT_CMP1 0 | ||
169 | #define MFGPT_CMP2 1 | ||
170 | |||
171 | #define MFGPT_EVENT_IRQ 0 | ||
172 | #define MFGPT_EVENT_NMI 1 | ||
173 | #define MFGPT_EVENT_RESET 3 | ||
174 | |||
175 | #define MFGPT_REG_CMP1 0 | ||
176 | #define MFGPT_REG_CMP2 2 | ||
177 | #define MFGPT_REG_COUNTER 4 | ||
178 | #define MFGPT_REG_SETUP 6 | ||
179 | |||
180 | #define MFGPT_SETUP_CNTEN (1 << 15) | ||
181 | #define MFGPT_SETUP_CMP2 (1 << 14) | ||
182 | #define MFGPT_SETUP_CMP1 (1 << 13) | ||
183 | #define MFGPT_SETUP_SETUP (1 << 12) | ||
184 | #define MFGPT_SETUP_STOPEN (1 << 11) | ||
185 | #define MFGPT_SETUP_EXTEN (1 << 10) | ||
186 | #define MFGPT_SETUP_REVEN (1 << 5) | ||
187 | #define MFGPT_SETUP_CLKSEL (1 << 4) | ||
188 | |||
189 | static inline void geode_mfgpt_write(int timer, u16 reg, u16 value) | ||
190 | { | ||
191 | u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); | ||
192 | outw(value, base + reg + (timer * 8)); | ||
193 | } | ||
194 | |||
195 | static inline u16 geode_mfgpt_read(int timer, u16 reg) | ||
196 | { | ||
197 | u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); | ||
198 | return inw(base + reg + (timer * 8)); | ||
199 | } | ||
200 | |||
201 | extern int __init geode_mfgpt_detect(void); | ||
202 | extern int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable); | ||
203 | extern int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable); | ||
204 | extern int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner); | ||
205 | |||
206 | #define geode_mfgpt_setup_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 1) | ||
207 | #define geode_mfgpt_release_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 0) | ||
208 | |||
159 | #endif | 209 | #endif |