
/* 
 * Generic mode timing module.
 */

#include "timing.h"	/* Types. */

#include "libvga.h"	/* for __svgalib_infotable (remove me) */
#include "driver.h"	/* for __svgalib_monitortype (remove me) */

/* Standard mode timings. */

MonitorModeTiming standard_timings[] = {
/* 320x200 @ 70 Hz, 31.5 kHz hsync */
{ 12588, 320, 336, 384, 400, 400, 409, 411, 450, DOUBLESCAN },
/* 320x240 @ 60 Hz, 31.5 kHz hsync */
{ 12588, 320, 336, 384, 400, 480, 491, 493, 525, DOUBLESCAN },
/* 320x240 @ 72 Hz, 36.5 kHz hsync */
{ 15750, 320, 336, 384, 400, 480, 488, 491, 521, DOUBLESCAN },
/* 640x400 at 70 Hz, 31.5 kHz hsync */
{ 25175, 640, 664, 760, 800, 400, 409, 411, 450, 0 },
/* 640x480 at 60 Hz, 31.5 kHz hsync */
{ 25175, 640, 664, 760, 800, 480, 491, 493, 525, 0 },
/* 640x480 at 72 Hz, 36.5 kHz hsync */
{ 31500, 640, 680, 720, 864, 480, 488, 491, 521, 0 },
/* 800x600 at 56 Hz, 35.15 kHz hsync */
{ 36000, 800, 824, 896, 1024, 600, 601, 603, 625, 0 },
/* 800x600 at 60 Hz, 37.8 kHz hsync */
{ 40000, 800, 840, 968, 1056, 600, 601, 605, 628, PHSYNC | PVSYNC },
/* 1024x768 at 87 Hz interlaced, 35.5 kHz hsync */
{ 44900, 1024, 1048, 1208, 1264, 768, 776, 784, 817, INTERLACED },
/* 800x600 at 72 Hz, 48.0 kHz hsync */
{ 50000, 800, 856, 976, 1040, 600, 637, 643, 666, PHSYNC | PVSYNC },
/* 1024x768 at 60 Hz, 48.4 kHz hsync */
{ 65000, 1024, 1032, 1176, 1344, 768, 771, 777, 806, NHSYNC | NVSYNC },
/* 1024x768 at 70 Hz, 56.6 kHz hsync */
{ 75000, 1024, 1048, 1184, 1328, 768, 771, 777, 806, NHSYNC | NVSYNC },
/* 1280x1024 at 87 Hz interlaced, 51 kHz hsync */
{ 80000, 1280, 1296, 1512, 1568, 1024, 1025, 1037, 1165, INTERLACED },
/* 1024x768 at 76 Hz, 62.5 kHz hsync */
{ 85000, 1024, 1032, 1152, 1360, 768, 784, 787, 823, 0 },
/* 1280x1024 at 60 Hz, ? kHz hsync */
{ 110000, 1280, 1328, 1512, 1712, 1024, 1025, 1028, 1054, 0 },
/* 1280x1024 at 70 Hz, ? kHz hsync */
{ 135000, 1280, 1312, 1456, 1712, 1024, 1027, 1030, 1064, 0 }
};

#define NUMBER_OF_STANDARD_MODES \
	(sizeof(standard_timings) / sizeof(standard_timings[0]))

/*
 * Monitor spec checking is done with a fixed table, rather than
 * calculations. The monitor types are defined in terms of the supported
 * mode timings.
 *
 * This should be extended into a configuration file with mode timings,
 * which are checked against monitor specs with calculations.
 */

#define TIMING_WITHIN_MONITOR_SPEC(i) \
	(timing_within_monitor_spec[__svgalib_monitortype] & (1 << i))

unsigned short timing_within_monitor_spec[7] = {
/* type 0, 31.5 kHz */
	0x07 +			/* doublescan modes */
	0x08 + 0x10,		/* 640x400 and 640x480 at 60 Hz */
/* type 1, 35.2 kHz */
	0x1F + 0x40,		/* adds 800x600 @ 56 Hz */
/* type 2, 35.5 kHz */
	0x5F + 0x100,		/* adds 1024x768 interlaced */
/* type 3, 38 kHz */
	0x15F + 0x20 + 0x80,	/* adds 640x480 @ 72 Hz, 800x600 @ 60 Hz */
/* type 4, 48.3 kHz */
	0x1FF + 0x200 + 0x400,	/* adds 800x600 @ 72 Hz, 1024x768 @ 60 Hz */
/* type 5, 56.0 kHz */
	0x7FF + 0x800 + 0x1000,	/* adds 1024x768 @ 70 Hz, 1280x1024 interl. */
/* type 6, 60 kHz */
	0x1FFF			/* no 1024x768 72 Hz timing yet */
};


/*
 * The getmodetiming function looks up a mode in the standard mode
 * timings, choosing the mode with the highest dot clock that matches
 * the requested svgalib mode, and is supported by the hardware
 * (card limits, and monitor type). cardlimits points to a structure
 * of type CardSpecs that describes the dot clocks the card supports
 * at different depths. Returns non-zero if no mode is found.
 */

/*
 * findclock is an auxilliary function that checks if a close enough
 * pixel clock is provided by the card. Returns clock number if
 * succesful (a special number if a programmable clock must be used), -1
 * otherwise.
 */
 
/*
 * Clock allowance in 1/1000ths. 10 (1%) corresponds to a 250 kHz
 * deviation at 25 MHz, 1 MHz at 100 MHz
 */
#define CLOCK_ALLOWANCE 10

#define PROGRAMMABLE_CLOCK_MAGIC_NUMBER 0x1234
 
static int findclock( int clock, CardSpecs *cardspecs ) {
	int i;
	/* Find a clock that is close enough. */
	for (i = 0; i < cardspecs->nClocks; i++) {
		int diff;
		diff = cardspecs->clocks[i] - clock;
		if (diff < 0)
			diff = -diff;
		if (diff * 1000 / clock < CLOCK_ALLOWANCE)
			return i;
	}
	/* Try programmable clocks if available. */
	if (cardspecs->flags & CLOCK_PROGRAMMABLE) {
		int diff;
		diff = cardspecs->matchProgrammableClock(clock) - clock;
		if (diff < 0)
			diff = -diff;
		if (diff * 1000 / clock < CLOCK_ALLOWANCE)
			return PROGRAMMABLE_CLOCK_MAGIC_NUMBER;
	}
	/* No close enough clock found. */
	return -1;
}

int getmodetiming( ModeTiming *modetiming, ModeInfo *modeinfo,
CardSpecs *cardspecs ) {
	int maxclock, bestclock, besttiming = 0, desiredclock;
	int i;

	/* Get the maximum pixel clock for the depth of the requested mode. */
	if (modeinfo->bitsPerPixel == 4)
		maxclock = cardspecs->maxPixelClock4bpp;
	else
	if (modeinfo->bitsPerPixel == 8)
		maxclock = cardspecs->maxPixelClock8bpp;
	else
	if (modeinfo->bitsPerPixel == 16) {
		if ((cardspecs->flags & NO_RGB16_565)
		&& modeinfo->greenWeight == 6)
			return 1;	/* No 5-6-5 RGB. */
		maxclock = cardspecs->maxPixelClock16bpp;
	}
	else
	if (modeinfo->bitsPerPixel == 24)
		maxclock = cardspecs->maxPixelClock24bpp;
	else
	if (modeinfo->bitsPerPixel == 32)
		maxclock = cardspecs->maxPixelClock32bpp;
	else
		maxclock = 0;

	/*
	 * bestclock is the highest pixel clock found for the resolution
	 * in the mode timings, within the spec of the card and
	 * monitor.
	 */
	bestclock = 0;

	/* Search the standard timings for the best matching mode. */
	for (i = 0; i < NUMBER_OF_STANDARD_MODES; i++)
		if (standard_timings[i].HDisplay == modeinfo->width
		&& (standard_timings[i].VDisplay == modeinfo->height
			|| ((standard_timings[i].flags & DOUBLESCAN)
		  		&& standard_timings[i].VDisplay ==
		  		modeinfo->height * 2))
		&& TIMING_WITHIN_MONITOR_SPEC(i)
		&& standard_timings[i].pixelClock <= maxclock
		&& standard_timings[i].pixelClock > bestclock
		&& cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
			standard_timings[i].pixelClock,
			standard_timings[i].HTotal)
			<= cardspecs->maxHorizontalCrtc
		/* Find the clock (possibly scaled by mapClock). */
		&& findclock(cardspecs->mapClock(modeinfo->bitsPerPixel,
			standard_timings[i].pixelClock), cardspecs) != -1
		) {
			bestclock = standard_timings[i].pixelClock;
			besttiming = i;
		}
	if (bestclock == 0)
		return 1;	/* Unsuccesful. */

	/*
	 * Copy the selected timings into the result, which may
	 * be adjusted for the chipset.
	 */

	modetiming->flags = standard_timings[besttiming].flags;
	modetiming->pixelClock = bestclock;	/* Formal clock. */

	/*
	 * We know a close enough clock is available; the following is the
	 * exact clock that fits the mode. This is probably different
	 * from the best matching clock that will be programmed.
	 */
	desiredclock = cardspecs->mapClock(modeinfo->bitsPerPixel,
		standard_timings[besttiming].pixelClock),

	/* Fill in the best-matching clock that will be programmed. */
	modetiming->selectedClockNo = findclock(desiredclock, cardspecs);
	if (modetiming->selectedClockNo == PROGRAMMABLE_CLOCK_MAGIC_NUMBER) {
		modetiming->programmedClock =
			cardspecs->matchProgrammableClock(desiredclock);
		modetiming->flags |= USEPROGRCLOCK;
	}
	else
		modetiming->programmedClock = cardspecs->clocks[
			modetiming->selectedClockNo];
	modetiming->HDisplay = standard_timings[besttiming].HDisplay;
	modetiming->HSyncStart = standard_timings[besttiming].HSyncStart;
	modetiming->HSyncEnd = standard_timings[besttiming].HSyncEnd;
	modetiming->HTotal = standard_timings[besttiming].HTotal;
	if (cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
			modetiming->programmedClock,
			standard_timings[besttiming].HTotal)
	!= standard_timings[besttiming].HTotal) {
		/* Horizontal CRTC timings are scaled in some way. */
		modetiming->CrtcHDisplay =
			cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
				modetiming->programmedClock,
				standard_timings[besttiming].HDisplay);
		modetiming->CrtcHSyncStart =
			cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
				modetiming->programmedClock,
				standard_timings[besttiming].HSyncStart);
		modetiming->CrtcHSyncEnd =
			cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
				modetiming->programmedClock,
				standard_timings[besttiming].HSyncEnd);
		modetiming->CrtcHTotal =
			cardspecs->mapHorizontalCrtc(modeinfo->bitsPerPixel,
				modetiming->programmedClock,
				standard_timings[besttiming].HTotal);
		modetiming->flags |= HADJUSTED;
	}
	else {
		modetiming->CrtcHDisplay = standard_timings[besttiming].HDisplay;
		modetiming->CrtcHSyncStart = standard_timings[besttiming].HSyncStart;
		modetiming->CrtcHSyncEnd = standard_timings[besttiming].HSyncEnd;
		modetiming->CrtcHTotal = standard_timings[besttiming].HTotal;
	}
	modetiming->VDisplay = standard_timings[besttiming].VDisplay;
	modetiming->CrtcVDisplay = standard_timings[besttiming].VDisplay;
	modetiming->VSyncStart = standard_timings[besttiming].VSyncStart;
	modetiming->CrtcVSyncStart = standard_timings[besttiming].VSyncStart;
	modetiming->VSyncEnd = standard_timings[besttiming].VSyncEnd;
	modetiming->CrtcVSyncEnd = standard_timings[besttiming].VSyncEnd;
	modetiming->VTotal = standard_timings[besttiming].VTotal;
	modetiming->CrtcVTotal = standard_timings[besttiming].VTotal;
	if (((modetiming->flags & INTERLACED)
	&& (cardspecs->flags & INTERLACE_DIVIDE_VERT))
	|| (modetiming->VTotal >= 1024
	&& (cardspecs->flags & GREATER_1024_DIVIDE_VERT))) {
		/*
		 * Card requires vertical CRTC timing to be halved for
		 * interlaced modes, or for all modes with vertical
		 * timing >= 1024.
		 */
		modetiming->CrtcVDisplay /= 2;
		modetiming->CrtcVSyncStart /= 2;
		modetiming->CrtcVSyncEnd /= 2;
		modetiming->CrtcVTotal /= 2;
		modetiming->flags |= VADJUSTED;
	}
	return 0;	/* Succesful. */
}
