--- /dev/null
+/*
+ * AVR32 Performance Counter Driver
+ *
+ * Copyright (C) 2005-2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Author: Ronny Pedersen
+ */
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/oprofile.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include <asm/intc.h>
+#include <asm/sysreg.h>
+#include <asm/system.h>
+
+#define AVR32_PERFCTR_IRQ_GROUP        0
+#define AVR32_PERFCTR_IRQ_LINE 1
+
+enum { PCCNT, PCNT0, PCNT1, NR_counter };
+
+struct avr32_perf_counter {
+       unsigned long   enabled;
+       unsigned long   event;
+       unsigned long   count;
+       unsigned long   unit_mask;
+       unsigned long   kernel;
+       unsigned long   user;
+
+       u32             ie_mask;
+       u32             flag_mask;
+};
+
+static struct avr32_perf_counter counter[NR_counter] = {
+       {
+               .ie_mask        = SYSREG_BIT(IEC),
+               .flag_mask      = SYSREG_BIT(FC),
+       }, {
+               .ie_mask        = SYSREG_BIT(IE0),
+               .flag_mask      = SYSREG_BIT(F0),
+       }, {
+               .ie_mask        = SYSREG_BIT(IE1),
+               .flag_mask      = SYSREG_BIT(F1),
+       },
+};
+
+static void avr32_perf_counter_reset(void)
+{
+       /* Reset all counter and disable/clear all interrupts */
+       sysreg_write(PCCR, (SYSREG_BIT(PCCR_R)
+                               | SYSREG_BIT(PCCR_C)
+                               | SYSREG_BIT(FC)
+                               | SYSREG_BIT(F0)
+                               | SYSREG_BIT(F1)));
+}
+
+static irqreturn_t avr32_perf_counter_interrupt(int irq, void *dev_id)
+{
+       struct avr32_perf_counter *ctr = dev_id;
+       struct pt_regs *regs;
+       u32 pccr;
+
+       if (likely(!(intc_get_pending(AVR32_PERFCTR_IRQ_GROUP)
+                                       & (1 << AVR32_PERFCTR_IRQ_LINE))))
+               return IRQ_NONE;
+
+       regs = get_irq_regs();
+       pccr = sysreg_read(PCCR);
+
+       /* Clear the interrupt flags we're about to handle */
+       sysreg_write(PCCR, pccr);
+
+       /* PCCNT */
+       if (ctr->enabled && (pccr & ctr->flag_mask)) {
+               sysreg_write(PCCNT, -ctr->count);
+               oprofile_add_sample(regs, PCCNT);
+       }
+       ctr++;
+       /* PCNT0 */
+       if (ctr->enabled && (pccr & ctr->flag_mask)) {
+               sysreg_write(PCNT0, -ctr->count);
+               oprofile_add_sample(regs, PCNT0);
+       }
+       ctr++;
+       /* PCNT1 */
+       if (ctr->enabled && (pccr & ctr->flag_mask)) {
+               sysreg_write(PCNT1, -ctr->count);
+               oprofile_add_sample(regs, PCNT1);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int avr32_perf_counter_create_files(struct super_block *sb,
+               struct dentry *root)
+{
+       struct dentry *dir;
+       unsigned int i;
+       char filename[4];
+
+       for (i = 0; i < NR_counter; i++) {
+               snprintf(filename, sizeof(filename), "%u", i);
+               dir = oprofilefs_mkdir(sb, root, filename);
+
+               oprofilefs_create_ulong(sb, dir, "enabled",
+                               &counter[i].enabled);
+               oprofilefs_create_ulong(sb, dir, "event",
+                               &counter[i].event);
+               oprofilefs_create_ulong(sb, dir, "count",
+                               &counter[i].count);
+
+               /* Dummy entries */
+               oprofilefs_create_ulong(sb, dir, "kernel",
+                               &counter[i].kernel);
+               oprofilefs_create_ulong(sb, dir, "user",
+                               &counter[i].user);
+               oprofilefs_create_ulong(sb, dir, "unit_mask",
+                               &counter[i].unit_mask);
+       }
+
+       return 0;
+}
+
+static int avr32_perf_counter_setup(void)
+{
+       struct avr32_perf_counter *ctr;
+       u32 pccr;
+       int ret;
+       int i;
+
+       pr_debug("avr32_perf_counter_setup\n");
+
+       if (sysreg_read(PCCR) & SYSREG_BIT(PCCR_E)) {
+               printk(KERN_ERR
+                       "oprofile: setup: perf counter already enabled\n");
+               return -EBUSY;
+       }
+
+       ret = request_irq(AVR32_PERFCTR_IRQ_GROUP,
+                       avr32_perf_counter_interrupt, IRQF_SHARED,
+                       "oprofile", counter);
+       if (ret)
+               return ret;
+
+       avr32_perf_counter_reset();
+
+       pccr = 0;
+       for (i = PCCNT; i < NR_counter; i++) {
+               ctr = &counter[i];
+               if (!ctr->enabled)
+                       continue;
+
+               pr_debug("enabling counter %d...\n", i);
+
+               pccr |= ctr->ie_mask;
+
+               switch (i) {
+               case PCCNT:
+                       /* PCCNT always counts cycles, so no events */
+                       sysreg_write(PCCNT, -ctr->count);
+                       break;
+               case PCNT0:
+                       pccr |= SYSREG_BF(CONF0, ctr->event);
+                       sysreg_write(PCNT0, -ctr->count);
+                       break;
+               case PCNT1:
+                       pccr |= SYSREG_BF(CONF1, ctr->event);
+                       sysreg_write(PCNT1, -ctr->count);
+                       break;
+               }
+       }
+
+       pr_debug("oprofile: writing 0x%x to PCCR...\n", pccr);
+
+       sysreg_write(PCCR, pccr);
+
+       return 0;
+}
+
+static void avr32_perf_counter_shutdown(void)
+{
+       pr_debug("avr32_perf_counter_shutdown\n");
+
+       avr32_perf_counter_reset();
+       free_irq(AVR32_PERFCTR_IRQ_GROUP, counter);
+}
+
+static int avr32_perf_counter_start(void)
+{
+       pr_debug("avr32_perf_counter_start\n");
+
+       sysreg_write(PCCR, sysreg_read(PCCR) | SYSREG_BIT(PCCR_E));
+
+       return 0;
+}
+
+static void avr32_perf_counter_stop(void)
+{
+       pr_debug("avr32_perf_counter_stop\n");
+
+       sysreg_write(PCCR, sysreg_read(PCCR) & ~SYSREG_BIT(PCCR_E));
+}
+
+static struct oprofile_operations avr32_perf_counter_ops __initdata = {
+       .create_files   = avr32_perf_counter_create_files,
+       .setup          = avr32_perf_counter_setup,
+       .shutdown       = avr32_perf_counter_shutdown,
+       .start          = avr32_perf_counter_start,
+       .stop           = avr32_perf_counter_stop,
+       .cpu_type       = "avr32",
+};
+
+int __init oprofile_arch_init(struct oprofile_operations *ops)
+{
+       if (!(current_cpu_data.features & AVR32_FEATURE_PCTR))
+               return -ENODEV;
+
+       memcpy(ops, &avr32_perf_counter_ops,
+                       sizeof(struct oprofile_operations));
+
+       printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n");
+
+       return 0;
+}
+
+void oprofile_arch_exit(void)
+{
+
+}