#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/delay.h>

#include <asm/l2cache.h>
#include <asm/metag_isa.h>

/* If non-0, then initialise the L2 cache */
static int l2cache_init = 1;
/* If non-0, then initialise the L2 cache prefetch */
static int l2cache_init_pf = 1;

int l2c_pfenable;

static volatile u32 l2c_testdata[16] __initdata __aligned(64);

static int __init parse_l2cache(char *p)
{
	char *cp = p;

	if (get_option(&cp, &l2cache_init) != 1) {
		pr_err("Bad l2cache parameter (%s)\n", p);
		return 1;
	}
	return 0;
}
early_param("l2cache", parse_l2cache);

static int __init parse_l2cache_pf(char *p)
{
	char *cp = p;

	if (get_option(&cp, &l2cache_init_pf) != 1) {
		pr_err("Bad l2cache_pf parameter (%s)\n", p);
		return 1;
	}
	return 0;
}
early_param("l2cache_pf", parse_l2cache_pf);

static int __init meta_l2c_setup(void)
{
	/*
	 * If the L2 cache isn't even present, don't do anything, but say so in
	 * the log.
	 */
	if (!meta_l2c_is_present()) {
		pr_info("L2 Cache: Not present\n");
		return 0;
	}

	/*
	 * Check whether the line size is recognised.
	 */
	if (!meta_l2c_linesize()) {
		pr_warn_once("L2 Cache: unknown line size id (config=0x%08x)\n",
			     meta_l2c_config());
	}

	/*
	 * Initialise state.
	 */
	l2c_pfenable = _meta_l2c_pf_is_enabled();

	/*
	 * Enable the L2 cache and print to log whether it was already enabled
	 * by the bootloader.
	 */
	if (l2cache_init) {
		pr_info("L2 Cache: Enabling... ");
		if (meta_l2c_enable())
			pr_cont("already enabled\n");
		else
			pr_cont("done\n");
	} else {
		pr_info("L2 Cache: Not enabling\n");
	}

	/*
	 * Enable L2 cache prefetch.
	 */
	if (l2cache_init_pf) {
		pr_info("L2 Cache: Enabling prefetch... ");
		if (meta_l2c_pf_enable(1))
			pr_cont("already enabled\n");
		else
			pr_cont("done\n");
	} else {
		pr_info("L2 Cache: Not enabling prefetch\n");
	}

	return 0;
}
core_initcall(meta_l2c_setup);

int meta_l2c_disable(void)
{
	unsigned long flags;
	int en;

	if (!meta_l2c_is_present())
		return 1;

	/*
	 * Prevent other threads writing during the writeback, otherwise the
	 * writes will get "lost" when the L2 is disabled.
	 */
	__global_lock2(flags);
	en = meta_l2c_is_enabled();
	if (likely(en)) {
		_meta_l2c_pf_enable(0);
		wr_fence();
		_meta_l2c_purge();
		_meta_l2c_enable(0);
	}
	__global_unlock2(flags);

	return !en;
}

int meta_l2c_enable(void)
{
	unsigned long flags;
	int en;

	if (!meta_l2c_is_present())
		return 0;

	/*
	 * Init (clearing the L2) can happen while the L2 is disabled, so other
	 * threads are safe to continue executing, however we must not init the
	 * cache if it's already enabled (dirty lines would be discarded), so
	 * this operation should still be atomic with other threads.
	 */
	__global_lock1(flags);
	en = meta_l2c_is_enabled();
	if (likely(!en)) {
		_meta_l2c_init();
		_meta_l2c_enable(1);
		_meta_l2c_pf_enable(l2c_pfenable);
	}
	__global_unlock1(flags);

	return en;
}

int meta_l2c_pf_enable(int pfenable)
{
	unsigned long flags;
	int en = l2c_pfenable;

	if (!meta_l2c_is_present())
		return 0;

	/*
	 * We read modify write the enable register, so this operation must be
	 * atomic with other threads.
	 */
	__global_lock1(flags);
	en = l2c_pfenable;
	l2c_pfenable = pfenable;
	if (meta_l2c_is_enabled())
		_meta_l2c_pf_enable(pfenable);
	__global_unlock1(flags);

	return en;
}

int meta_l2c_flush(void)
{
	unsigned long flags;
	int en;

	/*
	 * Prevent other threads writing during the writeback. This also
	 * involves read modify writes.
	 */
	__global_lock2(flags);
	en = meta_l2c_is_enabled();
	if (likely(en)) {
		_meta_l2c_pf_enable(0);
		wr_fence();
		_meta_l2c_purge();
		_meta_l2c_enable(0);
		_meta_l2c_init();
		_meta_l2c_enable(1);
		_meta_l2c_pf_enable(l2c_pfenable);
	}
	__global_unlock2(flags);

	return !en;
}