/*
 * linux/drivers/video/riva/conexant.c - Xbox driver for conexant chip
 *
 * Maintainer: Oliver Schwartz <Oliver.Schwartz@gmx.de>
 *
 * Contributors:
 * 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 *
 * Known bugs and issues:
 *
 *      none
 */

#include "conexant.h"
#include "focus.h"

#define ADR(x) (x / 2 - 0x17)

typedef struct {
	long v_activeo;
	long v_linesi;
	long h_clki;
	long h_clko;
	long h_blanki;
	long h_blanko;
	long v_blanki;
	long v_blanko;
	long vscale;
	double clk_ratio;
} xbox_tv_mode_parameter;


	// and here is all the video timing for every standard

static const conexant_video_parameter vidstda[] = {
	{ 3579545.00, 0.0000053, 0.00000782, 0.0000047, 0.000063555, 0.0000094, 0.000035667, 0.0000015, 243, 262.5, 0.0000092 },
	{ 3579545.00, 0.0000053, 0.00000782, 0.0000047, 0.000064000, 0.0000094, 0.000035667, 0.0000015, 243, 262.5, 0.0000092 },
	{ 4433618.75, 0.0000056, 0.00000785, 0.0000047, 0.000064000, 0.0000105, 0.000036407, 0.0000015, 288, 312.5, 0.0000105 },
	{ 4433618.75, 0.0000056, 0.00000785, 0.0000047, 0.000064000, 0.0000094, 0.000035667, 0.0000015, 288, 312.5, 0.0000092 },
	{ 3582056.25, 0.0000056, 0.00000811, 0.0000047, 0.000064000, 0.0000105, 0.000036407, 0.0000015, 288, 312.5, 0.0000105 },
	{ 3575611.88, 0.0000058, 0.00000832, 0.0000047, 0.000063555, 0.0000094, 0.000035667, 0.0000015, 243, 262.5, 0.0000092 },
	{ 4433619.49, 0.0000053, 0.00000755, 0.0000047, 0.000063555, 0.0000105, 0.000036407, 0.0000015, 243, 262.5, 0.0000092 }
};

static const unsigned char default_mode[] = {
	0x00,
	0x00, 0x28, 0x80, 0xE4, 0x00, 0x00, 0x80, 0x80,
	0x80, 0x13, 0xDA, 0x4B, 0x28, 0xA3, 0x9F, 0x25,
	0xA3, 0x9F, 0x25, 0x00, 0x00, 0x00, 0x00, 0x44,
	0xC7, 0x00, 0x00, 0x41, 0x35, 0x03, 0x46, 0x00,
	0x02, 0x00, 0x01, 0x60, 0x88, 0x8a, 0xa6, 0x68,
	0xc1, 0x2e, 0xf2, 0x27, 0x00, 0xb0, 0x0a, 0x0b,
	0x71, 0x5a, 0xe0, 0x36, 0x00, 0x50, 0x72, 0x1c,
	0x0d, 0x24, 0xf0, 0x58, 0x81, 0x49, 0x8c, 0x0c,
	0x8c, 0x79, 0x26, 0x52, 0x00, 0x24, 0x00, 0x00,
	0x00, 0x00, 0x01, 0x9C, 0x9B, 0xC0, 0xC0, 0x19,
	0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x57, 0x20,
	0x40, 0x6E, 0x7E, 0xF4, 0x51, 0x0F, 0xF1, 0x05,
	0xD3, 0x78, 0xA2, 0x25, 0x54, 0xA5, 0x00, 0x00
};

static const double pll_base = 13.5e6;

static void conexant_calc_blankings(
	xbox_video_mode * mode,
	xbox_tv_mode_parameter * param
);

static int conexant_calc_mode_params(
	xbox_video_mode * mode,
	xbox_tv_mode_parameter * param
);

static double fabs(double d) {
	if (d > 0) return d;
	else return -d;
}

int conexant_calc_vga_mode(
	xbox_av_type av_type,
	unsigned char pll_int,
	void **reg_ptr
){
	unsigned char *regs;
	*reg_ptr = malloc(sizeof(char) * NUM_CONEXANT_REGS);
	regs = *reg_ptr;
	memset(regs, 0, NUM_CONEXANT_REGS);
	// Protect against overclocking
	if (pll_int > 36) {
		pll_int = 36; // 36 / 6 * 13.5 MHz = 81 MHz, just above the limit.
	}
	if (pll_int == 0) {
		pll_int = 1;  // 0 will cause a burnout ...
	}
	if (av_type == AV_VGA) {
		// use internal sync signals
		regs[ADR(0x2e)] = 0xbd; // HDTV_EN = 1, RPR_SYNC_DIS = 1, BPB_SYNC_DIS = 1, GY_SYNC_DIS=1, HD_SYNC_EDGE = 1, RASTER_SEL = 01
	}
	else {
 		// use sync on green
		regs[ADR(0x2e)] = 0xad; // HDTV_EN = 1, RPR_SYNC_DIS = 1, BPB_SYNC_DIS = 1, HD_SYNC_EDGE = 1, RASTER_SEL = 01
	}
	regs[ADR(0x32)] = 0x48; // DRVS = 2, IN_MODE[3] = 1;
	regs[ADR(0x3c)] = 0x80; // MCOMPY
	regs[ADR(0x3e)] = 0x80; // MCOMPU
	regs[ADR(0x40)] = 0x80; // MCOMPV
	regs[ADR(0xc6)] = 0x98; // IN_MODE = 24 bit RGB multiplexed
	regs[ADR(0x6c)] = 0x46; // FLD_MODE = 10, EACTIVE = 1, EN_SCART = 0, EN_REG_RD = 1
	regs[ADR(0x9c)] = 0x00; // PLL_FRACT
	regs[ADR(0x9e)] = 0x00; // PLL_FRACT
	regs[ADR(0xa0)] = pll_int; // PLL_INT
	regs[ADR(0xba)] = 0x28; // SLAVER = 1, DACDISD = 1
	regs[ADR(0xce)] = 0xe1; // OUT_MUXA = 01, OUT_MUXB = 00, OUT_MUXC = 10, OUT_MUXD = 11
	regs[ADR(0xd6)] = 0x0c; // OUT_MODE = 11 (RGB / SCART / HDTV)

	return 1;
}

int conexant_calc_hdtv_mode(
	xbox_hdtv_mode hdtv_mode,
	unsigned char pll_int,
	void **reg_ptr
){
	unsigned char *regs;
	*reg_ptr = malloc(sizeof(char) * NUM_CONEXANT_REGS);
	regs = *reg_ptr;
	memset(regs, 0, NUM_CONEXANT_REGS);
	// Protect against overclocking
	if (pll_int > 36) {
		pll_int = 36; // 36 / 6 * 13.5 MHz = 81 MHz, just above the limit.
	}
	if (pll_int == 0) {
		pll_int = 1;  // 0 will cause a burnout ...
	}
	switch (hdtv_mode) {
		case HDTV_480p:
			// use sync on green
			regs[ADR(0x2e)] = 0xed; // HDTV_EN = 1, RGB2PRPB = 1, RPR_SYNC_DIS = 1, BPB_SYNC_DIS = 1, HD_SYNC_EDGE = 1, RASTER_SEL = 01
			regs[ADR(0x32)] = 0x48; // DRVS = 2, IN_MODE[3] = 1;
			regs[ADR(0x3e)] = 0x45; // MCOMPU
			regs[ADR(0x40)] = 0x51; // MCOMPV
			break;
		case HDTV_720p:
			// use sync on green
			regs[ADR(0x2e)] = 0xea; // HDTV_EN = 1, RGB2PRPB = 1, RPR_SYNC_DIS = 1, BPB_SYNC_DIS = 1, HD_SYNC_EDGE = 1, RASTER_SEL = 01
			regs[ADR(0x32)] = 0x49; // DRVS = 2, IN_MODE[3] = 1, CSC_SEL=1;
			regs[ADR(0x3e)] = 0x45; // MCOMPU
			regs[ADR(0x40)] = 0x51; // MCOMPV
			break;
		case HDTV_1080i:
			// use sync on green
			regs[ADR(0x2e)] = 0xeb; // HDTV_EN = 1, RGB2PRPB = 1, RPR_SYNC_DIS = 1, BPB_SYNC_DIS = 1, HD_SYNC_EDGE = 1, RASTER_SEL = 01
			regs[ADR(0x32)] = 0x49; // DRVS = 2, IN_MODE[3] = 1, CSC_SEL=1;
			regs[ADR(0x3e)] = 0x48; // MCOMPU
			regs[ADR(0x40)] = 0x5b; // MCOMPV
			break;
	}
	regs[ADR(0x3c)] = 0x80; // MCOMPY
	regs[ADR(0xa0)] = pll_int; // PLL_INT
	regs[ADR(0xc6)] = 0x98; // IN_MODE = 24 bit RGB multiplexed
	regs[ADR(0x6c)] = 0x46; // FLD_MODE = 10, EACTIVE = 1, EN_SCART = 0, EN_REG_RD = 1
	regs[ADR(0x9c)] = 0x00; // PLL_FRACT
	regs[ADR(0x9e)] = 0x00; // PLL_FRACT
	regs[ADR(0xba)] = 0x28; // SLAVER = 1, DACDISD = 1
	regs[ADR(0xce)] = 0xe1; // OUT_MUXA = 01, OUT_MUXB = 00, OUT_MUXC = 10, OUT_MUXD = 11
	regs[ADR(0xd6)] = 0x0c; // OUT_MODE = 11 (RGB / SCART / HDTV)

	return 1;
}

int conexant_calc_mode(xbox_video_mode * mode, struct riva_regs * riva_out)
{
	unsigned char b;
	unsigned int m = 0;
	double dPllOutputFrequency;
	xbox_tv_mode_parameter param;
	char *regs;

	riva_out->encoder_regs = malloc((sizeof(char) * NUM_CONEXANT_REGS));

	regs=(unsigned char *)riva_out->encoder_regs;

	if (conexant_calc_mode_params(mode, &param))
	{
		// copy default mode settings
		memcpy(regs,default_mode,sizeof(default_mode));

		regs[ADR(0x32)] = 0x28; // DRVS = 1, IN_MODE[3] = 1;

		// H_CLKI
		b=regs[ADR(0x8e)]&(~0x07);
		regs[ADR(0x8e)] = ((param.h_clki>>8)&0x07)|b;
		regs[ADR(0x8a)] = ((param.h_clki)&0xff);
		// H_CLKO
		b=regs[ADR(0x86)]&(~0x0f);
		regs[ADR(0x86)] = ((param.h_clko>>8)&0x0f)|b;
		regs[ADR(0x76)] = ((param.h_clko)&0xff);
		// V_LINESI
		b=regs[ADR(0x38)]&(~0x02);
		regs[ADR(0x38)] = ((param.v_linesi>>9)&0x02)|b;
		b=regs[ADR(0x96)]&(~0x03);
		regs[ADR(0x96)] = ((param.v_linesi>>8)&0x03)|b;
		regs[ADR(0x90)] = ((param.v_linesi)&0xff);
		// V_ACTIVEO
		/* TODO: Absolutely not sure about other modes than plain NTSC / PAL */
		switch(mode->tv_encoding) {
			case TV_ENC_NTSC:
			case TV_ENC_NTSC60:
			case TV_ENC_PALM:
			case TV_ENC_PAL60:
				m=param.v_activeo + 1;
				break;
			case TV_ENC_PALBDGHI:
				m=param.v_activeo + 2;
				break;
			default:
				m=param.v_activeo + 2;
				break;
		}
		b=regs[ADR(0x86)]&(~0x80);
		regs[ADR(0x86)] = ((m>>1)&0x80)|b;
		regs[ADR(0x84)] = ((m)&0xff);
		// H_ACTIVE
		b=regs[ADR(0x86)]&(~0x70);
		regs[ADR(0x86)] = (((mode->xres + 5)>>4)&0x70)|b;
		regs[ADR(0x78)] = ((mode->xres + 5)&0xff);
		// V_ACTIVEI
		b=regs[ADR(0x96)]&(~0x0c);
		regs[ADR(0x96)] = ((mode->yres>>6)&0x0c)|b;
		regs[ADR(0x94)] = ((mode->yres)&0xff);
		// H_BLANKI
		b=regs[ADR(0x38)]&(~0x01);
		regs[ADR(0x38)] = ((param.h_blanki>>9)&0x01)|b;
		b=regs[ADR(0x8e)]&(~0x08);
		regs[ADR(0x8e)] = ((param.h_blanki>>5)&0x08)|b;
		regs[ADR(0x8c)] = ((param.h_blanki)&0xff);
		// H_BLANKO
		b=regs[ADR(0x9a)]&(~0xc0);
		regs[ADR(0x9a)] = ((param.h_blanko>>2)&0xc0)|b;
		regs[ADR(0x80)] = ((param.h_blanko)&0xff);

		// V_SCALE
		b=regs[ADR(0x9a)]&(~0x3f);
		regs[ADR(0x9a)] = ((param.vscale>>8)&0x3f)|b;
		regs[ADR(0x98)] = ((param.vscale)&0xff);
		// V_BLANKO
		regs[ADR(0x82)] = ((param.v_blanko)&0xff);
		// V_BLANKI
		regs[ADR(0x92)] = ((param.v_blanki)&0xff);
		{
			unsigned int dwPllRatio, dwFract, dwInt;
			// adjust PLL
			dwPllRatio = (int)(6.0 * ((double)param.h_clko / vidstda[mode->tv_encoding].m_dSecHsyncPeriod) *
				param.clk_ratio * 0x10000 / pll_base + 0.5);
			dwInt = dwPllRatio / 0x10000;
			dwFract = dwPllRatio - (dwInt * 0x10000);
			b=regs[ADR(0xa0)]&(~0x3f);
			regs[ADR(0xa0)] = ((dwInt)&0x3f)|b;
			regs[ADR(0x9e)] = ((dwFract>>8)&0xff);
			regs[ADR(0x9c)] = ((dwFract)&0xff);
			// recalc value
			dPllOutputFrequency = ((double)dwInt + ((double)dwFract)/65536.0)/(6 * param.clk_ratio / pll_base);
			// enable 3:2 clocking mode
			b=regs[ADR(0x38)]&(~0x20);
			if (param.clk_ratio > 1.1) {
				b |= 0x20;
			}
			regs[ADR(0x38)] = b;

			// update burst start position
			m=(vidstda[mode->tv_encoding].m_dSecBurstStart) * dPllOutputFrequency + 0.5;
			b=regs[ADR(0x38)]&(~0x04);
			regs[ADR(0x38)] = ((m>>6)&0x04)|b;
			regs[ADR(0x7c)] = (m&0xff);
			// update burst end position (note +128 is in hardware)
			m=(vidstda[mode->tv_encoding].m_dSecBurstEnd) * dPllOutputFrequency + 0.5;
			if(m<128) m=128;
			b=regs[ADR(0x38)]&(~0x08);
			regs[ADR(0x38)] = (((m-128)>>5)&0x08)|b;
			regs[ADR(0x7e)] = ((m-128)&0xff);
			// update HSYNC width
			m=(vidstda[mode->tv_encoding].m_dSecHsyncWidth) * dPllOutputFrequency + 0.5;
			regs[ADR(0x7a)] = ((m)&0xff);
		}
		// adjust Subcarrier generation increment
		{
			unsigned int dwSubcarrierIncrement = (unsigned int) (
				(65536.0 * 65536.0) * (
					vidstda[mode->tv_encoding].m_dHzBurstFrequency
					* vidstda[mode->tv_encoding].m_dSecHsyncPeriod
					/ (double)param.h_clko
				) + 0.5
			);
			regs[ADR(0xae)] = (dwSubcarrierIncrement&0xff);
			regs[ADR(0xb0)] = ((dwSubcarrierIncrement>>8)&0xff);
			regs[ADR(0xb2)] = ((dwSubcarrierIncrement>>16)&0xff);
			regs[ADR(0xb4)] = ((dwSubcarrierIncrement>>24)&0xff);
		}
		// adjust WSS increment
		{
			unsigned int dwWssIncrement = 0;

			switch(mode->tv_encoding) {
				case TV_ENC_NTSC:
				case TV_ENC_NTSC60:
					dwWssIncrement=(unsigned int) ((1048576.0 / ( 0.000002234 * dPllOutputFrequency))+0.5);
					break;
				case TV_ENC_PALBDGHI:
				case TV_ENC_PALN:
				case TV_ENC_PALNC:
				case TV_ENC_PALM:
				case TV_ENC_PAL60:
					dwWssIncrement=(unsigned int) ((1048576.0 / ( 0.0000002 * dPllOutputFrequency))+0.5);
					break;
				default:
					break;
				}

			regs[ADR(0x66)] = (dwWssIncrement&0xff);
			regs[ADR(0x68)] = ((dwWssIncrement>>8)&0xff);
			regs[ADR(0x6a)] = ((dwWssIncrement>>16)&0xf);
		}
		// set mode register
		b=regs[ADR(0xa2)]&(0x41);
		switch(mode->tv_encoding) {
				case TV_ENC_NTSC:
					b |= 0x0a; // SETUP + VSYNC_DUR
					break;
				case TV_ENC_NTSC60:
					b |= 0x08; // VSYNC_DUR
					break;
				case TV_ENC_PALBDGHI:
				case TV_ENC_PALNC:
						b |= 0x24; // PAL_MD + 625LINE
					break;
				case TV_ENC_PALN:
					b |= 0x2e; // PAL_MD + SETUP + 625LINE + VSYNC_DUR
					break;
				case TV_ENC_PALM:
					b |= 0x2a; // PAL_MD + SETUP + VSYNC_DUR
					break;
				case TV_ENC_PAL60:
					b |= 0x28; // PAL_MD + VSYNC_DUR
					break;
				default:
					break;
		}
		regs[ADR(0xa2)] = b;
		regs[ADR(0xc6)] = 0x98; // IN_MODE = 24 bit RGB multiplexed
		switch(mode->av_type) {
			case AV_COMPOSITE:
			case AV_SVIDEO:
				regs[ADR(0x2e)] |= 0x40; // RGB2YPRPB = 1
				regs[ADR(0x6c)] = 0x46; // FLD_MODE = 10, EACTIVE = 1, EN_SCART = 0, EN_REG_RD = 1
				regs[ADR(0x5a)] = 0x00; // Y_OFF (Brightness)
				regs[ADR(0xa4)] = 0xe5; // SYNC_AMP
				regs[ADR(0xa6)] = 0x74; // BST_AMP
				regs[ADR(0xba)] = 0x24; // SLAVER = 1, DACDISC = 1
				regs[ADR(0xce)] = 0x19; // OUT_MUXA = 01, OUT_MUXB = 10, OUT_MUXC = 10, OUT_MUXD = 00
				regs[ADR(0xd6)] = 0x00; // OUT_MODE = 00 (CVBS)
				break;
			case AV_SCART_RGB:
				regs[ADR(0x6c)] = 0x4e; // FLD_MODE = 10, EACTIVE = 1, EN_SCART = 1, EN_REG_RD = 1
				regs[ADR(0x5a)] = 0xff; // Y_OFF (Brightness)
				regs[ADR(0xa4)] = 0xe7; // SYNC_AMP
				regs[ADR(0xa6)] = 0x77; // BST_AMP
				regs[ADR(0xba)] = 0x20; // SLAVER = 1, enable all DACs
				regs[ADR(0xce)] = 0xe1; // OUT_MUXA = 01, OUT_MUXB = 00, OUT_MUXC = 10, OUT_MUXD = 11
				regs[ADR(0xd6)] = 0x0c; // OUT_MODE = 11 (RGB / SCART / HDTV)
				break;
			default:
				break;
		}
		riva_out->ext.vend = mode->yres - 1;
		riva_out->ext.vtotal = param.v_linesi - 1;
		riva_out->ext.vcrtc = mode->yres - 1;
		riva_out->ext.vsyncstart = param.v_linesi - param.v_blanki + 1;
		riva_out->ext.vsyncend = riva_out->ext.vsyncstart + 3;
		riva_out->ext.vvalidstart = 0;
		riva_out->ext.vvalidend = mode->yres - 1;
		riva_out->ext.hend = mode->xres + 7;
		riva_out->ext.htotal = param.h_clki - 1;
		riva_out->ext.hcrtc = mode->xres - 1;
		riva_out->ext.hsyncstart = param.h_clki - param.h_blanki - 7;
		riva_out->ext.hsyncend = riva_out->ext.hsyncstart + 32;
		riva_out->ext.hvalidstart = 0;
		riva_out->ext.hvalidend = mode->xres - 1;
		riva_out->ext.crtchdispend = mode->xres + 8;
		riva_out->ext.crtcvstart = mode->yres + 32;
		riva_out->ext.crtcvtotal = param.v_linesi + 32;
		return 1;
	}
	else
	{
		return 0;
	}
}

static int conexant_calc_mode_params(
	xbox_video_mode * mode,
	xbox_tv_mode_parameter * param
){
	const double dMinHBT = 2.5e-6; // 2.5uSec time for horizontal syncing
	const double invalidMetric = 1000;

	/* algorithm shamelessly ripped from nvtv/calc_bt.c */
	double dTempVOC = 0;
	double dTempHOC = 0;
	double dBestMetric = invalidMetric;
	double dTempVSR = 0;
	double dBestVSR = 0;
	double dTempCLKRATIO = 1;
	double dBestCLKRATIO = 1;
	unsigned int  minTLI = 0;
	unsigned int  maxTLI = 0;
	unsigned int  tempTLI = 0;
	unsigned int  bestTLI = 0;
	unsigned int  minHCLKO = 0;
	unsigned int  maxHCLKO = 0;
	unsigned int  minHCLKI = 0;
	unsigned int  tempHCLKI = 0;
	unsigned int  bestHCLKI = 0;
	int    actCLKRATIO;
	unsigned int  dTempHCLKO = 0;
	double dTempVACTIVEO = 0;
	double dDelta = 0;
	double dMetric = 0;
	double alo =  vidstda[mode->tv_encoding].m_dwALO;
	double tlo =  vidstda[mode->tv_encoding].m_TotalLinesOut;
	double tto = vidstda[mode->tv_encoding].m_dSecHsyncPeriod;
	double ato = tto - (vidstda[mode->tv_encoding].m_dSecBlankBeginToHsync + vidstda[mode->tv_encoding].m_dSecActiveBegin);

	/* Range to search */
	double dMinHOC = mode->hoc - 0.02;
	double dMaxHOC = mode->hoc + 0.02;
	double dMinVOC = mode->voc - 0.02;
	double dMaxVOC = mode->voc + 0.02;

	if (dMinHOC < 0) dMinHOC = 0;
	if (dMinVOC < 0) dMinVOC = 0;

	minTLI= (unsigned int)(mode->yres / ((1 - dMinVOC) * alo) * tlo);
	maxTLI = min((unsigned int)(mode->yres / ((1 - dMaxVOC) * alo) * tlo), (unsigned int)1023);
	minHCLKO = (unsigned int) ((mode->xres * 2) /
				((1 - dMinHOC) * (ato / tto)));
	maxHCLKO = (unsigned int) ((mode->xres * 2) /
				((1 - dMaxHOC) * (ato / tto)));
	for (actCLKRATIO = 0; actCLKRATIO <= 1; actCLKRATIO++)
	{
		dTempCLKRATIO = 1.0;
		if (actCLKRATIO) dTempCLKRATIO = 3.0/2.0;
		for(tempTLI = minTLI; tempTLI <= maxTLI; tempTLI++)
		{
			dTempVSR = (double)tempTLI / tlo;
			dTempVACTIVEO = (int)((((double)mode->yres * tlo) +
						(tempTLI - 1)) / tempTLI);
			dTempVOC = 1 - dTempVACTIVEO / alo;

			for(dTempHCLKO = minHCLKO; dTempHCLKO <= maxHCLKO; dTempHCLKO++)
			{
				tempHCLKI = (unsigned int)((dTempHCLKO * dTempCLKRATIO) * (tlo / tempTLI) + 0.5);
				minHCLKI = ((dMinHBT / tto) * tempHCLKI) + mode->xres;
				// check if solution is valid
				if ((fabs((double)(tempTLI * tempHCLKI) - (tlo * dTempHCLKO * dTempCLKRATIO)) < 1e-3) &&
					(tempHCLKI >= minHCLKI) && (tempHCLKI < 2048))
				{
					dTempHOC = 1 - (((double)mode->xres / ((double)dTempHCLKO / 2)) /
						(ato / tto));
					dDelta = fabs(dTempHOC - mode->hoc) + fabs(dTempVOC - mode->voc);
					dMetric = ((dTempHOC - mode->hoc) * (dTempHOC - mode->hoc)) +
						((dTempVOC - mode->voc) * (dTempVOC - mode->voc)) +
						(2 * dDelta * dDelta);
					if(dMetric < dBestMetric)
					{
						dBestVSR = dTempVSR;
						dBestMetric = dMetric;
						bestTLI = tempTLI;
						bestHCLKI = tempHCLKI;
						dBestCLKRATIO = dTempCLKRATIO;
					}
				} /* valid solution */
			} /* dTempHCLKO loop */
		} /* tempTLI loop */
	} /* CLKRATIO loop */

	if(dBestMetric != invalidMetric)
	{
		param->v_linesi = bestTLI;
		param->h_clki = bestHCLKI;
		param->clk_ratio = dBestCLKRATIO;
		param->v_activeo = (unsigned int)(
			(
				(mode->yres * vidstda[mode->tv_encoding].m_TotalLinesOut)
				+ param->v_linesi - 1
			) / param->v_linesi
		);
		param->h_clko = (unsigned int)(
			(
				(param->v_linesi * param->h_clki) /
				(vidstda[mode->tv_encoding].m_TotalLinesOut * param->clk_ratio)
			)
			+ 0.5
		);
		conexant_calc_blankings(mode, param);
		return 1;
	}
	else
	{
		return 0;
	}
}

static void conexant_calc_blankings(
	xbox_video_mode * mode,
	xbox_tv_mode_parameter * param
){
	double dTotalHBlankI;
	double dFrontPorchIn;
	double dFrontPorchOut;
	double dMinFrontPorchIn;
	double dBackPorchIn;
	double dBackPorchOut;
	double dTotalHBlankO;
	double dHeadRoom;
	double dMaxHsyncDrift;
	double dFifoMargin;
	double vsrq;
	double dMaxHR;
	double tlo =  vidstda[mode->tv_encoding].m_TotalLinesOut;
	const int MFP = 14; // Minimum front porch
	const int MBP = 4;  // Minimum back porch
	const int FIFO_SIZE = 1024;
	double vsr = (double)param->v_linesi / vidstda[mode->tv_encoding].m_TotalLinesOut;

	// H_BLANKO
	param->h_blanko = 2 * (int)(
		vidstda[mode->tv_encoding].m_dSecImageCentre / (2 * vidstda[mode->tv_encoding].m_dSecHsyncPeriod) *
		param->h_clko
		+ 0.5
	) - mode->xres + 15;

	// V_BLANKO
	switch (mode->tv_encoding) {
		case TV_ENC_NTSC:
		case TV_ENC_NTSC60:
		case TV_ENC_PAL60:
		case TV_ENC_PALM:
			param->v_blanko = (int)( 140 - ( param->v_activeo / 2.0 ) + 0.5 );
			break;
		default:
			param->v_blanko = (int)( 167 - ( param->v_activeo / 2.0 ) + 0.5 );
			break;
	}

	// V_BLANKI
	vsrq = ( (int)( vsr * 4096.0 + .5 ) ) / 4096.0;
	param->vscale = (int)( ( vsr - 1 ) * 4096 + 0.5 );
	if( vsrq < vsr )
	{
	// These calculations are in units of dHCLKO
		dMaxHsyncDrift = ( vsrq - vsr ) * tlo / vsr * param->h_clko;
		dMinFrontPorchIn = MFP / ( (double)param->h_clki * vsr ) * param->h_clko;
		dFrontPorchOut = param->h_clko - param->h_blanko - mode->xres * 2;
		dFifoMargin = ( FIFO_SIZE - mode->xres ) * 2;

		// Check for fifo overflow
		if( dFrontPorchOut + dFifoMargin < -dMaxHsyncDrift + dMinFrontPorchIn )
		{
			dTotalHBlankO = param->h_clko - mode->xres * 2;
			dTotalHBlankI = ( (double)param->h_clki - (double)mode->xres ) / param->h_clki / vsr * param->h_clko;

			// Try forcing the Hsync drift the opposite direction
			dMaxHsyncDrift = ( vsrq + 1.0 / 4096 - vsr ) * tlo / vsr * param->h_clko;

			// Check that fifo overflow and underflow can be avoided
			if( dTotalHBlankO + dFifoMargin >= dTotalHBlankI + dMaxHsyncDrift )
			{
				vsrq = vsrq + 1.0 / 4096;
				param->vscale = (int)( ( vsrq - 1 ) * 4096 );
			}

			// NOTE: If fifo overflow and underflow can't be avoided,
			//       alternative overscan compensation ratios should
			//       be selected and all calculations repeated.  If
			//       that is not feasible, the calculations for
			//       H_BLANKI below will delay the overflow or under-
			//       flow as much as possible, to minimize the visible
			//       artifacts.
		}
	}

	param->v_blanki = (int)( ( param->v_blanko - 1 ) * vsrq );

	// H_BLANKI

	// These calculations are in units of dHCLKI
	dTotalHBlankI = param->h_clki - mode->xres;
	dFrontPorchIn = max( (double)MFP, min( dTotalHBlankI / 8.0, dTotalHBlankI - (double)MBP ) );
	dBackPorchIn = dTotalHBlankI - dFrontPorchIn;
	dMaxHsyncDrift = ( vsrq - vsr ) * tlo * param->h_clki;
	dTotalHBlankO = ( param->h_clko - mode->xres * 2.0 ) / param->h_clko * vsr * param->h_clki;
	dBackPorchOut = ((double)param->h_blanko) / (double)param->h_clko * vsr * param->h_clki;
	dFrontPorchOut = dTotalHBlankO - dBackPorchOut;
	dFifoMargin = ( FIFO_SIZE - mode->xres ) * 2.0 / param->h_clko * vsr * param->h_clki;
	// This may be excessive, but is adjusted by the code.
	dHeadRoom = 32.0;

	// Check that fifo overflow and underflow can be avoided
	if( ( dTotalHBlankO + dFifoMargin ) >= ( dTotalHBlankI + fabs( dMaxHsyncDrift ) ) )
	{
		dMaxHR = ( dTotalHBlankO + dFifoMargin ) - ( dTotalHBlankI - fabs( dMaxHsyncDrift ) );
		if( dMaxHR < ( dHeadRoom * 2.0 ) )
		{
			dHeadRoom = (int)( dMaxHR / 2.0);
		}

		// Check for overflow
		if( ( ( dFrontPorchOut + dFifoMargin ) - dHeadRoom ) < ( dFrontPorchIn - min( dMaxHsyncDrift, 0.0 ) ) )
		{
			dFrontPorchIn = max( (double)MFP, ( dFrontPorchOut + dFifoMargin + min( dMaxHsyncDrift, 0.0 ) - dHeadRoom ) );
			dBackPorchIn = dTotalHBlankI - dFrontPorchIn;
		}

		// Check for underflow
		if( dBackPorchOut - dHeadRoom < dBackPorchIn + max( dMaxHsyncDrift, 0.0 ) )
		{
			dBackPorchIn = max( (double)MBP, ( dBackPorchOut - max( dMaxHsyncDrift, 0.0 ) - dHeadRoom ) );
			dFrontPorchIn = dTotalHBlankI - dBackPorchIn;
		}
	}
	else if( dMaxHsyncDrift < 0 )
	{
		// Delay the overflow as long as possible
		dBackPorchIn = min( ( dBackPorchOut - 1 ), ( dTotalHBlankI - MFP ) );
		dFrontPorchIn = dTotalHBlankI - dBackPorchIn;
	}
	else
	{
		// Delay the underflow as long as possible
		dFrontPorchIn = min( ( dFrontPorchOut + dFifoMargin - 1 ), ( dTotalHBlankI - MBP ) );
		dBackPorchIn = dTotalHBlankI - dFrontPorchIn;
	}

	param->h_blanki = (int)( dBackPorchIn );

}
