| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define AVR32_PERFCTR_IRQ_GROUP	0
 | 
					
						
							|  |  |  | #define AVR32_PERFCTR_IRQ_LINE	1
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-09-03 12:50:32 +02:00
										 |  |  | void avr32_backtrace(struct pt_regs * const regs, unsigned int depth); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-19 15:52:42 +04:00
										 |  |  | static int avr32_perf_counter_create_files(struct dentry *root) | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct dentry *dir; | 
					
						
							|  |  |  | 	unsigned int i; | 
					
						
							|  |  |  | 	char filename[4]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < NR_counter; i++) { | 
					
						
							|  |  |  | 		snprintf(filename, sizeof(filename), "%u", i); | 
					
						
							| 
									
										
										
										
											2013-07-19 15:58:27 +04:00
										 |  |  | 		dir = oprofilefs_mkdir(root, filename); | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "enabled", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&counter[i].enabled); | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "event", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&counter[i].event); | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "count", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&counter[i].count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Dummy entries */ | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "kernel", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&counter[i].kernel); | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "user", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&counter[i].user); | 
					
						
							| 
									
										
										
										
											2013-07-19 16:10:36 +04:00
										 |  |  | 		oprofilefs_create_ulong(dir, "unit_mask", | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 				&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)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-09-03 12:50:32 +02:00
										 |  |  | 	ops->backtrace = avr32_backtrace; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-01-04 17:26:23 +01:00
										 |  |  | 	printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void oprofile_arch_exit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |