/********************************************************
 *vfd.c  program to drive vacuum fluorescent display	*
 *aka lcd.c for lcd 44780 Displays			*
 *(c) 31 May 1999  Andrew McMeikan GPL v2		*
 *andrewm@engineer.com ; andrew.mcmeikan@mitswa.com.au	*
 *toggles lines on the parallel port 			*
 *D0 is data						*
 *D1 is clock						*
 *D2 is reset line                                      *
 *this first version uses port at 0x378                 *
 *due to the use of outb macro you must compile with -O *
 *if you want non-root use remember to chmod +s it	*
 * Modified:	1 June 1999	Andrew McMeikan		*
 *			optimised display 		*
 *		1 Sept 1999	Andrew McMeikan		*
 *			seperated LCD and VFD pins	*
 *As it is intended to have a keypad hanging off the  	*
 *shift register for the LCD the VFD display is to be	*
 *re-located onto its own pins to prevent key reads	*
 *upsetting a VFD Display.  				*
 * D0 VFD DATA						*
 * D1 VFD CLOCK						*
 * D2 VFD RESET ; LCD E Signal				*
 * D3 LCD DATA FOR SHIFT REGISTER			*
 * D4 LCD CLOCK FOR SHIFT REGISTER			*
 ********************************************************/
#define LCDDATA 8
#define LCDCLOCK 16
#define VFDDATA 1
#define VFDCLOCK 2
#define RESET 4
#define E 4
#define PORT 0x378

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

void dochar( char);
char shiftreg(char);
void lcdinit(void);
void ldat(char);
void lcmd(char);
void lcdmessage(char*);

int main (int argc, char *argv[])
{
int i;
if (argc!=2) 
	{
	printf("%s:must specify text to display (max 16chars)\n",argv[0]);
	printf("%s:eg. %s \"HELLO WORLD\"\n",argv[0],argv[0]);
	return(1);
	}

ioperm(PORT,1,255);
if (strcmp(argv[0],"lcd")==0)	/*note:  ./lcd will not match! put in path*/
	{
	/*printf("doing lcd stuff\n");*/
	lcdmessage(argv[1]);	
	return(0);
	}
else
	{
	outb(RESET,PORT);
	outb(0,PORT);
	usleep(10);			/* allow recover time after reset  */
	dochar(255);			/* set display mode and brightness */
	for (i=0;(argv[1][i]!=0)&&(i!=16);i++)
		dochar(argv[1][i]);	/* display user string		*/
	if (i<16)
		for (;i!=16;i++)
			dochar(' ');	/*pad out rest of display to blank */
	return(0);			/* exit happy			*/
	}
}

void dochar(char d)		/* this function displays on char */
{
int i;
for (i=7;i>=0;i--)		/* MSB first 	*/
	{
	outb(VFDCLOCK|(((d>>i)&1)*VFDDATA),PORT);
	outb(((d>>i)&1)*VFDDATA,PORT);	/* falling clock latches bit	*/
	outb(VFDCLOCK|(((d>>i)&1)*VFDDATA),PORT);
	}
}

char shiftreg (char r)		/* this function sends r out onto   	*/
{				/* the shift register returning any 	*/
int i;				/* data collected [obsolete-k/b to be
				/* seperate program]			*/
char d;
d=0;
for (i=7;i>=0;i--)		/* MSB first	*/
	{
	outb(((r>>i)&1)*LCDDATA,PORT);		/*set up data	*/
	outb((((r>>i)&1)*LCDDATA)|LCDCLOCK,PORT);/*rising edge of clock	*/
	}
outb(E,PORT);			/*latch it	*/
outb(0,PORT);
return(d);
}

#define RS 16
void lcmd (char c)		/*this function sends the command c	*/
{				/* to the lcd display			*/
char n;				/*n is nibble of command		*/

n=c>>4;                         /*get high nibble                       */
n=n&15;
shiftreg(n);               /*clock it                                 */

n=c&15;
shiftreg(n);		/*clock it				*/
usleep(40);
}

void ldat (char c)              /*this function sends the data c        */
{                               /* to the lcd display                   */
char n;                         /*n is nibble of data                   */

n=c>>4;                         /*get high nibble                       */
n=n&15;
shiftreg(n|RS);                  /*clock it                              */


n=c&15;
shiftreg(n|RS);                  /*clock it                              */

}

void lcdinit(void)
{
shiftreg(3);
usleep(4100);

shiftreg(3);
usleep(100);

shiftreg(3);
usleep(40);

shiftreg(2);
usleep(40);

lcmd(0x28);
lcmd(0x06);
lcmd(0x0F);
lcmd(0x01);
usleep(1600);
lcmd(0x80);
}

void lcdmessage(char *m)
{
int i;
lcdinit();
for (i=0;i<strlen(m);i++)
	{
	ldat(m[i]);
	}
}


