diff options
Diffstat (limited to 'arch/avr32/oprofile/op_model_avr32.c')
-rw-r--r-- | arch/avr32/oprofile/op_model_avr32.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/arch/avr32/oprofile/op_model_avr32.c b/arch/avr32/oprofile/op_model_avr32.c new file mode 100644 index 00000000000..e2f876bfc86 --- /dev/null +++ b/arch/avr32/oprofile/op_model_avr32.c @@ -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) +{ + +} |