/*
 **********************************************************************************************
 * Renesas RX62N Compare Match Timer (CMT) module  
 *
 * State: Partiallay implemented. Clocks and start/stop bits missing.
 *
 * Copyright 2012 Jochen Karrer. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY Jochen Karrer ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
 * CONTRIBUTORS 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.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Jochen Karrer.
 *
 **********************************************************************************************
 */

#include <stdint.h>
#include <inttypes.h>
#include "bus.h"
#include "sglib.h"
#include "sgstring.h"
#include "cmt_rx62n.h"
#include "clock.h"
#include "cycletimer.h"
#include "signode.h"

#define REG_CMCR(base)	((base) + 0)
#define		CMCR_CKS_MSK	(3)
#define		CMCR_CKS_SHIFT	(0)
#define		CMCR_CMIE	(1 << 6)
#define	REG_CMCNT(base)	((base) + 2)
#define	REG_CMCOR(base)	((base) + 4)

typedef struct CMT CMT;
typedef struct CMTModule CMTModule; 

struct CMT {
	BusDevice bdev;
	CMTModule *cmtmod;
	uint16_t regCMCR;
	uint16_t regCMCNT;
	uint16_t regCMCOR;
	Clock_t *clkCntr;
	SigNode *sigIrq;
	CycleCounter_t lastUpdate;
	CycleCounter_t accCycles;
	CycleTimer eventTimer;
};

struct CMTModule {
	uint8_t regCMSTR;
	CMT *cmt[2];
};

static void
actualize_counter(CMT *cmt) 
{
	CycleCounter_t now = CycleCounter_Get();
	FractionU64_t frac;
	CycleCounter_t cycles = now - cmt->lastUpdate;
	CycleCounter_t acc;
	uint64_t ctrCycles;
	uint16_t nto;
	cmt->lastUpdate = now;
	acc = cmt->accCycles + cycles;

	frac = Clock_MasterRatio(cmt->clkCntr);
        if((frac.nom == 0) || (frac.denom == 0)) {
                fprintf(stderr,"Warning, No clock for timer module\n");
                return;
        }
        ctrCycles = acc * frac.nom / frac.denom;
        acc -= ctrCycles * frac.denom / frac.nom;
	cmt->accCycles = acc;
	/* Now calculate the time to the next timeout */
	nto = cmt->regCMCOR - cmt->regCMCNT;	
	if(ctrCycles >= nto) {
		ctrCycles -= nto;
		if(cmt->regCMCOR) {
			cmt->regCMCNT = (ctrCycles + cmt->regCMCNT) % cmt->regCMCOR;
		}
		if(cmt->regCMCR & CMCR_CMIE) {
			//fprintf(stderr,"Now posting an timer interrupt\n");
			SigNode_Set(cmt->sigIrq,SIG_LOW);
			SigNode_Set(cmt->sigIrq,SIG_HIGH);
		}
	} else {
		cmt->regCMCNT += ctrCycles;
	}
}

static void
update_timeout(CMT *cmt) 
{
	/* next Timeout: Intentional wrap */
	FractionU64_t frac;
	uint16_t nto = cmt->regCMCOR - cmt->regCMCNT;
	CycleCounter_t cpu_cycles;	
	frac = Clock_MasterRatio(cmt->clkCntr);
        if((frac.nom == 0) || (frac.denom == 0)) {
                fprintf(stderr,"Warning, No clock for timer module\n");
                return;
        }
	cpu_cycles = ((uint64_t)nto * frac.denom + (frac.nom - 1)) / frac.nom;
	if(cpu_cycles >= cmt->accCycles) {
		cpu_cycles -= cmt->accCycles;
	} else if(cpu_cycles == 0) {
		return;
	} else {
		fprintf(stderr,"shouldn't happen cy %"PRIu64" ac %"PRIu64"\n",cpu_cycles,cmt->accCycles);
		cpu_cycles = 0;
	}
	if(cmt->regCMCR & CMCR_CMIE) {
		CycleTimer_Mod(&cmt->eventTimer,cpu_cycles);
	} else {
		CycleTimer_Remove(&cmt->eventTimer);
	}
}

static uint32_t
cmcr_read(void *clientData,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	return cmt->regCMCR;
}

static void
cmcr_write(void *clientData,uint32_t value,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	actualize_counter(cmt);
	cmt->regCMCR = value;
	update_timeout(cmt);
}

static uint32_t
cmcnt_read(void *clientData,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	actualize_counter(cmt);
	return cmt->regCMCNT;
}

static void
cmcnt_write(void *clientData,uint32_t value,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	actualize_counter(cmt);
	cmt->regCMCNT = value;
	update_timeout(cmt);
}

static uint32_t
cmcor_read(void *clientData,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	return cmt->regCMCOR;
}

static void
cmcor_write(void *clientData,uint32_t value,uint32_t address,int rqlen)
{
	CMT *cmt = clientData;
	actualize_counter(cmt);
	cmt->regCMCOR = value;
	update_timeout(cmt);
}

static void
CMT_Unmap(void *owner,uint32_t base,uint32_t mask)
{
	IOH_Delete16(REG_CMCR(base));
	IOH_Delete16(REG_CMCNT(base));
	IOH_Delete16(REG_CMCOR(base));
}

static void
CMT_Map(void *owner,uint32_t base,uint32_t mask,uint32_t mapflags)
{
	CMT *cmt = owner;
	IOH_New16(REG_CMCR(base),cmcr_read,cmcr_write,cmt);
	IOH_New16(REG_CMCNT(base),cmcnt_read,cmcnt_write,cmt);
	IOH_New16(REG_CMCOR(base),cmcor_read,cmcor_write,cmt);
}

static void
timer_event(void *eventData)
{
	CMT *cmt = eventData;
        actualize_counter(cmt);
	if(cmt->regCMCNT != 0) {
		static int cntr = 0;
		cntr++;
		if(cntr < 20) {
			fprintf(stderr,"Cnt not 0 after event %u\n",cmt->regCMCNT);
		}
	}
        update_timeout(cmt);
}

BusDevice *
CMT_New(const char *name)
{
	CMT *cmt = sg_new(CMT); 
	cmt->clkCntr = Clock_New("%s.clkCntr",name);
	/* Only until clocktree exists */
	Clock_SetFreq(cmt->clkCntr,48000000/8);
	cmt->sigIrq = SigNode_New("%s.irq",name);
	CycleTimer_Init(&cmt->eventTimer,timer_event,cmt);
        cmt->bdev.first_mapping = NULL;
        cmt->bdev.Map = CMT_Map;
        cmt->bdev.UnMap = CMT_Unmap;
        cmt->bdev.owner = cmt;
        cmt->bdev.hw_flags = MEM_FLAG_WRITABLE|MEM_FLAG_READABLE;
	return &cmt->bdev;
}
