summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMayuresh Kulkarni <mkulkarni@nvidia.com>2011-03-09 16:00:12 +0530
committerVarun Colbert <vcolbert@nvidia.com>2011-03-14 17:30:53 -0800
commit4035006718b665c9e0bda2e4dc0c1b845bd9f7ca (patch)
tree28df7fbcaab25e31fbc3b2875741d09b4c034933
parent8bc4f710e981d53a9bd161c6c054241231e09149 (diff)
[ARM]: tegra: add support for mc/emc bandwidth statistics
provide a mechanism to perform statistical sampling of the memory controller usage on a client-by-client basis, and report the log through sysfs original work by: gking@nvidia.com Change-Id: I5d7f357af0353c55e14026b036ccf7e448df643b Signed-off-by: Mayuresh Kulkarni <mkulkarni@nvidia.com> Reviewed-on: http://git-master/r/20383 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/Kconfig8
-rw-r--r--arch/arm/mach-tegra/Makefile1
-rw-r--r--arch/arm/mach-tegra/tegra2_mc.c1149
-rw-r--r--arch/arm/mach-tegra/tegra2_mc.h271
4 files changed, 1429 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 9ad8c961c571..5d0bd6afe765 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -125,3 +125,11 @@ config TEGRA_CLOCK_DEBUG_WRITE
bool "Enable debugfs write access to clock tree"
depends on DEBUG_FS
default n
+
+config TEGRA_MC_PROFILE
+ tristate "Enable profiling memory controller utilization"
+ default n
+ help
+ When enabled, provides a mechanism to perform statistical
+ sampling of the memory controller usage on a client-by-client
+ basis, and report the log through sysfs.
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 3dc53f8ba8e1..4cc2ad82a796 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_CPU_FREQ) += cpu-tegra.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
obj-$(CONFIG_TEGRA_IOVMM) += iovmm.o
obj-$(CONFIG_TEGRA_IOVMM_GART) += iovmm-gart.o
+obj-$(CONFIG_TEGRA_MC_PROFILE) += tegra2_mc.o
obj-y += nv/
diff --git a/arch/arm/mach-tegra/tegra2_mc.c b/arch/arm/mach-tegra/tegra2_mc.c
new file mode 100644
index 000000000000..dd49380bbed3
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra2_mc.c
@@ -0,0 +1,1149 @@
+/*
+ * arch/arm/mach-tegra/tegra2_mc.c
+ *
+ * Memory controller bandwidth profiling interface
+ *
+ * Copyright (c) 2009-2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/kobject.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/sysdev.h>
+#include <linux/ktime.h>
+#include <linux/hrtimer.h>
+#include <linux/parser.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <mach/iomap.h>
+#include <asm/uaccess.h>
+
+#include "tegra2_mc.h"
+
+static void stat_start(void);
+static void stat_stop(void);
+static void stat_log(void);
+
+static struct hrtimer sample_timer;
+
+#define MC_COUNTER_INITIALIZER() \
+ { \
+ .enabled = false, \
+ .reschedule = false, \
+ .period = 10, \
+ .mode = FILTER_CLIENT, \
+ .address_low = 0, \
+ .address_length_1 = 0xfffffffful, \
+ .address_window_size_1 = PAGE_SIZE, \
+ .client_number = 0, \
+ }
+
+static struct tegra_mc_counter mc_counter0 = MC_COUNTER_INITIALIZER();
+static struct tegra_mc_counter mc_counter1 = MC_COUNTER_INITIALIZER();
+static struct tegra_mc_counter emc_llp_counter = MC_COUNTER_INITIALIZER();
+
+/* /sys/devices/system/tegra_mc */
+static bool sample_enable = SAMPLE_ENABLE_DEFAULT;
+static u16 sample_quantum = SAMPLE_QUANTUM_DEFAULT;
+static u8 sample_log[SAMPLE_LOG_SIZE];
+
+static DEFINE_SPINLOCK(sample_enable_lock);
+static DEFINE_SPINLOCK(sample_log_lock);
+
+static u8 *sample_log_wptr = sample_log, *sample_log_rptr = sample_log;
+static int sample_log_size = SAMPLE_LOG_SIZE - 1;
+
+static bool sampling(void)
+{
+ bool ret;
+
+ spin_lock_bh(&sample_enable_lock);
+ ret = (sample_enable == true)? true : false;
+ spin_unlock_bh(&sample_enable_lock);
+
+ return ret;
+}
+
+static struct sysdev_class tegra_mc_sysclass = {
+ .name = "tegra_mc",
+};
+
+static ssize_t tegra_mc_enable_show(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", sample_enable);
+}
+
+static ssize_t tegra_mc_enable_store(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int value, i;
+ struct tegra_mc_counter *counters[] = {
+ &mc_counter0,
+ &mc_counter1,
+ &emc_llp_counter
+ };
+
+ sscanf(buf, "%d", &value);
+
+ if (value == 0 || value == 1)
+ sample_enable = value;
+ else
+ return -EINVAL;
+
+ if (!sample_enable) {
+ stat_stop();
+ hrtimer_cancel(&sample_timer);
+ return count;
+ }
+
+ hrtimer_cancel(&sample_timer);
+
+ /* we need to initialize variables that change during sampling */
+ sample_log_wptr = sample_log_rptr = sample_log;
+ sample_log_size = SAMPLE_LOG_SIZE - 1;
+
+ for (i = 0; i < ARRAY_SIZE(counters); i++) {
+ struct tegra_mc_counter *c = counters[i];
+
+ if (!c->enabled)
+ continue;
+
+ c->current_address_low = c->address_low;
+ c->current_address_high = c->address_low;
+ c->address_range_change = (c->mode == FILTER_ADDR);
+ if (c->address_range_change)
+ c->current_address_high += c->address_window_size_1;
+ else
+ c->current_address_high += c->address_length_1;
+
+ c->current_client = 0;
+ c->sample_count = 0;
+ }
+
+ stat_start();
+
+ hrtimer_start(&sample_timer,
+ ktime_add_ns(ktime_get(), (u64)sample_quantum * 1000000),
+ HRTIMER_MODE_ABS);
+
+ return count;
+}
+
+static ssize_t tegra_mc_log_show(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr, char *buf)
+{
+ int index = 0, count = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sample_log_lock, flags);
+
+ while (sample_log_rptr != sample_log_wptr) {
+ if (sample_log_rptr < sample_log_wptr) {
+ count = sample_log_wptr - sample_log_rptr;
+ memcpy(buf + index, sample_log_rptr, count);
+ sample_log_rptr = sample_log_wptr;
+ sample_log_size += count;
+ } else {
+ count = SAMPLE_LOG_SIZE -
+ (sample_log_rptr - sample_log);
+ memcpy(buf + index, sample_log_rptr, count);
+ sample_log_rptr = sample_log;
+ sample_log_size += count;
+ }
+ index += count;
+ }
+
+ spin_unlock_irqrestore(&sample_log_lock, flags);
+
+ return index;
+}
+
+static ssize_t tegra_mc_log_store(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr,
+ const char *buf, size_t count)
+{
+ return -EPERM;
+}
+
+static ssize_t tegra_mc_quantum_show(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", sample_quantum);
+}
+
+static ssize_t tegra_mc_quantum_store(struct sysdev_class *class,
+ struct sysdev_class_attribute *attr,
+ const char *buf, size_t count)
+{
+ int value;
+
+ if (sampling())
+ return -EINVAL;
+
+ sscanf(buf, "%d", &value);
+ sample_quantum = value;
+
+ return count;
+}
+
+#define TEGRA_MC_EXPAND(_attr,_mode) \
+ static SYSDEV_CLASS_ATTR( \
+ _attr, _mode, tegra_mc_##_attr##_show, tegra_mc_##_attr##_store);
+
+#define TEGRA_MC_ATTRIBUTES(_attr1,_mode1,_attr2,_mode2,_attr3,_mode3) \
+ TEGRA_MC_EXPAND(_attr1,_mode1) \
+ TEGRA_MC_EXPAND(_attr2,_mode2) \
+ TEGRA_MC_EXPAND(_attr3,_mode3)
+
+TEGRA_MC_ATTRIBUTES(enable,0666,log,0444,quantum,0666)
+
+#undef TEGRA_MC_EXPAND
+
+#define TEGRA_MC_EXPAND(_attr,_mode) \
+ &attr_##_attr,
+
+static struct sysdev_class_attribute *tegra_mc_attrs[] = {
+ TEGRA_MC_ATTRIBUTES(enable,0666,log,0444,quantum,0666)
+ NULL
+};
+
+/* /sys/devices/system/tegra_mc/client */
+static bool tegra_mc_client_0_enabled = CLIENT_ENABLED_DEFAULT;
+static u8 tegra_mc_client_0_on_schedule_buffer[CLIENT_ON_SCHEDULE_LENGTH];
+static struct kobject *tegra_mc_client_kobj, *tegra_mc_client_0_kobj;
+
+struct match_mode {
+ const char *name;
+ int mode;
+};
+
+static const struct match_mode mode_list[] = {
+ [0] = {
+ .name = "none",
+ .mode = FILTER_NONE,
+ },
+ [1] = {
+ .name = "address",
+ .mode = FILTER_ADDR,
+ },
+ [2] = {
+ .name = "client",
+ .mode = FILTER_CLIENT,
+ },
+};
+
+static int tegra_mc_parse_mode(const char* str) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mode_list); i++) {
+ if (!strncmp(str, mode_list[i].name, strlen(mode_list[i].name)))
+ return mode_list[i].mode;
+ }
+ return -EINVAL;
+}
+
+static int tegra_mc_client_parse(const char *buf, size_t count,
+ tegra_mc_counter_t *counter0, tegra_mc_counter_t *counter1,
+ tegra_mc_counter_t *llp)
+{
+ char *options, *p, *ptr;
+ tegra_mc_counter_t *counter;
+ substring_t args[MAX_OPT_ARGS];
+ enum {
+ opt_period,
+ opt_mode,
+ opt_client,
+ opt_address_low,
+ opt_address_length,
+ opt_address_window_size,
+ opt_err,
+ };
+ const match_table_t tokens = {
+ {opt_period, "period=%s"},
+ {opt_mode, "mode=%s"},
+ {opt_client, "client=%s"},
+ {opt_address_low, "address_low=%s"},
+ {opt_address_length, "address_length=%s"},
+ {opt_address_window_size, "address_window_size=%s"},
+ {opt_err, NULL},
+ };
+ int ret = 0, i, token, client_number;
+ bool aggregate = false;
+ int period, *client_ids, mode;
+ bool fperiod = false, fmode = false, fclient = false;
+ u64 address_low = 0;
+ u64 address_length = 1ull<<32;
+ u64 address_window_size = PAGE_SIZE;
+
+ client_ids = kmalloc(sizeof(int) * (MC_COUNTER_CLIENT_SIZE + 1),
+ GFP_KERNEL);
+ if (!client_ids)
+ return -ENOMEM;
+
+ options = kstrdup(buf, GFP_KERNEL);
+ if (!options) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ while ((p = strsep(&options, " ")) != NULL) {
+ if (!*p)
+ continue;
+
+ pr_debug("\t %s\n", p);
+
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case opt_period:
+ if (match_int(&args[0], &period) || period<=0) {
+ ret = -EINVAL;
+ goto end;
+ }
+ fperiod = true;
+ break;
+
+ case opt_mode:
+ mode = tegra_mc_parse_mode(args[0].from);
+ if (mode<0) {
+ ret = mode;
+ goto end;
+ }
+ fmode = true;
+ break;
+
+ case opt_client:
+ client_ids[0] = 0;
+
+ ptr = get_options(args[0].from,
+ MC_COUNTER_CLIENT_SIZE+1 , client_ids);
+
+ if (client_ids[0] <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ for (i = 1; i <= client_ids[0]; i++) {
+ if (client_ids[i] < MC_STAT_END)
+ continue;
+
+ if ((client_ids[i] != MC_STAT_AGGREGATE) ||
+ client_ids[0] != 1) {
+ ret = -EINVAL;
+ goto end;
+ } else
+ aggregate = true;
+ }
+
+ client_number = client_ids[0];
+ fclient = true;
+ break;
+
+ case opt_address_low:
+ address_low = simple_strtoull(args[0].from, NULL, 0);
+ break;
+
+ case opt_address_length:
+ address_length = simple_strtoull(args[0].from, NULL, 0);
+ break;
+
+ case opt_address_window_size:
+ address_window_size = simple_strtoull(args[0].from,
+ NULL, 0);
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+ }
+
+ if (!fmode || !fclient || (mode == FILTER_CLIENT && aggregate)) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ address_low &= PAGE_MASK;
+ address_length += PAGE_SIZE-1;
+ address_length &= ~((1ull << PAGE_SHIFT)-1ull);
+
+ address_window_size += PAGE_SIZE-1;
+ address_window_size &= ~((1ull << PAGE_SHIFT)-1ull);
+
+ if (mode == FILTER_CLIENT) {
+ counter = counter0;
+ counter->reschedule = (client_number>1);
+ counter->client_number = client_number;
+ llp->enabled = false;
+ counter1->enabled = false;
+ for (i = 1; i <= client_number && i < MC_COUNTER_CLIENT_SIZE; i++)
+ counter->clients[i - 1] = client_ids[i];
+ } else if (mode == FILTER_ADDR || mode == FILTER_NONE) {
+ if (aggregate) {
+ counter = counter1;
+ llp->enabled = true;
+ counter0->enabled = false;
+ } else {
+ counter = counter0;
+ counter1->enabled = false;
+ llp->enabled = false;
+ }
+ counter->client_number = 1;
+ counter->clients[0] = client_ids[1];
+ counter->reschedule = (mode != FILTER_NONE);
+ } else {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ counter->mode = mode;
+ counter->enabled = true;
+ counter->address_low = (u32)address_low;
+ counter->address_length_1 = (u32)(address_length-1);
+ counter->address_window_size_1 = (u32)(address_window_size-1);
+
+ if (llp->enabled) {
+ llp->mode = counter->mode;
+ llp->reschedule = counter->reschedule;
+ llp->period = counter->period;
+ llp->address_low = counter->address_low;
+ llp->address_length_1 = counter->address_length_1;
+ llp->address_window_size_1 = counter->address_window_size_1;
+ }
+
+end:
+ if (options)
+ kfree(options);
+ if (client_ids)
+ kfree(client_ids);
+
+ return ret;
+}
+
+static ssize_t tegra_mc_client_0_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ if (strcmp(attr->attr.name, "enable") == 0)
+ return sprintf(buf, "%d\n", tegra_mc_client_0_enabled);
+ else if (strcmp(attr->attr.name, "on_schedule") == 0)
+ return sprintf(buf, "%s", tegra_mc_client_0_on_schedule_buffer);
+ else
+ return -EINVAL;
+}
+
+static ssize_t tegra_mc_client_0_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int value;
+
+ if (sampling())
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "enable") == 0) {
+ sscanf(buf, "%d\n", &value);
+ if (value == 0 || value == 1)
+ tegra_mc_client_0_enabled = value;
+ else
+ return -EINVAL;
+
+ return count;
+ } else if (strcmp(attr->attr.name, "on_schedule") == 0) {
+ if (tegra_mc_client_parse(buf, count,
+ &mc_counter0, &mc_counter1,
+ &emc_llp_counter)== 0) {
+
+ strncpy(tegra_mc_client_0_on_schedule_buffer,
+ buf, count);
+
+ return count;
+ } else
+ return -EINVAL;
+ } else
+ return -EINVAL;
+}
+
+static struct kobj_attribute tegra_mc_client_0_enable =
+ __ATTR(enable, 0660, tegra_mc_client_0_show, tegra_mc_client_0_store);
+
+static struct kobj_attribute tegra_mc_client_0_on_schedule =
+ __ATTR(on_schedule, 0660, tegra_mc_client_0_show, tegra_mc_client_0_store);
+
+static struct attribute *tegra_mc_client_0_attrs[] = {
+ &tegra_mc_client_0_enable.attr,
+ &tegra_mc_client_0_on_schedule.attr,
+ NULL,
+};
+
+static struct attribute_group tegra_mc_client_0_attr_group = {
+ .attrs = tegra_mc_client_0_attrs
+};
+
+/* /sys/devices/system/tegra_mc/dram */
+#define dram_counters(_x) \
+ _x(activate_cnt, ACTIVATE_CNT) \
+ _x(read_cnt, READ_CNT) \
+ _x(write_cnt, WRITE_CNT) \
+ _x(ref_cnt, REF_CNT) \
+ _x(cumm_banks_active_cke_eq1, CUMM_BANKS_ACTIVE_CKE_EQ1) \
+ _x(cumm_banks_active_cke_eq0, CUMM_BANKS_ACTIVE_CKE_EQ0) \
+ _x(cke_eq1_clks, CKE_EQ1_CLKS) \
+ _x(extclks_cke_eq1, EXTCLKS_CKE_EQ1) \
+ _x(extclks_cke_eq0, EXTCLKS_CKE_EQ0) \
+ _x(no_banks_active_cke_eq1, NO_BANKS_ACTIVE_CKE_EQ1) \
+ _x(no_banks_active_cke_eq0, NO_BANKS_ACTIVE_CKE_EQ0)
+
+#define DEFINE_COUNTER(_name, _val) { .enabled = false, .device_mask = 0, },
+
+static tegra_emc_dram_counter_t dram_counters[] = {
+ dram_counters(DEFINE_COUNTER)
+};
+
+#define DEFINE_SYSFS(_name, _val) \
+ \
+static struct kobject *tegra_mc_dram_##_name##_kobj; \
+ \
+static ssize_t tegra_mc_dram_##_name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+{ \
+ return tegra_mc_dram_show(kobj, attr, buf, \
+ _val - EMC_DRAM_STAT_BEGIN); \
+} \
+ \
+static ssize_t tegra_mc_dram_##_name##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, const char *buf, size_t count) \
+{ \
+ if (sampling()) \
+ return 0; \
+ \
+ return tegra_mc_dram_store(kobj, attr, buf, count, \
+ _val - EMC_DRAM_STAT_BEGIN); \
+} \
+ \
+ \
+static struct kobj_attribute tegra_mc_dram_##_name##_enable = \
+ __ATTR(enable, 0660, tegra_mc_dram_##_name##_show, \
+ tegra_mc_dram_##_name##_store); \
+ \
+static struct kobj_attribute tegra_mc_dram_##_name##_device_mask = \
+ __ATTR(device_mask, 0660, tegra_mc_dram_##_name##_show, \
+ tegra_mc_dram_##_name##_store); \
+ \
+static struct attribute *tegra_mc_dram_##_name##_attrs[] = { \
+ &tegra_mc_dram_##_name##_enable.attr, \
+ &tegra_mc_dram_##_name##_device_mask.attr, \
+ NULL, \
+}; \
+ \
+static struct attribute_group tegra_mc_dram_##_name##_attr_group = { \
+ .attrs = tegra_mc_dram_##_name##_attrs, \
+};
+
+static struct kobject *tegra_mc_dram_kobj;
+
+static ssize_t tegra_mc_dram_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf, int index)
+{
+ if (index >= EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN)
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "enable") == 0)
+ return sprintf(buf, "%d\n", dram_counters[index].enabled);
+ else if (strcmp(attr->attr.name, "device_mask") == 0)
+ return sprintf(buf, "%d\n", dram_counters[index].device_mask);
+ else
+ return -EINVAL;
+}
+static ssize_t tegra_mc_dram_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count, int index)
+{
+ int value;
+
+ if (index >= EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN)
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "enable") == 0) {
+ sscanf(buf, "%d\n", &value);
+ if (value == 0 || value == 1)
+ dram_counters[index].enabled = value;
+ else
+ return -EINVAL;
+
+ return count;
+ } else if (strcmp(attr->attr.name, "device_mask") == 0) {
+ sscanf(buf, "%d\n", &value);
+ dram_counters[index].device_mask = (u8)value;
+
+ return count;
+ } else
+ return -EINVAL;
+}
+
+dram_counters(DEFINE_SYSFS)
+
+/* Tegra Statistics */
+typedef struct {
+ void __iomem *mmio;
+} tegra_device_t;
+
+static tegra_device_t mc = {
+ .mmio = IO_ADDRESS(TEGRA_MC_BASE),
+};
+
+static tegra_device_t emc = {
+ .mmio = IO_ADDRESS(TEGRA_EMC_BASE),
+};
+
+void mc_stat_start(tegra_mc_counter_t *counter0, tegra_mc_counter_t *counter1)
+{
+ struct tegra_mc_counter *c;
+ u32 filter_client = ARMC_STAT_CONTROL_FILTER_CLIENT_DISABLE;
+ u32 filter_addr = ARMC_STAT_CONTROL_FILTER_ADDR_DISABLE;
+
+ if (!tegra_mc_client_0_enabled)
+ return;
+
+ c = (counter0->enabled) ? counter0 : counter1;
+
+ /* disable statistics */
+ writel((MC_STAT_CONTROL_0_EMC_GATHER_DISABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT),
+ mc.mmio + MC_STAT_CONTROL_0);
+
+ if (c->enabled && c->mode == FILTER_ADDR)
+ filter_addr = ARMC_STAT_CONTROL_FILTER_ADDR_ENABLE;
+ else if (c->enabled && c->mode == FILTER_CLIENT)
+ filter_client = ARMC_STAT_CONTROL_FILTER_CLIENT_ENABLE;
+
+ filter_addr <<= ARMC_STAT_CONTROL_FILTER_ADDR_SHIFT;
+ filter_client <<= ARMC_STAT_CONTROL_FILTER_CLIENT_SHIFT;
+
+ if (c->enabled) {
+ u32 reg = 0;
+ reg |= (ARMC_STAT_CONTROL_MODE_BANDWIDTH <<
+ ARMC_STAT_CONTROL_MODE_SHIFT);
+ reg |= (ARMC_STAT_CONTROL_EVENT_QUALIFIED <<
+ ARMC_STAT_CONTROL_EVENT_SHIFT);
+ reg |= (ARMC_STAT_CONTROL_FILTER_PRI_DISABLE <<
+ ARMC_STAT_CONTROL_FILTER_PRI_SHIFT);
+ reg |= (ARMC_STAT_CONTROL_FILTER_COALESCED_DISABLE <<
+ ARMC_STAT_CONTROL_FILTER_COALESCED_SHIFT);
+ reg |= filter_client;
+ reg |= filter_addr;
+ reg |= (c->clients[c->current_client] <<
+ ARMC_STAT_CONTROL_CLIENT_ID_SHIFT);
+
+ /* note these registers are shared */
+ writel(c->current_address_low,
+ mc.mmio + MC_STAT_EMC_ADDR_LOW_0);
+ writel(c->current_address_high,
+ mc.mmio + MC_STAT_EMC_ADDR_HIGH_0);
+ writel(0xFFFFFFFF, mc.mmio + MC_STAT_EMC_CLOCK_LIMIT_0);
+
+ writel(reg, mc.mmio + MC_STAT_EMC_CONTROL_0_0);
+ }
+
+ /* reset then enable statistics */
+ writel((MC_STAT_CONTROL_0_EMC_GATHER_CLEAR << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT),
+ mc.mmio + MC_STAT_CONTROL_0);
+
+ writel((MC_STAT_CONTROL_0_EMC_GATHER_ENABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT),
+ mc.mmio + MC_STAT_CONTROL_0);
+}
+
+void mc_stat_stop(tegra_mc_counter_t *counter0,
+ tegra_mc_counter_t *counter1)
+{
+ /* Disable statistics */
+ writel((MC_STAT_CONTROL_0_EMC_GATHER_DISABLE << MC_STAT_CONTROL_0_EMC_GATHER_SHIFT),
+ mc.mmio + MC_STAT_CONTROL_0);
+
+ if (counter0->enabled)
+ counter0->value = readl(mc.mmio + MC_STAT_EMC_COUNT_0_0);
+ else
+ counter1->value = readl(mc.mmio + MC_STAT_EMC_COUNT_1_0);
+}
+
+void emc_stat_start(tegra_mc_counter_t *llp_counter,
+ tegra_emc_dram_counter_t *dram_counter)
+{
+ u32 llmc_stat = 0;
+ u32 llmc_ctrl =
+ (AREMC_STAT_CONTROL_MODE_BANDWIDTH <<
+ AREMC_STAT_CONTROL_MODE_SHIFT) |
+ (AREMC_STAT_CONTROL_CLIENT_TYPE_MPCORER <<
+ AREMC_STAT_CONTROL_CLIENT_TYPE_SHIFT) |
+ (AREMC_STAT_CONTROL_EVENT_QUALIFIED <<
+ AREMC_STAT_CONTROL_EVENT_SHIFT);
+
+ /* disable statistics */
+ llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_DISABLE <<
+ EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT);
+ llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_DISABLE <<
+ EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT);
+ writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0);
+
+ if (tegra_mc_client_0_enabled && llp_counter->enabled) {
+ if (llp_counter->mode == FILTER_ADDR) {
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_ADDR_ENABLE <<
+ AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT);
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE <<
+ AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT);
+ } else if (llp_counter->mode == FILTER_CLIENT) {
+ /* not allow aggregate client in client mode */
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_ADDR_DISABLE <<
+ AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT);
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE <<
+ AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT);
+ } else if (llp_counter->mode == FILTER_NONE) {
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_ADDR_DISABLE <<
+ AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT);
+ llmc_ctrl |=
+ (AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE <<
+ AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT);
+ }
+
+ writel(llp_counter->current_address_low,
+ emc.mmio + EMC_STAT_LLMC_ADDR_LOW_0);
+ writel(llp_counter->current_address_high,
+ emc.mmio + EMC_STAT_LLMC_ADDR_HIGH_0);
+ writel(0xFFFFFFFF, emc.mmio + EMC_STAT_LLMC_CLOCK_LIMIT_0);
+ writel(llmc_ctrl, emc.mmio + EMC_STAT_LLMC_CONTROL_0_0);
+ }
+
+ writel(0xFFFFFFFF, emc.mmio + EMC_STAT_DRAM_CLOCK_LIMIT_LO_0);
+ writel(0xFF, emc.mmio + EMC_STAT_DRAM_CLOCK_LIMIT_HI_0);
+
+ /* Reset then enable statistics */
+ llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_CLEAR <<
+ EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT);
+ llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_CLEAR <<
+ EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT);
+ writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0);
+
+ llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_ENABLE <<
+ EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT);
+ llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_ENABLE <<
+ EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT);
+ writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0);
+}
+
+void emc_stat_stop(tegra_mc_counter_t *llp_counter,
+ tegra_emc_dram_counter_t *dram_counter)
+{
+ u32 llmc_stat = 0;
+ int i;
+ int dev0_offsets_lo[] = {
+ EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_LO_0,
+ EMC_STAT_DRAM_DEV0_READ_CNT_LO_0,
+ EMC_STAT_DRAM_DEV0_WRITE_CNT_LO_0,
+ EMC_STAT_DRAM_DEV0_REF_CNT_LO_0,
+ EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0,
+ EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_LO_0,
+ EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_LO_0,
+ EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_LO_0,
+ };
+ int dev0_offsets_hi[] = {
+ EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_HI_0,
+ EMC_STAT_DRAM_DEV0_READ_CNT_HI_0,
+ EMC_STAT_DRAM_DEV0_WRITE_CNT_HI_0,
+ EMC_STAT_DRAM_DEV0_REF_CNT_HI_0,
+ EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0,
+ EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_HI_0,
+ EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_HI_0,
+ EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_HI_0,
+ };
+ int dev1_offsets_lo[] = {
+ EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_LO_0,
+ EMC_STAT_DRAM_DEV1_READ_CNT_LO_0,
+ EMC_STAT_DRAM_DEV1_WRITE_CNT_LO_0,
+ EMC_STAT_DRAM_DEV1_REF_CNT_LO_0,
+ EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0,
+ EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_LO_0,
+ EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_LO_0,
+ EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_LO_0,
+ EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_LO_0,
+ };
+ int dev1_offsets_hi[] = {
+ EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_HI_0,
+ EMC_STAT_DRAM_DEV1_READ_CNT_HI_0,
+ EMC_STAT_DRAM_DEV1_WRITE_CNT_HI_0,
+ EMC_STAT_DRAM_DEV1_REF_CNT_HI_0,
+ EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0,
+ EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_HI_0,
+ EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_HI_0,
+ EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_HI_0,
+ EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_HI_0,
+ };
+
+ /* Disable statistics */
+ llmc_stat |= (EMC_STAT_CONTROL_0_LLMC_GATHER_DISABLE <<
+ EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT);
+ llmc_stat |= (EMC_STAT_CONTROL_0_DRAM_GATHER_DISABLE <<
+ EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT);
+ writel(llmc_stat, emc.mmio + EMC_STAT_CONTROL_0);
+
+ if (tegra_mc_client_0_enabled == true)
+ llp_counter->value = readl(emc.mmio + EMC_STAT_LLMC_COUNT_0_0);
+
+ for (i = 0; i < EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN; i++) {
+ if (dram_counter[i].enabled) {
+ dram_counter[i].value = 0;
+ if (!(dram_counter[i].device_mask & 0x1)) {
+ if (readl(emc.mmio + dev0_offsets_hi[i]) != 0) {
+ dram_counter[i].value = 0xFFFFFFFF;
+ continue;
+ }
+ dram_counter[i].value +=
+ readl(emc.mmio + dev0_offsets_lo[i]);
+ }
+ if (!(dram_counter[i].device_mask & 0x2)) {
+ if (readl(emc.mmio + dev1_offsets_hi[i]) != 0) {
+ dram_counter[i].value = 0xFFFFFFFF;
+ continue;
+ }
+ dram_counter[i].value +=
+ readl(emc.mmio + dev1_offsets_lo[i]);
+ }
+ }
+ }
+}
+
+static void stat_reschedule(tegra_mc_counter_t *counter0,
+ tegra_mc_counter_t *counter1,
+ tegra_mc_counter_t *llp)
+{
+ int i;
+ struct tegra_mc_counter *counters[] = {
+ counter0,
+ counter1,
+ llp
+ };
+
+ if (!tegra_mc_client_0_enabled)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(counters); i++) {
+ struct tegra_mc_counter *c = counters[i];
+
+ c->address_range_change = false;
+ if (!c->enabled || !c->reschedule)
+ continue;
+
+ c->sample_count++;
+
+ if (c->sample_count < c->period)
+ continue;
+
+ c->sample_count = 0;
+
+ if (c->mode == FILTER_CLIENT) {
+ c->current_client++;
+ if (c->current_client == c->client_number)
+ c->current_client = 0;
+ continue;
+ }
+
+ c->address_range_change = true;
+ c->current_address_low = c->current_address_high+1;
+
+ if (c->current_address_low >= c->address_low+c->address_length_1)
+ c->current_address_low = c->address_low;
+
+ c->current_address_high = c->current_address_low +
+ c->address_window_size_1;
+ }
+}
+
+static void stat_start(void)
+{
+ mc_stat_start(&mc_counter0, &mc_counter1);
+ emc_stat_start(&emc_llp_counter, dram_counters);
+}
+
+static void stat_stop(void)
+{
+ mc_stat_stop(&mc_counter0, &mc_counter1);
+ emc_stat_stop(&emc_llp_counter, dram_counters);
+}
+
+static size_t stat_log_counter(struct tegra_mc_counter *c,
+ struct tegra_mc_counter *l, log_event_t *e, u32* value)
+{
+ size_t size = 0;
+
+ *value = c->value;
+ if (l)
+ *value += l->value;
+
+ if (!c->enabled || (l && !l->enabled) || !*value)
+ return 0;
+
+ e->word0.enabled = 1;
+ e->word0.address_range_change = c->address_range_change;
+ e->word0.event_id = (l) ? MC_STAT_AGGREGATE :
+ c->clients[c->current_client];
+ e->word0.address_range_low_pfn = __phys_to_pfn(c->current_address_low);
+ size += sizeof(e->word0);
+
+ if (c->address_range_change) {
+ e->word1.address_range_length_pfn =
+ __phys_to_pfn(c->address_window_size_1+1);
+ size += sizeof(e->word1);
+ }
+
+ size += sizeof(*value);
+ return size;
+}
+
+#define statcpy(_buf, _bufstart, _buflen, _elem) \
+ do { \
+ size_t s = sizeof(_elem); \
+ memcpy(_buf, &_elem, s); \
+ _buf += s; \
+ if (_buf >= _bufstart + _buflen) \
+ _buf = _bufstart; \
+ } while (0);
+
+static void stat_log(void)
+{
+ log_header_t header = {0, 0};
+ log_event_t event[LOG_EVENT_NUMBER_MAX];
+ u32 value[LOG_EVENT_NUMBER_MAX];
+ int i, count = 0;
+ unsigned long flags;
+ size_t elem;
+ int required_log_size = 0;
+
+ required_log_size += sizeof(header);
+
+ if (tegra_mc_client_0_enabled) {
+ elem = stat_log_counter(&mc_counter0, NULL, &event[count],
+ &value[count]);
+ if (elem) {
+ required_log_size += elem;
+ count++;
+ }
+
+ elem = stat_log_counter(&mc_counter1, &emc_llp_counter,
+ &event[count], &value[count]);
+
+ if (elem) {
+ required_log_size += elem;
+ count++;
+ }
+ }
+
+ for (i = 0; i < EMC_DRAM_STAT_END - EMC_DRAM_STAT_BEGIN &&
+ count < LOG_EVENT_NUMBER_MAX; i++) {
+ if (dram_counters[i].enabled && dram_counters[i].value != 0) {
+ event[count].word0.enabled = 1;
+ event[count].word0.address_range_change = false;
+ event[count].word0.event_id = i + EMC_DRAM_STAT_BEGIN;
+ event[count].word0.address_range_low_pfn = 0;
+ required_log_size += sizeof(event[count].word0);
+
+ event[count].word1.address_range_length_pfn =
+ 0xFFFFFFFFUL >> SHIFT_4K;
+
+ value[count] = dram_counters[i].value;
+ required_log_size += sizeof(value[count]);
+
+ count++;
+ }
+ }
+
+ header.time_quantum = sample_quantum * MILLISECONDS_TO_TIME_QUANTUM;
+ for (i = 0; i < count; i++) {
+ header.event_state_change |= 1 << i;
+ }
+
+ if (header.event_state_change != 0) {
+ spin_lock_irqsave(&sample_log_lock, flags);
+ if (unlikely(required_log_size > sample_log_size)) {
+ pr_err("%s: sample log too small!\n", __func__);
+ WARN_ON(1);
+ spin_unlock_irqrestore(&sample_log_lock, flags);
+ goto reschedule;
+ }
+
+ statcpy(sample_log_wptr, sample_log, SAMPLE_LOG_SIZE, header);
+
+ for (i=0; i<count; i++) {
+ statcpy(sample_log_wptr, sample_log,
+ SAMPLE_LOG_SIZE, event[i].word0);
+ if (!event[i].word0.address_range_change)
+ continue;
+ statcpy(sample_log_wptr, sample_log,
+ SAMPLE_LOG_SIZE, event[i].word1);
+ }
+
+ for (i=0; i<count; i++) {
+ statcpy(sample_log_wptr, sample_log,
+ SAMPLE_LOG_SIZE, value[i]);
+ }
+
+ sample_log_size -= required_log_size;
+ spin_unlock_irqrestore(&sample_log_lock, flags);
+ }
+
+reschedule:
+ stat_reschedule(&mc_counter0, &mc_counter1, &emc_llp_counter);
+}
+
+static enum hrtimer_restart sample_timer_function(struct hrtimer *handle)
+{
+ stat_stop();
+ stat_log();
+
+ if (!sample_enable)
+ return HRTIMER_NORESTART;
+
+ stat_start();
+
+ hrtimer_add_expires_ns(&sample_timer, (u64)sample_quantum * 1000000);
+ return HRTIMER_RESTART;
+}
+
+/* module init */
+#define REGISTER_SYSFS(_name, _val) \
+ tegra_mc_dram_##_name##_kobj = \
+ kobject_create_and_add(#_name, tegra_mc_dram_kobj); \
+ sysfs_create_group(tegra_mc_dram_##_name##_kobj, \
+ &tegra_mc_dram_##_name##_attr_group);
+
+static int tegra_mc_init(void)
+{
+ int i;
+ int rc;
+
+ /* /sys/devices/system/tegra_mc */
+ rc = sysdev_class_register(&tegra_mc_sysclass);
+ if(rc)
+ goto out;
+
+ for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) {
+ rc = sysdev_class_create_file(&tegra_mc_sysclass,
+ tegra_mc_attrs[i]);
+ if(rc) {
+ printk("\n sysdev_class_create_file : failed \n");
+ goto out_unreg_class;
+ }
+ }
+
+ /* /sys/devices/system/tegra_mc/client */
+ tegra_mc_client_kobj = kobject_create_and_add("client",
+ &tegra_mc_sysclass.kset.kobj);
+ if(!tegra_mc_client_kobj)
+ goto out_remove_sysdev_files;
+
+ tegra_mc_client_0_kobj = kobject_create_and_add("0",
+ tegra_mc_client_kobj);
+ if(!tegra_mc_client_0_kobj)
+ goto out_put_kobject_client;
+
+ rc = sysfs_create_group(tegra_mc_client_0_kobj,
+ &tegra_mc_client_0_attr_group);
+ if(rc)
+ goto out_put_kobject_client_0;
+
+ /* /sys/devices/system/tegra_mc/dram */
+ tegra_mc_dram_kobj = kobject_create_and_add("dram",
+ &tegra_mc_sysclass.kset.kobj);
+ if(!tegra_mc_dram_kobj)
+ goto out_remove_group_client_0;
+
+ dram_counters(REGISTER_SYSFS)
+
+ /* hrtimer */
+ hrtimer_init(&sample_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+ sample_timer.function = sample_timer_function;
+
+ return 0;
+
+out_remove_group_client_0:
+ sysfs_remove_group(tegra_mc_client_0_kobj, &tegra_mc_client_0_attr_group);
+
+out_put_kobject_client_0:
+ kobject_put(tegra_mc_client_0_kobj);
+
+out_put_kobject_client:
+ kobject_put(tegra_mc_client_kobj);
+
+out_remove_sysdev_files:
+ for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) {
+ sysdev_class_remove_file(&tegra_mc_sysclass, tegra_mc_attrs[i]);
+ }
+
+out_unreg_class:
+ sysdev_class_unregister(&tegra_mc_sysclass);
+
+out:
+ return rc;
+}
+
+/* module deinit */
+#define REMOVE_SYSFS(_name, _val) \
+ sysfs_remove_group(tegra_mc_dram_##_name##_kobj, \
+ &tegra_mc_dram_##_name##_attr_group); \
+ kobject_put(tegra_mc_dram_##_name##_kobj);
+
+static void tegra_mc_exit(void)
+{
+ int i;
+
+ stat_stop();
+
+ /* hrtimer */
+ hrtimer_cancel(&sample_timer);
+
+ /* /sys/devices/system/tegra_mc/client */
+ sysfs_remove_group(tegra_mc_client_0_kobj,
+ &tegra_mc_client_0_attr_group);
+ kobject_put(tegra_mc_client_0_kobj);
+ kobject_put(tegra_mc_client_kobj);
+
+ /* /sys/devices/system/tegra_mc/dram */
+ dram_counters(REMOVE_SYSFS)
+ kobject_put(tegra_mc_dram_kobj);
+
+ /* /sys/devices/system/tegra_mc */
+ for (i = 0; i < ARRAY_SIZE(tegra_mc_attrs)-1; i++) {
+ sysdev_class_remove_file(&tegra_mc_sysclass, tegra_mc_attrs[i]);
+ }
+ sysdev_class_unregister(&tegra_mc_sysclass);
+}
+
+module_init(tegra_mc_init);
+module_exit(tegra_mc_exit);
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/arch/arm/mach-tegra/tegra2_mc.h b/arch/arm/mach-tegra/tegra2_mc.h
new file mode 100644
index 000000000000..41589b8e986a
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra2_mc.h
@@ -0,0 +1,271 @@
+/*
+ * arch/arm/mach-tegra/tegra2_mc.c
+ *
+ * Memory controller bandwidth profiling interface
+ *
+ * Copyright (c) 2009-2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _INCLUDE_TEGRA2_MC_H_
+#define _INCLUDE_TEGRA2_MC_H_
+
+#define SAMPLE_ENABLE_DEFAULT 0
+#define SAMPLE_LOG_SIZE 1024 /* need to be DWORD aligned */
+#define SAMPLE_QUANTUM_DEFAULT 1 /* in milliseconds */
+#define CLIENT_ENABLED_DEFAULT false
+#define CLIENT_ON_SCHEDULE_LENGTH 256
+#define SHIFT_4K 12
+
+typedef enum {
+ FILTER_NONE,
+ FILTER_ADDR,
+ FILTER_CLIENT,
+} FILTER_MODE;
+
+#define MC_COUNTER_CLIENT_SIZE 256
+
+#define MC_STAT_CONTROL_0 0x90
+#define MC_STAT_CONTROL_0_EMC_GATHER_SHIFT 8
+#define MC_STAT_CONTROL_0_EMC_GATHER_CLEAR 1
+#define MC_STAT_CONTROL_0_EMC_GATHER_DISABLE 2
+#define MC_STAT_CONTROL_0_EMC_GATHER_ENABLE 3
+
+#define MC_STAT_EMC_ADDR_LOW_0 0x98
+#define MC_STAT_EMC_ADDR_HIGH_0 0x9c
+#define MC_STAT_EMC_CLOCK_LIMIT_0 0xa0
+#define MC_STAT_EMC_CONTROL_0_0 0xa8
+#define MC_STAT_EMC_COUNT_0_0 0xb8
+#define MC_STAT_EMC_COUNT_1_0 0xbc
+
+#define ARMC_STAT_CONTROL_FILTER_ADDR_SHIFT 27
+#define ARMC_STAT_CONTROL_FILTER_ADDR_DISABLE 0
+#define ARMC_STAT_CONTROL_FILTER_ADDR_ENABLE 1
+#define ARMC_STAT_CONTROL_FILTER_CLIENT_SHIFT 26
+#define ARMC_STAT_CONTROL_FILTER_CLIENT_DISABLE 0
+#define ARMC_STAT_CONTROL_FILTER_CLIENT_ENABLE 1
+#define ARMC_STAT_CONTROL_FILTER_PRI_SHIFT 28
+#define ARMC_STAT_CONTROL_FILTER_PRI_DISABLE 0
+#define ARMC_STAT_CONTROL_FILTER_COALESCED_SHIFT 30
+#define ARMC_STAT_CONTROL_FILTER_COALESCED_DISABLE 0
+#define ARMC_STAT_CONTROL_CLIENT_ID_SHIFT 8
+#define ARMC_STAT_CONTROL_MODE_SHIFT 0
+#define ARMC_STAT_CONTROL_MODE_BANDWIDTH 0
+#define ARMC_STAT_CONTROL_EVENT_SHIFT 16
+#define ARMC_STAT_CONTROL_EVENT_QUALIFIED 0
+
+#define EMC_STAT_CONTROL_0 0x160
+#define EMC_STAT_CONTROL_0_LLMC_GATHER_SHIFT 0
+#define EMC_STAT_CONTROL_0_LLMC_GATHER_CLEAR 1
+#define EMC_STAT_CONTROL_0_LLMC_GATHER_DISABLE 2
+#define EMC_STAT_CONTROL_0_LLMC_GATHER_ENABLE 3
+#define EMC_STAT_CONTROL_0_DRAM_GATHER_SHIFT 16
+#define EMC_STAT_CONTROL_0_DRAM_GATHER_CLEAR 1
+#define EMC_STAT_CONTROL_0_DRAM_GATHER_DISABLE 2
+#define EMC_STAT_CONTROL_0_DRAM_GATHER_ENABLE 3
+
+#define AREMC_STAT_CONTROL_MODE_SHIFT 0
+#define AREMC_STAT_CONTROL_MODE_BANDWIDTH 0
+#define AREMC_STAT_CONTROL_FILTER_ADDR_SHIFT 27
+#define AREMC_STAT_CONTROL_FILTER_ADDR_ENABLE 1
+#define AREMC_STAT_CONTROL_CLIENT_TYPE_SHIFT 8
+#define AREMC_STAT_CONTROL_CLIENT_TYPE_MPCORER 0
+#define AREMC_STAT_CONTROL_FILTER_CLIENT_SHIFT 26
+#define AREMC_STAT_CONTROL_FILTER_CLIENT_DISABLE 0
+#define AREMC_STAT_CONTROL_FILTER_ADDR_DISABLE 0
+#define AREMC_STAT_CONTROL_EVENT_SHIFT 16
+#define AREMC_STAT_CONTROL_EVENT_QUALIFIED 0
+
+#define EMC_STAT_LLMC_ADDR_LOW_0 0x168
+#define EMC_STAT_LLMC_ADDR_HIGH_0 0x16c
+#define EMC_STAT_LLMC_CLOCK_LIMIT_0 0x170
+#define EMC_STAT_LLMC_CONTROL_0_0 0x178
+#define EMC_STAT_LLMC_COUNT_0_0 0x188
+
+#define EMC_STAT_DRAM_CLOCK_LIMIT_LO_0 0x1a4
+#define EMC_STAT_DRAM_CLOCK_LIMIT_HI_0 0x1a8
+#define EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_LO_0 0x1b4
+#define EMC_STAT_DRAM_DEV0_ACTIVATE_CNT_HI_0 0x1b8
+#define EMC_STAT_DRAM_DEV0_READ_CNT_LO_0 0x1bc
+#define EMC_STAT_DRAM_DEV0_READ_CNT_HI_0 0x1c0
+#define EMC_STAT_DRAM_DEV0_WRITE_CNT_LO_0 0x1c4
+#define EMC_STAT_DRAM_DEV0_WRITE_CNT_HI_0 0x1c8
+#define EMC_STAT_DRAM_DEV0_REF_CNT_LO_0 0x1cc
+#define EMC_STAT_DRAM_DEV0_REF_CNT_HI_0 0x1d0
+#define EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0 0x1d4
+#define EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0 0x1d8
+#define EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0 0x1dc
+#define EMC_STAT_DRAM_DEV0_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0 0x1e0
+#define EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_LO_0 0x1e4
+#define EMC_STAT_DRAM_DEV0_CKE_EQ1_CLKS_HI_0 0x1e8
+#define EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_LO_0 0x1ec
+#define EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ1_HI_0 0x1f0
+#define EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_LO_0 0x1f4
+#define EMC_STAT_DRAM_DEV0_EXTCLKS_CKE_EQ0_HI_0 0x1f8
+#define EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_LO_0 0x1fc
+#define EMC_STAT_DRAM_DEV1_ACTIVATE_CNT_HI_0 0x200
+#define EMC_STAT_DRAM_DEV1_READ_CNT_LO_0 0x204
+#define EMC_STAT_DRAM_DEV1_READ_CNT_HI_0 0x208
+#define EMC_STAT_DRAM_DEV1_WRITE_CNT_LO_0 0x20c
+#define EMC_STAT_DRAM_DEV1_WRITE_CNT_HI_0 0x210
+#define EMC_STAT_DRAM_DEV1_REF_CNT_LO_0 0x214
+#define EMC_STAT_DRAM_DEV1_REF_CNT_HI_0 0x218
+#define EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_LO_0 0x21c
+#define EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ1_HI_0 0x220
+#define EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_LO_0 0x224
+#define EMC_STAT_DRAM_DEV1_CUMM_BANKS_ACTIVE_CKE_EQ0_HI_0 0x228
+#define EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_LO_0 0x22c
+#define EMC_STAT_DRAM_DEV1_CKE_EQ1_CLKS_HI_0 0x230
+#define EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_LO_0 0x234
+#define EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ1_HI_0 0x238
+#define EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_LO_0 0x23c
+#define EMC_STAT_DRAM_DEV1_EXTCLKS_CKE_EQ0_HI_0 0x240
+#define EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_LO_0 0x244
+#define EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ1_HI_0 0x248
+#define EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_LO_0 0x24c
+#define EMC_STAT_DRAM_DEV0_NO_BANKS_ACTIVE_CKE_EQ0_HI_0 0x250
+#define EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_LO_0 0x254
+#define EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ1_HI_0 0x258
+#define EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_LO_0 0x25c
+#define EMC_STAT_DRAM_DEV1_NO_BANKS_ACTIVE_CKE_EQ0_HI_0 0x260
+
+typedef struct tegra_mc_counter {
+ /* constants during sampling */
+ bool enabled;
+ bool reschedule;
+ u32 period;
+ FILTER_MODE mode;
+ u32 address_low;
+ u32 address_length_1; /* This represents (length - 1) */
+ u32 address_window_size_1; /* This represents (size - 1) */
+ u8 client_number;
+ u8 clients[MC_COUNTER_CLIENT_SIZE];
+
+ /* variables during sampling */
+ bool address_range_change;
+ u32 current_address_low;
+ u32 current_address_high;
+ u32 value;
+ u8 current_client;
+ u32 sample_count;
+} tegra_mc_counter_t;
+
+typedef struct tegra_emc_dram_counter {
+ /* constants during sampling */
+ bool enabled;
+ u8 device_mask;
+
+ /* variables during sampling */
+ u32 value;
+} tegra_emc_dram_counter_t;
+
+#pragma pack(push)
+#pragma pack(1)
+#define LOG_EVENT_NUMBER_MAX 16
+#define MILLISECONDS_TO_TIME_QUANTUM 10
+typedef struct {
+ u16 time_quantum;
+ u16 event_state_change;
+} log_header_t;
+
+typedef struct {
+ struct _word0 {
+ u32 enabled : 1; /* 0:0 */
+ u32 address_range_change : 1; /* 1:1 */
+ u32 reserved1 : 2; /* 3:2 */
+ u32 event_id : 8; /* 11:4 */
+ u32 address_range_low_pfn : 20; /* 31:12 */
+ } word0;
+ struct _word1 {
+ u32 address_range_length_pfn : 20; /* 19:0 */
+ u32 reserved1 : 12; /* 31:20 */
+ } word1;
+} log_event_t;
+#pragma pack(pop)
+
+/* client ids of mc/emc */
+typedef enum {
+ MC_STAT_BEGIN = 0,
+ CBR_DISPLAY0A = 0,
+ CBR_DISPLAY0AB,
+ CBR_DISPLAY0B,
+ CBR_DISPLAY0BB,
+ CBR_DISPLAY0C,
+ CBR_DISPLAY0CB,
+ CBR_DISPLAY1B,
+ CBR_DISPLAY1BB,
+ CBR_EPPUP,
+ CBR_G2PR,
+ CBR_G2SR,
+ CBR_MPEUNIFBR,
+ CBR_VIRUV,
+ CSR_AVPCARM7R,
+ CSR_DISPLAYHC,
+ CSR_DISPLAYHCB,
+ CSR_FDCDRD,
+ CSR_G2DR,
+ CSR_HOST1XDMAR,
+ CSR_HOST1XR,
+ CSR_IDXSRD,
+ CSR_MPCORER,
+ CSR_MPE_IPRED,
+ CSR_MPEAMEMRD,
+ CSR_MPECSRD,
+ CSR_PPCSAHBDMAR,
+ CSR_PPCSAHBSLVR,
+ CSR_TEXSRD,
+ CSR_VDEBSEVR,
+ CSR_VDEMBER,
+ CSR_VDEMCER,
+ CSR_VDETPER,
+ CBW_EPPU,
+ CBW_EPPV,
+ CBW_EPPY,
+ CBW_MPEUNIFBW,
+ CBW_VIWSB,
+ CBW_VIWU,
+ CBW_VIWV,
+ CBW_VIWY,
+ CCW_G2DW,
+ CSW_AVPCARM7W,
+ CSW_FDCDWR,
+ CSW_HOST1XW,
+ CSW_ISPW,
+ CSW_MPCOREW,
+ CSW_MPECSWR,
+ CSW_PPCSAHBDMAW,
+ CSW_PPCSAHBSLVW,
+ CSW_VDEBSEVW,
+ CSW_VDEMBEW,
+ CSW_VDETPMW,
+ MC_STAT_END,
+ EMC_DRAM_STAT_BEGIN = 128,
+ ACTIVATE_CNT = 128,
+ READ_CNT,
+ WRITE_CNT,
+ REF_CNT,
+ CUMM_BANKS_ACTIVE_CKE_EQ1,
+ CUMM_BANKS_ACTIVE_CKE_EQ0,
+ CKE_EQ1_CLKS,
+ EXTCLKS_CKE_EQ1,
+ EXTCLKS_CKE_EQ0,
+ NO_BANKS_ACTIVE_CKE_EQ1,
+ NO_BANKS_ACTIVE_CKE_EQ0,
+ EMC_DRAM_STAT_END,
+ MC_STAT_AGGREGATE = 255,
+} device_id;
+
+#endif