/* 2008-01-22: File added by Sony Corporation */
/*
 * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @defgroup MXC_Oprofile ARM11 EVTMON Driver for Oprofile
 */

/*!
 * @file op_model_arm11_evtmon.c
 *
 *Based on the op_model_xscale.c driver by author Zwane Mwaikambo
 *
 * @ingroup MXC_Oprofile
 */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/oprofile.h>
#include <linux/interrupt.h>
#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/system.h>

#include "op_counter.h"
#include "op_arm_model.h"
#include "op_model_arm11_core.h"
#include "evtmon_regs.h"


enum { EMC0 = MAX_PMUCOUNTERS, EMC1, EMC2, EMC3, MAX_L2COUNTERS };

#define L2EM_COUNTER(number)	((number) + EMC0)

/* bitmask for used EVTMON counters */
static unsigned int  arm11_evtmon_used;

#ifdef CONFIG_CACHE_L2X0
extern void l2x0_evtbus_enable(void);
extern void l2x0_evtbus_disable(void);
#define arm11_evtbus_enable()	l2x0_evtbus_enable()
#define arm11_evtbus_disable()	l2x0_evtbus_disable()

#else /* CONFIG_CPU_CACHE_L210 */
extern void l2_evtbus_enable(void);
extern void l2_evtbus_disable(void);
#define arm11_evtbus_enable()	l2_evtbus_enable()
#define arm11_evtbus_disable()	l2_evtbus_disable()
#endif

/*!
 * function is used to write the EVTMON counter configuration register.
 */
static int l2em_configure_counter(int nr, int event)
{
	/* The lowest 5 bits of event number are valid */
	event &= 0x1f;

	/* Configure the counter event source */
	__raw_writel(((event << 2) & 0x7c), L2EM_CC(nr));

	/* Set interrupt type and enable/clear mask bit */
	if (event)
		__raw_writel((__raw_readl(L2EM_CC(nr)) | EM_SET_INT),
			     L2EM_CC(nr));
	else
		__raw_writel((__raw_readl(L2EM_CC(nr)) & ~EM_SET_INT),
			     L2EM_CC(nr));

	return 0;
}

/*!
 * function is used to reset value of the EVTMON counters
 */
static void l2em_reset_counter(int nr)
{
	__raw_writel(-(u32)counter_config[L2EM_COUNTER(nr)].count, L2EM_CNT(nr));
}

/*!
 * function is used to check the status of the ARM11 evtmon counters
 */
int arm11_evtmon_setup_ctrs(void)
{
	int i;

	arm11_evtmon_used = 0;

	for (i = 0; i < NUM_EVTMON_COUNTERS; i++) {
		if (counter_config[L2EM_COUNTER(i)].enabled &&
		    counter_config[L2EM_COUNTER(i)].event) {
			l2em_reset_counter(i);
			l2em_configure_counter(i,
				counter_config[L2EM_COUNTER(i)].event);
			arm11_evtmon_used |= (1 << i);
		}
	}

	return 0;
}

/*!
 * function used to start the ARM11 evtmon counters
 */
void arm11_evtmon_stop(void)
{
	unsigned int i;

#ifdef CONFIG_SNSC_OPROFILE_ARM11_EVTMON_ECT_WORKAROUND
	/* Unlock the AHB Interface */
	__raw_writel(UNLOCK_ECT_CODE, ECT_CTI_LOCK);
	/* Disable CTI Logic */
	__raw_writel(0, ECT_CTI_CONTROL);
#endif

	if(!arm11_evtmon_used)
		return ;

	/* Disable the EVTMON */
	__raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL);

	for(i = 0; i < NUM_EVTMON_COUNTERS; i++) {
		if (arm11_evtmon_used & (1 << i))
			l2em_configure_counter(i, 0);
	}

	/* Disable the EVTBUS */
	arm11_evtbus_disable();

}

/*!
 * function used to start the ARM11 evtmon counters
 */
int arm11_evtmon_start(void)
{

#ifdef CONFIG_SNSC_OPROFILE_ARM11_EVTMON_ECT_WORKAROUND
	__raw_writel(ENABLE_CTI_CLOCK, CLKCTL_SET_CTRL);
	/* Unlock the AHB Interface */
	__raw_writel(UNLOCK_ECT_CODE, ECT_CTI_LOCK);
	/* Trigger to Channel Mapping */
	__raw_writel(ECT_CTI_CHAN_2, ECT_CTI_INEN(ECT_CTI_TRIGIN_1));
	/* Channel to triggers mapping */
	__raw_writel(ECT_CTI_CHAN_2, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_2));
	/* Trigger to Channel Mapping */
	__raw_writel(ECT_CTI_CHAN_3, ECT_CTI_INEN(ECT_CTI_TRIGIN_7));
	/* Channel to triggers mapping */
	__raw_writel(ECT_CTI_CHAN_3, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_6));
	/* Enable CTI Logic */
	__raw_writel(ENABLE_ECT, ECT_CTI_CONTROL);
#endif

	if(!arm11_evtmon_used)
		return 0;

	/* Enable the EVTBUS */
	arm11_evtbus_enable();

	/* Enable EVTMON with Edge triggered interrupt of one Clock Cycle */
	__raw_writel((__raw_readl(L2EM_CTRL) |
		      (L2EM_INT_EDGE | L2EM_INT_CLK_CYCLES)), L2EM_CTRL);
	__raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL);

	return 0;
}

