/**
 * l2c310 (L2 Cache Controller) event counters for gator
 *
 * Copyright (C) ARM Limited 2010-2012. 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 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/io.h>
#include <asm/hardware/cache-l2x0.h>

#include "gator.h"

#define L2C310_COUNTERS_NUM 2

static struct {
	unsigned long enabled;
	unsigned long event;
	unsigned long key;
} l2c310_counters[L2C310_COUNTERS_NUM];

static int l2c310_buffer[L2C310_COUNTERS_NUM * 2];

static void __iomem *l2c310_base;



static void gator_events_l2c310_reset_counters(void)
{
	u32 val = readl(l2c310_base + L2X0_EVENT_CNT_CTRL);

	val |= ((1 << L2C310_COUNTERS_NUM) - 1) << 1;

	writel(val, l2c310_base + L2X0_EVENT_CNT_CTRL);
}


static int gator_events_l2c310_create_files(struct super_block *sb,
		struct dentry *root)
{
	int i;

	for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
		char buf[16];
		struct dentry *dir;

		snprintf(buf, sizeof(buf), "L2C-310_cnt%d", i);
		dir = gatorfs_mkdir(sb, root, buf);
		if (WARN_ON(!dir))
			return -1;
		gatorfs_create_ulong(sb, dir, "enabled",
				&l2c310_counters[i].enabled);
		gatorfs_create_ulong(sb, dir, "event",
				&l2c310_counters[i].event);
		gatorfs_create_ro_ulong(sb, dir, "key",
				&l2c310_counters[i].key);
	}

	return 0;
}

static int gator_events_l2c310_start(void)
{
	static const unsigned long l2x0_event_cntx_cfg[L2C310_COUNTERS_NUM] = {
		L2X0_EVENT_CNT0_CFG,
		L2X0_EVENT_CNT1_CFG,
	};
	int i;

	/* Counter event sources */
	for (i = 0; i < L2C310_COUNTERS_NUM; i++)
		writel((l2c310_counters[i].event & 0xf) << 2,
				l2c310_base + l2x0_event_cntx_cfg[i]);

	gator_events_l2c310_reset_counters();

	/* Event counter enable */
	writel(1, l2c310_base + L2X0_EVENT_CNT_CTRL);

	return 0;
}

static void gator_events_l2c310_stop(void)
{
	/* Event counter disable */
	writel(0, l2c310_base + L2X0_EVENT_CNT_CTRL);
}

static int gator_events_l2c310_read(int **buffer)
{
	static const unsigned long l2x0_event_cntx_val[L2C310_COUNTERS_NUM] = {
		L2X0_EVENT_CNT0_VAL,
		L2X0_EVENT_CNT1_VAL,
	};
	int i;
	int len = 0;

	if (smp_processor_id())
		return 0;

	for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
		if (l2c310_counters[i].enabled) {
			l2c310_buffer[len++] = l2c310_counters[i].key;
			l2c310_buffer[len++] = readl(l2c310_base +
					l2x0_event_cntx_val[i]);
		}
	}
	
	/* l2c310 counters are saturating, not wrapping in case of overflow */
	gator_events_l2c310_reset_counters();

	if (buffer)
		*buffer = l2c310_buffer;

	return len;
}

static struct gator_interface gator_events_l2c310_interface = {
	.create_files = gator_events_l2c310_create_files,
	.start = gator_events_l2c310_start,
	.stop = gator_events_l2c310_stop,
	.read = gator_events_l2c310_read,
};

static void __maybe_unused gator_events_l2c310_probe(unsigned long phys)
{
	if (l2c310_base)
		return;

	l2c310_base = ioremap(phys, SZ_4K);
	if (l2c310_base) {
		u32 cache_id = readl(l2c310_base + L2X0_CACHE_ID);

		if ((cache_id & 0xff0003c0) != 0x410000c0) {
			iounmap(l2c310_base);
			l2c310_base = NULL;
		}
	}
}

int gator_events_l2c310_init(void)
{
	int i;

	if (gator_cpuid() != CORTEX_A5 && gator_cpuid() != CORTEX_A9)
		return -1;

#if defined(CONFIG_ARCH_EXYNOS4) || defined(CONFIG_ARCH_S5PV310)
	gator_events_l2c310_probe(0x10502000);
#endif
#if defined(CONFIG_ARCH_OMAP4)
	gator_events_l2c310_probe(0x48242000);
#endif
#if defined(CONFIG_ARCH_TEGRA)
	gator_events_l2c310_probe(0x50043000);
#endif
#if defined(CONFIG_ARCH_U8500)
	gator_events_l2c310_probe(0xa0412000);
#endif
#if defined(CONFIG_ARCH_VEXPRESS)
	// A9x4 core tile (HBI-0191)
	gator_events_l2c310_probe(0x1e00a000);
	// New memory map tiles
	gator_events_l2c310_probe(0x2c0f0000);
#endif
	if (!l2c310_base)
		return -1;

	for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
		l2c310_counters[i].enabled = 0;
		l2c310_counters[i].key = gator_events_get_key();
	}

	return gator_events_install(&gator_events_l2c310_interface);
}
gator_events_init(gator_events_l2c310_init);
