/*
 *
 Copyright (c) Eicon Networks, 2002.
 *
 This source file is supplied for the use with
 Eicon Networks range of DIVA Server Adapters.
 *
 Eicon File Revision :    2.1
 *
 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, or (at your option)
 any later version.
 *
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY OF ANY KIND WHATSOEVER INCLUDING ANY
 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.
 *
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include "platform.h"
#include "di_defs.h"
#include "pc.h"
#include "pr_pc.h"
#include "di.h"
#include "mi_pc.h"
#include "pc_maint.h"
#include "divasync.h"
#include "pc_init.h"
#include "io.h"
#include "helpers.h"
#include "dsrv4bri.h"
#include "dsp_defs.h"
#include "sdp_hdr.h"

/*****************************************************************************/
#define	MAX_XLOG_SIZE	(64 * 1024)

/* --------------------------------------------------------------------------
   Recovery XLOG from QBRI Card
   -------------------------------------------------------------------------- */
static void qBri_cpu_trapped(PISDN_ADAPTER IoAdapter) {
	byte  __iomem *base;
	word *Xlog;
	dword   regs[4], TrapID, offset, size;
	Xdesc   xlogDesc;
	int factor = (IoAdapter->tasks == 1) ? 1 : 2;

/*
 *	check for trapped MIPS 46xx CPU, dump exception frame
 */

	base = DIVA_OS_MEM_ATTACH_CONTROL(IoAdapter);
	offset = IoAdapter->ControllerNumber * (IoAdapter->MemorySize >> factor);

	TrapID = READ_DWORD(&base[0x80]);

	if ((TrapID == 0x99999999) || (TrapID == 0x99999901))
	{
		dump_trap_frame(IoAdapter, &base[0x90]);
		IoAdapter->trapped = 1;
	}

	regs[0] = READ_DWORD((base + offset) + 0x70);
	regs[1] = READ_DWORD((base + offset) + 0x74);
	regs[2] = READ_DWORD((base + offset) + 0x78);
	regs[3] = READ_DWORD((base + offset) + 0x7c);
	regs[0] &= IoAdapter->MemorySize - 1;

	if ((regs[0] >= offset)
	    && (regs[0] < offset + (IoAdapter->MemorySize >> factor) - 1))
	{
		if (!(Xlog = (word *)diva_os_malloc(0, MAX_XLOG_SIZE))) {
			DIVA_OS_MEM_DETACH_CONTROL(IoAdapter, base);
			return;
		}

		size = offset + (IoAdapter->MemorySize >> factor) - regs[0];
		if (size > MAX_XLOG_SIZE)
			size = MAX_XLOG_SIZE;
		memcpy_fromio(Xlog, &base[regs[0]], size);
		xlogDesc.buf = Xlog;
		xlogDesc.cnt = READ_WORD(&base[regs[1] & (IoAdapter->MemorySize - 1)]);
		xlogDesc.out = READ_WORD(&base[regs[2] & (IoAdapter->MemorySize - 1)]);
		dump_xlog_buffer(IoAdapter, &xlogDesc);
		diva_os_free(0, Xlog);
		IoAdapter->trapped = 2;
	}
	DIVA_OS_MEM_DETACH_CONTROL(IoAdapter, base);
}

/* --------------------------------------------------------------------------
   Reset QBRI Hardware
   -------------------------------------------------------------------------- */
static void reset_qBri_hardware(PISDN_ADAPTER IoAdapter) {
	word volatile __iomem *qBriReset;
	byte  volatile __iomem *qBriCntrl;
	byte  volatile __iomem *p;

	qBriReset = (word volatile __iomem *)DIVA_OS_MEM_ATTACH_PROM(IoAdapter);
	WRITE_WORD(qBriReset, READ_WORD(qBriReset) | PLX9054_SOFT_RESET);
	diva_os_wait(1);
	WRITE_WORD(qBriReset, READ_WORD(qBriReset) & ~PLX9054_SOFT_RESET);
	diva_os_wait(1);
	WRITE_WORD(qBriReset, READ_WORD(qBriReset) | PLX9054_RELOAD_EEPROM);
	diva_os_wait(1);
	WRITE_WORD(qBriReset, READ_WORD(qBriReset) & ~PLX9054_RELOAD_EEPROM);
	diva_os_wait(1);
	DIVA_OS_MEM_DETACH_PROM(IoAdapter, qBriReset);

	qBriCntrl = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	p = &qBriCntrl[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_RISC) : (MQ_BREG_RISC)];
	WRITE_DWORD(p, 0);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, qBriCntrl);

	DBG_TRC(("resetted board @ reset addr 0x%08lx", qBriReset))
		DBG_TRC(("resetted board @ cntrl addr 0x%08lx", p))
		}

/* --------------------------------------------------------------------------
   Start Card CPU
   -------------------------------------------------------------------------- */
void start_qBri_hardware(PISDN_ADAPTER IoAdapter) {
	byte volatile __iomem *qBriReset;
	byte volatile __iomem *p;

	p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	qBriReset = &p[(DIVA_4BRI_REVISION(IoAdapter)) ? (MQ2_BREG_RISC) : (MQ_BREG_RISC)];
	WRITE_DWORD(qBriReset, MQ_RISC_COLD_RESET_MASK);
	diva_os_wait(2);
	WRITE_DWORD(qBriReset, MQ_RISC_WARM_RESET_MASK | MQ_RISC_COLD_RESET_MASK);
	diva_os_wait(10);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p);

	DBG_TRC(("started processor @ addr 0x%08lx", qBriReset))
		}

/* --------------------------------------------------------------------------
   Stop Card CPU
   -------------------------------------------------------------------------- */
static void stop_qBri_hardware(PISDN_ADAPTER IoAdapter) {
	byte volatile __iomem *p;
	dword volatile __iomem *qBriReset;
	dword volatile __iomem *qBriIrq;
	dword volatile __iomem *qBriIsacDspReset;
	int rev2 = DIVA_4BRI_REVISION(IoAdapter);
	int reset_offset = rev2 ? (MQ2_BREG_RISC)      : (MQ_BREG_RISC);
	int irq_offset   = rev2 ? (MQ2_BREG_IRQ_TEST)  : (MQ_BREG_IRQ_TEST);
	int hw_offset    = rev2 ? (MQ2_ISAC_DSP_RESET) : (MQ_ISAC_DSP_RESET);

	if (IoAdapter->ControllerNumber > 0)
		return;
	p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	qBriReset = (dword volatile __iomem *)&p[reset_offset];
	qBriIsacDspReset = (dword volatile __iomem *)&p[hw_offset];
/*
 *	clear interrupt line (reset Local Interrupt Test Register)
 */
	WRITE_DWORD(qBriReset, 0);
	WRITE_DWORD(qBriIsacDspReset, 0);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p);

	p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter);
	WRITE_BYTE(&p[PLX9054_INTCSR], 0x00);	/* disable PCI interrupts */
	DIVA_OS_MEM_DETACH_RESET(IoAdapter, p);

	p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	qBriIrq   = (dword volatile __iomem *)&p[irq_offset];
	WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p);

	DBG_TRC(("stopped processor @ addr 0x%08lx", qBriReset))

		}

/* --------------------------------------------------------------------------
   FPGA download
   -------------------------------------------------------------------------- */
#define FPGA_NAME_OFFSET         0x10

static byte *qBri_check_FPGAsrc(PISDN_ADAPTER IoAdapter, char *FileName,
				dword *Length, dword *code) {
	byte *File;
	char *fpgaFile, *fpgaType, *fpgaDate, *fpgaTime;
	dword fpgaFlen, fpgaTlen, fpgaDlen, cnt, year, i;

	if (!(File = (byte *)xdiLoadFile(FileName, Length, 0))) {
		return (NULL);
	}
/*
 *	 scan file until FF and put id string into buffer
 */
	for (i = 0; File[i] != 0xff;)
	{
		if (++i >= *Length)
		{
			DBG_FTL(("FPGA download: start of data header not found"))
				xdiFreeFile(File);
			return (NULL);
		}
	}
	*code = i++;

	if ((File[i] & 0xF0) != 0x20)
	{
		DBG_FTL(("FPGA download: data header corrupted"))
			xdiFreeFile(File);
		return (NULL);
	}
	fpgaFlen = (dword)File[FPGA_NAME_OFFSET - 1];
	if (fpgaFlen == 0)
		fpgaFlen = 12;
	fpgaFile = (char *)&File[FPGA_NAME_OFFSET];
	fpgaTlen = (dword)fpgaFile[fpgaFlen + 2];
	if (fpgaTlen == 0)
		fpgaTlen = 10;
	fpgaType = (char *)&fpgaFile[fpgaFlen + 3];
	fpgaDlen = (dword)  fpgaType[fpgaTlen + 2];
	if (fpgaDlen == 0)
		fpgaDlen = 11;
	fpgaDate = (char *)&fpgaType[fpgaTlen + 3];
	fpgaTime = (char *)&fpgaDate[fpgaDlen + 3];
	cnt = (dword)(((File[i] & 0x0F) << 20) + (File[i + 1] << 12)
		      + (File[i + 2] << 4) + (File[i + 3] >> 4));

	if ((dword)(i + (cnt / 8)) > *Length)
	{
		DBG_FTL(("FPGA download: '%s' file too small (%ld < %ld)",
			 FileName, *Length, code + ((cnt + 7) / 8)))
			xdiFreeFile(File);
		return (NULL);
	}
	i = 0;
	do
	{
		while ((fpgaDate[i] != '\0')
		       && ((fpgaDate[i] < '0') || (fpgaDate[i] > '9')))
		{
			i++;
		}
		year = 0;
		while ((fpgaDate[i] >= '0') && (fpgaDate[i] <= '9'))
			year = year * 10 + (fpgaDate[i++] - '0');
	} while ((year < 2000) && (fpgaDate[i] != '\0'));

	switch (IoAdapter->cardType) {
	case CARDTYPE_DIVASRV_B_2F_PCI:
		break;

	default:
		if (year >= 2001) {
			IoAdapter->fpga_features |= PCINIT_FPGA_PLX_ACCESS_SUPPORTED;
		}
	}

	DBG_LOG(("FPGA[%s] file %s (%s %s) len %d",
		 fpgaType, fpgaFile, fpgaDate, fpgaTime, cnt))
		return (File);
}

/******************************************************************************/

#define FPGA_PROG   0x0001		/* PROG enable low */
#define FPGA_BUSY   0x0002		/* BUSY high, DONE low */
#define	FPGA_CS     0x000C		/* Enable I/O pins */
#define FPGA_CCLK   0x0100
#define FPGA_DOUT   0x0400
#define FPGA_DIN    FPGA_DOUT   /* bidirectional I/O */

int qBri_FPGA_download(PISDN_ADAPTER IoAdapter) {
	int            bit;
	byte           *File;
	dword          code, FileLength;
	word volatile __iomem *addr = (word volatile __iomem *)DIVA_OS_MEM_ATTACH_PROM(IoAdapter);
	word           val, baseval = FPGA_CS | FPGA_PROG;



	if (DIVA_4BRI_REVISION(IoAdapter))
	{
		char *name;

		switch (IoAdapter->cardType) {
		case CARDTYPE_DIVASRV_B_2F_PCI:
			name = "dsbri2f.bit";
			break;

		case CARDTYPE_DIVASRV_B_2M_V2_PCI:
		case CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI:
			name = "dsbri2m.bit";
			break;

		default:
			name = "ds4bri2.bit";
		}

		File = qBri_check_FPGAsrc(IoAdapter, name,
					  &FileLength, &code);
	}
	else
	{
		File = qBri_check_FPGAsrc(IoAdapter, "ds4bri.bit",
					  &FileLength, &code);
	}
	if (!File) {
		DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr);
		return (0);
	}
/*
 *	prepare download, pulse PROGRAM pin down.
 */
	WRITE_WORD(addr, baseval & ~FPGA_PROG); /* PROGRAM low pulse */
	WRITE_WORD(addr, baseval);              /* release */
	diva_os_wait(50);  /* wait until FPGA finished internal memory clear */
/*
 *	check done pin, must be low
 */
	if (READ_WORD(addr) & FPGA_BUSY)
	{
		DBG_FTL(("FPGA download: acknowledge for FPGA memory clear missing"))
			xdiFreeFile(File);
		DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr);
		return (0);
	}
/*
 *	put data onto the FPGA
 */
	while (code < FileLength)
	{
		val = ((word)File[code++]) << 3;

		for (bit = 8; bit-- > 0; val <<= 1) /* put byte onto FPGA */
		{
			baseval &= ~FPGA_DOUT;             /* clr  data bit */
			baseval |= (val & FPGA_DOUT);      /* copy data bit */
			WRITE_WORD(addr, baseval);
			WRITE_WORD(addr, baseval | FPGA_CCLK);     /* set CCLK hi */
			WRITE_WORD(addr, baseval | FPGA_CCLK);     /* set CCLK hi */
			WRITE_WORD(addr, baseval);                 /* set CCLK lo */
		}
	}
	xdiFreeFile(File);
	diva_os_wait(100);
	val = READ_WORD(addr);

	DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr);

	if (!(val & FPGA_BUSY))
	{
		DBG_FTL(("FPGA download: chip remains in busy state (0x%04x)", val))
			return (0);
	}

	return (1);
}

static int load_qBri_hardware(PISDN_ADAPTER IoAdapter) {
	return (0);
}

/* --------------------------------------------------------------------------
   Card ISR
   -------------------------------------------------------------------------- */
static int qBri_ISR(struct _ISDN_ADAPTER *IoAdapter) {
	dword volatile     __iomem *qBriIrq;

	PADAPTER_LIST_ENTRY QuadroList = IoAdapter->QuadroList;

	word			i;
	int			serviced = 0;
	byte __iomem *p;

	p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter);

	if (!(READ_BYTE(&p[PLX9054_INTCSR]) & 0x80)) {
		DIVA_OS_MEM_DETACH_RESET(IoAdapter, p);
		return (0);
	}
	DIVA_OS_MEM_DETACH_RESET(IoAdapter, p);

/*
 *	clear interrupt line (reset Local Interrupt Test Register)
 */
	p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	qBriIrq = (dword volatile __iomem *)(&p[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_IRQ_TEST)  : (MQ_BREG_IRQ_TEST)]);
	WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p);

	for (i = 0; i < IoAdapter->tasks; ++i)
	{
		IoAdapter = QuadroList->QuadroAdapter[i];

		if (IoAdapter && IoAdapter->Initialized
		    && IoAdapter->tst_irq(&IoAdapter->a))
		{
			IoAdapter->IrqCount++;
			serviced = 1;
			diva_os_schedule_soft_isr(&IoAdapter->isr_soft_isr);
		}
	}

	return (serviced);
}

/* --------------------------------------------------------------------------
   Does disable the interrupt on the card
   -------------------------------------------------------------------------- */
static void disable_qBri_interrupt(PISDN_ADAPTER IoAdapter) {
	dword volatile __iomem *qBriIrq;
	byte __iomem *p;

	if (IoAdapter->ControllerNumber > 0)
		return;
/*
 *	clear interrupt line (reset Local Interrupt Test Register)
 */
	p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter);
	WRITE_BYTE(&p[PLX9054_INTCSR], 0x00);	/* disable PCI interrupts */
	DIVA_OS_MEM_DETACH_RESET(IoAdapter, p);

	p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter);
	qBriIrq = (dword volatile __iomem *)(&p[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_IRQ_TEST)  : (MQ_BREG_IRQ_TEST)]);
	WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF);
	DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p);
}

/* --------------------------------------------------------------------------
   Install Adapter Entry Points
   -------------------------------------------------------------------------- */
static void set_common_qBri_functions(PISDN_ADAPTER IoAdapter) {
	ADAPTER *a;

	a = &IoAdapter->a;

	a->ram_in           = mem_in;
	a->ram_inw          = mem_inw;
	a->ram_in_buffer    = mem_in_buffer;
	a->ram_look_ahead   = mem_look_ahead;
	a->ram_out          = mem_out;
	a->ram_outw         = mem_outw;
	a->ram_out_buffer   = mem_out_buffer;
	a->ram_inc          = mem_inc;

	IoAdapter->out = pr_out;
	IoAdapter->dpc = pr_dpc;
	IoAdapter->tst_irq = scom_test_int;
	IoAdapter->clr_irq  = scom_clear_int;
	IoAdapter->pcm  = (struct pc_maint *)MIPS_MAINT_OFFS;

	IoAdapter->load = load_qBri_hardware;

	IoAdapter->disIrq = disable_qBri_interrupt;
	IoAdapter->rstFnc = reset_qBri_hardware;
	IoAdapter->stop = stop_qBri_hardware;
	IoAdapter->trapFnc = qBri_cpu_trapped;

	IoAdapter->diva_isr_handler = qBri_ISR;

	IoAdapter->a.io = (void *)IoAdapter;
}

static void set_qBri_functions(PISDN_ADAPTER IoAdapter) {
	if (!IoAdapter->tasks) {
		IoAdapter->tasks = MQ_INSTANCE_COUNT;
	}
	IoAdapter->MemorySize = MQ_MEMORY_SIZE;
	set_common_qBri_functions(IoAdapter);
	diva_os_set_qBri_functions(IoAdapter);
}

static void set_qBri2_functions(PISDN_ADAPTER IoAdapter) {
	if (!IoAdapter->tasks) {
		IoAdapter->tasks = MQ_INSTANCE_COUNT;
	}
	IoAdapter->MemorySize = (IoAdapter->tasks == 1) ? BRI2_MEMORY_SIZE : MQ2_MEMORY_SIZE;
	set_common_qBri_functions(IoAdapter);
	diva_os_set_qBri2_functions(IoAdapter);
}

/******************************************************************************/

void prepare_qBri_functions(PISDN_ADAPTER IoAdapter) {

	set_qBri_functions(IoAdapter->QuadroList->QuadroAdapter[0]);
	set_qBri_functions(IoAdapter->QuadroList->QuadroAdapter[1]);
	set_qBri_functions(IoAdapter->QuadroList->QuadroAdapter[2]);
	set_qBri_functions(IoAdapter->QuadroList->QuadroAdapter[3]);

}

void prepare_qBri2_functions(PISDN_ADAPTER IoAdapter) {
	if (!IoAdapter->tasks) {
		IoAdapter->tasks = MQ_INSTANCE_COUNT;
	}

	set_qBri2_functions(IoAdapter->QuadroList->QuadroAdapter[0]);
	if (IoAdapter->tasks > 1) {
		set_qBri2_functions(IoAdapter->QuadroList->QuadroAdapter[1]);
		set_qBri2_functions(IoAdapter->QuadroList->QuadroAdapter[2]);
		set_qBri2_functions(IoAdapter->QuadroList->QuadroAdapter[3]);
	}

}

/* -------------------------------------------------------------------------- */