/* Calypso DBB internal TPU (Time Processing Unit) Driver */

/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <stdint.h>
#include <stdio.h>

#include <debug.h>
#include <delay.h>
#include <memory.h>
#include <calypso/tpu.h>
#include <calypso/tsp.h>

/* Using TPU_DEBUG you will send special HLDC messages to the host PC
 * containing the full TPU RAM content at the time you call tpu_enable() */
//#define TPU_DEBUG

#define BASE_ADDR_TPU	0xffff1000
#define TPU_REG(x)	(BASE_ADDR_TPU+(x))

#define BASE_ADDR_TPU_RAM	0xffff9000
#define TPU_RAM_END		0xffff97ff

enum tpu_reg_arm {
	TPU_CTRL	= 0x0,	/* Control & Status Register */
	INT_CTRL	= 0x2,	/* Interrupt Control Register */
	INT_STAT	= 0x4,	/* Interrupt Status Register */
	TPU_OFFSET	= 0xC,	/* Offset operand value register */
	TPU_SYNCHRO	= 0xE,	/* synchro operand value register */
	IT_DSP_PG	= 0x20,
};

enum tpu_ctrl_bits {
	TPU_CTRL_RESET		= (1 << 0),
	TPU_CTRL_PAGE		= (1 << 1),
	TPU_CTRL_EN		= (1 << 2),
	/* unused */
	TPU_CTRL_DSP_EN		= (1 << 4),
	/* unused */
	TPU_CTRL_MCU_RAM_ACC	= (1 << 6),
	TPU_CTRL_TSP_RESET	= (1 << 7),
	TPU_CTRL_IDLE		= (1 << 8),
	TPU_CTRL_WAIT		= (1 << 9),
	TPU_CTRL_CK_ENABLE	= (1 << 10),
	TPU_CTRL_FULL_WRITE	= (1 << 11),
};

enum tpu_int_ctrl_bits {
	ICTRL_MCU_FRAME		= (1 << 0),
	ICTRL_MCU_PAGE		= (1 << 1),
	ICTRL_DSP_FRAME		= (1 << 2),
	ICTRL_DSP_FRAME_FORCE	= (1 << 3),
};

static uint16_t *tpu_ptr = (uint16_t *)BASE_ADDR_TPU_RAM;

#ifdef TPU_DEBUG
#include <comm/sercomm.h>
#include <layer1/sync.h>
static struct msgb *tpu_debug_msg = NULL;

static void tpu_debug_alloc(void)
{
	tpu_debug_msg = sercomm_alloc_msgb(sizeof(uint32_t) + 64*2);
	if (!tpu_debug_msg)
		printf("UNABLE TO ALLOC TPU DBG\n");
}
static void tpu_debug_flush(void)
{
	if (tpu_debug_msg) {
		sercomm_sendmsg(SC_DLCI_DEBUG, tpu_debug_msg);
		tpu_debug_msg = NULL;
	}
	tpu_debug_alloc();
}
static void tpu_debug_enqueue(uint16_t instr)
{
	uint16_t *u16_out;

	if (!tpu_debug_msg)
		return;
	if (tpu_ptr == (uint16_t *) BASE_ADDR_TPU_RAM) {
		/* prepend tpu memory dump with frame number */
		uint32_t *fn = (uint32_t *) msgb_put(tpu_debug_msg, sizeof(fn));
		*fn = l1s.current_time.fn;
	}
	if (msgb_tailroom(tpu_debug_msg) >= sizeof(instr)) {
		/* cannot use msgb_put_u16 as host program expects little endian */
		u16_out = (uint16_t *) msgb_put(tpu_debug_msg, sizeof(instr));
		*u16_out = instr;
	}
}
#else
static void tpu_debug_alloc(void) { }
static void tpu_debug_flush(void) { }
static void tpu_debug_enqueue(uint16_t instr) { }
#endif

#define BIT_SET	1
#define BIT_CLEAR 0

/* wait for a certain control bit to be set */
static int tpu_wait_ctrl_bit(uint16_t bit, int set)
{
	int timeout = 10*1000;

	while (1) {
		uint16_t reg = readw(TPU_REG(TPU_CTRL));
		if (set) {
			if (reg & bit)
				break;
		} else {
			if (!(reg & bit))
				break;
		}
		timeout--;
		if (timeout <= 0) {
			puts("Timeout while waiting for TPU ctrl bit!\n");
			return -1;
		}
	}

	return 0;
}

/* assert or de-assert TPU reset */
void tpu_reset(int active)
{
	uint16_t reg;

	printd("tpu_reset(%u)\n", active);
	reg = readw(TPU_REG(TPU_CTRL));
	if (active) {
		reg |= (TPU_CTRL_RESET|TPU_CTRL_TSP_RESET);
		writew(reg, TPU_REG(TPU_CTRL));
		tpu_wait_ctrl_bit(TPU_CTRL_RESET, BIT_SET);
	} else {
		reg &= ~(TPU_CTRL_RESET|TPU_CTRL_TSP_RESET);
		writew(reg, TPU_REG(TPU_CTRL));
		tpu_wait_ctrl_bit(TPU_CTRL_RESET, BIT_CLEAR);
	}
}

/* Enable or Disable a new scenario loaded into the TPU */
void tpu_enable(int active)
{
	uint16_t reg = readw(TPU_REG(TPU_CTRL));

	printd("tpu_enable(%u)\n", active);

	if (active)
		reg |= TPU_CTRL_EN;
	else
		reg &= ~TPU_CTRL_EN;
	writew(reg, TPU_REG(TPU_CTRL));

	tpu_debug_flush();

	/* After the new scenario is loaded, TPU switches the MCU-visible memory
	 * page, i.e. we can write without any danger */
	tpu_rewind();
#if 0
	{
		int i;
		uint16_t oldreg = 0;

		for (i = 0; i < 100000; i++) {
			reg = readw(TPU_REG(TPU_CTRL));
			if (i == 0 || oldreg != reg) {
				printd("%d TPU state: 0x%04x\n", i, reg);
			}
			oldreg = reg;
		}
	}
#endif
}

/* Enable or Disable the clock of the TPU Module */
void tpu_clk_enable(int active)
{
	uint16_t reg = readw(TPU_REG(TPU_CTRL));

	printd("tpu_clk_enable(%u)\n", active);
	if (active) {
		reg |= TPU_CTRL_CK_ENABLE;
		writew(reg, TPU_REG(TPU_CTRL));
		tpu_wait_ctrl_bit(TPU_CTRL_CK_ENABLE, BIT_SET);
	} else {
		reg &= ~TPU_CTRL_CK_ENABLE;
		writew(reg, TPU_REG(TPU_CTRL));
		tpu_wait_ctrl_bit(TPU_CTRL_CK_ENABLE, BIT_CLEAR);
	}
}

/* Enable Frame Interrupt generation on next frame.  DSP will reset it */
void tpu_dsp_frameirq_enable(void)
{
	uint16_t reg = readw(TPU_REG(TPU_CTRL));
	reg |= TPU_CTRL_DSP_EN;
	writew(reg, TPU_REG(TPU_CTRL));

	tpu_wait_ctrl_bit(TPU_CTRL_DSP_EN, BIT_SET);
}

/* Is a Frame interrupt still pending for the DSP ? */
int tpu_dsp_fameirq_pending(void)
{
	uint16_t reg = readw(TPU_REG(TPU_CTRL));

	if (reg & TPU_CTRL_DSP_EN)
		return 1;

	return 0;
}

void tpu_rewind(void)
{
	dputs("tpu_rewind()\n");
	tpu_ptr = (uint16_t *) BASE_ADDR_TPU_RAM;
}

void tpu_enqueue(uint16_t instr)
{
	printd("tpu_enqueue(tpu_ptr=%p, instr=0x%04x)\n", tpu_ptr, instr);
	tpu_debug_enqueue(instr);
	*tpu_ptr++ = instr;
	if (tpu_ptr > (uint16_t *) TPU_RAM_END)
		puts("TPU enqueue beyond end of TPU memory\n");
}

void tpu_init(void)
{
	uint16_t *ptr;

	/* Put TPU into Reset and enable clock */
	tpu_reset(1);
	tpu_clk_enable(1);

	/* set all TPU RAM to zero */
	for (ptr = (uint16_t *) BASE_ADDR_TPU_RAM; ptr < (uint16_t *) TPU_RAM_END; ptr++)
		*ptr = 0x0000;

	/* Get TPU out of reset */
	tpu_reset(0);
	/* Disable all interrupts */
	writeb(0x7, TPU_REG(INT_CTRL));

	tpu_rewind();
	tpu_enq_offset(0);
	tpu_enq_sync(0);
}

void tpu_test(void)
{
	int i;

	/* program a sequence of TSPACT events into the TPU */
	for (i = 0; i < 10; i++) {
		puts("TSP ACT enable: ");
		tsp_act_enable(0x0001);
		tpu_enq_wait(10);
		puts("TSP ACT disable: ");
		tsp_act_disable(0x0001);
		tpu_enq_wait(10);
	}
	tpu_enq_sleep();

	/* tell the chip to execute the scenario */
	tpu_enable(1);
}

void tpu_wait_idle(void)
{
	dputs("Waiting for TPU Idle ");
	/* Wait until TPU is doing something */
	delay_us(3);
	/* Wait until TPU is idle */
	while (readw(TPU_REG(TPU_CTRL)) & TPU_CTRL_IDLE)
		dputchar('.');
	dputs("Done!\n");
}

void tpu_frame_irq_en(int mcu, int dsp)
{
	uint8_t reg = readb(TPU_REG(INT_CTRL));
	if (mcu)
		reg &= ~ICTRL_MCU_FRAME;
	else
		reg |= ICTRL_MCU_FRAME;

	if (dsp)
		reg &= ~ICTRL_DSP_FRAME;
	else
		reg |= ICTRL_DSP_FRAME;

	writeb(reg, TPU_REG(INT_CTRL));
}

void tpu_force_dsp_frame_irq(void)
{
	uint8_t reg = readb(TPU_REG(INT_CTRL));
	reg |= ICTRL_DSP_FRAME_FORCE;
	writeb(reg, TPU_REG(INT_CTRL));
}

uint16_t tpu_get_offset(void)
{
	return readw(TPU_REG(TPU_OFFSET));
}

uint16_t tpu_get_synchro(void)
{
	return readw(TPU_REG(TPU_SYNCHRO));
}

/* add two numbers, modulo 5000, and ensure the result is positive */
uint16_t add_mod5000(int16_t a, int16_t b)
{
	int32_t sum = (int32_t)a + (int32_t)b;

	sum %= 5000;

	/* wrap around zero */
	if (sum < 0)
		sum += 5000;

	return sum;
}
