2010-02-02 20:23:15 +01:00
/*
* linux / arch / arm / kernel / pmu . c
*
* Copyright ( C ) 2009 picoChip Designs Ltd , Jamie Iles
2010-04-29 17:13:24 +01:00
* Copyright ( C ) 2010 ARM Ltd , Will Deacon
2010-02-02 20:23:15 +01:00
*
* 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 .
*
*/
2010-04-29 17:13:24 +01:00
# define pr_fmt(fmt) "PMU: " fmt
2010-02-02 20:23:15 +01:00
# include <linux/cpumask.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
2011-06-22 15:33:55 +01:00
# include <linux/of_device.h>
2010-04-29 17:13:24 +01:00
# include <linux/platform_device.h>
2010-02-02 20:23:15 +01:00
# include <asm/pmu.h>
2010-04-29 17:13:24 +01:00
static volatile long pmu_lock ;
static struct platform_device * pmu_devices [ ARM_NUM_PMU_DEVICES ] ;
2011-06-22 15:30:51 +01:00
static int __devinit pmu_register ( struct platform_device * pdev ,
enum arm_pmu_type type )
2010-04-29 17:13:24 +01:00
{
2011-06-22 15:30:51 +01:00
if ( type < 0 | | type > = ARM_NUM_PMU_DEVICES ) {
2010-04-29 17:13:24 +01:00
pr_warning ( " received registration request for unknown "
2011-06-22 15:30:51 +01:00
" device %d \n " , type ) ;
2010-04-29 17:13:24 +01:00
return - EINVAL ;
}
2011-06-22 15:32:48 +01:00
if ( pmu_devices [ type ] ) {
pr_warning ( " rejecting duplicate registration of PMU device "
" type %d. " , type ) ;
return - ENOSPC ;
}
2010-02-02 20:23:15 +01:00
2011-06-22 15:32:48 +01:00
pr_info ( " registered new PMU device of type %d \n " , type ) ;
2011-06-22 15:30:51 +01:00
pmu_devices [ type ] = pdev ;
2010-04-29 17:13:24 +01:00
return 0 ;
}
2011-06-22 15:33:55 +01:00
# define OF_MATCH_PMU(_name, _type) { \
. compatible = _name , \
. data = ( void * ) _type , \
}
# define OF_MATCH_CPU(name) OF_MATCH_PMU(name, ARM_PMU_DEVICE_CPU)
static struct of_device_id armpmu_of_device_ids [ ] = {
OF_MATCH_CPU ( " arm,cortex-a9-pmu " ) ,
OF_MATCH_CPU ( " arm,cortex-a8-pmu " ) ,
OF_MATCH_CPU ( " arm,arm1136-pmu " ) ,
OF_MATCH_CPU ( " arm,arm1176-pmu " ) ,
{ } ,
} ;
2011-06-22 15:34:56 +01:00
# define PLAT_MATCH_PMU(_name, _type) { \
. name = _name , \
. driver_data = _type , \
}
# define PLAT_MATCH_CPU(_name) PLAT_MATCH_PMU(_name, ARM_PMU_DEVICE_CPU)
static struct platform_device_id armpmu_plat_device_ids [ ] = {
PLAT_MATCH_CPU ( " arm-pmu " ) ,
{ } ,
} ;
2011-06-22 15:33:55 +01:00
enum arm_pmu_type armpmu_device_type ( struct platform_device * pdev )
{
const struct of_device_id * of_id ;
2011-06-22 15:34:56 +01:00
const struct platform_device_id * pdev_id ;
2011-06-22 15:33:55 +01:00
/* provided by of_device_id table */
if ( pdev - > dev . of_node ) {
of_id = of_match_device ( armpmu_of_device_ids , & pdev - > dev ) ;
BUG_ON ( ! of_id ) ;
return ( enum arm_pmu_type ) of_id - > data ;
}
2011-06-22 15:34:56 +01:00
/* Provided by platform_device_id table */
pdev_id = platform_get_device_id ( pdev ) ;
BUG_ON ( ! pdev_id ) ;
return pdev_id - > driver_data ;
2011-06-22 15:33:55 +01:00
}
2011-06-22 15:30:51 +01:00
static int __devinit armpmu_device_probe ( struct platform_device * pdev )
{
2011-06-22 15:33:55 +01:00
return pmu_register ( pdev , armpmu_device_type ( pdev ) ) ;
2011-06-22 15:30:51 +01:00
}
static struct platform_driver armpmu_driver = {
2010-04-29 17:13:24 +01:00
. driver = {
. name = " arm-pmu " ,
2011-06-22 15:33:55 +01:00
. of_match_table = armpmu_of_device_ids ,
2010-04-29 17:13:24 +01:00
} ,
2011-06-22 15:30:51 +01:00
. probe = armpmu_device_probe ,
2011-06-22 15:34:56 +01:00
. id_table = armpmu_plat_device_ids ,
2010-02-02 20:23:15 +01:00
} ;
2010-04-29 17:13:24 +01:00
static int __init register_pmu_driver ( void )
{
2011-06-22 15:30:51 +01:00
return platform_driver_register ( & armpmu_driver ) ;
2010-04-29 17:13:24 +01:00
}
device_initcall ( register_pmu_driver ) ;
2010-02-02 20:23:15 +01:00
2010-04-29 17:13:24 +01:00
struct platform_device *
reserve_pmu ( enum arm_pmu_type device )
2010-02-02 20:23:15 +01:00
{
2010-04-29 17:13:24 +01:00
struct platform_device * pdev ;
if ( test_and_set_bit_lock ( device , & pmu_lock ) ) {
pdev = ERR_PTR ( - EBUSY ) ;
} else if ( pmu_devices [ device ] = = NULL ) {
clear_bit_unlock ( device , & pmu_lock ) ;
pdev = ERR_PTR ( - ENODEV ) ;
} else {
pdev = pmu_devices [ device ] ;
}
return pdev ;
2010-02-02 20:23:15 +01:00
}
EXPORT_SYMBOL_GPL ( reserve_pmu ) ;
int
2011-06-22 15:30:51 +01:00
release_pmu ( enum arm_pmu_type device )
2010-02-02 20:23:15 +01:00
{
2011-06-22 15:30:51 +01:00
if ( WARN_ON ( ! pmu_devices [ device ] ) )
2010-02-02 20:23:15 +01:00
return - EINVAL ;
2011-06-22 15:30:51 +01:00
clear_bit_unlock ( device , & pmu_lock ) ;
2010-02-02 20:23:15 +01:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( release_pmu ) ;
static int
set_irq_affinity ( int irq ,
unsigned int cpu )
{
# ifdef CONFIG_SMP
int err = irq_set_affinity ( irq , cpumask_of ( cpu ) ) ;
if ( err )
pr_warning ( " unable to set irq affinity (irq=%d, cpu=%u) \n " ,
irq , cpu ) ;
return err ;
# else
2011-02-18 16:21:06 +01:00
return - EINVAL ;
2010-02-02 20:23:15 +01:00
# endif
}
2010-04-29 17:13:24 +01:00
static int
init_cpu_pmu ( void )
2010-02-02 20:23:15 +01:00
{
2011-02-18 16:21:06 +01:00
int i , irqs , err = 0 ;
2010-04-29 17:13:24 +01:00
struct platform_device * pdev = pmu_devices [ ARM_PMU_DEVICE_CPU ] ;
2011-02-18 16:21:06 +01:00
if ( ! pdev )
return - ENODEV ;
irqs = pdev - > num_resources ;
/*
* If we have a single PMU interrupt that we can ' t shift , assume that
* we ' re running on a uniprocessor machine and continue .
*/
if ( irqs = = 1 & & ! irq_can_set_affinity ( platform_get_irq ( pdev , 0 ) ) )
return 0 ;
2010-02-02 20:23:15 +01:00
2011-02-18 16:21:06 +01:00
for ( i = 0 ; i < irqs ; + + i ) {
2010-04-29 17:13:24 +01:00
err = set_irq_affinity ( platform_get_irq ( pdev , i ) , i ) ;
2010-02-02 20:23:15 +01:00
if ( err )
break ;
}
2010-04-29 17:13:24 +01:00
return err ;
}
int
init_pmu ( enum arm_pmu_type device )
{
int err = 0 ;
switch ( device ) {
case ARM_PMU_DEVICE_CPU :
err = init_cpu_pmu ( ) ;
break ;
default :
pr_warning ( " attempt to initialise unknown device %d \n " ,
device ) ;
err = - EINVAL ;
}
2010-02-02 20:23:15 +01:00
return err ;
}
EXPORT_SYMBOL_GPL ( init_pmu ) ;