summaryrefslogtreecommitdiff
path: root/arch/arm/oprofile/op_model_arm11.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/oprofile/op_model_arm11.c')
-rw-r--r--arch/arm/oprofile/op_model_arm11.c477
1 files changed, 477 insertions, 0 deletions
diff --git a/arch/arm/oprofile/op_model_arm11.c b/arch/arm/oprofile/op_model_arm11.c
new file mode 100644
index 000000000000..573269e36a48
--- /dev/null
+++ b/arch/arm/oprofile/op_model_arm11.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2005-2009 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 Driver for Oprofile
+ */
+
+/*!
+ * @file op_model_arm11.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 <mach/hardware.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include "op_counter.h"
+#include "op_arm_model.h"
+#include "evtmon_regs.h"
+
+/*!
+ * defines used in ARM11 performance unit
+ */
+#define PMU_ENABLE 0x001 /* Enable counters */
+#define EVTMON_ENABLE 0x001 /* Enable EVTMON */
+#define PMN_RESET 0x002 /* Reset event counters */
+#define CCNT_RESET 0x004 /* Reset clock counter */
+#define PMU_RESET (CCNT_RESET | PMN_RESET)
+#define PMU_CNT64 0x008 /* Make CCNT count every 64th cycle */
+
+#define PMU_FLAG_CR0 0x080
+#define PMU_FLAG_CR1 0x100
+#define PMU_FLAG_CC 0x200
+
+/*!
+ * Different types of events that can be counted by the ARM11 PMU
+ * as used by Oprofile userspace.
+ */
+#define EVT_ICACHE_MISS 0x00
+#define EVT_STALL_INSTR 0x01
+#define EVT_DATA_STALL 0x02
+#define EVT_ITLB_MISS 0x03
+#define EVT_DTLB_MISS 0x04
+#define EVT_BRANCH 0x05
+#define EVT_BRANCH_MISS 0x06
+#define EVT_INSTRUCTION 0x07
+#define EVT_DCACHE_FULL_STALL_CONTIG 0x09
+#define EVT_DCACHE_ACCESS 0x0A
+#define EVT_DCACHE_MISS 0x0B
+#define EVT_DCACE_WRITE_BACK 0x0C
+#define EVT_PC_CHANGED 0x0D
+#define EVT_TLB_MISS 0x0F
+#define EVT_BCU_REQUEST 0x10
+#define EVT_BCU_FULL 0x11
+#define EVT_BCU_DRAIN 0x12
+#define EVT_ETMEXTOT0 0x20
+#define EVT_ETMEXTOT1 0x21
+/* EVT_CCNT is not hardware defined */
+#define EVT_CCNT 0xFE
+#define EVT_INCREMENT 0xFF
+#define EVT_UNUSED 0x100
+
+#define ECT_WORKAROUND
+
+#define COUNTER_MSB 0x80000000
+#define ENABLE_L2CACHE 0x1
+#define ENABLE_EVTBUS 0x100000
+#define PMU_OVERFLOWBIT_MASK 0x700
+#define VAR_NUM 0x0
+#define REV_NUM 0x2
+
+#ifdef ECT_WORKAROUND
+#define ENABLE_CTI_CLOCK 0x00000020
+#define UNLOCK_ECT_CODE 0x0ACCE550
+#define ECT_CTI_CHAN_2 0x4
+#define ECT_CTI_CHAN_3 0x8
+#define ECT_CTI_TRIGIN_1 1
+#define ECT_CTI_TRIGIN_7 7
+#define ECT_CTI_TRIGOUT_2 2
+#define ECT_CTI_TRIGOUT_6 6
+#define ENABLE_ECT 0x1
+#define ACK_TRIG_OUT_2 0x4
+#define EM_SET_INT L2EM_ENABLE_CNTINCRINT
+#define EVENT_OVERFLOW_INT INT_ECT
+#else
+#define EVENT_OVERFLOW_INT ARM11_PMU_IRQ
+#define EM_SET_INT L2EM_ENABLE_OVERFLOWINT
+#endif
+
+struct pmu_counter {
+ volatile unsigned long ovf;
+ unsigned long reset_counter;
+};
+
+static unsigned int r0p2_or_older_core;
+enum { CCNT, PMN0, PMN1, MAX_PMUCOUNTERS };
+enum { EMC0 = MAX_PMUCOUNTERS, EMC1, EMC2, EMC3, MAX_L2COUNTERS };
+
+static struct pmu_counter results[MAX_L2COUNTERS];
+
+struct pmu_type {
+ int id;
+ char *name;
+ int num_counters;
+ unsigned int int_enable;
+ unsigned int cnt_ovf[MAX_L2COUNTERS];
+ unsigned int int_mask[MAX_L2COUNTERS];
+};
+
+static struct pmu_type pmu_parms[] = {
+ {
+ .id = 0,
+ .name = "arm/arm11",
+ .num_counters = MAX_L2COUNTERS,
+ .int_mask = {[PMN0] = 0x10,[PMN1] = 0x20,
+ [CCNT] = 0x40,[EMC0] = 0x800,[EMC1] = 0x400,[EMC2] =
+ 0x200,[EMC3] = 0x100},
+ .cnt_ovf = {[CCNT] = 0x400,[PMN0] = 0x100,
+ [PMN1] = 0x200,[EMC0] = 0x1,[EMC1] = 0x2,[EMC2] =
+ 0x4,[EMC3] = 0x8},
+ },
+};
+
+static struct pmu_type *pmu;
+
+extern void l2_evtbus_enable(void);
+extern void l2_evtbus_disable(void);
+
+/*!
+ * function is used to write the EVTMON counter configuration register.
+ */
+static int l2em_configure_counter(int nr, int type)
+{
+ /* Configure the counter event source */
+ __raw_writel(((type << 2) & 0x7c), L2EM_CC(nr));
+ if (type)
+ __raw_writel((__raw_readl(L2EM_CC(nr)) | EM_SET_INT),
+ L2EM_CC(nr));
+
+ return 0;
+}
+
+/*!
+ * function is used to write the EVTMON counters
+ */
+static void write_l2counter(int nr, u32 val)
+{
+ __raw_writel(val, L2EM_CNT(nr));
+}
+
+/*!
+ * function is used to write the control register for the ARM11 performance counters
+ */
+static void write_pmnc(u32 val)
+{
+ pr_debug("PMC value written is %#08x\n", val);
+ __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 0"::"r"(val));
+}
+
+/*!
+ * function is used to read the control register for the ARM11 performance counters
+ */
+static u32 read_pmnc(void)
+{
+ u32 val;
+ pr_debug("In function %s\n", __FUNCTION__);
+ __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 0":"=r"(val));
+ pr_debug("PMC value read is %#08x\n", val);
+ return val;
+}
+
+/*!
+ * function is used to read the ARM11 performance counters
+ */
+static u32 read_counter(int counter)
+{
+ u32 val = 0;
+ pr_debug("In function %s\n", __FUNCTION__);
+
+ switch (counter) {
+ case CCNT:
+ __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 1":"=r"(val));
+ break;
+ case PMN0:
+ __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 2":"=r"(val));
+ break;
+ case PMN1:
+ __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 3":"=r"(val));
+ break;
+ }
+
+ pr_debug("counter %d value read is %#08x\n", counter, val);
+ return val;
+}
+
+/*!
+ * function is used to write to the ARM11 performance counters
+ */
+static void write_counter(int counter, u32 val)
+{
+ pr_debug("counter %d value written is %#08x\n", counter, val);
+
+ switch (counter) {
+ case CCNT:
+ __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 1": :"r"(val));
+ break;
+ case PMN0:
+ __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 2": :"r"(val));
+ break;
+ case PMN1:
+ __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 3": :"r"(val));
+ break;
+ }
+}
+
+/*!
+ * function is used to check the status of the ARM11 performance counters
+ */
+static int arm11_setup_ctrs(void)
+{
+ u32 pmnc = 0;
+ int i;
+
+ for (i = CCNT; i < MAX_L2COUNTERS; i++) {
+ if (counter_config[i].enabled)
+ continue;
+ counter_config[i].event = EVT_UNUSED;
+ }
+
+ if (counter_config[PMN0].enabled)
+ pmnc |= (counter_config[PMN0].event << 20);
+
+ if (counter_config[PMN1].enabled)
+ pmnc |= (counter_config[PMN1].event << 12);
+
+ pr_debug("arm11_setup_ctrs: pmnc: %#08x\n", pmnc);
+ write_pmnc(pmnc);
+
+ for (i = CCNT; i < MAX_L2COUNTERS; i++) {
+ if (counter_config[i].event == EVT_UNUSED) {
+ counter_config[i].event = 0;
+ pmu->int_enable &= ~pmu->int_mask[i];
+ pr_debug
+ ("arm11_setup_ctrs: The counter event is %d for counter%d\n",
+ counter_config[i].event, i);
+ continue;
+ }
+
+ results[i].reset_counter = counter_config[i].count;
+ if (i < MAX_PMUCOUNTERS)
+ write_counter(i, -(u32) counter_config[i].count);
+ else {
+ write_l2counter(i - EMC0,
+ -(u32) counter_config[i].count);
+ l2em_configure_counter(i - EMC0,
+ counter_config[i].event);
+ }
+ pmu->int_enable |= pmu->int_mask[i];
+ pr_debug
+ ("arm11_setup_ctrs: The values of int mask and enables are %x, %x\n",
+ pmu->int_mask[i], pmu->int_enable);
+
+ pr_debug("arm11_setup_ctrs: counter%d %#08x from %#08lx\n", i,
+ read_counter(i), counter_config[i].count);
+ }
+
+ return 0;
+}
+
+/*!
+ * function is the interrupt service handler for the ARM11 performance counters
+ */
+static irqreturn_t arm11_pmu_interrupt(int irq, void *arg, struct pt_regs *regs)
+{
+ int i;
+ u32 pmnc, emcs;
+
+ /* Disable L2_EVTMON */
+ emcs = __raw_readl(L2EM_STAT);
+ __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL);
+
+ /* Disable ARM11 PMU while retaining interrupts and overflow bits */
+ pmnc = read_pmnc();
+ pmnc &= ~(PMU_ENABLE | PMU_OVERFLOWBIT_MASK);
+ write_pmnc(pmnc);
+
+ /* Read the overflow flag bits */
+ pmnc = read_pmnc();
+
+#ifdef ECT_WORKAROUND
+ for (i = CCNT; i < MAX_PMUCOUNTERS; i++) {
+#else
+ for (i = CCNT; i < MAX_L2COUNTERS; i++) {
+#endif
+ /* Process the counters only if respective overflow interrupt is enabled */
+ if (!(pmu->int_mask[i] & pmu->int_enable)) {
+ continue;
+ }
+
+ /* As per ARM11 errata ARM11 cores with revision less than or equal to R0P2
+ * have known bug i.e., missing overflow interrupt for two events(event
+ * no 0x7 and 0x22) due to double increament for cycle. In this case we will
+ * discard the sample as the pc value belongs to different interrupt.
+ */
+ if (r0p2_or_older_core && !(pmnc & pmu->cnt_ovf[i])
+ && !(read_counter(i) & COUNTER_MSB)) {
+ write_counter(i, -(u32) (results[i].reset_counter));
+ }
+
+ /* Check for the overflowed counter by checking set overflow flag bits */
+ if (!(pmnc & pmu->cnt_ovf[i]) && !(emcs & pmu->cnt_ovf[i])) {
+ continue;
+ }
+
+ /* Reload the overflowed counter with preset value and
+ * add the sample for respective event.
+ */
+ pr_debug("arm11_pmu_interrupt: writing to file\n");
+ if (i < MAX_PMUCOUNTERS)
+ write_counter(i, -(u32) results[i].reset_counter);
+ else
+ write_l2counter(i - EMC0,
+ -(u32) counter_config[i].count);
+
+ oprofile_add_sample(regs, i);
+ }
+
+ /* Clear overflow flags */
+ write_pmnc(pmnc);
+
+#ifdef ECT_WORKAROUND
+ /*
+ * If ECTTRIGOUT signal is interrupt it should be acknowledged
+ * until trigger is off.
+ */
+ while (__raw_readl(ECT_CTI_TRIGOUTSTATUS) & ECT_CTI_CHAN_2)
+ __raw_writel(ACK_TRIG_OUT_2, ECT_CTI_INTACK);
+#endif
+
+ /* Re-enable ARM11 PMU */
+ pmnc |= PMU_ENABLE;
+ write_pmnc(pmnc);
+
+ /* Re-enable L2_EVTMON */
+ __raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL);
+
+ return IRQ_HANDLED;
+}
+
+/*!
+ * function used to start the ARM11 performance counters
+ */
+static void arm11_pmu_stop(void)
+{
+ u32 pmnc = read_pmnc();
+
+ pmnc &= ~PMU_ENABLE;
+ write_pmnc(pmnc);
+ /* Disable the EVTMON */
+ __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL);
+
+ /* Disable the EVTBUS */
+ l2_evtbus_disable();
+
+ free_irq(EVENT_OVERFLOW_INT, results);
+}
+
+/*!
+ * function used to start the ARM11 performance counters
+ */
+static int arm11_pmu_start(void)
+{
+ int ret;
+ u32 pmnc = read_pmnc();
+
+ ret = request_irq(EVENT_OVERFLOW_INT, arm11_pmu_interrupt, SA_INTERRUPT,
+ "ARM11 PMU", (void *)results);
+ pr_debug("requested IRQ\n");
+
+ if (ret < 0) {
+ printk(KERN_ERR
+ "oprofile: unable to request IRQ%d for ARM11 PMU\n",
+ ARM11_PMU_IRQ);
+ return ret;
+ }
+
+ /* Enable the EVTBUS */
+ l2_evtbus_enable();
+
+#ifdef 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
+ pmnc |= pmu->int_enable;
+ pmnc |= PMU_ENABLE;
+
+ write_pmnc(pmnc);
+ pr_debug("arm11_pmu_start: pmnc: %#08x mask: %08x\n", pmnc,
+ pmu->int_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;
+}
+
+/*!
+ * function detect the ARM11 performance counters
+ */
+static int arm11_detect_pmu(void)
+{
+ int ret = 0;
+ u32 id, rev;
+
+ id = (read_cpuid(CPUID_ID) >> 0x10) & 0xF;
+
+ switch (id) {
+ case 7:
+ pmu = &pmu_parms[0];
+ rev = read_cpuid(CPUID_ID);
+ /* Check if the ARM11 core is less than or equal to R0P2 */
+ if ((((rev >> 0x14) & 0xF) == VAR_NUM)
+ && (((rev & 0xF) <= REV_NUM))) {
+ r0p2_or_older_core = 1;
+ }
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+
+ if (!ret) {
+ op_arm_spec.name = pmu->name;
+ op_arm_spec.num_counters = pmu->num_counters;
+ pr_debug("arm11_detect_pmu: detected %s PMU\n", pmu->name);
+ }
+
+ return ret;
+}
+
+struct op_arm_model_spec op_arm_spec = {
+ .init = arm11_detect_pmu,
+ .setup_ctrs = arm11_setup_ctrs,
+ .start = arm11_pmu_start,
+ .stop = arm11_pmu_stop,
+};