/*
 * pl_counter.c
 *
 *  Created on: May 7, 2018
 *      Author: barysmax
 */

#include "prem_counter.h"

#include <assert.h>
#include <stdio.h>

#include "xil_io.h"
#include "csaccess.h"
#include "csregisters.h"

#include "coresight-addrs.h"


#define CTI_A53_TRIGIN_EXT0 4U

#define ETM_EXTIN 0U

#define ETM_COUNTER 0U
#define ETM_COUNTER_SELF_RELOAD_MASK (1U << 16U)

#define ETM_RESSEL_EXTIN 2U
#define ETM_RESSEL_EXTIN_GROUP_MASK (0b0000 << 16U)

#define ETM_RESSEL_COUNTER 4U
#define ETM_RESSEL_COUNTER_GROUP_MASK (0b0010 << 16U)

#define PREM_PHASE 	   	0x0
#define PREM_CNTR_PREF 	0x10
#define PREM_CNTR_COMP 	0x14
#define PREM_CNTR_WB   	0x18
#define PREM_LIMIT_PREF 0x4
#define PREM_LIMIT_COMP 0x8
#define PREM_LIMIT_WB   0xc

void __attribute__((optimize("O0"))) prem_configure(uint32_t cpu, prem_conf_s * config)
{
	//Xil_In16(prem_count_addr[cpu]+PREM_PHASE);
	Xil_Out32(prem_count_addr[cpu], PREM_PHASE_CONF);
	//Xil_In32(prem_count_addr[cpu]+PREM_CNTR_PREF);
	Xil_Out32(prem_count_addr[cpu] + PREM_LIMIT_PREF, config->lim_prefetch);
	//Xil_In32(prem_count_addr[cpu]+PREM_CNTR_COMP);
	Xil_Out32(prem_count_addr[cpu] + PREM_LIMIT_COMP, config->lim_compute);
	//Xil_In32(prem_count_addr[cpu]+PREM_CNTR_WB);
	Xil_Out32(prem_count_addr[cpu] + PREM_LIMIT_WB, config->lim_writeback);
}
void prem_set_phase(uint32_t cpu, prem_phase_t phase)
{
	Xil_Out32(prem_count_addr[cpu] + PREM_PHASE, phase);
}
void prem_print_state(uint32_t cpu)
{
	printf("phase \t%u \n\r", Xil_In32(prem_count_addr[cpu] + PREM_PHASE));
	printf("cntr pref \t%u\n\r", Xil_In32(prem_count_addr[cpu] + PREM_CNTR_PREF)*ETM_EVENTS_BUFF_NUM);
	printf("cntr comp \t%u\n\r",Xil_In32(prem_count_addr[cpu] + PREM_CNTR_COMP)*ETM_EVENTS_BUFF_NUM);
	printf("cntr wb \t%u\n\r",Xil_In32(prem_count_addr[cpu] + PREM_CNTR_WB)*ETM_EVENTS_BUFF_NUM);
}


void cs_prem_count_init()
{
    cs_device_t cti_soc1 = cs_device_register(cti_soc1_addr);
	// Set up CTI that triggers PL (through FTM)
	cs_cti_reset(cti_soc1);
	cs_cti_set_global_channels(cti_soc1, 0);
	cs_cti_enable(cti_soc1);
}

static void setup_cti_a53_percpu(uint32_t cpu_id)
{
	cs_device_t cti_cpu = cs_device_register(cti_a53_addr[cpu_id]);
	assert(cs_cti_reset(cti_cpu) == 0);
	cs_cti_set_trigin_channels(cti_cpu, CTI_A53_TRIGIN_EXT0, 1 << cpu_id);
	cs_cti_set_global_channels(cti_cpu, 1 << cpu_id);
	assert(cs_cti_enable(cti_cpu) == 0);
}

static void setup_etm_percpu(uint32_t cpu_id)
{
    cs_device_t etm = cs_device_register(etm_addr[cpu_id]);
	assert(cs_etm_clean(etm) == 0);
	// Enable event exporting at that cpu's PMU
	cs_etmv4_config_t etm_cf;
    cs_etm_config_init_ex(etm, &etm_cf);

    /* Select external event input 0 to detect PMUBUS_EVENT */;
    etm_cf.flags |= CS_ETMC_RES_SEL;
    etm_cf.extinselr = 0x00000000 | PMUBUS_EVENT;

    /* configure resource selector (rs) 2 for selecting extinput 0 */
    etm_cf.rsctlr[2] =  ETM_RESSEL_EXTIN_GROUP_MASK | 1U << ETM_EXTIN;
    /* configure resource selector (rs) 4 for selecting counter 0 */
    /*21:20 = PAIRINV:INV = b0, GROUP = 0b0010, SELECT = b1*/
    etm_cf.rsctlr[4] = ETM_RESSEL_COUNTER_GROUP_MASK| 1U << ETM_COUNTER;
    etm_cf.rsctlr_acc_mask = (1 << ETM_RESSEL_EXTIN) | (1 << ETM_RESSEL_COUNTER);


    etm_cf.flags |= CS_ETMC_COUNTER;
    etm_cf.counter[0].cntctlr = ETM_COUNTER_SELF_RELOAD_MASK | ETM_RESSEL_EXTIN;
    etm_cf.counter[0].cntrldvr = ETM_EVENTS_BUFF_NUM - 1;
    etm_cf.counter[0].cntvr = ETM_EVENTS_BUFF_NUM - 1;
    etm_cf.counter_acc_mask = 1 << ETM_COUNTER;


    etm_cf.flags |= CS_ETMC_EVENTSELECT;
    /* configure external output (eo) 0 to fire when rs4 signals */
    etm_cf.eventctlr0r = ETM_RESSEL_COUNTER;
    //etm_cf.eventctlr0r = ETM_RESSEL_EXTIN;

    etm_cf.eventctlr1r |= 1U << cpu_id;

    assert(cs_etm_config_put_ex(etm, &etm_cf) == 0);
    assert(cs_etm_disable_programming(etm) == 0); /* This brings an ETM to the action! */
}

void setup_pmu_percpu(uint32_t cpu_id)
{
    cs_device_t pmu = cs_device_register(pmu_addr[cpu_id]);
	cs_pmu_bus_export(pmu, 1);
}

void cs_prem_count_percpu_init(uint32_t cpu_id)
{
    cs_device_t cti_soc1 = cs_device_register(cti_soc1_addr);

	// Open a channel for that CPU in SoC's CTI
	cs_cti_set_trigout_channels(cti_soc1, cpu_id, 1 << cpu_id);
    uint32_t glob_chan_mask = cs_device_read(cti_soc1, CS_CTIGATE);
    cs_cti_set_global_channels(cti_soc1, glob_chan_mask | (1U << cpu_id));

    setup_cti_a53_percpu(cpu_id);
    setup_etm_percpu(cpu_id);
    setup_pmu_percpu(cpu_id);
}

void _deprecated_pl_count_reset(uint32_t cpu)
{
	uint32_t phase = Xil_In32(prem_count_addr[cpu] + PREM_PHASE);
	assert(phase > 0 && phase < 4);
    Xil_Out32(prem_count_addr[cpu] + PREM_PHASE, 0);
    Xil_Out32(prem_count_addr[cpu] + PREM_PHASE, phase);
}

uint32_t phase_ctr_off[3] = { PREM_CNTR_PREF, PREM_CNTR_COMP, PREM_CNTR_WB};

uint32_t _deprecated_pl_count_read(uint32_t cpu)
{
	uint32_t phase = Xil_In32(prem_count_addr[cpu] + PREM_PHASE);
	assert(phase > 0 && phase < 4);
	return Xil_In32(prem_count_addr[cpu] + phase_ctr_off[phase-1]);
}


