aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilly Tarreau <w@1wt.eu>2008-11-09 23:19:17 +0100
committerWilly Tarreau <w@1wt.eu>2008-11-09 23:30:26 +0100
commit49b5b9bbc6fd103b5f8b59bb1f903eefc37591c7 (patch)
treedbc76abdb1be35c337ed013a1406f0fe15a1eb25
parent71eb110d0ce07fd9993d81127a7131e6886910e2 (diff)
downloadlinux-2.4-49b5b9bbc6fd103b5f8b59bb1f903eefc37591c7.tar.gz
i386: add support for AMD Geode MFGPT timers
This is a second backport of the 2.6 Geode MFGPT code to 2.4. It enables use of the Geode on-chip MFGPT timers which may be used by a watchdog timer. This code does not include the clocksource nor IRQ features. The driver does not try to touch anything as long as no timer allocation is requested. This limits the risk of side effects in presence of broken BIOSes for instance, which reset the timers while they shouldn't. In such a case, simply passing "mfgptfix" to the kernel command line is enough to fix the problem. When the driver was backported, 2.6 driver was at commit ID 0d5cdc97e242a5589e5dca23277675f4b4482490. Signed-off-by: Willy Tarreau <w@1wt.eu>
-rw-r--r--arch/i386/kernel/Makefile5
-rw-r--r--arch/i386/kernel/geode-mfgpt.c224
-rw-r--r--arch/i386/kernel/mfgpt-compat.h251
-rw-r--r--include/asm-i386/geode-mfgpt.h85
-rw-r--r--include/linux/pci_ids.h2
5 files changed, 567 insertions, 0 deletions
diff --git a/arch/i386/kernel/Makefile b/arch/i386/kernel/Makefile
index 544b9b590b0f0f..aa921e9aaf803d 100644
--- a/arch/i386/kernel/Makefile
+++ b/arch/i386/kernel/Makefile
@@ -30,6 +30,11 @@ obj-y += pci-pc.o pci-irq.o
endif
endif
+ifdef CONFIG_MGEODE_LX
+obj-y += geode-mfgpt.o
+export-objs += geode-mfgpt.o
+endif
+
obj-$(CONFIG_MCA) += mca.o
obj-$(CONFIG_MTRR) += mtrr.o
obj-$(CONFIG_X86_MSR) += msr.o
diff --git a/arch/i386/kernel/geode-mfgpt.c b/arch/i386/kernel/geode-mfgpt.c
new file mode 100644
index 00000000000000..1ecb0495971e65
--- /dev/null
+++ b/arch/i386/kernel/geode-mfgpt.c
@@ -0,0 +1,224 @@
+/* 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>
+ * Backported to 2.4 by Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/interrupt.h>
+#include <asm/geode-mfgpt.h>
+#include <asm/io.h>
+#include <asm/msr.h>
+
+/* for pci_iomap/pci_iounmap */
+#include "mfgpt-compat.h"
+
+static void *mfgpt_iobase;
+
+static struct mfgpt_timer_t {
+ unsigned int avail:1;
+} mfgpt_timers[MFGPT_MAX_TIMERS];
+
+void geode_mfgpt_write(int i, u16 r, u16 v)
+{
+ outw(v, (unsigned long)(mfgpt_iobase + (r + (i * 8))));
+}
+EXPORT_SYMBOL(geode_mfgpt_write);
+
+u16 geode_mfgpt_read(int i, u16 r)
+{
+ return inw((unsigned long)(mfgpt_iobase + (r + (i * 8))));
+}
+EXPORT_SYMBOL(geode_mfgpt_read);
+
+int geode_mfgpt_toggle_event(int timer, int cmp, int event, int setup)
+{
+ u32 msr, mask, value, dummy;
+ int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
+
+ if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
+ return -EIO;
+
+ switch(event) {
+ case MFGPT_EVENT_RESET:
+ msr = MSR_MFGPT_NR;
+ mask = 1 << (timer + 24);
+ break;
+
+ case MFGPT_EVENT_NMI:
+ msr = MSR_MFGPT_NR;
+ mask = 1 << (timer + shift);
+ break;
+
+ case MFGPT_EVENT_IRQ:
+ msr = MSR_MFGPT_IRQ;
+ mask = 1 << (timer + shift);
+ break;
+
+ default:
+ return -EIO;
+ }
+
+ rdmsr(msr, value, dummy);
+
+ if (setup)
+ value |= mask;
+ else
+ value &= ~mask;
+
+ wrmsr(msr, value, dummy);
+ return 0;
+}
+
+EXPORT_SYMBOL(geode_mfgpt_toggle_event);
+
+/* Allow for disabling of MFGPTs */
+static int disable;
+static int __init mfgpt_disable(char *s)
+{
+ disable = 1;
+ return 1;
+}
+__setup("nomfgpt", mfgpt_disable);
+
+/* Reset the MFGPT timers. This is required by some broken BIOSes which already
+ * do the same and leave the system in an unstable state. TinyBIOS 0.98 is
+ * affected at least (0.99 is OK with MFGPT workaround left to off).
+ */
+static int __init mfgpt_fix(char *s)
+{
+ u32 val, dummy;
+
+ /* The following udocumented bit resets the MFGPT timers */
+ val = 0xFF; dummy = 0;
+ wrmsr(MSR_MFGPT_SETUP, val, dummy);
+ return 1;
+}
+__setup("mfgptfix", mfgpt_fix);
+
+
+/*
+ * 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.
+ */
+
+static struct pci_device_id geode_sbdevs[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }
+};
+
+static int timers = -1;
+
+static void geode_mfgpt_detect(void)
+{
+ struct pci_dev *pdev = NULL;
+ int i, ret, dev, count = 0;
+ u16 val;
+
+ timers = 0;
+
+ if (disable) {
+ printk(KERN_INFO "geode-mfgpt: MFGPT support is disabled\n");
+ goto done;
+ }
+
+ if (!is_geode()) {
+ printk(KERN_INFO "geode-mfgpt: Not a Geode GX/LX processor\n");
+ goto done;
+ }
+
+ for (dev = 0; dev < ARRAY_SIZE(geode_sbdevs); dev++) {
+ pdev = pci_find_device(geode_sbdevs[dev].vendor,
+ geode_sbdevs[dev].device, NULL);
+
+ if (pdev != NULL)
+ break;
+ }
+
+ if (pdev == NULL) {
+ printk(KERN_ERR "geode-mfgpt: No PCI devices found\n");
+ goto done;
+ }
+
+ if ((ret = pci_enable_device_bars(pdev, 1 << MFGPT_PCI_BAR)))
+ goto err;
+
+ if ((ret = pci_request_region(pdev, MFGPT_PCI_BAR, "geode-mfgpt")))
+ goto err;
+
+ mfgpt_iobase = pci_iomap(pdev, MFGPT_PCI_BAR, 64);
+
+ if (mfgpt_iobase == NULL)
+ goto ereq;
+
+ for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
+ val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
+ if (!(val & MFGPT_SETUP_SETUP)) {
+ mfgpt_timers[i].avail = 1;
+ timers++;
+ }
+ }
+
+ done:
+ printk(KERN_INFO "geode-mfgpt: %d MFGPT timers available.\n", timers);
+ return;
+ ereq:
+ pci_release_region(pdev, MFGPT_PCI_BAR);
+ err:
+ printk(KERN_ERR "geode-mfgpt: Error initalizing the timers\n");
+ return;
+}
+
+static int mfgpt_get(int timer)
+{
+ mfgpt_timers[timer].avail = 0;
+ printk(KERN_INFO "geode-mfgpt: Registered timer %d\n", timer);
+ return timer;
+}
+
+int geode_mfgpt_alloc_timer(int timer, int domain)
+{
+ int i;
+
+ if (timers == -1) {
+ /* timers haven't been detected yet */
+ geode_mfgpt_detect();
+ }
+
+ if (!timers)
+ return -1;
+
+ if (timer >= MFGPT_MAX_TIMERS)
+ return -1;
+
+ if (timer < 0) {
+ /* Try to find an available timer */
+ for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
+ if (mfgpt_timers[i].avail)
+ return mfgpt_get(i);
+
+ if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
+ break;
+ }
+ } else {
+ /* If they requested a specific timer, try to honor that */
+ if (mfgpt_timers[timer].avail)
+ return mfgpt_get(timer);
+ }
+
+ /* No timers available - too bad */
+ return -1;
+}
+EXPORT_SYMBOL(geode_mfgpt_alloc_timer);
diff --git a/arch/i386/kernel/mfgpt-compat.h b/arch/i386/kernel/mfgpt-compat.h
new file mode 100644
index 00000000000000..bc9ebb99ca6684
--- /dev/null
+++ b/arch/i386/kernel/mfgpt-compat.h
@@ -0,0 +1,251 @@
+/* compatibility layer for iomap functions used by mfgpt.
+ *
+ * This file has been extracted and inlined from linux-2.6.22/lib/iomap.c with
+ * the following original header and copyright :
+ *
+ * Implement the default iomap interfaces
+ *
+ * (C) Copyright 2004 Linus Torvalds
+ */
+#include <linux/pci.h>
+
+/*
+ * Read/write from/to an (offsettable) iomem cookie. It might be a PIO
+ * access or a MMIO access, these functions don't care. The info is
+ * encoded in the hardware mapping set up by the mapping functions
+ * (or the cookie itself, depending on implementation and hw).
+ *
+ * The generic routines don't assume any hardware mappings, and just
+ * encode the PIO/MMIO as part of the cookie. They coldly assume that
+ * the MMIO IO mappings are not in the low address range.
+ *
+ * Architectures for which this is not true can't use this generic
+ * implementation and should do their own copy.
+ */
+
+#ifndef HAVE_ARCH_PIO_SIZE
+/*
+ * We encode the physical PIO addresses (0-0xffff) into the
+ * pointer by offsetting them with a constant (0x10000) and
+ * assuming that all the low addresses are always PIO. That means
+ * we can do some sanity checks on the low bits, and don't
+ * need to just take things for granted.
+ */
+#define PIO_OFFSET 0x10000UL
+#define PIO_MASK 0x0ffffUL
+#define PIO_RESERVED 0x40000UL
+#endif
+
+static inline void bad_io_access(unsigned long port, const char *access)
+{
+ static int count = 10;
+ if (count) {
+ count--;
+ printk(KERN_ERR "Bad IO access at port %lx (%s)\n", port, access);
+ WARN_ON(1);
+ }
+}
+
+/*
+ * Ugly macros are a way of life.
+ */
+#define IO_COND(addr, is_pio, is_mmio) do { \
+ unsigned long port = (unsigned long)addr; \
+ if (port >= PIO_RESERVED) { \
+ is_mmio; \
+ } else if (port > PIO_OFFSET) { \
+ port &= PIO_MASK; \
+ is_pio; \
+ } else \
+ bad_io_access(port, #is_pio ); \
+} while (0)
+
+#ifndef pio_read16be
+#define pio_read16be(port) swab16(inw(port))
+#define pio_read32be(port) swab32(inl(port))
+#endif
+
+#ifndef mmio_read16be
+#define mmio_read16be(addr) be16_to_cpu(__raw_readw(addr))
+#define mmio_read32be(addr) be32_to_cpu(__raw_readl(addr))
+#endif
+
+static inline unsigned int ioread8(void __iomem *addr)
+{
+ IO_COND(addr, return inb(port), return readb(addr));
+ return 0xff;
+}
+static inline unsigned int ioread16(void __iomem *addr)
+{
+ IO_COND(addr, return inw(port), return readw(addr));
+ return 0xffff;
+}
+static inline unsigned int ioread16be(void __iomem *addr)
+{
+ IO_COND(addr, return pio_read16be(port), return mmio_read16be(addr));
+ return 0xffff;
+}
+static inline unsigned int ioread32(void __iomem *addr)
+{
+ IO_COND(addr, return inl(port), return readl(addr));
+ return 0xffffffff;
+}
+static inline unsigned int ioread32be(void __iomem *addr)
+{
+ IO_COND(addr, return pio_read32be(port), return mmio_read32be(addr));
+ return 0xffffffff;
+}
+
+#ifndef pio_write16be
+#define pio_write16be(val,port) outw(swab16(val),port)
+#define pio_write32be(val,port) outl(swab32(val),port)
+#endif
+
+#ifndef mmio_write16be
+#define mmio_write16be(val,port) __raw_writew(be16_to_cpu(val),port)
+#define mmio_write32be(val,port) __raw_writel(be32_to_cpu(val),port)
+#endif
+
+static inline void iowrite8(u8 val, void __iomem *addr)
+{
+ IO_COND(addr, outb(val,port), writeb(val, addr));
+}
+static inline void iowrite16(u16 val, void __iomem *addr)
+{
+ IO_COND(addr, outw(val,port), writew(val, addr));
+}
+static inline void iowrite16be(u16 val, void __iomem *addr)
+{
+ IO_COND(addr, pio_write16be(val,port), mmio_write16be(val, addr));
+}
+static inline void iowrite32(u32 val, void __iomem *addr)
+{
+ IO_COND(addr, outl(val,port), writel(val, addr));
+}
+static inline void iowrite32be(u32 val, void __iomem *addr)
+{
+ IO_COND(addr, pio_write32be(val,port), mmio_write32be(val, addr));
+}
+
+/*
+ * These are the "repeat MMIO read/write" functions.
+ * Note the "__raw" accesses, since we don't want to
+ * convert to CPU byte order. We write in "IO byte
+ * order" (we also don't have IO barriers).
+ */
+#ifndef mmio_insb
+static inline void mmio_insb(void __iomem *addr, u8 *dst, int count)
+{
+ while (--count >= 0) {
+ u8 data = __raw_readb(addr);
+ *dst = data;
+ dst++;
+ }
+}
+static inline void mmio_insw(void __iomem *addr, u16 *dst, int count)
+{
+ while (--count >= 0) {
+ u16 data = __raw_readw(addr);
+ *dst = data;
+ dst++;
+ }
+}
+static inline void mmio_insl(void __iomem *addr, u32 *dst, int count)
+{
+ while (--count >= 0) {
+ u32 data = __raw_readl(addr);
+ *dst = data;
+ dst++;
+ }
+}
+#endif
+
+#ifndef mmio_outsb
+static inline void mmio_outsb(void __iomem *addr, const u8 *src, int count)
+{
+ while (--count >= 0) {
+ __raw_writeb(*src, addr);
+ src++;
+ }
+}
+static inline void mmio_outsw(void __iomem *addr, const u16 *src, int count)
+{
+ while (--count >= 0) {
+ __raw_writew(*src, addr);
+ src++;
+ }
+}
+static inline void mmio_outsl(void __iomem *addr, const u32 *src, int count)
+{
+ while (--count >= 0) {
+ __raw_writel(*src, addr);
+ src++;
+ }
+}
+#endif
+
+static inline void ioread8_rep(void __iomem *addr, void *dst, unsigned long count)
+{
+ IO_COND(addr, insb(port,dst,count), mmio_insb(addr, dst, count));
+}
+static inline void ioread16_rep(void __iomem *addr, void *dst, unsigned long count)
+{
+ IO_COND(addr, insw(port,dst,count), mmio_insw(addr, dst, count));
+}
+static inline void ioread32_rep(void __iomem *addr, void *dst, unsigned long count)
+{
+ IO_COND(addr, insl(port,dst,count), mmio_insl(addr, dst, count));
+}
+
+static inline void iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)
+{
+ IO_COND(addr, outsb(port, src, count), mmio_outsb(addr, src, count));
+}
+static inline void iowrite16_rep(void __iomem *addr, const void *src, unsigned long count)
+{
+ IO_COND(addr, outsw(port, src, count), mmio_outsw(addr, src, count));
+}
+static inline void iowrite32_rep(void __iomem *addr, const void *src, unsigned long count)
+{
+ IO_COND(addr, outsl(port, src,count), mmio_outsl(addr, src, count));
+}
+
+/* Create a virtual mapping cookie for an IO port range */
+static inline void __iomem *ioport_map(unsigned long port, unsigned int nr)
+{
+ if (port > PIO_MASK)
+ return NULL;
+ return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
+}
+
+static inline void ioport_unmap(void __iomem *addr)
+{
+ /* Nothing to do */
+}
+
+/* Create a virtual mapping cookie for a PCI BAR (memory or IO) */
+static inline void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen)
+{
+ unsigned long start = pci_resource_start(dev, bar);
+ unsigned long len = pci_resource_len(dev, bar);
+ unsigned long flags = pci_resource_flags(dev, bar);
+
+ if (!len || !start)
+ return NULL;
+ if (maxlen && len > maxlen)
+ len = maxlen;
+ if (flags & IORESOURCE_IO)
+ return ioport_map(start, len);
+ if (flags & IORESOURCE_MEM) {
+ if (flags & IORESOURCE_CACHEABLE)
+ return ioremap(start, len);
+ return ioremap_nocache(start, len);
+ }
+ /* What? */
+ return NULL;
+}
+
+static inline void pci_iounmap(struct pci_dev *dev, void __iomem * addr)
+{
+ IO_COND(addr, /* nothing */, iounmap(addr));
+}
diff --git a/include/asm-i386/geode-mfgpt.h b/include/asm-i386/geode-mfgpt.h
new file mode 100644
index 00000000000000..e30b231772882d
--- /dev/null
+++ b/include/asm-i386/geode-mfgpt.h
@@ -0,0 +1,85 @@
+/* Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT)
+ *
+ * Copyright (C) 2006, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#ifndef MFGPT_GEODE_H_
+#define MFGPT_GEODE_H_
+
+#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 MSR_MFGPT_IRQ 0x51400028
+#define MSR_MFGPT_NR 0x51400029
+#define MSR_MFGPT_SETUP 0x5140002B
+
+#define MFGPT_MAX_TIMERS 8
+#define MFGPT_PCI_BAR 2
+
+#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)
+
+extern int geode_mfgpt_toggle_event(int, int, int, int);
+
+#define geode_mfgpt_set_event(t,c,e) geode_mfgpt_toggle_event(t,c,e,1)
+#define geode_mfgpt_clear_event(t,c,e) geode_mfgpt_toggle_event(t,c,e,0)
+
+extern void geode_mfgpt_set_irq(int, int, int, int);
+
+#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)
+
+extern void geode_mfgpt_write(int, u16, u16);
+extern u16 geode_mfgpt_read(int, u16);
+
+extern int geode_mfgpt_alloc_timer(int, int);
+
+/* Specific geode tests */
+
+static inline int is_geode_gx(void)
+{
+ return ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC) &&
+ (boot_cpu_data.x86 == 5) &&
+ (boot_cpu_data.x86_model == 5));
+}
+
+static inline int is_geode_lx(void)
+{
+ return ((boot_cpu_data.x86_vendor == X86_VENDOR_AMD) &&
+ (boot_cpu_data.x86 == 5) &&
+ (boot_cpu_data.x86_model == 10));
+}
+
+static inline int is_geode(void)
+{
+ return (is_geode_gx() || is_geode_lx());
+}
+
+#endif
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 52e1dbe6536b9a..d92ae7686e61fa 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -347,6 +347,7 @@
#define PCI_DEVICE_ID_NS_87560_USB 0x0012
#define PCI_DEVICE_ID_NS_83815 0x0020
#define PCI_DEVICE_ID_NS_83820 0x0022
+#define PCI_DEVICE_ID_NS_CS5535_ISA 0x002b
#define PCI_DEVICE_ID_NS_SCx200_BRIDGE 0x0500
#define PCI_DEVICE_ID_NS_SCx200_SMI 0x0501
#define PCI_DEVICE_ID_NS_SCx200_IDE 0x0502
@@ -434,6 +435,7 @@
#define PCI_DEVICE_ID_AMD_LANCE 0x2000
#define PCI_DEVICE_ID_AMD_LANCE_HOME 0x2001
#define PCI_DEVICE_ID_AMD_SCSI 0x2020
+#define PCI_DEVICE_ID_AMD_CS5536_ISA 0x2090
#define PCI_DEVICE_ID_AMD_CS5536_IDE 0x209A
#define PCI_DEVICE_ID_AMD_SERENADE 0x36c0
#define PCI_DEVICE_ID_AMD_FE_GATE_7006 0x7006