/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2015, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/** \file */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"
#include "peripherals/l2cc.h"
#include "cortex-a/cp15.h"
#include "trace.h"

#include <assert.h>
#include <stdbool.h>
#include <string.h>

/*----------------------------------------------------------------------------
 *        Functions
 *----------------------------------------------------------------------------*/

uint32_t l2cc_is_enabled(void)
{
	return ((L2CC->L2CC_CR) & L2CC_CR_L2CEN);
}

void l2cc_enable(void)
{
	L2CC->L2CC_CR |= L2CC_CR_L2CEN;
	asm volatile("": : :"memory");
	asm("dsb");
	asm("isb");
	trace_info("L2 cache is enabled\r\n");
}

void l2cc_disable(void)
{
	L2CC->L2CC_CR &= ~L2CC_CR_L2CEN;
	asm volatile("": : :"memory");
	asm("dsb");
	asm("isb");
	trace_info("L2 cache is disabled\r\n");
}

void l2cc_exclusive_cache(uint8_t enable)
{
	uint32_t cfg;
	if (l2cc_is_enabled()) {
		l2cc_disable();
	}
	cfg = L2CC->L2CC_ACR;
	if (enable) {
		cp15_exclusive_cache();
		cfg |= L2CC_ACR_EXCC;
		trace_info("L2 Exclusive mode enabled\n\r");
	} else {
		cp15_non_exclusive_cache();
		cfg &= ~L2CC_ACR_EXCC;
		trace_info("L2 Exclusive mode disabled\n\r");
	}
	L2CC->L2CC_ACR |= cfg;
}

void l2cc_config_lat_ram(struct _ram_latency_control * latencies)
{
	if (l2cc_is_enabled()) {
		l2cc_disable();
	}

	L2CC->L2CC_TRCR =
		(L2CC_TRCR_TSETLAT(latencies->tag.setup) |
		 L2CC_TRCR_TRDLAT(latencies->tag.read) |
		 L2CC_TRCR_TWRLAT(latencies->tag.write));
	L2CC->L2CC_DRCR =
		(L2CC_DRCR_DSETLAT(latencies->data.setup) |
		 L2CC_DRCR_DRDLAT(latencies->data.read) |
		 L2CC_DRCR_DWRLAT(latencies->data.write));
}

void l2cc_set_config(const struct _l2cc_control* cfg)
{
	uint32_t aux_control, debug_control, prefetch_control, power_control;

	if (cfg->offset > 31) {
		assert(0);
	}
	if ((cfg->offset > 7) && (cfg->offset < 15)) {
		assert(0);
	}
	if ((cfg->offset > 15) && (cfg->offset < 23)) {
		assert(0);
	}
	if ((cfg->offset > 23) && (cfg->offset < 31)) {
		assert(0);
	}

	if (l2cc_is_enabled()) {
		l2cc_disable();
	}

	aux_control = ((cfg->high_prior_so << 10) |
		       (cfg->store_buff_dev_limit << 11) |
		       (cfg->shared_attr_invalidate << 13) |
		       (cfg->evt_mon_bus << 20) |
		       (cfg->parity << 21) |
		       (cfg->shared_attr_override << 22) |
		       (L2CC_ACR_FWA(cfg->force_write_alloc)) |
		       (cfg->cache_replacement << 25) |
		       (cfg->non_sec_lockdown << 26) |
		       (cfg->it_acces_non_sec << 27) |
		       (cfg->data_prefetch << 28) |
		       (cfg->instruct_prefetch << 29));

	debug_control = ((cfg->no_cache_linefill << 0) |
			 (cfg->no_write_back << 1));

	prefetch_control = ((L2CC_PCR_OFFSET(cfg->offset << 0)) |
			    (cfg->exclusive_seq_same_id << 21) |
			    (cfg->incr_double_linefill << 23) |
			    (cfg->prefetch_drop << 24) |
			    (cfg->DLFWRDIS << 27) |
			    (cfg->data_prefetch << 28) |
			    (cfg->instruct_prefetch << 29) |
			    (cfg->double_linefill << 30));

	power_control = ((cfg->standby_mode << 0) |
			 (cfg->dyn_clock_gating << 1));

	L2CC->L2CC_ACR = aux_control;
	L2CC->L2CC_DCR = debug_control;
	L2CC->L2CC_PCR = prefetch_control;
	L2CC->L2CC_POWCR = power_control;
}

void l2cc_data_prefetch_enable(void)
{
	L2CC->L2CC_PCR |= L2CC_PCR_DATPEN;
}

void l2cc_inst_prefetch_enable(void)
{
	L2CC->L2CC_PCR |= L2CC_PCR_INSPEN;
}

void l2cc_enable_reset_counter(uint8_t event_counter)
{
	assert((event_counter > 3) ? 0 : 1);
	L2CC->L2CC_ECR = (L2CC_ECR_EVCEN | (event_counter << 1));
}

void l2cc_event_config(uint8_t event_counter, uint8_t source, uint8_t it)
{
	if (l2cc_is_enabled()) {
		L2CC->L2CC_CR = false;
	}
	assert((event_counter > 1) ? 0 : 1);
	if (!event_counter) {
		L2CC->L2CC_ECFGR0 = (source | it);
	} else {
		L2CC->L2CC_ECFGR1 = (source | it);
	}

}

uint32_t l2cc_event_counter_value(uint8_t event_counter)
{
	assert((event_counter > 1) ? 0 : 1);
	if (!event_counter) {
		return L2CC->L2CC_EVR0;
	} else {
		return L2CC->L2CC_EVR1;
	}
}

void l2cc_enable_it(uint16_t sources)
{
	L2CC->L2CC_IMR |= sources;
}

void l2cc_disable_it(uint16_t sources)
{
	L2CC->L2CC_IMR &= (!sources);
}

unsigned short l2cc_it_status_raw(uint16_t sources)
{
	return ((L2CC->L2CC_RISR) & sources) ? 1 : 0;
}

uint16_t l2cc_it_status_mask(uint16_t sources)
{
	return ((L2CC->L2CC_MISR) & sources) ? 1 : 0;
}

void l2cc_it_clear(uint16_t sources)
{
	L2CC->L2CC_ICR |= sources;
}

uint8_t l2cc_poll_spniden()
{
	return ((L2CC->L2CC_DCR & L2CC_DCR_SPNIDEN) >> 2);
}

void l2cc_cache_sync()
{
	while ((L2CC->L2CC_CSR) & L2CC_CSR_C) ;
	L2CC->L2CC_CSR = L2CC_CSR_C;
	while ((L2CC->L2CC_CSR) & L2CC_CSR_C) ;
}

void l2cc_invalidate_pal(uint32_t phys_addr)
{
	static uint32_t Tag;
	static uint16_t Index;
	Tag = (phys_addr >> (OFFSET_BIT + INDEX_BIT));
	Index = (phys_addr >> OFFSET_BIT) & ((1 << INDEX_BIT) - 1);
	L2CC->L2CC_IPALR = (L2CC_IPALR_TAG(Tag) | L2CC_IPALR_IDX(Index) | L2CC_IPALR_C);
	while ((L2CC->L2CC_IPALR) & L2CC_IPALR_C) ;
}

void l2cc_clean_pal(uint32_t phys_addr)
{
	static uint32_t Tag;
	static uint16_t Index;

	Tag = (phys_addr >> (OFFSET_BIT + INDEX_BIT));
	Index = (phys_addr >> OFFSET_BIT) & ((1 << INDEX_BIT) - 1);
	L2CC->L2CC_CPALR =
	    (L2CC_CPALR_TAG(Tag) | L2CC_CPALR_IDX(Index) | L2CC_CPALR_C);
	while ((L2CC->L2CC_CPALR) & L2CC_CPALR_C) ;
}

void l2cc_clean_ix(uint32_t phys_addr)
{
	static uint32_t Tag;
	static uint16_t Index;

	Tag = (phys_addr >> (OFFSET_BIT + INDEX_BIT));
	Index = (phys_addr >> OFFSET_BIT) & ((1 << INDEX_BIT) - 1);
	L2CC->L2CC_CIPALR =
	    (L2CC_CIPALR_TAG(Tag) | L2CC_CIPALR_IDX(Index) | L2CC_CIPALR_C);
	while ((L2CC->L2CC_CIPALR) & L2CC_CIPALR_C) ;
}

void l2cc_invalidate_way(uint8_t way)
{
	L2CC->L2CC_IWR = way;
	while (L2CC->L2CC_IWR) ;
	while (L2CC->L2CC_CSR) ;
}

void l2cc_clean_way(uint8_t way)
{
	L2CC->L2CC_CWR = way;
	while (L2CC->L2CC_CWR) ;
	while (L2CC->L2CC_CSR) ;
}

/**
 * \brief Clean Invalidate cache by way
 * \param way  way number
 */
static void l2cc_clean_invalidate_way(uint8_t way)
{
	L2CC->L2CC_CIWR = way;
	while (L2CC->L2CC_CSR) ;
}

void l2cc_clean_index(uint32_t phys_addr, uint8_t way)
{
	static uint16_t Index;

	Index = (phys_addr >> OFFSET_BIT) & ((1 << INDEX_BIT) - 1);
	L2CC->L2CC_CIR =
	    (L2CC_CIR_IDX(Index) | L2CC_CIR_WAY(way) | L2CC_CIR_C);
	while ((L2CC->L2CC_CIR) & L2CC_CIR_C) ;
}

void l2cc_clean_invalidate_index(uint32_t phys_addr, uint8_t way)
{
	static uint16_t Index;

	(void) way;
	Index = (phys_addr >> OFFSET_BIT) & ((1 << INDEX_BIT) - 1);
	L2CC->L2CC_CIIR =
	    (L2CC_CIIR_IDX(Index) | L2CC_CIIR_WAY(Index) | L2CC_CIIR_C);
	while ((L2CC->L2CC_CIIR) & L2CC_CIIR_C) ;
}

void l2cc_data_lockdown(uint8_t way)
{
	L2CC->L2CC_DLKR = way;
	while (L2CC->L2CC_CSR) ;
}

void l2cc_instruction_lockdown(uint8_t way)
{
	L2CC->L2CC_ILKR = way;
	while (L2CC->L2CC_CSR) ;
}

static void l2cc_clean(void)
{
	// Clean of L1; This is broadcast within the cluster
	cp15_dcache_clean();
	if (l2cc_is_enabled()) {
		// forces the address out past level 2
		l2cc_clean_way(0xFF);
		// Ensures completion of the L2 clean
		l2cc_cache_sync();
	}
}

static void l2cc_invalidate(void)
{
	if (l2cc_is_enabled()) {
		// forces the address out past level 2
		l2cc_invalidate_way(0xFF);
		// Ensures completion of the L2 inval
		l2cc_cache_sync();
	}
	// Inval of L1; This is broadcast within the cluster
	cp15_dcache_invalidate();
}

static void l2cc_clean_invalidate(void)
{
	/* Clean of L1; This is broadcast within the cluster */
	cp15_dcache_clean();

	if (l2cc_is_enabled()) {
		/* forces the address out past level 2 */
		l2cc_clean_invalidate_way(0xFF);
		/* Ensures completion of the L2 inval */
		l2cc_cache_sync();
	}

	/* Inval of L1; This is broadcast within the cluster */
	cp15_dcache_invalidate();
}

void l2cc_cache_maintenance(enum _maint_op maintenance)
{
	switch (maintenance) {
		case L2CC_DCACHE_CLEAN:
			l2cc_clean();
			break;
		case L2CC_DCACHE_INVAL:
			l2cc_invalidate();
			break;
		case L2CC_DCACHE_FLUSH:
			l2cc_clean_invalidate();
			break;
	}
}

void l2cc_invalidate_region(uint32_t start, uint32_t end)
{
	assert(start < end);
	uint32_t current = start & ~0x1fUL;
	if (l2cc_is_enabled()) {
		while (current <= end) {
			l2cc_invalidate_pal(current);
			current += 32;
		}
		l2cc_invalidate_pal(end);
	}
	cp15_invalidate_dcache_for_dma(start, end);
}

void l2cc_clean_region(uint32_t start, uint32_t end)
{
	assert(start < end);
	uint32_t current = start & ~0x1fUL;
	if (l2cc_is_enabled()) {
		while (current <= end) {
			l2cc_clean_pal(current);
			current += 32;
		}
		l2cc_clean_pal(end);
	}
	cp15_clean_dcache_for_dma(start, end);
}

void l2cc_configure(const struct _l2cc_control* cfg)
{
	l2cc_event_config(0, L2CC_ECFGR0_ESRC_SRC_DRHIT,
			  L2CC_ECFGR0_EIGEN_INT_DIS);
	l2cc_event_config(1, L2CC_ECFGR0_ESRC_SRC_DWHIT,
			  L2CC_ECFGR0_EIGEN_INT_DIS);

	l2cc_enable_reset_counter(L2CC_RESET_BOTH_COUNTER);
	l2cc_set_config(cfg);
	/* Enable Prefetch */
	l2cc_inst_prefetch_enable();
	l2cc_data_prefetch_enable();
	/* Invalidate whole L2CC */
	l2cc_invalidate_way(0xFF);
	/* Disable all L2CC Interrupt */
	l2cc_disable_it(0x1FF);
	/* Clear all L2CC Interrupt */
	l2cc_it_clear(0xFF);
	l2cc_exclusive_cache(true);
	l2cc_enable();
}
