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 a57c1f216b..2128de6d80 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 c624193740..1035644399 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 41e8aec4c6..f12d8c5d98 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 0000000000..3a63a2dd8d --- /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 6da4bbbea3..d94898831b 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 |
