blob: 43a0ae807b5d78c578837037d5bd08940fea22e8 [file] [log] [blame]
/*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
* Jonghwan Choi <Jonghwan Choi@samsung.com>
*
* EXYNOS - PPMU polling support
* This version supports EXYNOS5250 only. This changes bus frequencies.
*
* 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.
*
*/
#include <linux/kernel.h>
#include <linux/debugfs.h>
#include <linux/hrtimer.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <mach/exynos5_bus.h>
#include <mach/map.h>
#include "exynos_ppmu.h"
#include "exynos5_ppmu.h"
#define CREATE_TRACE_POINTS
#include <trace/events/memory_bus.h>
#define FIXED_POINT_OFFSET 8
#define FIXED_POINT_MASK ((1 << FIXED_POINT_OFFSET) - 1)
enum exynos5_ppmu_list {
PPMU_DDR_C,
PPMU_DDR_R1,
PPMU_DDR_L,
PPMU_RIGHT,
PPMU_CPU,
PPMU_END,
};
static DEFINE_SPINLOCK(exynos5_ppmu_lock);
static LIST_HEAD(exynos5_ppmu_handle_list);
struct exynos5_ppmu_handle {
struct list_head node;
struct exynos_ppmu ppmu[PPMU_END];
};
static const char *exynos5_ppmu_name[PPMU_END] = {
[PPMU_DDR_C] = "DDR_C",
[PPMU_DDR_R1] = "DDR_R1",
[PPMU_DDR_L] = "DDR_L",
[PPMU_RIGHT] = "RIGHT",
[PPMU_CPU] = "CPU",
};
static struct exynos_ppmu ppmu[PPMU_END] = {
[PPMU_DDR_C] = {
.hw_base = S5P_VA_PPMU_DDR_C,
},
[PPMU_DDR_R1] = {
.hw_base = S5P_VA_PPMU_DDR_R1,
},
[PPMU_DDR_L] = {
.hw_base = S5P_VA_PPMU_DDR_L,
},
[PPMU_RIGHT] = {
.hw_base = S5P_VA_PPMU_RIGHT,
},
[PPMU_CPU] = {
.hw_base = S5P_VA_PPMU_CPU,
},
};
static struct exynos5_ppmu_handle *exynos5_ppmu_trace_handle;
static void exynos5_ppmu_reset(struct exynos_ppmu *ppmu)
{
unsigned long flags;
void __iomem *ppmu_base = ppmu->hw_base;
/* Reset PPMU */
exynos_ppmu_reset(ppmu_base);
/* Set PPMU Event */
ppmu->event[PPMU_PMNCNT0] = RD_DATA_COUNT;
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT0,
ppmu->event[PPMU_PMNCNT0]);
ppmu->event[PPMU_PMCCNT1] = WR_DATA_COUNT;
exynos_ppmu_setevent(ppmu_base, PPMU_PMCCNT1,
ppmu->event[PPMU_PMCCNT1]);
ppmu->event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
ppmu->event[PPMU_PMNCNT3]);
local_irq_save(flags);
ppmu->reset_time = ktime_get();
/* Start PPMU */
exynos_ppmu_start(ppmu_base);
local_irq_restore(flags);
}
static void exynos5_ppmu_read(struct exynos_ppmu *ppmu)
{
int j;
unsigned long flags;
ktime_t read_time;
ktime_t t;
u32 reg;
void __iomem *ppmu_base = ppmu->hw_base;
local_irq_save(flags);
read_time = ktime_get();
/* Stop PPMU */
exynos_ppmu_stop(ppmu_base);
local_irq_restore(flags);
/* Update local data from PPMU */
ppmu->ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
reg = __raw_readl(ppmu_base + PPMU_FLAG);
ppmu->ccnt_overflow = reg & PPMU_CCNT_OVERFLOW;
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
if (ppmu->event[j] == 0)
ppmu->count[j] = 0;
else
ppmu->count[j] = exynos_ppmu_read(ppmu_base, j);
}
t = ktime_sub(read_time, ppmu->reset_time);
ppmu->ns = ktime_to_ns(t);
}
static void exynos5_ppmu_add(struct exynos_ppmu *to, struct exynos_ppmu *from)
{
int i;
int j;
for (i = 0; i < PPMU_END; i++) {
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++)
to[i].count[j] += from[i].count[j];
to[i].ccnt += from[i].ccnt;
if (to[i].ccnt < from[i].ccnt)
to[i].ccnt_overflow = true;
to[i].ns += from[i].ns;
if (from[i].ccnt_overflow)
to[i].ccnt_overflow = true;
}
}
static void exynos5_ppmu_handle_clear(struct exynos5_ppmu_handle *handle)
{
memset(&handle->ppmu, 0, sizeof(struct exynos_ppmu) * PPMU_END);
}
static void exynos5_ppmu_update(void)
{
int i;
struct exynos5_ppmu_handle *handle;
for (i = 0; i < PPMU_END; i++) {
exynos5_ppmu_read(&ppmu[i]);
exynos5_ppmu_reset(&ppmu[i]);
}
list_for_each_entry(handle, &exynos5_ppmu_handle_list, node)
exynos5_ppmu_add(handle->ppmu, ppmu);
}
static int exynos5_ppmu_get_filter(enum exynos_ppmu_sets filter,
enum exynos5_ppmu_list *start, enum exynos5_ppmu_list *end)
{
switch (filter) {
case PPMU_SET_DDR:
*start = PPMU_DDR_C;
*end = PPMU_DDR_L;
break;
case PPMU_SET_RIGHT:
*start = PPMU_RIGHT;
*end = PPMU_RIGHT;
break;
case PPMU_SET_CPU:
*start = PPMU_CPU;
*end = PPMU_CPU;
break;
default:
return -EINVAL;
}
return 0;
}
int exynos5_ppmu_get_busy(struct exynos5_ppmu_handle *handle,
enum exynos_ppmu_sets filter)
{
unsigned long flags;
int i;
int busy = 0;
int temp;
enum exynos5_ppmu_list start;
enum exynos5_ppmu_list end;
int ret;
ret = exynos5_ppmu_get_filter(filter, &start, &end);
if (ret < 0)
return ret;
spin_lock_irqsave(&exynos5_ppmu_lock, flags);
exynos5_ppmu_update();
for (i = start; i <= end; i++) {
if (handle->ppmu[i].ccnt_overflow) {
busy = -EOVERFLOW;
break;
}
temp = handle->ppmu[i].count[PPMU_PMNCNT3] * 100;
if (handle->ppmu[i].ccnt > 0)
temp /= handle->ppmu[i].ccnt;
if (temp > busy)
busy = temp;
}
exynos5_ppmu_handle_clear(handle);
spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
return busy;
}
void exynos5_ppmu_put(struct exynos5_ppmu_handle *handle)
{
unsigned long flags;
spin_lock_irqsave(&exynos5_ppmu_lock, flags);
list_del(&handle->node);
spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
kfree(handle);
}
struct exynos5_ppmu_handle *exynos5_ppmu_get(void)
{
struct exynos5_ppmu_handle *handle;
unsigned long flags;
handle = kzalloc(sizeof(struct exynos5_ppmu_handle), GFP_KERNEL);
if (!handle)
return NULL;
spin_lock_irqsave(&exynos5_ppmu_lock, flags);
exynos5_ppmu_update();
list_add_tail(&handle->node, &exynos5_ppmu_handle_list);
spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
return handle;
}
void exynos5_ppmu_trace(void)
{
unsigned long flags;
int i;
spin_lock_irqsave(&exynos5_ppmu_lock, flags);
exynos5_ppmu_update();
for (i = 0; i < PPMU_END; i++) {
struct exynos_ppmu *ppmu = &exynos5_ppmu_trace_handle->ppmu[i];
if (!ppmu->ccnt_overflow)
trace_memory_bus_usage(exynos5_ppmu_name[i],
ppmu->count[PPMU_PMNCNT3] * 16ULL,
ppmu->count[PPMU_PMNCNT0] * 16ULL,
ppmu->count[PPMU_PMCCNT1] * 16ULL,
ppmu->ccnt,
ppmu->ns);
}
exynos5_ppmu_handle_clear(exynos5_ppmu_trace_handle);
spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
}
static int exynos5_ppmu_trace_init(void)
{
exynos5_ppmu_trace_handle = exynos5_ppmu_get();
return 0;
}
late_initcall(exynos5_ppmu_trace_init);
static void exynos5_ppmu_debug_compute(struct exynos_ppmu *ppmu,
enum ppmu_counter i, u32 *sat, u32 *freq, u32 *bw)
{
u64 ns = ppmu->ns;
u32 busy = ppmu->count[i];
u32 total = ppmu->ccnt;
u64 s;
u64 f;
u64 b;
s = (u64)busy * 100 * (1 << FIXED_POINT_OFFSET);
s += total / 2;
do_div(s, total);
f = (u64)total * 1000 * (1 << FIXED_POINT_OFFSET);
f += ns / 2;
f = div64_u64(f, ns);
b = (u64)busy * (128 / 8) * 1000 * (1 << FIXED_POINT_OFFSET);
b += ns / 2;
b = div64_u64(b, ns);
*sat = s;
*freq = f;
*bw = b;
}
static void exynos5_ppmu_debug_show_one_counter(struct seq_file *s,
const char *name, const char *type, struct exynos_ppmu *ppmu,
enum ppmu_counter i, u32 *bw_total)
{
u32 sat;
u32 freq;
u32 bw;
exynos5_ppmu_debug_compute(ppmu, i, &sat, &freq, &bw);
seq_printf(s, "%-10s %-10s %4u.%02u MBps %3u.%02u MHz %2u.%02u%%\n",
name, type,
bw >> FIXED_POINT_OFFSET,
(bw & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
freq >> FIXED_POINT_OFFSET,
(freq & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
sat >> FIXED_POINT_OFFSET,
(sat & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET));
*bw_total += bw;
}
static void exynos5_ppmu_debug_show_one(struct seq_file *s,
const char *name, struct exynos_ppmu *ppmu,
u32 *bw_total)
{
exynos5_ppmu_debug_show_one_counter(s, name, "read+write",
ppmu, PPMU_PMNCNT3, &bw_total[PPMU_PMNCNT3]);
exynos5_ppmu_debug_show_one_counter(s, "", "read",
ppmu, PPMU_PMNCNT0, &bw_total[PPMU_PMNCNT0]);
exynos5_ppmu_debug_show_one_counter(s, "", "write",
ppmu, PPMU_PMCCNT1, &bw_total[PPMU_PMCCNT1]);
}
static int exynos5_ppmu_debug_show(struct seq_file *s, void *d)
{
int i;
u32 bw_total[PPMU_PMNCNT_MAX];
struct exynos5_ppmu_handle *handle;
unsigned long flags;
memset(bw_total, 0, sizeof(bw_total));
handle = exynos5_ppmu_get();
msleep(100);
spin_lock_irqsave(&exynos5_ppmu_lock, flags);
exynos5_ppmu_update();
for (i = 0; i < PPMU_CPU; i++)
exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[i],
&handle->ppmu[i], bw_total);
seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "total", "read+write",
bw_total[PPMU_PMNCNT3] >> FIXED_POINT_OFFSET,
(bw_total[PPMU_PMNCNT3] & FIXED_POINT_MASK) *
100 / (1 << FIXED_POINT_OFFSET));
seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "read",
bw_total[PPMU_PMNCNT0] >> FIXED_POINT_OFFSET,
(bw_total[PPMU_PMNCNT0] & FIXED_POINT_MASK) *
100 / (1 << FIXED_POINT_OFFSET));
seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "write",
bw_total[PPMU_PMCCNT1] >> FIXED_POINT_OFFSET,
(bw_total[PPMU_PMCCNT1] & FIXED_POINT_MASK) *
100 / (1 << FIXED_POINT_OFFSET));
seq_printf(s, "\n");
exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[PPMU_CPU],
&ppmu[PPMU_CPU], bw_total);
spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
exynos5_ppmu_put(handle);
return 0;
}
static int exynos5_ppmu_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos5_ppmu_debug_show, inode->i_private);
}
const static struct file_operations exynos5_ppmu_debug_fops = {
.open = exynos5_ppmu_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init exynos5_ppmu_debug_init(void)
{
debugfs_create_file("exynos5_bus", S_IRUGO, NULL, NULL,
&exynos5_ppmu_debug_fops);
return 0;
}
late_initcall(exynos5_ppmu_debug_init);