diff options
Diffstat (limited to 'drivers/misc/tegra-profiler/pl310.c')
-rw-r--r-- | drivers/misc/tegra-profiler/pl310.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/misc/tegra-profiler/pl310.c b/drivers/misc/tegra-profiler/pl310.c new file mode 100644 index 000000000000..010830823817 --- /dev/null +++ b/drivers/misc/tegra-profiler/pl310.c @@ -0,0 +1,317 @@ +/* + * drivers/misc/tegra-profiler/pl310.c + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <asm/hardware/cache-l2x0.h> + +#include <linux/tegra_profiler.h> + +#include "quadd.h" +#include "pl310.h" +#include "debug.h" + +DEFINE_PER_CPU(u32, pl310_prev_val); + +static struct l2x0_context l2x0_ctx; + +static void l2x0_enable_event_counters(u32 event0, u32 event1) +{ + u32 reg_val; + void __iomem *base = l2x0_ctx.l2x0_base; + + /* configure counter0 */ + reg_val = event0; + writel_relaxed(reg_val, base + L2X0_EVENT_CNT0_CFG); + + /* configure counter1 */ + reg_val = event1; + writel_relaxed(reg_val, base + L2X0_EVENT_CNT1_CFG); + + /* enable event counting */ + reg_val = L2X0_EVENT_CNT_ENABLE; + writel_relaxed(reg_val, base + L2X0_EVENT_CNT_CTRL); +} + +static void __maybe_unused l2x0_disable_event_counters(void) +{ + u32 reg_val; + void __iomem *base = l2x0_ctx.l2x0_base; + + /* disable event counting */ + reg_val = 0; + writel_relaxed(reg_val, base + L2X0_EVENT_CNT_CTRL); +} + +static void l2x0_stop_event_counters(void) +{ + void __iomem *base = l2x0_ctx.l2x0_base; + + writel_relaxed(0, base + L2X0_EVENT_CNT_CTRL); + + writel_relaxed(0, base + L2X0_EVENT_CNT0_CFG); + writel_relaxed(0, base + L2X0_EVENT_CNT1_CFG); +} + +static void l2x0_reset_event_counters(void) +{ + u32 reg_val; + void __iomem *base = l2x0_ctx.l2x0_base; + + reg_val = readl_relaxed(base + L2X0_EVENT_CNT_CTRL); + reg_val |= L2X0_EVENT_CNT_RESET_CNT0 | L2X0_EVENT_CNT_RESET_CNT1; + writel_relaxed(reg_val, base + L2X0_EVENT_CNT_CTRL); +} + +static u32 l2x0_read_event_counter(enum quadd_l2x0_counter counter) +{ + u32 reg_val = 0; + void __iomem *base = l2x0_ctx.l2x0_base; + + switch (counter) { + case QUADD_L2X0_COUNTER0: + reg_val = readl_relaxed(base + L2X0_EVENT_CNT0_VAL); + break; + case QUADD_L2X0_COUNTER1: + reg_val = readl_relaxed(base + L2X0_EVENT_CNT1_VAL); + break; + } + + return reg_val; +} + +static void l2x0_enable_perf_event(enum quadd_l2x0_event_type type) +{ + l2x0_reset_event_counters(); + + switch (type) { + case QUADD_L2X0_TYPE_DATA_READ_MISSES: + l2x0_enable_event_counters(L2X0_EVENT_CNT_CFG_DRREQ, + L2X0_EVENT_CNT_CFG_DRHIT); + break; + case QUADD_L2X0_TYPE_DATA_WRITE_MISSES: + l2x0_enable_event_counters(L2X0_EVENT_CNT_CFG_DWREQ, + L2X0_EVENT_CNT_CFG_DWHIT); + break; + case QUADD_L2X0_TYPE_INSTRUCTION_MISSES: + l2x0_enable_event_counters(L2X0_EVENT_CNT_CFG_IRREQ, + L2X0_EVENT_CNT_CFG_IRHIT); + break; + } +} + +static u32 l2x0_read_perf_event(void) +{ + u32 count_req, count_hit, count_miss; + + count_req = l2x0_read_event_counter(QUADD_L2X0_COUNTER0); + count_hit = l2x0_read_event_counter(QUADD_L2X0_COUNTER1); + + count_miss = count_req - count_hit; + if (count_req < count_hit) + return 0; + + return count_miss; +} + +static void l2x0_clear_values(void) +{ + int cpu_id; + for (cpu_id = 0; cpu_id < nr_cpu_ids; cpu_id++) + per_cpu(pl310_prev_val, cpu_id) = 0; +} + +static int l2x0_events_enable(void) +{ + return 0; +} + +static void l2x0_events_disable(void) +{ +} + +static void l2x0_events_start(void) +{ + unsigned long flags; + + if (l2x0_ctx.l2x0_event_type < 0) + return; + + spin_lock_irqsave(&l2x0_ctx.lock, flags); + l2x0_clear_values(); + l2x0_enable_perf_event(l2x0_ctx.l2x0_event_type); + spin_unlock_irqrestore(&l2x0_ctx.lock, flags); + + qm_debug_start_source(QUADD_EVENT_SOURCE_PL310); +} + +static void l2x0_events_stop(void) +{ + unsigned long flags; + + if (l2x0_ctx.l2x0_event_type < 0) + return; + + spin_lock_irqsave(&l2x0_ctx.lock, flags); + l2x0_stop_event_counters(); + l2x0_clear_values(); + spin_unlock_irqrestore(&l2x0_ctx.lock, flags); + + qm_debug_stop_source(QUADD_EVENT_SOURCE_PL310); +} + +static int __maybe_unused l2x0_events_read(struct event_data *events) +{ + unsigned long flags; + + if (l2x0_ctx.l2x0_event_type < 0) { + pr_err_once("pl310 value: %u\n", events[0].val); + return 0; + } + + events[0].event_source = QUADD_EVENT_SOURCE_PL310; + events[0].event_id = l2x0_ctx.event_id; + + spin_lock_irqsave(&l2x0_ctx.lock, flags); + events[0].val = l2x0_read_perf_event(); + spin_unlock_irqrestore(&l2x0_ctx.lock, flags); + + events[0].prev_val = __get_cpu_var(pl310_prev_val); + + __get_cpu_var(pl310_prev_val) = events[0].val; + + qm_debug_read_counter(l2x0_ctx.event_id, events[0].prev_val, + events[0].val); + + return 1; +} + +static int __maybe_unused l2x0_events_read_emulate(struct event_data *events) +{ + static u32 val; + + if (val > 100) + val = 0; + + events[0].event_source = QUADD_EVENT_SOURCE_PL310; + events[0].event_id = QUADD_L2X0_TYPE_DATA_READ_MISSES; + + events[0].val = val; + events[0].prev_val = __get_cpu_var(pl310_prev_val); + + __get_cpu_var(pl310_prev_val) = val; + + val += 10; + + return 1; +} + +static int l2x0_set_events(int *events, int size) +{ + if (!events || size == 0) { + l2x0_ctx.l2x0_event_type = -1; + l2x0_ctx.event_id = -1; + return 0; + } + + if (size != 1) { + pr_err("Error: number of events more than one\n"); + return -ENOSPC; + } + + switch (*events) { + case QUADD_EVENT_TYPE_L2_DCACHE_READ_MISSES: + l2x0_ctx.l2x0_event_type = QUADD_L2X0_TYPE_DATA_READ_MISSES; + break; + case QUADD_EVENT_TYPE_L2_DCACHE_WRITE_MISSES: + l2x0_ctx.l2x0_event_type = QUADD_L2X0_TYPE_DATA_WRITE_MISSES; + break; + case QUADD_EVENT_TYPE_L2_ICACHE_MISSES: + l2x0_ctx.l2x0_event_type = QUADD_L2X0_TYPE_INSTRUCTION_MISSES; + break; + default: + pr_err("Error event: %s\n", quadd_get_event_str(*events)); + return 1; + } + l2x0_ctx.event_id = *events; + + pr_info("Event has been added: id/l2x0: %s/%#x\n", + quadd_get_event_str(*events), l2x0_ctx.l2x0_event_type); + return 0; +} + +static int get_supported_events(int *events) +{ + events[0] = QUADD_EVENT_TYPE_L2_DCACHE_READ_MISSES; + events[1] = QUADD_EVENT_TYPE_L2_DCACHE_WRITE_MISSES; + events[2] = QUADD_EVENT_TYPE_L2_ICACHE_MISSES; + return 3; +} + +static struct quadd_event_source_interface l2x0_int = { + .enable = l2x0_events_enable, + .disable = l2x0_events_disable, + + .start = l2x0_events_start, + .stop = l2x0_events_stop, + +#ifndef QUADD_USE_EMULATE_COUNTERS + .read = l2x0_events_read, +#else + .read = l2x0_events_read_emulate, +#endif + .set_events = l2x0_set_events, + .get_supported_events = get_supported_events, +}; + +struct quadd_event_source_interface *quadd_l2x0_events_init(void) +{ + void __iomem *base; + unsigned long phys_addr; + + l2x0_ctx.l2x0_event_type = -1; + l2x0_ctx.event_id = -1; + + l2x0_ctx.l2x0_base = NULL; + + phys_addr = quadd_get_pl310_phys_addr(); + if (!phys_addr) + return NULL; + + base = ioremap(phys_addr, SZ_4K); + if (base) { + u32 cache_id = readl(base + L2X0_CACHE_ID); + + if ((cache_id & 0xff0003c0) != 0x410000c0) { + iounmap(base); + return NULL; + } + } + + if (!base) + return NULL; + + l2x0_ctx.l2x0_base = base; + + l2x0_clear_values(); + spin_lock_init(&l2x0_ctx.lock); + + pr_debug("pl310 init success, l2x0_base: %p\n", base); + return &l2x0_int; +} |