#include<unistd.h>
#include<asm/io.h>
#include<stdio.h>
#include<stdlib.h>

#define DEMO

#define PORT 0x378


/* Ensoniq or ASCII charset */
#define ENSONIQ_CHARS


/* vfd controller bits */
#define V_CL 1
#define V_LS 2
#define V_C_CLK 4
#define V_R_CLK 8
#define V_DATA 16


/* video attributes */
#define BLINK   1
#define ULINE   2
#define INVERSE 4
#define DOTTED	8
#define USCROLL 0x10
#define DSCROLL 0x20
#define LSCROLL 0x40
#define RSCROLL 0x80

/* attribute timing */
#define BLINK_INTERVAL   0x40
#define HSCROLL_INTERVAL 0x04
#define VSCROLL_INTERVAL 0x08


/* charset */
unsigned char bits[0x80][12]=
{

// 0x00-0x1f: user defined characters 
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0,0,0},	// udef 0x00
{0,0,0x00,0x00,0x00,0x00,0x00,0x1f,0x00,0,0,0},	// udef 0x01
{0,0,0x00,0x00,0x00,0x00,0x1f,0x00,0x00,0,0,0},	// udef 0x02
{0,0,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0,0,0},	// udef 0x03
{0,0,0x00,0x00,0x1f,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x04
{0,0,0x00,0x1f,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x05
{0,0,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x06
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x07

{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0,0,0},	// udef 0x08
{0,0,0x00,0x00,0x00,0x00,0x00,0x1f,0x1f,0,0,0},	// udef 0x09
{0,0,0x00,0x00,0x00,0x00,0x1f,0x1f,0x1f,0,0,0},	// udef 0x0a
{0,0,0x00,0x00,0x00,0x1f,0x1f,0x1f,0x1f,0,0,0},	// udef 0x0b
{0,0,0x00,0x00,0x1f,0x1f,0x1f,0x1f,0x1f,0,0,0},	// udef 0x0c
{0,0,0x00,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0,0,0},	// udef 0x0d
{0,0,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0,0,0},	// udef 0x0e
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x0f

{0,0,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0,0,0},	// udef 0x10
{0,0,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0,0,0},	// udef 0x11
{0,0,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0,0,0},	// udef 0x12
{0,0,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0,0,0},	// udef 0x13
{0,0,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0,0,0},	// udef 0x14
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x15
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x16
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x17

{0,0,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0,0,0},	// udef 0x18
{0,0,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0,0,0},	// udef 0x19
{0,0,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0,0,0},	// udef 0x1a
{0,0,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0,0,0},	// udef 0x1b
{0,0,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0,0,0},	// udef 0x1c
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x1d
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x1e
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// udef 0x1f


// 0x20
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// space

// 0x21-0x2a
#ifdef ENSONIQ_CHARS
{0,0,0x0e,0x11,0x19,0x15,0x13,0x11,0x0e,0,0x10,0},	// 0.
{0,0,0x04,0x06,0x05,0x04,0x04,0x04,0x1f,0,0x10,0},	// 1.
{0,0,0x0e,0x11,0x10,0x08,0x04,0x02,0x1f,0,0x10,0},	// 2.
{0,0,0x0e,0x11,0x10,0x0c,0x10,0x11,0x0e,0,0x10,0},	// 3.
{0,0,0x08,0x0c,0x0a,0x09,0x1f,0x08,0x08,0,0x10,0},	// 4.
{0,0,0x1f,0x01,0x01,0x0f,0x10,0x10,0x0f,0,0x10,0},	// 5.
{0,0,0x0e,0x11,0x01,0x0f,0x11,0x11,0x0e,0,0x10,0},	// 6.
{0,0,0x1f,0x10,0x08,0x04,0x04,0x04,0x04,0,0x10,0},	// 7.
{0,0,0x0e,0x11,0x11,0x0e,0x11,0x11,0x0e,0,0x10,0},	// 8.
{0,0,0x0e,0x11,0x11,0x1e,0x10,0x11,0x0e,0,0x10,0},	// 9.
#else
{0,0,0x04,0x04,0x04,0x04,0x04,0x00,0x04,0,0,0},	// !
{0,0,0x0a,0x0a,0x0a,0x00,0x00,0x00,0x00,0,0,0},	// "
{0,0,0x00,0x0a,0x1f,0x0a,0x1f,0x0a,0x00,0,0,0},	// #
{0,0,0x04,0x1e,0x01,0x0e,0x10,0x0e,0x04,0,0,0},	// $
{0,0,0x00,0x11,0x08,0x04,0x02,0x11,0x00,0,0,0},	// %
{0,0,0x06,0x09,0x05,0x02,0x15,0x09,0x16,0,0,0},	// &
{0,0,0x04,0x02,0x01,0x00,0x00,0x00,0x00,0,0,0},	// '
{0,0,0x08,0x04,0x04,0x04,0x04,0x04,0x08,0,0,0},	// (
{0,0,0x02,0x04,0x04,0x04,0x04,0x04,0x02,0,0,0},	// )
{0,0,0x00,0x11,0x0a,0x1f,0x0a,0x11,0x00,0,0,0},	// *
#endif

// 0x2b-0x2f
{0,0,0x00,0x04,0x04,0x1f,0x04,0x04,0x00,0,0,0},	// +
{0,0,0x00,0x00,0x00,0x00,0x00,0x04,0x02,0,0,0},	// ,
{0,0,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0,0,0},	// -
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0,0,0},	// .
{0,0,0x00,0x10,0x08,0x04,0x02,0x01,0x00,0,0,0},	// /

// 0x30-0x39
{0,0,0x0e,0x11,0x19,0x15,0x13,0x11,0x0e,0,0,0},	// 0
{0,0,0x04,0x06,0x05,0x04,0x04,0x04,0x1f,0,0,0},	// 1
{0,0,0x0e,0x11,0x10,0x08,0x04,0x02,0x1f,0,0,0},	// 2
{0,0,0x0e,0x11,0x10,0x0c,0x10,0x11,0x0e,0,0,0},	// 3
{0,0,0x08,0x0c,0x0a,0x09,0x1f,0x08,0x08,0,0,0},	// 4
{0,0,0x1f,0x01,0x01,0x0f,0x10,0x10,0x0f,0,0,0},	// 5
{0,0,0x0e,0x11,0x01,0x0f,0x11,0x11,0x0e,0,0,0},	// 6
{0,0,0x1f,0x10,0x08,0x04,0x04,0x04,0x04,0,0,0},	// 7
{0,0,0x0e,0x11,0x11,0x0e,0x11,0x11,0x0e,0,0,0},	// 8
{0,0,0x0e,0x11,0x11,0x1e,0x10,0x11,0x0e,0,0,0},	// 9

// 0x3a-0x40
{0,0,0x00,0x00,0x04,0x00,0x04,0x00,0x00,0,0,0},	// :
{0,0,0x00,0x00,0x04,0x00,0x04,0x02,0x00,0,0,0},	// ;
{0,0,0x00,0x10,0x08,0x04,0x08,0x10,0x00,0,0,0},	// >
{0,0,0x00,0x00,0x1f,0x00,0x1f,0x00,0x00,0,0,0},	// =
{0,0,0x00,0x01,0x02,0x04,0x02,0x01,0x00,0,0,0},	// <
{0,0,0x0e,0x11,0x10,0x08,0x04,0x00,0x04,0,0,0},	// ?
{0,0,0x0e,0x11,0x15,0x15,0x0d,0x01,0x0e,0,0,0},	// @

// 0x41-0x5a
{0,0,0x04,0x0a,0x11,0x1f,0x11,0x11,0x11,0,0,0},	// A
{0,0,0x0f,0x11,0x11,0x0f,0x11,0x11,0x0f,0,0,0},	// B
{0,0,0x0e,0x11,0x01,0x01,0x01,0x11,0x0e,0,0,0},	// C
{0,0,0x07,0x09,0x11,0x11,0x11,0x09,0x07,0,0,0},	// D
{0,0,0x1f,0x01,0x01,0x07,0x01,0x01,0x1f,0,0,0},	// E
{0,0,0x1f,0x01,0x01,0x07,0x01,0x01,0x01,0,0,0},	// F
{0,0,0x0e,0x11,0x01,0x0d,0x11,0x11,0x0e,0,0,0},	// G
{0,0,0x11,0x11,0x11,0x1f,0x11,0x11,0x11,0,0,0},	// H
{0,0,0x1f,0x04,0x04,0x04,0x04,0x04,0x1f,0,0,0},	// I
{0,0,0x10,0x10,0x10,0x10,0x10,0x11,0x0e,0,0,0},	// J
{0,0,0x11,0x09,0x05,0x03,0x05,0x09,0x11,0,0,0},	// K
{0,0,0x01,0x01,0x01,0x01,0x01,0x01,0x1f,0,0,0},	// L
{0,0,0x11,0x1b,0x15,0x11,0x11,0x11,0x11,0,0,0},	// M
{0,0,0x11,0x13,0x15,0x19,0x11,0x11,0x11,0,0,0},	// N
{0,0,0x0e,0x11,0x11,0x11,0x11,0x11,0x0e,0,0,0},	// O
{0,0,0x0f,0x11,0x11,0x0f,0x01,0x01,0x01,0,0,0},	// P
{0,0,0x0e,0x11,0x11,0x11,0x15,0x09,0x16,0,0,0},	// Q
{0,0,0x0f,0x11,0x11,0x0f,0x05,0x09,0x11,0,0,0},	// R
{0,0,0x1e,0x01,0x01,0x0e,0x10,0x10,0x0f,0,0,0},	// S
{0,0,0x1f,0x04,0x04,0x04,0x04,0x04,0x04,0,0,0},	// T
{0,0,0x11,0x11,0x11,0x11,0x11,0x11,0x0e,0,0,0},	// U
{0,0,0x11,0x11,0x11,0x11,0x11,0x0a,0x04,0,0,0},	// V
{0,0,0x11,0x11,0x11,0x11,0x15,0x0a,0x0a,0,0,0},	// W
{0,0,0x11,0x11,0x0a,0x04,0x0a,0x11,0x11,0,0,0},	// X
{0,0,0x11,0x11,0x0a,0x04,0x04,0x04,0x04,0,0,0},	// Y
{0,0,0x1f,0x10,0x08,0x04,0x02,0x01,0x1f,0,0,0},	// Z

// 0x5b-0x5d
{0,0,0x1c,0x04,0x04,0x04,0x04,0x04,0x1c,0,0,0},	// [
{0,0,0x00,0x01,0x02,0x04,0x08,0x10,0x00,0,0,0},	// \\
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0},	// ??? 
{0,0,0x07,0x04,0x04,0x04,0x04,0x04,0x07,0,0,0},	// ]

// 0x5e/0x5f
#ifdef ENSONIQ_CHARS
{0,0,0x04,0x0a,0x15,0x00,0x00,0x00,0x00,0,0,0},	// ^
{0,0,0x00,0x00,0x00,0x00,0x15,0x0a,0x04,0,0,0},	// v
#else
{0,0,0x04,0x0a,0x11,0x00,0x00,0x00,0x00,0,0,0},	// ^
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0,0,0},	// _
#endif

// 0x60
{0,0,0x04,0x08,0x10,0x00,0x00,0x00,0x00,0,0,0},	// `


// 0x61-0x7a
{0,0,0x00,0x00,0x0e,0x10,0x1e,0x11,0x1e,0,0,0},	// a
{0,0,0x01,0x01,0x0f,0x11,0x11,0x11,0x0f,0,0,0},	// b
{0,0,0x00,0x00,0x0e,0x11,0x01,0x11,0x0e,0,0,0},	// c
{0,0,0x10,0x10,0x1e,0x11,0x11,0x11,0x1e,0,0,0},	// d
{0,0,0x00,0x00,0x0e,0x11,0x0f,0x01,0x0e,0,0,0},	// e
{0,0,0x18,0x04,0x04,0x0e,0x04,0x04,0x04,0,0,0},	// f
{0,0,0x00,0x00,0x1e,0x11,0x1e,0x10,0x0e,0,0,0},	// g
{0,0,0x01,0x01,0x0f,0x11,0x11,0x11,0x11,0,0,0},	// h
{0,0,0x04,0x00,0x04,0x04,0x04,0x04,0x04,0,0,0},	// i
{0,0,0x04,0x00,0x04,0x04,0x04,0x04,0x03,0,0,0},	// j
{0,0,0x01,0x01,0x01,0x0b,0x05,0x09,0x11,0,0,0},	// k
{0,0,0x04,0x04,0x04,0x04,0x04,0x04,0x18,0,0,0},	// l
{0,0,0x00,0x00,0x0b,0x15,0x11,0x11,0x11,0,0,0},	// m
{0,0,0x00,0x00,0x0f,0x11,0x11,0x11,0x11,0,0,0},	// n
{0,0,0x00,0x00,0x0e,0x11,0x11,0x11,0x0e,0,0,0},	// o
{0,0,0x00,0x00,0x0f,0x11,0x0f,0x01,0x01,0,0,0},	// p
{0,0,0x00,0x00,0x1e,0x11,0x1e,0x10,0x10,0,0,0},	// q
{0,0,0x00,0x00,0x0f,0x11,0x01,0x01,0x01,0,0,0},	// r
{0,0,0x00,0x00,0x1e,0x01,0x0e,0x10,0x0f,0,0,0},	// s
{0,0,0x04,0x04,0x1f,0x04,0x04,0x04,0x18,0,0,0},	// t
{0,0,0x00,0x00,0x11,0x11,0x11,0x11,0x0e,0,0,0},	// u
{0,0,0x00,0x00,0x11,0x11,0x11,0x0a,0x04,0,0,0},	// v
{0,0,0x00,0x00,0x11,0x11,0x15,0x0a,0x0a,0,0,0},	// w
{0,0,0x00,0x00,0x11,0x0a,0x04,0x0a,0x11,0,0,0},	// x
{0,0,0x00,0x00,0x11,0x11,0x1e,0x10,0x0e,0,0,0},	// y
{0,0,0x00,0x00,0x1f,0x08,0x04,0x02,0x1f,0,0,0},	// z

// 0x7b-0x7e
{0,0,0x18,0x04,0x04,0x02,0x04,0x04,0x18,0,0,0},	// {
{0,0,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0,0,0},	// |
{0,0,0x03,0x04,0x04,0x08,0x04,0x04,0x03,0,0,0},	// }
{0,0,0x00,0x00,0x0a,0x05,0x00,0x00,0x00,0,0,0},	// ~

// 0x7f
{0,0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0,0,0}	// del

};

unsigned char vmem[2*40]={
//	"0123456789012345678901234567890123456789",
	"Video Attributes bl ul in dd us ds ls rsCharset Demo >>!\"#$%&'()*+,-./01234567<<"
};

unsigned char amem[2*40]={
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,
	0x02,0x02,0x00,0x04,0x04,0x00,0x08,0x08,0x00,0x10,
	0x10,0x00,0x20,0x20,0x00,0x40,0x40,0x00,0x80,0x80,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40
};

int blink_cnt=0, hscroll_cnt=0, vscroll_cnt=0;

int hscroll_pos=0, vscroll_pos=0;

void demo(int x)
{
	if ((x>13)&&(x<38)&&(vscroll_cnt==0))
		vmem[40+x]=(vmem[40+x]+1)&0x7f;
}


/******************/
/* VFD primitives */
/******************/

void cls(void)
{
	outb(0,PORT);
}

void clk_c(int data)
{
	int x= 1*V_CL+(data?1:0)*V_DATA; 
	outb( x, PORT);
	outb( x | V_C_CLK, PORT);
}
void clk_r(int data)
{
	int x= 1*V_CL+(data?1:0)*V_DATA; 
	outb( x, PORT);
	outb( x | V_R_CLK, PORT);
}

void latchup(void)
{
	int x=V_CL*1;

	outb( x , PORT);
	outb( x + V_LS , PORT);
	outb( x , PORT);
}


/******************/
/* VFD char level */
/******************/

void cline(int m, int xchar, int attrib) {
int i, xpat, n, pos, pattern;

	if (attrib&USCROLL)
		pos=(hscroll_pos+m)%12;
	else if (attrib&DSCROLL)
		pos=(12-hscroll_pos+m)%12;
	else
		pos=m;
		
	for(n=0;n<5;n++) {
		pattern=bits[xchar][ (pos==0)?11:(pos-1) ];

		if ((attrib&ULINE)&&(pos==0)) 
			pattern=0x1f;

		if ((attrib&DOTTED)&&(pos==11))
			pattern|=0x10;
			
		if ((attrib&BLINK)&&(blink_cnt>BLINK_INTERVAL>>1))
			pattern=0;

		if (attrib&INVERSE) {
			pattern^=0x1f;
			if (attrib&ULINE) {
				if (pos==0) pattern=0x1f;
				if (pos==11) pattern=0;
			}
		}

		if (attrib&RSCROLL) {
			xpat=((pattern<<vscroll_pos)&0x1f) | (pattern >> (5-vscroll_pos));
			pattern=xpat;
		} else if (attrib&LSCROLL) {
			xpat=( ( (pattern<<(5-vscroll_pos) )&0x1f) | (pattern>>vscroll_pos));
			pattern=xpat;
		}
		clk_r(pattern & (1<<n));
	}
}

void video_attributes(void)
{
	blink_cnt++;
	if (blink_cnt>BLINK_INTERVAL) 
		blink_cnt=0;

	if (hscroll_cnt==HSCROLL_INTERVAL) {
		hscroll_pos++;
		hscroll_cnt=0;
	} else hscroll_cnt++;
		
	hscroll_pos=hscroll_pos%12;

	if (vscroll_cnt==VSCROLL_INTERVAL) {
		vscroll_pos++;
		vscroll_cnt=0;
	} else vscroll_cnt++;
		
	vscroll_pos=vscroll_pos%5;

}

void vmem_out(void)
{
int x,m,n;
int xchar, ychar, xattrib, yattrib, pattern;

	for(x=0;x<40;x++)
	{
		int xchar=vmem[x]&0x7f;
		int ychar=vmem[x+40]&0x7f;

		int xattrib=amem[x];
		int yattrib=amem[x+40];
	
		int pattern;

		/* shift to char position */
		if (x==0)
			for(n=0;n<40;n++)
				clk_c((n==39));
		else
			clk_c(0);				
			
		/* shift in char */
		for(m=0;m<12;m++)
		{
			cline(m,xchar,xattrib);
			cline(m,ychar,yattrib);
		}		

		latchup();	


#ifdef DEMO
		demo(x);
#endif
	}

	video_attributes();

}


/**********************/
/* video memory level */
/**********************/

void hscroll(int start, int end, int dir)
{
int i,dummy;
	if(dir==0)
		dummy=vmem[start];
	else
		dummy=vmem[end];
		
	for(i=start;i<end;i++) {
		if(dir==0)
			vmem[i]=vmem[i+1];
		else
			vmem[i+1]=vmem[i];
	}
	
	if(dir==0)
		vmem[end]=dummy;
	else
		vmem[start]=dummy;
}

void vscroll(int start, int end, int dir)
{
int i,dummy;
	for(i=start;i<=end;i++) {
		if(dir==0) {
			dummy=vmem[i];
			vmem[i]=vmem[i+40];
			vmem[i+40]=dummy;
		} else {
			dummy=vmem[i+40];
			vmem[i+40]=vmem[i];
			vmem[i]=dummy;
		}
	}
}

void set_attrib(int start, int end, int attr)
{
int i;
	for(i=start;i<=end;i++)
		amem[i]|=attr;
}

void clr_attrib(int start, int end, int attr)
{
int i;
	for(i=start;i<=end;i++)
		amem[i]&=(0xff^attr);
}

/*************/


int main(void) {
	if (ioperm(PORT,3,1)!=0) {
		perror("PPORT 0x378");
		exit(-1);
	}

	cls();
	
	while(1) 
		vmem_out();
		
	cls();
		
	ioperm(PORT,3,0);
	exit(0);
}	
