|
@@ -0,0 +1,235 @@
|
|
|
+/*
|
|
|
+ * 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)
|
|
|
+{
|
|
|
+
|
|
|
+}
|