/*
 * Copyright(C) Paul und Scherer (mct.de/mct.net)
 *
 * This example demonstrates how to...
 *
 *  ... use the DAC as simple video generator.
 *
 * Note: Compile with "-O1" for correct timing!
 */

#include <stdio.h>
#include <conio.h>
#include <sys/arm7tdmi.h>
#include <target.h>

/*
 * CVBS signal levels
 */
#define SYNC	  0				// sync
#define BLACK	200				// black
#define WHITE	600				// white (max. 1023)

#define FL	 23				// 1st  visible line
#define LL	310				// last visible line

#define WIDTH	53				// max. with of bars

static int white = WHITE;			// white level
static int width = WIDTH;			// bar width
static int blank;				// blank screen
static int skip;				// # of border lines

/*
 * Delay loop
 */
static void
wait(int n)
{
	while (n--) ;
}

/*
 * Timer0 interrupt service
 *
 * The CVBS requires an extremely exact timing.
 * Varying interrupt latencies become visible -
 * as line jitter.
 *
 * If the main program "manages" to enter sleep
 * mode BEFORE an interrupt occurs, this can be
 * avoided (when interrupted in sleep mode, the
 * latency is constant, so the screen lines are
 * exactly vertically aligned).
 */
static void __attribute__((interrupt))		// handle as ISR!
t0_isr(void)
{
	static int line;			// line counter

	int i;

	Intern_dacr  = SYNC<<6;			// start of sync
	Intern_t0ir  = 1;			// clear int flag
	Intern_vicvectaddr = 0;			// reset VIC priority logic

	if (line > 311) line = 0;		// 312 lines done, reset
	if (++line < 4) return;			// vertical   sync pulse
	wait(40);				// horizontal sync pulse
	Intern_dacr = BLACK<<6;			// black level reference

	if (blank				// blank screen
	 || line < FL+skip			// top and
	 || line > LL-skip) return;		// bottom border

	/*
	 * Left border
	 *
	 * To keep the picture centered, when the
	 * bar width changes, the left border has
	 * to be adjusted accordingly. As the bar
	 * width changes by one, the screen width
	 * changes by 8 leaving 4 to each border.
	 */
	wait(70+width+(WIDTH-width)*4);

	/*
	 * Set RGB bits, and write gray scale pixels.
	 */
	i = (white-BLACK)/8<<6;						 // scale
	Intern_iopin = 0x20000, /* R   */ Intern_dacr += i, wait(width); // gray1
	Intern_iopin = 0x40000, /* G   */ Intern_dacr += i, wait(width); //     2
	Intern_iopin = 0x60000, /* RG  */ Intern_dacr += i, wait(width); //     3
	Intern_iopin = 0x80000, /* B   */ Intern_dacr += i, wait(width); //     4
	Intern_iopin = 0xa0000, /* BR  */ Intern_dacr += i, wait(width); //     5
	Intern_iopin = 0xc0000, /* BG  */ Intern_dacr += i, wait(width); //     6
	Intern_iopin = 0xe0000, /* RGB */ Intern_dacr += i, wait(width); // white

	Intern_iopin = 0;			// right
	Intern_dacr  = BLACK<<6;		//  border
}

/*
 * Usage
 */
static void
usage(void)
{
	puts("\n"
	     "LPC2138 Video Generator\n"
	     "=======================\n"
	     "Space : blank screen\n"
	     " -/+  : -/+ height\n"
	     " </>  : -/+ width\n"
	     " w/W  : -/+ white level\n"
	     " r    : R off/on\n"
	     " g    : G off/on\n"
	     " b    : B off/on\n"
	     " d    : default settings\n"
	);
}

/*
 * How it works:
 *
 * A TV video signal ("Composite Video Blanking and Sync" =CVBS)
 * has the following (simplified) form (PAL norm, no color):
 *
 * 100% (white) ca. 1V       ___________________________
 *                          |                           |
 *                          |       PICTURE LINE        |
 *  30% (black)        _____|___________________________|     ..
 *   0% (sync )  |____|                                 |____|
 *
 *         5us ->|    |<-
 *        12us ->|          |<-
 *        64us ->|                                      |<-
 *
 * This represents one TV screen line. One full picture has 625
 * lines, written interlaced in two fields of 312.5 lines each.
 * A vertical sync signal (composed of lots of small pulses, in
 * the first few lines of a field) indicates the beginning of a
 * new field and defines its starting position on the screen.
 *
 * Here, a much simpler form is used: Non-interlaced mode (i.e.
 * the same field, written twice to the same position). To keep
 * timing as close as possible, 312 lines/field are used (which
 * results in a slightly higher vertical frequency). The signal
 * form for the vertical sync simplifies to a long low pulse in
 * the first three lines of each field.
 *
 * The CVBS is created by a simple ISR, which gets called every
 * 64us. The DAC then sets the required levels for one line and
 * holds the first three lines of each field at the sync level.
 *
 * The video signal is used to create eight vertical bars, gray
 * scale, black to white from left to right. In addition to the
 * gray scale picture at the DAC output, the ISR provides color
 * information (P0.17 to P0.19), which can drive RGB inputs via
 * SCART. The combination of the three RGB bits allows 8 colors
 * (including black and white).
 *
 * Connect the DAC output (=Aout =P0.25) to a TV's video input,
 * either via cinch, or SCART (pin 20) and select "AV" as video
 * source.
 *
 * For color, the SCART connector must be wired as shown below:
 *
 *  R out (=P0.17) --> SCART pin 15 (R in)
 *  G out (=P0.18) -->       pin 11 (G in)
 *  B out (=P0.19) -->       pin  7 (B in)
 *  Vcc            -->       pin 16 (Blanking)
 */
int
main(void)
{
	Intern_vicvectaddr0 = (long)t0_isr;	// set ISR addr
	Intern_vicintenable = 0x10;		// enable t0 int
	Intern_vicvectcntl0 = 0x24;		//  vector

	Intern_t0mr0  = _PCLK/1000000*64-1;	// set MR0 to 64us
	Intern_t0mcr |= 3;			// int on MR0, reset
	Intern_t0tcr  = 1;			// go...

	Intern_pinsel1 |= 0x80000;		// enable DAC pin
	Intern_iodir   |= 0xe0000;		// RGB output

	usage();

	ENABLE_INTERRUPTS;			// enable core ints

	while (1) {
		int c;

		/*
		 * Command interface
		 */
		if (kbhit()) switch (c = getchar()) {
		default	: usage()			   ; break; // usage
		case ' ': blank = !blank		   ; break; // blank screen
		case '-': if (skip < (LL-FL)/2) skip++	   ; break; // height -1
		case '+': if (skip)		skip--	   ; break; //        +1
		case '<': if (width)		width--	   ; break; // width  -1
		case '>': if (width < WIDTH)	width++	   ; break; //        +1
		case 'w': if (white > BLACK+10)	white -= 10; break; // white  -10
		case 'W': if (white < WHITE-10)	white += 10; break; //        +10
		case 'r': Intern_iodir ^= 0x20000	   ; break; // toggle R
		case 'g': Intern_iodir ^= 0x40000	   ; break; //        G
		case 'b': Intern_iodir ^= 0x80000	   ; break; //        B
		case 'd': Intern_iodir |= 0xe0000	   ;	    // default
			  width = WIDTH			   ;
			  white = WHITE, blank = skip = 0  ; break; //  settings
		}
		Intern_pcon = 1;		// go to sleep...
	}
}

