(* Aos, Copyright 2004, Andre Fischer, ETH Zurich *)

MODULE DisplayGTF; (** AUTHOR "afi"; PURPOSE "Construct a configuration Init= string
			with embedded GTF timing parameters *)

IMPORT SYSTEM, MathL, Strings, Commands;

CONST
	GTFLockVF = 1;		(* Lock to vertical frequency				*)
	GTFLockHF = 2;		(* Lock to horizontal frequency			*)
	GTFLockPF = 3;		(* Lock to pixel clock frequency			*)

TYPE
	GTFConstants = RECORD
		margin: LONGREAL;	(* Margin size as percentage of display *)
		cellGran: LONGREAL;	(* Character cell granularity *)
		minPorch: LONGREAL;	(* Minimum front porch in lines/chars *)
		vSyncRqd: LONGREAL;	(* Width of V sync in lines *)
		hSync: LONGREAL;	(* Width of H sync as percent of total *)
		minVSyncBP: LONGREAL;	(* Minimum vertical sync + back porch (us) *)
		m: LONGREAL;	(* Blanking formula gradient *)
		c: LONGREAL;	(* Blanking formula offset *)
		k: LONGREAL;	(* Blanking formula scaling factor *)
		j: LONGREAL	(* Blanking formula scaling factor weight *)
	END;

	GTFHCRTC = RECORD
		hTotal: LONGINT;	(* Horizontal total *)
		hDisp: LONGINT;	(* Horizontal displayed *)
		hSyncStart: LONGINT;	(* Horizontal sync start *)
		hSyncEnd: LONGINT;	(* Horizontal sync end *)
	END;

	GTFVCRTC = RECORD
		vTotal: LONGINT;	(* Vertical total *)
		vDisp: LONGINT;	(* Vertical displayed *)
		vSyncStart: LONGINT;	(* Vertical sync start *)
		vSyncEnd: LONGINT;	(* Vertical sync end *)
	END;

	GTFTimings = RECORD
		h: GTFHCRTC;	(* Horizontal CRTC paremeters *)
		v: GTFVCRTC;	(* Vertical CRTC parameters *)
		hSyncPol: CHAR;	(* Horizontal sync polarity *)
		vSyncPol: CHAR;	(* Vertical sync polarity *)
		interlace: CHAR;	(* 'I' for Interlace, 'N' for Non *)
		vFreq: LONGREAL;	(* Vertical frequency (Hz) *)
		hFreq: LONGREAL;	(* Horizontal frequency (KHz) *)
	END;
VAR
	conf*: ARRAY 512 OF CHAR;
	hex: ARRAY 17 OF CHAR;

PROCEDURE pow(x: LONGREAL; n: LONGINT): LONGREAL;
VAR s: LONGREAL;
BEGIN
	s := 1;
	WHILE n > 0 DO s := s * x; DEC(n) END;
	RETURN s
END pow;

PROCEDURE Round(v: LONGREAL): LONGREAL;
BEGIN
	RETURN ENTIER(v + 0.5)
END Round;

PROCEDURE GetInternalConstants(VAR c: GTFConstants);
VAR GC: GTFConstants;
BEGIN
	GC.margin := 1.8; GC.cellGran := 8; GC.minPorch := 1; GC.vSyncRqd := 3;
	GC.hSync := 8; GC.minVSyncBP := 550; GC.m := 600; GC.c := 40; GC.k := 128; GC.j := 20;

	c.margin := GC.margin; c.cellGran := Round(GC.cellGran);
	c.minPorch := Round(GC.minPorch); c.vSyncRqd := Round(GC.vSyncRqd);
	c.hSync := GC.hSync; c.minVSyncBP := GC.minVSyncBP;
	IF GC.k = 0 THEN c.k := 0.001 ELSE c.k := GC.k END;
	c.m := (c.k / 256) * GC.m; c.c := (GC.c - GC.j) * (c.k / 256) + GC.j;
	c.j := GC.j
END GetInternalConstants;

(*
Calculate a set of GTF timing parameters given a specified resolution and vertical frequency.
The horizontal frequency and dot clock will be automatically generated by this routines.

For interlaced modes the CRTC parameters are calculated for a single field,
so will be half what would be used in a non-interlaced mode.

hPixels - X resolution
vLines - Y resolution
freq - Frequency (Hz, KHz or MHz depending on type)
type - 1 - vertical, 2 - horizontal, 3 - dot clock
wantMargins - TRUE if margins should be generated
wantInterlace - TRUE if interlaced timings to be generated
t - Place to store the resulting timings
*)

PROCEDURE GTFCalcTimings(hPixels, vLines, freq: LONGREAL; type: LONGINT; wantMargins, wantInterlace: BOOLEAN; VAR t: GTFTimings);
VAR
	interlace,vFieldRate,hPeriod: LONGREAL;
	topMarginLines,botMarginLines: LONGREAL;
	leftMarginPixels,rightMarginPixels: LONGREAL;
	hPeriodEst,vSyncBP,vBackPorch: LONGREAL;
	vTotalLines,vFieldRateEst: LONGREAL;
	hTotalPixels,hTotalActivePixels,hBlankPixels: LONGREAL;
	idealDutyCycle,hSyncWidth,hSyncBP,hBackPorch: LONGREAL;
	idealHPeriod: LONGREAL;
	vFreq,hFreq,dotClock: LONGREAL;
	c: GTFConstants;
BEGIN
	GetInternalConstants(c);
	vFreq := freq; hFreq := freq; dotClock := freq;

	(* Round pixels to character cell granularity *)
	hPixels := Round(hPixels / c.cellGran) * c.cellGran;

	(* For interlaced mode halve the vertical parameters, and double the required field refresh rate. *)
	IF wantInterlace THEN
		vLines := Round(vLines / 2);
		vFieldRate := vFreq * 2;
		dotClock := dotClock * 2;
		interlace := 0.5;
	ELSE vFieldRate := vFreq; interlace := 0
	END;

	(* Determine the lines for margins *)
	IF wantMargins THEN
		topMarginLines := Round(c.margin / 100 * vLines);
		botMarginLines := Round(c.margin / 100 * vLines)
	ELSE topMarginLines := 0; botMarginLines := 0
	END;

	IF type # GTFLockPF THEN
		IF type = GTFLockVF THEN
			(* Estimate the horizontal period *)
			hPeriodEst := ((1/vFieldRate)-(c.minVSyncBP/1000000))/
					(vLines+(2*topMarginLines)+c.minPorch+interlace)*1000000;

			(* Find the number of lines in vSync + back porch *)
			vSyncBP := Round(c.minVSyncBP / hPeriodEst);
		ELSIF type = GTFLockHF THEN
			(* Find the number of lines in vSync + back porch *)
			vSyncBP := Round((c.minVSyncBP * hFreq) / 1000);
		END;

		(* Find the number of lines in the V back porch alone *)
		vBackPorch := vSyncBP - c.vSyncRqd;

		(* Find the total number of lines in the vertical period *)
		vTotalLines := vLines + topMarginLines + botMarginLines + vSyncBP
				+ interlace + c.minPorch;

		IF type = GTFLockVF THEN
			(* Estimate the vertical frequency *)
			vFieldRateEst := 1000000 / (hPeriodEst * vTotalLines);

			(* Find the actual horizontal period *)
			hPeriod := (hPeriodEst * vFieldRateEst) / vFieldRate;

			(* Find the actual vertical field frequency *)
			vFieldRate := 1000000 / (hPeriod * vTotalLines);
		ELSIF type = GTFLockHF THEN
			(* Find the actual vertical field frequency *)
			vFieldRate := (hFreq / vTotalLines) * 1000;
		END
	END;

	(* Find the number of pixels in the left and right margins *)
	IF wantMargins THEN
		leftMarginPixels := Round(hPixels * c.margin) / (100 * c.cellGran);
		rightMarginPixels := Round(hPixels * c.margin) / (100 * c.cellGran);
	ELSE leftMarginPixels := 0; rightMarginPixels := 0
	END;

	(* Find the total number of active pixels in image + margins *)
	hTotalActivePixels := hPixels + leftMarginPixels + rightMarginPixels;

	IF type = GTFLockVF THEN
		(* Find the ideal blanking duty cycle *)
		idealDutyCycle := c.c - ((c.m * hPeriod) / 1000)
	ELSIF type = GTFLockHF THEN
		(* Find the ideal blanking duty cycle *)
		idealDutyCycle := c.c - (c.m / hFreq);
	ELSIF type = GTFLockPF THEN
		(* Find ideal horizontal period from blanking duty cycle formula *)
		idealHPeriod := (((c.c - 100) + (MathL.sqrt((pow(100-c.c,2)) +
			(0.4 * c.m * (hTotalActivePixels + rightMarginPixels +
			leftMarginPixels) / dotClock)))) / (2 * c.m)) * 1000;

		(* Find the ideal blanking duty cycle *)
		idealDutyCycle := c.c - ((c.m * idealHPeriod) / 1000);
	END;

	(* Find the number of pixels in blanking time *)
	hBlankPixels := Round((hTotalActivePixels * idealDutyCycle) /
		((100 - idealDutyCycle) * c.cellGran)) * c.cellGran;

	(* Find the total number of pixels *)
	hTotalPixels := hTotalActivePixels + hBlankPixels;

	(* Find the horizontal back porch *)
	hBackPorch := Round((hBlankPixels / 2) / c.cellGran) * c.cellGran;

	(* Find the horizontal sync width *)
	hSyncWidth := Round(((c.hSync/100) * hTotalPixels) / c.cellGran) * c.cellGran;

	(* Find the horizontal sync + back porch *)
	hSyncBP := hBackPorch + hSyncWidth;

	IF type = GTFLockPF THEN
		(* Find the horizontal frequency *)
		hFreq := (dotClock / hTotalPixels) * 1000;

		(* Find the number of lines in vSync + back porch *)
		vSyncBP := Round((c.minVSyncBP * hFreq) / 1000);

		(* Find the number of lines in the V back porch alone *)
		vBackPorch := vSyncBP - c.vSyncRqd;

		(* Find the total number of lines in the vertical period *)
		vTotalLines := vLines + topMarginLines + botMarginLines + vSyncBP
			+ interlace + c.minPorch;

		(* Find the actual vertical field frequency *)
		vFieldRate := (hFreq / vTotalLines) * 1000;
	ELSE
		IF type = GTFLockVF THEN
			(* Find the horizontal frequency *)
			hFreq := 1000 / hPeriod;
		ELSIF type = GTFLockHF THEN
			(* Find the horizontal frequency *)
			hPeriod := 1000 / hFreq;
		END;

		(* Find the pixel clock frequency *)
		dotClock := hTotalPixels / hPeriod;
	END;

	(* Find the vertical frame frequency *)
	IF wantInterlace THEN vFreq := vFieldRate / 2; dotClock := dotClock / 2;
	ELSE vFreq := vFieldRate
	END;

	(* Return the computed frequencies *)
	t.vFreq := vFreq;
	t.hFreq := hFreq;

	(* Determine the vertical timing parameters *)
	t.h.hTotal := ENTIER(hTotalPixels);
	t.h.hDisp := ENTIER(hTotalActivePixels);
	t.h.hSyncStart := ENTIER(t.h.hTotal - hSyncBP);
	t.h.hSyncEnd := ENTIER(t.h.hTotal - hBackPorch);

	(* Determine the vertical timing parameters *)
	t.v.vTotal := ENTIER(vTotalLines);
	t.v.vDisp := ENTIER(vLines);
	t.v.vSyncStart := ENTIER(t.v.vTotal - vSyncBP);
	t.v.vSyncEnd := ENTIER(t.v.vTotal - vBackPorch);

	(* Mark as GTF timing using the sync polarities *)
	IF wantInterlace THEN t.interlace := 'I' ELSE t.interlace := 'N' END;
	t.hSyncPol := '-';
	t.vSyncPol := '+'
END GTFCalcTimings;

PROCEDURE VesaConf(mode, width, height, depth, hz: LONGINT; VAR conf: ARRAY OF CHAR);
VAR mode1: LONGINT; flags: SET; t: GTFTimings; valstr : ARRAY 10 OF CHAR; cr : ARRAY 2 OF CHAR;

	PROCEDURE HexByte(x: LONGINT);
	VAR s: ARRAY 3 OF CHAR;
	BEGIN
		s[0] := hex[x DIV 10H];
		s[1] := hex[x MOD 10H];
		s[2] := CHR(0);
		Strings.Append(conf, s);
	END HexByte;

	PROCEDURE HexWord(x: LONGINT);
	BEGIN
		HexByte(x MOD 100H); HexByte(x DIV 100H)
	END HexWord;

	PROCEDURE HexDWord(x: LONGINT);
	BEGIN
		HexWord(x MOD 10000H); HexWord(x DIV 10000H)
	END HexDWord;

BEGIN
	cr[0] := 0DX; cr[1] := 0X;
	Strings.Append(conf, cr);
	Strings.Append(conf, 'Init="');
	IF hz > 0 THEN	(* VESA 3.0 - set refresh rate *)
		mode1 := mode + 4800H;
		GTFCalcTimings(width, height, hz, GTFLockVF, FALSE, FALSE, t);
		Strings.Append(conf, "b80b4fbb0000ba"); HexWord(mode1);	(* mov ax, 4F0BH; mov bx, 0; mov dx, mode *)
		Strings.Append(conf, "66b9"); HexDWord(hz * t.h.hTotal * t.v.vTotal);	(* mov ecx, val *)
		Strings.Append(conf, "cd1026c705"); HexWord(t.h.hTotal);	(* int 10H; mov [es:di], val *)
		Strings.Append(conf, "26c74502"); HexWord(t.h.hSyncStart);	(* mov [es:di+2], val *)
		Strings.Append(conf, "26c74504"); HexWord(t.h.hSyncEnd);	(* mov [es:di+4], val *)
		Strings.Append(conf, "26c74506"); HexWord(t.v.vTotal);	(* mov [es:di+6], val *)
		Strings.Append(conf, "26c74508"); HexWord(t.v.vSyncStart);	(* mov [es:di+8], val *)
		Strings.Append(conf, "26c7450a"); HexWord(t.v.vSyncEnd);	(* mov [es:di+0AH], val *)
		flags := {};
		IF t.interlace = "I" THEN INCL(flags, 1) END;
		IF t.hSyncPol = "-" THEN INCL(flags, 2) END;
		IF t.vSyncPol = "-" THEN INCL(flags, 3) END;
		Strings.Append(conf, "26c6450c"); HexByte(SYSTEM.VAL(LONGINT, flags));	(* mov [es:di+0CH], val *)
		Strings.Append(conf, "2666894d0d26c74511"); HexWord(hz*100);	(* mov [es:di+0DH], ecx; mov [es:di+11H], val *)
		Strings.Append(conf, cr);

	ELSE	(* VESA 2.0 *)
		mode1 := mode + 4000H	(* enable framebuffer *)
	END;
	Strings.Append(conf, "b8024fbb"); HexWord(mode1);	(* mov ax, 4F02H; mov bx, mode1 *)
	Strings.Append(conf, "cd10b8014fb9"); HexWord(mode);	(* int 10H; mov ax, 4F01H; mov cx, mode *)
	Strings.Append(conf, "cd10268b4d28268b552a");	(* int 10H; mov cx, [es:di+28H]; mov dx, [es:di+2AH] *)
	Strings.Append(conf, '"');
	Strings.Append(conf, cr);
	Strings.Append(conf, 'DWidth="');
	Strings.IntToStr(width, valstr);
	Strings.Append(conf, valstr);
	Strings.Append(conf, '"');
	Strings.Append(conf, cr);
	Strings.Append(conf, 'DHeight="');
	Strings.IntToStr(height, valstr);
	Strings.Append(conf, valstr);
	Strings.Append(conf, '"');
	Strings.Append(conf, cr);
	Strings.Append(conf, 'DDepth="');
	Strings.IntToStr(depth, valstr);
	Strings.Append(conf, valstr);
	Strings.Append(conf, '"');
	Strings.Append(conf, cr);

(*	Summary of the calculated timing parameters *)
(*
	IF hz > 0 THEN	(* VESA 3.0 - set refresh rate *)
		HexDWord(hz * t.h.hTotal * t.v.vTotal);
		HexWord(t.h.hTotal);
		HexWord(t.h.hSyncStart);
		HexWord(t.h.hSyncEnd);
		HexWord(t.v.vTotal);
		HexWord(t.v.vSyncStart);
		HexWord(t.v.vSyncEnd);
		HexByte(SYSTEM.VAL(LONGINT, flags));
	ELSE	(* VESA 2.0 *)
		HexWord(hz*100);
		HexWord(mode1);
		HexWord(mode);
	END;
	Strings.Append(conf, cr);
*)
END VesaConf;

(* NOTE: use f=0 to obtain the string required for setting
	the VBE mode m to operate at a default 60Hz refresh rate. *)
PROCEDURE Mode*(context : Commands.Context);	(* mode (in decimal) DWidth DHeight Frequency *)
VAR display: ARRAY 512 OF CHAR;
	m, w, h, d, f: LONGINT;
BEGIN
	context.arg.SkipWhitespace; context.arg.Int(m, FALSE); context.out.Int(m, 5);
	context.arg.SkipWhitespace; context.arg.Int(w, FALSE); context.out.Int(w, 5);
	context.arg.SkipWhitespace; context.arg.Int(h, FALSE); context.out.Int(h, 5);
	context.arg.SkipWhitespace; context.arg.Int(d, FALSE); context.out.Int(d, 5);
	context.arg.SkipWhitespace; context.arg.Int(f, FALSE); context.out.Int(f, 5);
	display[0] := 0X;
	VesaConf(m, w, h, d, f, display);
	context.out.String(display);
END Mode;

BEGIN
	hex := "0123456789abcdef"
END DisplayGTF.
-------------------------
Result on EIZO Flexscan T57S -> -> ->                                           fV=     Hz   fH=      kHz
Aos.Call DisplayGTF.Mode 279 1024 768 16 60 ~ 279 is 117H       59.7          47.8
Aos.Call DisplayGTF.Mode 279 1024 768 16 70 ~                           70.4          56.7
Aos.Call DisplayGTF.Mode 279 1024 768 16 80 ~                           81.3          65.5
Aos.Call DisplayGTF.Mode 279 1024 768 16 100 ~                       104.4          84.1
Aos.Call DisplayGTF.Mode 283 1280 1024 16 80 ~ 279 is 11BH

Default 60 Hz operation example:

Aos.Call DisplayGTF.Mode 279 1024 768 16 0 ~

The values obtained using a VBE setting with Init=117H results in fV= 60.5Hz   fH= 48.4kHz
-------------------------
System.Free DisplayGTF ~

Execute Partitions.GetConfig dev#part

then Aos.Call DisplayGTF.Mode parameter-list

Get the Init="..." string with the corresponding resolution values from the Kernel.Log

Example: Aos.Call DisplayGTF.Mode 279 1024 768 16 80 ~
Result in Kernel.Log:
Init="b80b4fbb0000ba174966b9009e3e05cd1026c705580526c74502380426c74504a80426c74506240326c74508010326c7450a040326c6450c042666894d0d26c74511401fb8024fbb1749cd10b8014fb91701cd10268b4d28268b552a"
DWidth="1024"
DHeight="768"
DDepth="16"

IMPORTANT: Make sure to remove any spurious carrier return inserted by the Oberon editor.
Use the [Grow] Button to verify!!!!!

Execute Partitions.SetConfig appearing in the configuration text.

That's all.