/*
 * video-related stuff
 * 2004-03-03  dmp@davidmpye.dyndns.org	Synced with kernel fb driver, added proper focus support etc
 * 2003-02-02  andy@warmcat.com  	Major reshuffle, threw out tons of unnecessary init
                                 	Made a good start on separating the video mode from the AV cable
				 	Consolidated init tables into a big struct (see boot.h)
 * 2002-12-04  andy@warmcat.com  Video now working :-)
 */

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "boot.h"
#include "BootEEPROM.h"
#include "config.h"
#include "BootVideo.h"
#include "video.h"
#include "memory_layout.h"
#include "VideoInitialization.h"
#include "BootVgaInitialization.h"
#include "encoder.h"
#include "xcalibur.h"

void DetectVideoEncoder(void) {
	if (I2CTransmitByteGetReturn(0x45,0x00) != ERR_I2C_ERROR_BUS) video_encoder = ENCODER_CONEXANT;
	else if (I2CTransmitByteGetReturn(0x6a,0x00) != ERR_I2C_ERROR_BUS) video_encoder = ENCODER_FOCUS;
	else video_encoder = ENCODER_XCALIBUR;
}

char *VideoEncoderName(void) {
	char *focus_name="Focus";
	char *conexant_name="Conexant";
	char *xcalibur_name="Xcalibur";
	char *unknown_name="Unknown";

	switch (video_encoder) {
		case ENCODER_CONEXANT:
			return conexant_name;
		case ENCODER_FOCUS:
			return focus_name;
		case ENCODER_XCALIBUR:
			return xcalibur_name;
		default:
			return unknown_name;
	}
}

char *AvCableName(void) {
	char *composite_name="Composite";
	char *svideo_name="SVideo";
	char *rgb_name="RGB SCART";
	char *hdtv_name="HDTV";
	char *vga_name="VGA";
	char *vgasog_name="VGA SoG";
	char *unknown_name="Unknown";
	
	xbox_av_type av_type = DetectAvType();
	switch (av_type) {
		case AV_SCART_RGB:
			return rgb_name;
		case AV_SVIDEO:
			return svideo_name;
		case AV_VGA_SOG:
			return vgasog_name;
		case AV_HDTV:
			return hdtv_name;
		case AV_COMPOSITE:
			return composite_name;
		case AV_VGA:
			return vga_name;
		default:
			return unknown_name;

	}
}

void BootVgaInitializationKernelNG(CURRENT_VIDEO_MODE_DETAILS * pvmode) {
	xbox_tv_encoding tv_encoding; 
	xbox_av_type av_type;
	u8 b;
	RIVA_HW_INST riva;
        struct riva_regs newmode;
	int encoder_ok = 0;
	int i=0;
	GPU_PARAMETER gpu;
	xbox_video_mode encoder_mode;
	
	tv_encoding = DetectVideoStd();
	DetectVideoEncoder();
	
        // Dump to global variable
	VIDEO_AV_MODE=I2CTransmitByteGetReturn(0x10, 0x04);
	av_type = DetectAvType();
	gpu.av_type = av_type;

   	memset((void *)pvmode,0,sizeof(CURRENT_VIDEO_MODE_DETAILS));

	//Focus driver (presumably XLB also) doesnt do widescreen yet - only blackscreens otherwise.
	if(((u8 *)&eeprom)[0x96]&0x01 && video_encoder == ENCODER_CONEXANT) { // 16:9 widescreen TV
		pvmode->m_nVideoModeIndex=VIDEO_MODE_1024x576;
	} else { // 4:3 TV
		pvmode->m_nVideoModeIndex=VIDEO_PREFERRED_MODE;
	}

	pvmode->m_pbBaseAddressVideo=(u8 *)0xfd000000;
	pvmode->m_fForceEncoderLumaAndChromaToZeroInitially=1;

        // If the client hasn't set the frame buffer start address, assume
        // it should be at 4M from the end of RAM.

	pvmode->m_dwFrameBufferStart = FB_START;

        (*(unsigned int*)0xFD600800) = (FB_START & 0x0fffffff);

	pvmode->m_bAvPack=I2CTransmitByteGetReturn(0x10, 0x04);
	pvmode->m_pbBaseAddressVideo=(u8 *)0xfd000000;
	pvmode->m_fForceEncoderLumaAndChromaToZeroInitially=1;
	pvmode->m_bBPP = 32;

	// The values for hoc and voc are stolen from nvtv small mode
	switch (tv_encoding) {
		case TV_ENC_PALBDGHI:
			pvmode->hoc = 13.44;
			pvmode->voc = 14.24;
			break;
		case TV_ENC_NTSC:
		default:
			pvmode->hoc = 15.11;
			pvmode->voc = 14.81;
			break;
	}

	pvmode->hoc /= 100.0;
	pvmode->voc /= 100.0;

	mapNvMem(&riva,pvmode->m_pbBaseAddressVideo);
	unlockCrtNv(&riva,0);

	if (xbox_ram == 128) {
		MMIO_H_OUT32(riva.PFB,0,0x200,0x03070103);
	} else {
		MMIO_H_OUT32(riva.PFB,0,0x200,0x03070003);
	}

	MMIO_H_OUT32 (riva.PCRTC, 0, 0x800, pvmode->m_dwFrameBufferStart);

	IoOutputByte(0x80d3, 5);  // Kill all video out using an ACPI control pin
	
	MMIO_H_OUT32(riva.PRAMDAC,0,0x884,0x0);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x888,0x0);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x88c,0x10001000);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x890,0x10000000);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x894,0x10000000);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x898,0x10000000);
	MMIO_H_OUT32(riva.PRAMDAC,0,0x89c,0x10000000);

	if (video_encoder==ENCODER_XCALIBUR) {
        	MMIO_H_OUT32(riva.PRAMDAC,0,0x880,0x21101100);
		//Leave GPU in YUV for Xcalibur
		MMIO_H_OUT32(riva.PRAMDAC,0,0x630,0x2);
		MMIO_H_OUT32(riva.PRAMDAC,0,0x84c,0x00801080);
		MMIO_H_OUT32(riva.PRAMDAC,0,0x8c4,0x40801080);
	}
	else {
		MMIO_H_OUT32(riva.PRAMDAC,0,0x880,0);
		//Other encoders use RGB	
		MMIO_H_OUT32(riva.PRAMDAC,0,0x630,0x0);
		MMIO_H_OUT32(riva.PRAMDAC,0,0x84c,0x0);
		MMIO_H_OUT32(riva.PRAMDAC,0,0x8c4,0x0);
	}
	
	writeCrtNv (&riva, 0, 0x14, 0x00);
	writeCrtNv (&riva, 0, 0x17, 0xe3); // Set CRTC mode register
	writeCrtNv (&riva, 0, 0x19, 0x10); // ?
	writeCrtNv (&riva, 0, 0x1b, 0x05); // arbitration0
	writeCrtNv (&riva, 0, 0x22, 0xff); // ?
	writeCrtNv (&riva, 0, 0x33, 0x11); // ?
	
	if (av_type == AV_HDTV) {
		unsigned char pll_int;
		xbox_hdtv_mode hdtv_mode = HDTV_480p;
		//Only 480p supported at present
		/*if (video_mode->yres > 800) {
			hdtv_mode = HDTV_1080i;
		}
		else if (video_mode->yres > 600) {
			hdtv_mode = HDTV_720p;
		}*/
		
		// Settings for 720x480@60Hz (480p)
		pvmode->width=720;
		pvmode->height=480;
		pvmode->xmargin=0;
	 	pvmode->ymargin=0;

		if (video_encoder==ENCODER_XCALIBUR) {
			/* This info should be in the xcalibur.c file, but
			*  the HDTV api doesn't allow for it right now. */
			gpu.nvhtotal = 779;
			gpu.nvvtotal = 524;
		}
		else {	
			/* HDTV uses hardcoded settings for these - these are the
			 * correct ones for 480p */
			gpu.nvhtotal = 858;
			gpu.nvvtotal = 525;
		}

		gpu.xres = pvmode->width;
		gpu.yres = pvmode->height;
       		gpu.nvhstart = 738;
		gpu.nvvstart = 489;
		gpu.pixelDepth = (32 + 1) / 8;
		gpu.crtchdispend = pvmode->width;
		gpu.crtcvstart = gpu.nvvstart;
		gpu.crtcvtotal = gpu.nvvtotal;
		
		pll_int = (unsigned char)((double)27027 * 6.0 / 13.5e3 + 0.5);
		switch (video_encoder) {
			case ENCODER_CONEXANT:
				encoder_ok = conexant_calc_hdtv_mode(hdtv_mode, pll_int, &(newmode.encoder_regs));
				break;
			case ENCODER_FOCUS:
				encoder_ok = focus_calc_hdtv_mode(hdtv_mode, pll_int, &(newmode.encoder_regs));
				break;
			case ENCODER_XCALIBUR:
				encoder_ok = xcalibur_calc_hdtv_mode(hdtv_mode, pll_int, &(newmode.encoder_regs));
				break;
		}
	}
	else if ((av_type == AV_VGA_SOG) || (av_type == AV_HDTV)) {
		unsigned char pll_int;
		// Settings for 800x600@56Hz, 35 kHz HSync
		pvmode->width=800;
		pvmode->height=600;
		pvmode->xmargin=20;
		pvmode->ymargin=20;
	
		gpu.xres = pvmode->width;
       		gpu.nvhstart = 900;
		gpu.nvhtotal = 1028;
		gpu.yres = pvmode->height;
		gpu.nvvstart = 614;
		gpu.nvvtotal = 630;
		gpu.pixelDepth = (32 + 1) / 8;
		gpu.crtchdispend = pvmode->width;
		gpu.crtcvstart = gpu.nvvstart;
		gpu.crtcvtotal = gpu.nvvtotal;
		pll_int = (unsigned char)((double)36000 * 6.0 / 13.5e3 + 0.5);
	
		switch (video_encoder) {
			case ENCODER_CONEXANT:
				encoder_ok = conexant_calc_vga_mode(av_type, pll_int, &(newmode.encoder_regs));
				break;
			case ENCODER_FOCUS:
			case ENCODER_XCALIBUR:
				//No support for these yet
				break;
		}
	}
	else {	
	/* All other cable types - normal SDTV */
		switch(pvmode->m_nVideoModeIndex) {
			case VIDEO_MODE_640x480:
				pvmode->width=640;
				pvmode->height=480;
				pvmode->xmargin=0;
				pvmode->ymargin=0;
				break;
			case VIDEO_MODE_640x576:
				pvmode->width=640;
				pvmode->height=576;
				pvmode->xmargin=40; // pixels
				pvmode->ymargin=40; // lines
				break;
			case VIDEO_MODE_720x576:
				pvmode->width=720;
				pvmode->height=576;
				pvmode->xmargin=40; // pixels
				pvmode->ymargin=40; // lines
				break;
			case VIDEO_MODE_800x600: // 800x600
				pvmode->width=800;
				pvmode->height=600;
				pvmode->xmargin=20;
				pvmode->ymargin=20; // lines
				break;
			case VIDEO_MODE_1024x576: // 1024x576
				pvmode->width=1024;
				pvmode->height=576;
				pvmode->xmargin=20;
				pvmode->ymargin=20; // lines
				break;
		}	
		encoder_mode.xres = pvmode->width; 
		encoder_mode.yres = pvmode->height;
		encoder_mode.tv_encoding = tv_encoding;
		encoder_mode.bpp = 32;
		encoder_mode.hoc = pvmode->hoc;
		encoder_mode.voc = pvmode->voc;
		encoder_mode.av_type = av_type;
		encoder_mode.tv_encoding = tv_encoding;

		switch (video_encoder) {
			case ENCODER_CONEXANT:
				encoder_ok = conexant_calc_mode(&encoder_mode, &newmode);
				break;
			case ENCODER_FOCUS:
				encoder_ok = focus_calc_mode(&encoder_mode, &newmode);
				break;
			case ENCODER_XCALIBUR:
				encoder_ok = xcalibur_calc_mode(&encoder_mode, &newmode);
				break;
		}
        	
		gpu.xres = pvmode->width;
	       	gpu.nvhstart = newmode.ext.hsyncstart;
		gpu.nvhtotal = newmode.ext.htotal;
		gpu.yres = pvmode->height;
		gpu.nvvstart = newmode.ext.vsyncstart;
		gpu.nvvtotal = newmode.ext.vtotal;
		gpu.pixelDepth = (32 + 1) / 8;
		gpu.crtchdispend = pvmode->width;
		gpu.crtcvstart = newmode.ext.vsyncstart;
		gpu.crtcvtotal = newmode.ext.vtotal;
	}

	if (encoder_ok) {
		//Load registers into chip
		unsigned char *regs;
		unsigned long *XCal_Reg;
		int n1=0;

		//Set up the GPU 
		SetGPURegister(&gpu, pvmode->m_pbBaseAddressVideo);
		
		switch (video_encoder) {
			case ENCODER_CONEXANT:
				I2CWriteBytetoRegister(0x45,0xc4, 0x00); // EN_OUT = 1
		        	// Conexant init (starts at register 0x2e)
				regs = (unsigned char*)newmode.encoder_regs;
		        	for(i=0x2e,n1=0;i<0x100;i+=2,n1++) {
		        		switch(i) {
		        			case 0x6c: // reset
		        	  			I2CWriteBytetoRegister(0x45,i, regs[n1] & 0x7f);
	                	          		break;
	                	    		case 0xc4: // EN_OUT
							I2CWriteBytetoRegister(0x45,i, regs[n1] & 0xfe);
					 		break;
						case 0xb8: // autoconfig
							break;
						default:
							I2CWriteBytetoRegister(0x45,i, regs[n1]);
							break;
					}
					wait_us(500);	
				}
                		// Timing Reset
				b=I2CTransmitByteGetReturn(0x45,0x6c) & (0x7f);
				I2CWriteBytetoRegister(0x45, 0x6c, 0x80|b);
				b=I2CTransmitByteGetReturn(0x45,0xc4) & (0xfe);
				I2CWriteBytetoRegister(0x45, 0xc4, 0x01|b); // EN_OUT = 1
				break;
			case ENCODER_FOCUS:
				regs = (unsigned char*)newmode.encoder_regs;
	             		for (i=0; i<0xc4; ++i) {
                        		I2CWriteBytetoRegister(0x6a, i, regs[i]);
					wait_us(500);
				}
				break;
			case ENCODER_XCALIBUR:
				//Xlb init
				XCal_Reg = (unsigned long*)newmode.encoder_regs;
				regs = malloc(4);
				
				ReadfromSMBus(0x70,4,4,&i);
				WriteToSMBus(0x70,4,4,0x0F000000);
				ReadfromSMBus(0x70,0,4,&i);
				WriteToSMBus(0x70,0,4,0x00000000);
			               
				for(i = 0; i < 0x90; i++) {
					//Endianness.
					memcpy(regs,(unsigned char*)(&XCal_Reg[i])+3,0x01);
					memcpy(regs+1,(unsigned char*)(&XCal_Reg[i])+2,0x01);
					memcpy(regs+2,(unsigned char*)(&XCal_Reg[i])+1,0x01);
					memcpy(regs+3,(unsigned char*)(&XCal_Reg[i]),0x01);
				
					WriteToSMBus(0x70, i, 4, *(unsigned long*)regs);
					wait_us(500);
				}
				free(regs);
				break;
		}	
	}
	//Free the malloc'd registers
	free((char *)newmode.encoder_regs);
        
	NVDisablePalette (&riva, 0);
	writeCrtNv (&riva, 0, 0x44, 0x03);
	NVInitGrSeq(&riva);
	writeCrtNv (&riva, 0, 0x44, 0x00);
	NVInitAttr(&riva,0);
	IoOutputByte(0x80d8, 4);  // ACPI IO thing seen in kernel, set to 4
	IoOutputByte(0x80d6, 5);  // ACPI IO thing seen in kernel, set to 4 or 5
	NVVertIntrEnabled (&riva,0);
	NVSetFBStart (&riva, 0, pvmode->m_dwFrameBufferStart);
	IoOutputByte(0x80d3, 4);  // ACPI IO video enable REQUIRED <-- particularly crucial to get composite out
	// We dim the Video out - focus video is implicitly disabled.

	if (video_encoder == ENCODER_CONEXANT) {
		I2CTransmitWord(0x45, (0xa8<<8)|0);
		I2CTransmitWord(0x45, (0xaa<<8)|0);
		I2CTransmitWord(0x45, (0xac<<8)|0);
	}
	
	NVWriteSeq(&riva, 0x01, 0x01);  /* reenable display */

	switch (video_encoder) {
		case ENCODER_CONEXANT:
			I2CWriteBytetoRegister(0x45, 0xA8, 0x81);
			I2CWriteBytetoRegister(0x45, 0xAA, 0x49);
			I2CWriteBytetoRegister(0x45, 0xAC, 0x8C);
			break;
		case ENCODER_FOCUS:
	     		b = I2CTransmitByteGetReturn(0x6a,0x0c);
			b &= ~0x01;
			I2CWriteBytetoRegister(0x6a,0x0c,b);
			b = I2CTransmitByteGetReturn(0x6a,0x0d);
			I2CWriteBytetoRegister(0x6a,0x0d,b);
			break;
		case ENCODER_XCALIBUR:
			//No action required
			break;
	}
}


static void NVSetFBStart (RIVA_HW_INST *riva, int head, u32 dwFBStart) {
       MMIO_H_OUT32 (riva->PCRTC, head, 0x8000, dwFBStart);
       MMIO_H_OUT32 (riva->PMC, head, 0x8000, dwFBStart);
}

static void NVVertIntrEnabled (RIVA_HW_INST *riva, int head)
{
        MMIO_H_OUT32 (riva->PCRTC, head, 0x140, 0x1);
        MMIO_H_OUT32 (riva->PCRTC, head, 0x100, 0x1);
        MMIO_H_OUT32 (riva->PCRTC, head, 0x140, 1);
        MMIO_H_OUT32 (riva->PMC, head, 0x140, 0x1);
        MMIO_H_OUT32 (riva->PMC, head, 0x100, 0x1);
        MMIO_H_OUT32 (riva->PMC, head, 0x140, 1);
}

static inline void unlockCrtNv (RIVA_HW_INST *riva, int head)
{
	writeCrtNv (riva, head, 0x1f, 0x57); /* unlock extended registers */
}

static inline void lockCrtNv (RIVA_HW_INST *riva, int head)
{
	writeCrtNv (riva, head, 0x1f, 0x99); /* lock extended registers */
}


static void writeCrtNv (RIVA_HW_INST *riva, int head, int reg, u8 val)
{
        VGA_WR08(riva->PCIO, CRT_INDEX(head), reg);
        VGA_WR08(riva->PCIO, CRT_DATA(head), val);
}

static void mapNvMem (RIVA_HW_INST *riva, u8 *IOAddress)
{
	riva->PMC     = IOAddress+0x000000;
	riva->PFB     = IOAddress+0x100000;
	riva->PEXTDEV = IOAddress+0x101000;
	riva->PCRTC   = IOAddress+0x600000;
	riva->PCIO    = riva->PCRTC + 0x1000;
	riva->PVIO    = IOAddress+0x0C0000;
	riva->PRAMDAC = IOAddress+0x680000;
	riva->PDIO    = riva->PRAMDAC + 0x1000;
	riva->PVIDEO  = IOAddress+0x008000;
	riva->PTIMER  = IOAddress+0x009000;
}

static void NVDisablePalette (RIVA_HW_INST *riva, int head)
{
	volatile CARD8 tmp;
	tmp = VGA_RD08(riva->PCIO + head * HEAD, VGA_IOBASE_COLOR + VGA_IN_STAT_1_OFFSET);
	VGA_WR08(riva->PCIO + head * HEAD, VGA_ATTR_INDEX, 0x20);
}


static void NVWriteSeq(RIVA_HW_INST *riva, CARD8 index, CARD8 value)
{

        VGA_WR08(riva->PVIO, VGA_SEQ_INDEX, index);
		        VGA_WR08(riva->PVIO, VGA_SEQ_DATA,  value);

}

static void NVWriteGr(RIVA_HW_INST *riva, CARD8 index, CARD8 value)
{
        VGA_WR08(riva->PVIO, VGA_GRAPH_INDEX, index);
        VGA_WR08(riva->PVIO, VGA_GRAPH_DATA,  value);
}

static void NVInitGrSeq (RIVA_HW_INST *riva)
{
        NVWriteSeq(riva, 0x00, 0x03);
        NVWriteSeq(riva, 0x01, 0x21);
       	NVWriteSeq(riva, 0x02, 0x0f);
       	NVWriteSeq(riva, 0x03, 0x00);
       	NVWriteSeq(riva, 0x04, 0x06);
       	NVWriteGr(riva, 0x00, 0x00);
       	NVWriteGr(riva, 0x01, 0x00);
       	NVWriteGr(riva, 0x02, 0x00);
      	NVWriteGr(riva, 0x03, 0x00);
      	NVWriteGr(riva, 0x04, 0x00);    /* depth != 1 */
     	NVWriteGr(riva, 0x05, 0x40);    /* depth != 1 && depth != 4 */
     	NVWriteGr(riva, 0x06, 0x05);
      	NVWriteGr(riva, 0x07, 0x0f);
    	NVWriteGr(riva, 0x08, 0xff);
}

static void NVWriteAttr(RIVA_HW_INST *riva, int head, CARD8 index, CARD8 value)
{
 	MMIO_H_OUT8(riva->PCIO, head, VGA_ATTR_INDEX,  index);
        MMIO_H_OUT8(riva->PCIO, head, VGA_ATTR_DATA_W, value);
}

static void NVInitAttr (RIVA_HW_INST *riva, int head)
{
        NVWriteAttr(riva,0, 0, 0x01);
        NVWriteAttr(riva,0, 1, 0x02);
        NVWriteAttr(riva,0, 2, 0x03);
        NVWriteAttr(riva,0, 3, 0x04);
        NVWriteAttr(riva,0, 4, 0x05);
        NVWriteAttr(riva,0, 5, 0x06);
       	NVWriteAttr(riva,0, 6, 0x07);
        NVWriteAttr(riva,0, 7, 0x08);
        NVWriteAttr(riva,0, 8, 0x09);
       	NVWriteAttr(riva,0, 9, 0x0a);
        NVWriteAttr(riva,0, 10, 0x0b);
        NVWriteAttr(riva,0, 11, 0x0c);
      	NVWriteAttr(riva,0, 12, 0x0d);
     	NVWriteAttr(riva,0, 13, 0x0e);
     	NVWriteAttr(riva,0, 14, 0x0f);
        NVWriteAttr(riva,0, 15, 0x01);
       	NVWriteAttr(riva,0, 16, 0x4a);
        NVWriteAttr(riva,0, 17, 0x0f);
      	NVWriteAttr(riva,0, 18, 0x00);
      	NVWriteAttr(riva,0, 19, 0x00);
}

