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

#include "prem_counter.h"

#include <assert.h>
#include <stdio.h>
#include "csaccess.h"
#include "csregisters.h"

#include "coresight-addrs.h"

#ifdef BAREMETAL
#include "xil_io.h"
#else

void * prem_count_addr[4];

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
const char *memdev="/dev/mem";

static uint32_t reg_read(void * addr)
{
	return *(volatile uint32_t *) addr;
}

static void reg_write(void * addr, uint32_t data)
{
	volatile uint32_t *localaddr = (volatile uint32_t *)addr;
	*localaddr = data;
}

/*
 * The support function which returns pointer to the virtual
 * address at which starts remapped physical region in the
 * process virtual memory space.
 */
void *map_phys_address(off_t region_base, size_t region_size, int page_offset, int opt_cached)
{
	unsigned long mem_window_size;
	unsigned long pagesize;
	unsigned char *mm;
	unsigned char *mem;
	int fd;  /*
	 * Open a device ("/dev/mem") representing physical address space
	 * in POSIX systems
	 */
	fd = open(memdev, O_RDWR | (!opt_cached? O_SYNC: 0));
	if (fd < 0) {
		fprintf(stderr, "cannot open %s\n", memdev);
		return NULL;
	}  /*
	 * The virtual to physical address mapping translation granularity
	 * corresponds to memory page size. This call obtains the page
	 * size used by running operating system at given CPU architecture.
	 * 4kB are used by Linux running on ARM, ARM64, x86 and x86_64 systems.
	 */
	pagesize=sysconf(_SC_PAGESIZE);  /*
	 * Extend physical region start address and size to page size boundaries
	 * to cover complete requested region.
	 */
	mem_window_size = ((region_base & (pagesize-1)) + region_size + pagesize-1) & ~(pagesize-1);
	//printf("memw %d\n",mem_window_size);
	/*
	 * Map file (in our case physical memory) range at specified offset
	 * to virtual memory ragion/area (see VMA Linux kernel structures)
	 * of the process.
	 */

	mm = mmap(NULL, mem_window_size, PROT_WRITE|PROT_READ,
			MAP_SHARED, fd, (region_base & ~(pagesize-1)) + pagesize*page_offset);  /* Report failure if the mmap is not allowed for given file or its region */
	if (mm == MAP_FAILED) {
		fprintf(stderr,"mmap error\n");
		return NULL;
	}  /*
	 * Add offset in the page to the returned pointer for non-page-aligned
	 * requests.
	 */
	mem = mm + (region_base & (pagesize-1));  return mem;
}
#endif

#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_LIMIT_PREF 0x4
#define PREM_LIMIT_COMP 0x8
#define PREM_LIMIT_WB   0xC
#define PREM_CNTR_PREF 	0x10
#define PREM_CNTR_COMP 	0x14
#define PREM_CNTR_WB   	0x18

void __attribute__((optimize("O0"))) prem_configure(uint32_t cpu, prem_conf_s * config)
{
	reg_write(prem_count_addr[cpu], PREM_PHASE_CONF);
	reg_write(prem_count_addr[cpu] + PREM_LIMIT_PREF, config->lim_prefetch);
	reg_write(prem_count_addr[cpu] + PREM_LIMIT_COMP, config->lim_compute);
	reg_write(prem_count_addr[cpu] + PREM_LIMIT_WB, config->lim_writeback);
}
void prem_set_phase(uint32_t cpu, prem_phase_t phase)
{
	reg_write(prem_count_addr[cpu] + PREM_PHASE, phase);
}
void prem_print_state(uint32_t cpu)
{
	printf("phase \t%u \n\r", reg_read(prem_count_addr[cpu] + PREM_PHASE));
	printf("cntr pref \t%u\n\r", reg_read(prem_count_addr[cpu] + PREM_CNTR_PREF)*ETM_EVENTS_BUFF_NUM);
	printf("cntr comp \t%u\n\r",reg_read(prem_count_addr[cpu] + PREM_CNTR_COMP)*ETM_EVENTS_BUFF_NUM);
	printf("cntr wb \t%u\n\r",reg_read(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)
{
#ifndef BAREMETAL
	prem_count_addr[cpu_id] = map_phys_address(prem_count_phys_addr[cpu_id], 0x1000, 0, 0);
#endif

    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 = reg_read(prem_count_addr[cpu] + PREM_PHASE);
	//prem_print_state(cpu);
	assert(phase > 0 && phase < 4);
    reg_write(prem_count_addr[cpu] + PREM_PHASE, 0);
    reg_read(prem_count_addr[cpu] + PREM_PHASE);
    reg_write(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 = reg_read(prem_count_addr[cpu] + PREM_PHASE);
	assert(phase > 0 && phase < 4);
	return reg_read(prem_count_addr[cpu] + phase_ctr_off[phase-1]);
}


