diff options
Diffstat (limited to 'recipes/linux/linux/alix/geode-mfgpt-support-for-geode-class-machines.patch')
-rw-r--r-- | recipes/linux/linux/alix/geode-mfgpt-support-for-geode-class-machines.patch | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/recipes/linux/linux/alix/geode-mfgpt-support-for-geode-class-machines.patch b/recipes/linux/linux/alix/geode-mfgpt-support-for-geode-class-machines.patch new file mode 100644 index 0000000000..533412c483 --- /dev/null +++ b/recipes/linux/linux/alix/geode-mfgpt-support-for-geode-class-machines.patch @@ -0,0 +1,311 @@ +From: Andres Salomon <dilinger@queued.net> + +This adds support for Multi-Function General Purpose Timers. It detects the +available timers during southbridge init, and provides an API for allocating +and setting the timers. They're higher resolution than the standard PIT, so +the MFGPTs come in handy for quite a few things. + +Note that we never clobber the timers that the BIOS might have opted to use; +we just check for unused timers. + +Signed-off-by: Jordan Crouse <jordan.crouse@amd.com> +Signed-off-by: Andres Salomon <dilinger@debian.org> +Cc: Andi Kleen <ak@suse.de> +Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> +Signed-off-by: Andrew Morton <akpm@linux-foundation.org> +--- + + Documentation/kernel-parameters.txt | 3 + arch/i386/kernel/Makefile | 2 + arch/i386/kernel/geode.c | 4 + arch/i386/kernel/mfgpt.c | 199 ++++++++++++++++++++++++++ + include/asm-i386/geode.h | 50 ++++++ + 5 files changed, 257 insertions(+), 1 deletion(-) + +--- linux-2.6.22.orig/arch/i386/kernel/Makefile ++++ linux-2.6.22/arch/i386/kernel/Makefile +@@ -40,7 +40,7 @@ + obj-$(CONFIG_EARLY_PRINTK) += early_printk.o + obj-$(CONFIG_HPET_TIMER) += hpet.o + obj-$(CONFIG_K8_NB) += k8.o +-obj-$(CONFIG_MGEODE_LX) += geode.o ++obj-$(CONFIG_MGEODE_LX) += geode.o mfgpt.o + + obj-$(CONFIG_VMI) += vmi.o vmiclock.o + obj-$(CONFIG_PARAVIRT) += paravirt.o +--- linux-2.6.22.orig/arch/i386/kernel/geode.c ++++ linux-2.6.22/arch/i386/kernel/geode.c +@@ -146,10 +146,14 @@ + + static int __init geode_southbridge_init(void) + { ++ int timers; ++ + if (!is_geode()) + return -ENODEV; + + init_lbars(); ++ timers = geode_mfgpt_detect(); ++ printk(KERN_INFO "geode: %d MFGPT timers available.\n", timers); + return 0; + } + +--- /dev/null ++++ linux-2.6.22/arch/i386/kernel/mfgpt.c +@@ -0,0 +1,199 @@ ++/* ++ * Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT) ++ * ++ * Copyright (C) 2006, Advanced Micro Devices, Inc. ++ * Copyright (C) 2007, Andres Salomon <dilinger@debian.org> ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of version 2 of the GNU General Public License ++ * as published by the Free Software Foundation. ++ * ++ * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book. ++ */ ++ ++/* ++ * We are using the 32Khz input clock - its the only one that has the ++ * ranges we find desirable. The following table lists the suitable ++ * divisors and the associated hz, minimum interval ++ * and the maximum interval: ++ * ++ * Divisor Hz Min Delta (S) Max Delta (S) ++ * 1 32000 .0005 2.048 ++ * 2 16000 .001 4.096 ++ * 4 8000 .002 8.192 ++ * 8 4000 .004 16.384 ++ * 16 2000 .008 32.768 ++ * 32 1000 .016 65.536 ++ * 64 500 .032 131.072 ++ * 128 250 .064 262.144 ++ * 256 125 .128 524.288 ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/interrupt.h> ++#include <linux/module.h> ++#include <asm/geode.h> ++ ++#define F_AVAIL 0x01 ++ ++static struct mfgpt_timer_t { ++ int flags; ++ struct module *owner; ++} mfgpt_timers[MFGPT_MAX_TIMERS]; ++ ++/* Selected from the table above */ ++ ++#define MFGPT_DIVISOR 16 ++#define MFGPT_SCALE 4 /* divisor = 2^(scale) */ ++#define MFGPT_HZ (32000 / MFGPT_DIVISOR) ++#define MFGPT_PERIODIC (MFGPT_HZ / HZ) ++ ++/* Allow for disabling of MFGPTs */ ++static int disable; ++static int __init mfgpt_disable(char *s) ++{ ++ disable = 1; ++ return 1; ++} ++__setup("nomfgpt", mfgpt_disable); ++ ++/* ++ * Check whether any MFGPTs are available for the kernel to use. In most ++ * cases, firmware that uses AMD's VSA code will claim all timers during ++ * bootup; we certainly don't want to take them if they're already in use. ++ * In other cases (such as with VSAless OpenFirmware), the system firmware ++ * leaves timers available for us to use. ++ */ ++int __init geode_mfgpt_detect(void) ++{ ++ int count = 0, i; ++ u16 val; ++ ++ if (disable) { ++ printk(KERN_INFO "geode-mfgpt: Skipping MFGPT setup\n"); ++ return 0; ++ } ++ ++ for (i = 0; i < MFGPT_MAX_TIMERS; i++) { ++ val = geode_mfgpt_read(i, MFGPT_REG_SETUP); ++ if (!(val & MFGPT_SETUP_SETUP)) { ++ mfgpt_timers[i].flags = F_AVAIL; ++ count++; ++ } ++ } ++ ++ return count; ++} ++ ++int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable) ++{ ++ u32 msr, mask, value, dummy; ++ int shift = (cmp == MFGPT_CMP1) ? 0 : 8; ++ ++ if (timer < 0 || timer >= MFGPT_MAX_TIMERS) ++ return -EIO; ++ ++ /* ++ * The register maps for these are described in sections 6.17.1.x of ++ * the AMD Geode CS5536 Companion Device Data Book. ++ */ ++ switch (event) { ++ case MFGPT_EVENT_RESET: ++ /* ++ * XXX: According to the docs, we cannot reset timers above ++ * 6; that is, resets for 7 and 8 will be ignored. Is this ++ * a problem? -dilinger ++ */ ++ msr = MFGPT_NR_MSR; ++ mask = 1 << (timer + 24); ++ break; ++ ++ case MFGPT_EVENT_NMI: ++ msr = MFGPT_NR_MSR; ++ mask = 1 << (timer + shift); ++ break; ++ ++ case MFGPT_EVENT_IRQ: ++ msr = MFGPT_IRQ_MSR; ++ mask = 1 << (timer + shift); ++ break; ++ ++ default: ++ return -EIO; ++ } ++ ++ rdmsr(msr, value, dummy); ++ ++ if (enable) ++ value |= mask; ++ else ++ value &= ~mask; ++ ++ wrmsr(msr, value, dummy); ++ return 0; ++} ++EXPORT_SYMBOL(geode_mfgpt_toggle_event); ++ ++int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable) ++{ ++ u32 val, dummy; ++ int offset; ++ ++ if (timer < 0 || timer >= MFGPT_MAX_TIMERS) ++ return -EIO; ++ ++ if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable)) ++ return -EIO; ++ ++ rdmsr(0x51400022, val, dummy); ++ ++ offset = (timer % 4) * 4; ++ ++ val &= ~((0xF << offset) | (0xF << (offset + 16))); ++ ++ if (enable) { ++ val |= (irq & 0x0F) << (offset); ++ val |= (irq & 0x0F) << (offset + 16); ++ } ++ ++ wrmsr(0x51400022, val, dummy); ++ return 0; ++} ++EXPORT_SYMBOL(geode_mfgpt_set_irq); ++ ++static int mfgpt_get(int timer, struct module *owner) ++{ ++ mfgpt_timers[timer].flags &= ~F_AVAIL; ++ mfgpt_timers[timer].owner = owner; ++ printk(KERN_INFO "geode-mfgpt: Registered timer %d\n", timer); ++ return timer; ++} ++ ++int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner) ++{ ++ int i; ++ ++ if (!geode_get_dev_base(GEODE_DEV_MFGPT)) ++ return -ENODEV; ++ if (timer >= MFGPT_MAX_TIMERS) ++ return -EIO; ++ ++ if (timer < 0) { ++ /* Try to find an available timer */ ++ for (i = 0; i < MFGPT_MAX_TIMERS; i++) { ++ if (mfgpt_timers[i].flags & F_AVAIL) ++ return mfgpt_get(i, owner); ++ ++ if (i == 5 && domain == MFGPT_DOMAIN_WORKING) ++ break; ++ } ++ } else { ++ /* If they requested a specific timer, try to honor that */ ++ if (mfgpt_timers[timer].flags & F_AVAIL) ++ return mfgpt_get(timer, owner); ++ } ++ ++ /* No timers available - too bad */ ++ return -1; ++} ++EXPORT_SYMBOL(geode_mfgpt_alloc_timer); +--- linux-2.6.22.orig/include/asm-i386/geode.h ++++ linux-2.6.22/include/asm-i386/geode.h +@@ -156,4 +156,54 @@ + return (is_geode_gx() || is_geode_lx()); + } + ++/* MFGPTs */ ++ ++#define MFGPT_MAX_TIMERS 8 ++#define MFGPT_TIMER_ANY -1 ++ ++#define MFGPT_DOMAIN_WORKING 1 ++#define MFGPT_DOMAIN_STANDBY 2 ++#define MFGPT_DOMAIN_ANY (MFGPT_DOMAIN_WORKING | MFGPT_DOMAIN_STANDBY) ++ ++#define MFGPT_CMP1 0 ++#define MFGPT_CMP2 1 ++ ++#define MFGPT_EVENT_IRQ 0 ++#define MFGPT_EVENT_NMI 1 ++#define MFGPT_EVENT_RESET 3 ++ ++#define MFGPT_REG_CMP1 0 ++#define MFGPT_REG_CMP2 2 ++#define MFGPT_REG_COUNTER 4 ++#define MFGPT_REG_SETUP 6 ++ ++#define MFGPT_SETUP_CNTEN (1 << 15) ++#define MFGPT_SETUP_CMP2 (1 << 14) ++#define MFGPT_SETUP_CMP1 (1 << 13) ++#define MFGPT_SETUP_SETUP (1 << 12) ++#define MFGPT_SETUP_STOPEN (1 << 11) ++#define MFGPT_SETUP_EXTEN (1 << 10) ++#define MFGPT_SETUP_REVEN (1 << 5) ++#define MFGPT_SETUP_CLKSEL (1 << 4) ++ ++static inline void geode_mfgpt_write(int timer, u16 reg, u16 value) ++{ ++ u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); ++ outw(value, base + reg + (timer * 8)); ++} ++ ++static inline u16 geode_mfgpt_read(int timer, u16 reg) ++{ ++ u32 base = geode_get_dev_base(GEODE_DEV_MFGPT); ++ return inw(base + reg + (timer * 8)); ++} ++ ++extern int __init geode_mfgpt_detect(void); ++extern int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable); ++extern int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable); ++extern int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner); ++ ++#define geode_mfgpt_setup_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 1) ++#define geode_mfgpt_release_irq(t, c, i) geode_mfgpt_set_irq((t), (c), (i), 0) ++ + #endif |