| /* |
| * 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) |
| { |
| |
| } |