2006-01-04 19:26:23 +03: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 14:50:32 +04:00
void avr32_backtrace ( struct pt_regs * const regs , unsigned int depth ) ;
2006-01-04 19:26:23 +03: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 19:26:23 +03: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 19:26:23 +03:00
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " enabled " ,
2006-01-04 19:26:23 +03:00
& counter [ i ] . enabled ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " event " ,
2006-01-04 19:26:23 +03:00
& counter [ i ] . event ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " count " ,
2006-01-04 19:26:23 +03:00
& counter [ i ] . count ) ;
/* Dummy entries */
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " kernel " ,
2006-01-04 19:26:23 +03:00
& counter [ i ] . kernel ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " user " ,
2006-01-04 19:26:23 +03:00
& counter [ i ] . user ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " unit_mask " ,
2006-01-04 19:26:23 +03: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 14:50:32 +04:00
ops - > backtrace = avr32_backtrace ;
2006-01-04 19:26:23 +03:00
printk ( KERN_INFO " oprofile: using AVR32 performance monitoring. \n " ) ;
return 0 ;
}
void oprofile_arch_exit ( void )
{
}