diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/perf_event_kryo.c | 376 |
2 files changed, 377 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 7105f4993394..ebed5302bdd3 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_MSM_GLADIATOR_HANG_DETECT) += gladiator_hang_detect.o obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_stats.o obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o obj-$(CONFIG_MSM_AVTIMER) += avtimer.o +obj-$(CONFIG_HW_PERF_EVENTS) += perf_event_kryo.o diff --git a/drivers/soc/qcom/perf_event_kryo.c b/drivers/soc/qcom/perf_event_kryo.c new file mode 100644 index 000000000000..4d02fc58ebee --- /dev/null +++ b/drivers/soc/qcom/perf_event_kryo.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "kryo perfevents: " fmt + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/perf_event.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/perf/arm_pmu.h> + +#include <soc/qcom/perf_event_kryo.h> + +#define ARMV8_IDX_CYCLE_COUNTER 0 + +#define COUNT_MASK 0xffffffff + +u32 evt_type_base[3] = {0xd8, 0xe0, 0xe8}; + +static struct arm_pmu *cpu_pmu; + +struct kryo_evt { + /* + * The group_setval field corresponds to the value that the pmresr + * register needs to be set to. This value is calculated from the row + * and column that the event belongs to in the event table + */ + u32 pmresr_setval; + /* + * The PMRESR reg that the event belongs to. + * Kryo has 3 groups of events PMRESR0, 1, 2 + */ + u8 reg; + u8 group; + /* The armv8 defined event code that the Kryo events map to */ + u32 armv8_evt_type; + /* indicates whether the low (0) or high (1) RESR is used */ + int l_h; +}; + +static unsigned int get_kryo_evtinfo(unsigned int evt_type, + struct kryo_evt *evtinfo) +{ + u8 prefix = (evt_type & KRYO_EVT_PREFIX_MASK) >> KRYO_EVT_PREFIX_SHIFT; + u8 reg = (evt_type & KRYO_EVT_REG_MASK) >> KRYO_EVT_REG_SHIFT; + u8 code = (evt_type & KRYO_EVT_CODE_MASK) >> KRYO_EVT_CODE_SHIFT; + u8 group = (evt_type & KRYO_EVT_GROUP_MASK) >> KRYO_EVT_GROUP_SHIFT; + + if ((group > KRYO_MAX_GROUP) || (reg > KRYO_MAX_L1_REG)) + return -EINVAL; + + if (prefix != KRYO_EVT_PREFIX) + return -EINVAL; + + evtinfo->pmresr_setval = code << ((group & 0x3) * 8); + if (group <= 3) { + evtinfo->l_h = RESR_L; + } else { + evtinfo->l_h = RESR_H; + evtinfo->pmresr_setval |= RESR_ENABLE; + } + evtinfo->reg = reg; + evtinfo->group = group; + evtinfo->armv8_evt_type = evt_type_base[reg] | group; + + return evtinfo->armv8_evt_type; +} + +static void kryo_write_pmxevcntcr(u32 val) +{ + asm volatile("msr " pmxevcntcr_el0 ", %0" : : "r" (val)); +} + +static void kryo_write_pmresr(int reg, int l_h, u32 val) +{ + if (reg > KRYO_MAX_L1_REG) { + pr_err("Invalid write to RESR reg %d\n", reg); + return; + } + + if (l_h == RESR_L) { + switch (reg) { + case 0: + asm volatile("msr " pmresr0l_el0 ", %0" : : "r" (val)); + break; + case 1: + asm volatile("msr " pmresr1l_el0 ", %0" : : "r" (val)); + break; + case 2: + asm volatile("msr " pmresr2l_el0 ", %0" : : "r" (val)); + break; + } + } else { + switch (reg) { + case 0: + asm volatile("msr " pmresr0h_el0 ", %0" : : "r" (val)); + break; + case 1: + asm volatile("msr " pmresr1h_el0 ", %0" : : "r" (val)); + break; + case 2: + asm volatile("msr " pmresr2h_el0 ", %0" : : "r" (val)); + break; + } + } +} + +static u32 kryo_read_pmresr(int reg, int l_h) +{ + u32 val; + + if (reg > KRYO_MAX_L1_REG) { + pr_err("Invalid read of RESR reg %d\n", reg); + return 0; + } + + if (l_h == RESR_L) { + switch (reg) { + case 0: + asm volatile("mrs %0, " pmresr0l_el0 : "=r" (val)); + break; + case 1: + asm volatile("mrs %0, " pmresr1l_el0 : "=r" (val)); + break; + case 2: + asm volatile("mrs %0, " pmresr2l_el0 : "=r" (val)); + break; + } + } else { + switch (reg) { + case 0: + asm volatile("mrs %0," pmresr0h_el0 : "=r" (val)); + break; + case 1: + asm volatile("mrs %0," pmresr1h_el0 : "=r" (val)); + break; + case 2: + asm volatile("mrs %0," pmresr2h_el0 : "=r" (val)); + break; + } + } + + return val; +} + +static inline u32 kryo_get_columnmask(u32 g) +{ + u32 mask; + + mask = ~(0xff << ((g & 0x3) * 8)); + if (g == KRYO_MAX_GROUP) + mask |= RESR_ENABLE; + + return mask; +} + +static void kryo_set_resr(struct kryo_evt *evtinfo) +{ + u32 val; + + val = kryo_read_pmresr(evtinfo->reg, evtinfo->l_h) & + kryo_get_columnmask(evtinfo->group); + val |= evtinfo->pmresr_setval; + kryo_write_pmresr(evtinfo->reg, evtinfo->l_h, val); + /* + * If we just wrote the RESR_L, we have to make sure the + * enable bit is set in RESR_H + */ + if (evtinfo->l_h == RESR_L) { + val = kryo_read_pmresr(evtinfo->reg, RESR_H); + if ((val & RESR_ENABLE) == 0) { + val |= RESR_ENABLE; + kryo_write_pmresr(evtinfo->reg, RESR_H, val); + } + } +} + +static void kryo_clear_resrs(void) +{ + int i; + + for (i = 0; i <= KRYO_MAX_L1_REG; i++) { + kryo_write_pmresr(i, RESR_L, 0); + kryo_write_pmresr(i, RESR_H, 0); + } +} + +static void kryo_clear_resr(struct kryo_evt *evtinfo) +{ + u32 val; + + val = kryo_read_pmresr(evtinfo->reg, evtinfo->l_h) & + kryo_get_columnmask(evtinfo->group); + kryo_write_pmresr(evtinfo->reg, evtinfo->l_h, val); +} + +static void kryo_pmu_disable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + unsigned long ev_num; + struct kryo_evt evtinfo; + struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events); + + /* Disable counter and interrupt */ + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + /* Disable counter */ + armv8pmu_disable_counter(idx); + + /* + * Clear pmresr code + * We don't need to set the event if it's a cycle count + */ + if (idx != ARMV8_IDX_CYCLE_COUNTER) { + val = hwc->config_base & KRYO_EVT_MASK; + + if (val & KRYO_EVT_PREFIX_MASK) { + ev_num = get_kryo_evtinfo(val, &evtinfo); + if (ev_num == -EINVAL) + goto kryo_dis_out; + kryo_clear_resr(&evtinfo); + } + } + /* Disable interrupt for this counter */ + armv8pmu_disable_intens(idx); + +kryo_dis_out: + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void kryo_pmu_enable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + unsigned long ev_num; + struct kryo_evt evtinfo; + unsigned long long prev_count = local64_read(&hwc->prev_count); + struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events); + + /* + * Enable counter and interrupt, and set the counter to count + * the event that we're interested in. + */ + raw_spin_lock_irqsave(&events->pmu_lock, flags); + + /* Disable counter */ + armv8pmu_disable_counter(idx); + + val = hwc->config_base & KRYO_EVT_MASK; + + /* set event for ARM-architected events, and filter for CC */ + if (!(val & KRYO_EVT_PREFIX_MASK) || (idx == ARMV8_IDX_CYCLE_COUNTER)) { + armv8pmu_write_evtype(idx, hwc->config_base); + } else { + ev_num = get_kryo_evtinfo(val, &evtinfo); + if (ev_num == -EINVAL) + goto kryo_en_out; + + /* Restore Mode-exclusion bits */ + ev_num |= (hwc->config_base & KRYO_MODE_EXCL_MASK); + + armv8pmu_write_evtype(idx, ev_num); + kryo_write_pmxevcntcr(0); + kryo_set_resr(&evtinfo); + } + + /* Enable interrupt for this counter */ + armv8pmu_enable_intens(idx); + + /* Restore prev val */ + cpu_pmu->write_counter(idx, prev_count & COUNT_MASK); + + /* Enable counter */ + armv8pmu_enable_counter(idx); + +kryo_en_out: + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +#ifdef CONFIG_PERF_EVENTS_USERMODE +static void kryo_init_usermode(void) +{ + u32 val; + + asm volatile("mrs %0, " pmactlr_el0 : "=r" (val)); + val |= PMACTLR_UEN; + asm volatile("msr " pmactlr_el0 ", %0" : : "r" (val)); + asm volatile("mrs %0, pmuserenr_el0" : "=r" (val)); + val |= PMUSERENR_UEN; + asm volatile("msr pmuserenr_el0, %0" : : "r" (val)); +} +#else +static inline void kryo_init_usermode(void) +{ +} +#endif + +static void kryo_pmu_reset(void *info) +{ + u32 idx, nb_cnt = cpu_pmu->num_events; + + /* Stop all counters and their interrupts */ + for (idx = ARMV8_IDX_CYCLE_COUNTER; idx < nb_cnt; ++idx) { + armv8pmu_disable_counter(idx); + armv8pmu_disable_intens(idx); + } + + /* Clear all pmresrs */ + kryo_clear_resrs(); + + kryo_init_usermode(); + + /* Reset irq status reg */ + armv8pmu_getreset_flags(); + + /* Reset all counters */ + armv8pmu_pmcr_write(ARMV8_PMCR_P | ARMV8_PMCR_C); +} + +/* NRCCG format for perf RAW codes. */ +PMU_FORMAT_ATTR(prefix, "config:16-19"); +PMU_FORMAT_ATTR(reg, "config:12-15"); +PMU_FORMAT_ATTR(code, "config:4-11"); +PMU_FORMAT_ATTR(grp, "config:0-3"); + +static struct attribute *kryo_ev_formats[] = { + &format_attr_prefix.attr, + &format_attr_reg.attr, + &format_attr_code.attr, + &format_attr_grp.attr, + NULL, +}; + +/* + * Format group is essential to access PMU from userspace + * via its .name field. + */ +static struct attribute_group kryo_pmu_format_group = { + .name = "format", + .attrs = kryo_ev_formats, +}; + +static const struct attribute_group *kryo_pmu_attr_grps[] = { + &kryo_pmu_format_group, + NULL, +}; + +int kryo_pmu_init(struct arm_pmu *armv8_pmu) +{ + pr_info("CPU pmu for kryo-pmuv3 detected\n"); + + cpu_pmu = armv8_pmu; + + cpu_pmu->enable = kryo_pmu_enable_event; + cpu_pmu->disable = kryo_pmu_disable_event; + cpu_pmu->reset = kryo_pmu_reset; + cpu_pmu->pmu.attr_groups = kryo_pmu_attr_grps; + cpu_pmu->name = "qcom,kryo-pmuv3"; + + kryo_clear_resrs(); + + return 0; +} + |