239 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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/sysreg.h>
 | |
| #include <asm/system.h>
 | |
| 
 | |
| #define AVR32_PERFCTR_IRQ_GROUP	0
 | |
| #define AVR32_PERFCTR_IRQ_LINE	1
 | |
| 
 | |
| void avr32_backtrace(struct pt_regs * const regs, unsigned int depth);
 | |
| 
 | |
| 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));
 | |
| 
 | |
| 	ops->backtrace = avr32_backtrace;
 | |
| 
 | |
| 	printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n");
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void oprofile_arch_exit(void)
 | |
| {
 | |
| 
 | |
| }
 |