[PATCH] Add a system level driver and API for the MFGPT timers

From: Jordan Crouse <jordan.crouse@xxxxxxx>

The CS5535 and CS5536 provide a series of timers that are suitable
for various applications.  This patch provides a simple API for other
drivers to attach to.

Signed-off-by: Jordan Crouse <jordan.crouse@xxxxxxx>
---

 arch/i386/kernel/Makefile      |    1 
 arch/i386/kernel/geode-mfgpt.c |  309 ++++++++++++++++++++++++++++++++++++++++
 include/asm-i386/geode-mfgpt.h |   57 +++++++
 3 files changed, 367 insertions(+), 0 deletions(-)

diff --git a/arch/i386/kernel/Makefile b/arch/i386/kernel/Makefile
index 1e8988e..93877ed 100644
--- a/arch/i386/kernel/Makefile
+++ b/arch/i386/kernel/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_VM86)		+= vm86.o
 obj-$(CONFIG_EARLY_PRINTK)	+= early_printk.o
 obj-$(CONFIG_HPET_TIMER) 	+= hpet.o
 obj-$(CONFIG_K8_NB)		+= k8.o
+obj-y    					+= geode-mfgpt.o
 
 # Make sure this is linked after any other paravirt_ops structs: see head.S
 obj-$(CONFIG_PARAVIRT)		+= paravirt.o
diff --git a/arch/i386/kernel/geode-mfgpt.c b/arch/i386/kernel/geode-mfgpt.c
new file mode 100644
index 0000000..d36684c
--- /dev/null
+++ b/arch/i386/kernel/geode-mfgpt.c
@@ -0,0 +1,318 @@
+/*     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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/interrupt.h>
+#include <linux/cdev.h>
+#include <asm/geode-mfgpt.h>
+#include <asm/io.h>
+#include <asm/msr.h>
+
+#define WORKAROUND
+
+#define MFGPT_IRQ_MSR 0x51400028
+#define MFGPT_NR_MSR  0x51400029
+
+#define MFGPT_MAX_TIMERS 7
+#define MFGPT_PCI_BAR 2
+
+#define F_AVAIL    0x01
+#define F_RESERVED 0x02
+
+static void *mfgpt_iobase;
+static int reserved_mask = 0;
+static struct class *mfgpt_class;
+
+static struct mfgpt_timer_t {
+	int index;
+	int flags;
+	struct module *owner;
+	struct class_device *cdev;
+} mfgpt_timers[MFGPT_MAX_TIMERS];
+
+void
+geode_mfgpt_write(int i, u16 r, u16 v)
+{
+	iowrite16(v, mfgpt_iobase + (r + (i * 8)));
+}
+
+EXPORT_SYMBOL(geode_mfgpt_write);
+
+u16
+geode_mfgpt_read(int i, u16 r)
+{
+	return ioread16(mfgpt_iobase + (r + (i * 8)));
+}
+
+EXPORT_SYMBOL(geode_mfgpt_read);
+
+static ssize_t
+sys_print_register(struct class_device *dev, char *buf, int reg)
+{
+	struct mfgpt_timer_t *timer = class_get_devdata(dev);
+	u16 val = geode_mfgpt_read(timer->index, reg);
+
+	return sprintf(buf, "%4.4X\n", val);
+}
+
+static ssize_t
+sys_show_setup(struct class_device *dev, char *buf)
+{
+	return sys_print_register(dev, buf, MFGPT_REG_SETUP);
+}
+
+static ssize_t
+sys_show_counter(struct class_device *dev, char *buf)
+{
+	return sys_print_register(dev, buf, MFGPT_REG_COUNTER);
+}
+
+static ssize_t
+sys_show_cmp1(struct class_device *dev, char *buf)
+{
+	return sys_print_register(dev, buf, MFGPT_REG_CMP1);
+}
+
+static ssize_t
+sys_show_cmp2(struct class_device *dev, char *buf)
+{
+	return sys_print_register(dev, buf, MFGPT_REG_CMP2);
+}
+
+static struct class_device_attribute mfgpt_attrs[] = {
+	__ATTR(setup, S_IRUGO, sys_show_setup, NULL),
+	__ATTR(counter, S_IRUGO, sys_show_counter, NULL),
+	__ATTR(cmp1, S_IRUGO, sys_show_cmp1, NULL),
+	__ATTR(cmp2, S_IRUGO, sys_show_cmp2, NULL),
+};
+
+void
+geode_mfgpt_toggle_event(int timer, int cmp, int event, int setup)
+{
+	u32 msr, mask, value, dummy;
+	int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
+
+	switch(event) {
+	case MFGPT_EVENT_RESET:
+		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;
+	}
+
+	rdmsr(msr, value, dummy);
+
+	if (setup)
+		value |= mask;
+	else
+		value &= ~mask;
+
+	wrmsr(msr, value, dummy);
+}
+
+EXPORT_SYMBOL(geode_mfgpt_toggle_event);
+
+void
+geode_mfgpt_set_irq(int timer, int cmp, int irq, int setup)
+{
+	u32 val, dummy;
+	int offset;
+
+	geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, setup);
+
+	rdmsr(0x51400022, val, dummy);
+
+	offset = (timer % 4) * 4;
+
+	val &= ~((0xF << offset) | (0xF << (offset + 16)));
+
+	if (setup) {
+		val |= (irq & 0x0F) << (offset);
+		val |= (irq & 0x0F) << (offset + 16);
+	}
+
+	wrmsr(0x51400022, val, dummy);
+}
+
+int
+geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner)
+{
+	int i;
+
+	/* If they requested a specific timer, try to honor that */
+	if (mfgpt_iobase == NULL)
+		return -ENODEV;
+
+	if (timer != MFGPT_TIMER_ANY) {
+		if (mfgpt_timers[timer].flags & F_AVAIL) {
+			mfgpt_timers[timer].flags &= ~F_AVAIL;
+			mfgpt_timers[timer].owner = owner;
+
+			printk("geode-mfgpt:  Registered timer %d\n", timer);
+			return timer;
+		}
+		else if (mfgpt_timers[timer].owner == owner)
+			return timer;
+
+		/* Request failed - somebody else owns it */
+		return -1;
+	}
+
+	/* Try to find an available timer */
+
+	for(i = 0; i < MFGPT_MAX_TIMERS; i++) {
+
+		if ((mfgpt_timers[i].flags & F_AVAIL) &&
+		    !(mfgpt_timers[i].flags & F_RESERVED)) {
+			mfgpt_timers[i].flags &= ~F_AVAIL;
+			mfgpt_timers[i].owner = owner;
+			
+			printk("geode-mfgpt:  Registered timer %d\n", i);
+			return i;
+		}
+
+		if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
+			break;
+	}
+
+	/* No timers available - too bad */
+	return -1;
+}
+
+EXPORT_SYMBOL(geode_mfgpt_alloc_timer);
+
+static int
+mfgpt_setup_timer(struct pci_dev *pdev, int timer)
+{
+	dev_t devid = MKDEV(0, 0);
+	u16 val = geode_mfgpt_read(timer, MFGPT_REG_SETUP);
+	mfgpt_timers[timer].index = timer;
+
+	if (reserved_mask & (1 << timer))
+		mfgpt_timers[timer].flags |= F_RESERVED;
+
+	if (!(val & MFGPT_SETUP_SETUP)) {
+		int v;
+
+		mfgpt_timers[timer].flags = F_AVAIL;
+
+		/* Add the class device */
+		mfgpt_timers[timer].cdev =
+			class_device_create(mfgpt_class, NULL, devid,
+				&pdev->dev, "mfgpt%d", timer);
+
+	        class_set_devdata(mfgpt_timers[timer].cdev, &mfgpt_timers[timer]);
+
+		for(v = 0; v < ARRAY_SIZE(mfgpt_attrs); v++)
+			if (class_device_create_file(mfgpt_timers[timer].cdev,
+				&mfgpt_attrs[v]))
+				printk(KERN_ERR "geode-mfpgt:  Couldn't create %s\n",
+				mfgpt_attrs[v].attr.name);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+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 __init
+geode_mfgpt_init(void)
+{
+	struct pci_dev *pdev = NULL;
+	int i, ret, dev, count = 0;
+
+	if (!is_geode())
+	{
+		goto not_geode;
+	}
+	
+#ifdef WORKAROUND
+	u32 val, dummy;
+
+	/* The following udocumented bit resets the MFGPT timers */
+
+	val = 0xFF;
+	wrmsr(0x5140002B, val, dummy);
+#endif
+
+	for (dev = 0; dev < ARRAY_SIZE(geode_sbdevs); dev++) {
+		pdev = pci_get_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 err;
+	}
+
+	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;
+
+	mfgpt_class = class_create(THIS_MODULE, "mfgpt");
+
+	if (IS_ERR(mfgpt_class)) {
+		printk(KERN_ERR "geode-mfgpt: Unable to allocate the class.\n");
+		goto eiobase;
+	}
+
+	for(i = 0; i < MFGPT_MAX_TIMERS; i++)
+		count += mfgpt_setup_timer(pdev, i);
+
+	printk("geode-mfgpt:  %d timers available.\n", count);
+	return 0;
+
+ eiobase:
+	pci_iounmap(pdev, mfgpt_iobase);
+	mfgpt_iobase = NULL;
+
+ ereq:
+	pci_release_region(pdev, MFGPT_PCI_BAR);
+
+ err:
+ 	printk("geode-mfgpt:  Error initalizing the timers\n");
+	return -1;
+	
+ not_geode:
+ 	printk("geode-mfgpt:  Not a Geode GX/LX processor\n");
+	return -ENODEV;
+}
+
+device_initcall(geode_mfgpt_init);
diff --git a/include/asm-i386/geode-mfgpt.h b/include/asm-i386/geode-mfgpt.h
new file mode 100644
index 0000000..bc63460
--- /dev/null
+++ b/include/asm-i386/geode-mfgpt.h
@@ -0,0 +1,78 @@
+/*     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 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 void 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, struct module *);
+
+/* 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
