/*
 * version : 0.0.1
 */

#include <sys/param.h>
#include <sys/fcntl.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/exec.h>
#include <sys/conf.h>
#include <sys/lkm.h>
#include <sys/types.h>
#include <machine/pio.h>
#include <string.h>


#include "plcm_ioctl.h"

/*
 * Device Depend Definition
 */
#define LPT1 0x378
#define LPT2 0x278
#define LPT3 0x3BC

#define MAXMSGLEN 40

unsigned int nodata = 1;

#define udelay(a) delay(a);

int plcmopen __P((dev_t dev, int oflags, int devtype, struct proc *p));
int plcmclose __P((dev_t dev, int fflag, int devtype, struct proc *p));
int plcmioctl __P((dev_t dev, u_long cmd, caddr_t data, int fflag,struct proc *p));
int plcmhandler __P((struct lkm_table *lkmtp, int cmd));

/*
 * Device Depend Function Prototypes
 */
void LCM_Init(void);
void LCM_Command(unsigned char RS, unsigned char RWn, unsigned char CMD, unsigned int uDelay, unsigned char *Ret);
void LCM_Backlight(void);


/*
 * Device Depend Variables
 */
unsigned int  Port_Addr = 0; // LPTx Port Address
unsigned int  DataPort = 0;
unsigned int  StatusPort = 0;
unsigned int  ControlPort = 0;
unsigned char Backlight = 0; // Backlight ON
unsigned char Cur_Line = 1; // Current Line#
unsigned char Cur_EntryMode = 0x04; // Current Entry Mode Set CMD
unsigned char Cur_Display = 0x08; // Current Display On/Off Ctrl
unsigned char Cur_Shift = 0x10; // Current Cursor/Dsiplay Shift Ctrl



static struct plcm_io dio;

cdev_decl(plcm);

static struct cdevsw cdev_plcm = cdev_plcm_init(1, plcm);


MOD_DEV("plcm",LM_DT_CHAR,-1,&cdev_plcm)


//plcm fun 
void LCM_Init(void)
{
	unsigned int i = 0;

	if(inb(LPT1) != 0xFF)
	{
		Port_Addr = LPT1;
	}
	else if(inb(LPT2) != 0xFF)
	{
		Port_Addr = LPT2;
	}
	else if(inb(LPT3) != 0xFF)
	{
		Port_Addr = LPT3;
	}
	else
	{
		Port_Addr = 0;
	}
	if(Port_Addr == 0)
	{
		printf("plcm_drv: Can not find any LPTx to use...\n");
	}
	else
	{
		printf("plcm_drv: LPTx Address = %x\n", Port_Addr);
		DataPort = Port_Addr;
		StatusPort = Port_Addr + 1;
		ControlPort = Port_Addr + 2;
		LCM_Command(0, 0, 0x38, 8000, NULL); // Function Set
		LCM_Command(0, 0, 0x38,  300, NULL);
		LCM_Command(0, 0, 0x38,  300, NULL);
		LCM_Command(0, 0, 0x38,  300, NULL);
		LCM_Command(0, 0, 0x0F,  300, NULL); // Display On/OFF
		LCM_Command(0, 0, 0x01, 3000, NULL); // Display Clear
		LCM_Command(0, 0, 0x06,  300, NULL); // Entry Mode Set
		LCM_Command(0, 0, 0x80,  300, NULL); // Set DDRAM Address	
		for(i = 0; i < 40; i++) // Range: 0x00~0x27
		{
			LCM_Command(1, 0, ' ', 300, NULL); // Write Data
		}
		LCM_Command(0, 0, 0xC0, 300, NULL); // Set DDRAM Address
		for(i = 0; i < 40; i++) // Range: 0x40~0x67
		{
			LCM_Command(1, 0, ' ', 300, NULL); // Write Data
		}
		// Add character in CGRAM as below.
		// 11111 
		// 10001
		// 10101
		// 10101
		// 10101
		// 10001
		// 11111
		// 00000
		for(i = 0; i < 8; i++)
		{
			LCM_Command(0, 0, 0x40+i*8+0, 300, NULL);
			LCM_Command(1, 0, 0x1F, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+1, 300, NULL);
			LCM_Command(1, 0, 0x11, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+2, 300, NULL);
			LCM_Command(1, 0, 0x15, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+3, 300, NULL);
			LCM_Command(1, 0, 0x15, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+4, 300, NULL);
			LCM_Command(1, 0, 0x15, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+5, 300, NULL);
			LCM_Command(1, 0, 0x11, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+6, 300, NULL);
			LCM_Command(1, 0, 0x1F, 300, NULL);
			LCM_Command(0, 0, 0x40+i*8+7, 300, NULL);
			LCM_Command(1, 0, 0x00, 300, NULL);
		}
	}
	return;
}

#define ENABLE 0x02
void LCM_Command(unsigned char RS, unsigned char RWn, unsigned char CMD, unsigned int uDelay, unsigned char *Ret)
{
	unsigned char Ctrl = 0;

	Ctrl |= Backlight;
	if(RS == 0)
	{
		Ctrl |= 0x08; // RS: Real RS = ~RS
	}
	if(RWn == 1)
	{
		Ctrl |= 0x24; // RWn: Read = 1, Write = 0
	}
	outb(ControlPort,Ctrl | ENABLE);  // Set RS and RWn, E = 0
	udelay(40);
	outb(ControlPort,Ctrl & ~ENABLE);  // E = 1
	udelay(10);
	if(RWn == 0)
	{
		outb(DataPort,CMD);  // LCM Data Write 
	}
	if((RWn == 1) && (Ret != NULL))
	{
		udelay(10);	
		*Ret = inb(DataPort); // LCM Data Read	
	}
	udelay(300);
	outb(ControlPort,Ctrl | ENABLE);  // E = 0
	udelay(uDelay + 1);
	return;
}

void LCM_Backlight(void)
{
	unsigned char Ctrl = inb(ControlPort);
	printf("backlight is : %d\n",Backlight);
	if(Backlight == 1)
	{
		Ctrl |= 0x01;
	}
	else
	{
		Ctrl &= ~0x01;
	}
	outb(ControlPort,Ctrl);
	return;
}


int
plcmopen(dev, oflags, devtype, p)
	dev_t dev;
	int oflags, devtype;
	struct proc *p;
{
	printf("Lanner LCM Driver Opened!\n");
	
	return 0;
}

int
plcmclose(dev, fflag, devtype, p)
	dev_t dev;
	int fflag, devtype;
	struct proc *p;
{
	printf("Lanner LCM Driver Closed!\n");

	return 0;
}


int
plcmioctl(dev, cmd, data, fflag, p)
	dev_t dev;
	u_long cmd;
	caddr_t data;
	int fflag;
	struct proc *p;
{
	struct plcm_io *plcm = (struct plcm_io *)data ;
	int error = 0;
	int i=0;
	int length=40;
	unsigned char LCM_Message[MAXMSGLEN]="test123";
	switch(cmd) {
	
		case PLCM_IOCTL_STOP_THREAD:
			
			printf("plcm_drv : PLCM_IOCTL_STOP_THREAD\n");
			break;
		case PLCM_IOCTL_BACKLIGHT:
			if(plcm->value == 0)
			{
				Backlight = 0;
				LCM_Backlight();
			}
			else if (plcm->value == 1)
			{
				Backlight = 1;
				LCM_Backlight();
			}
			break;
		case PLCM_IOCTL_SET_LINE:
			if(plcm->value == 1)
				Cur_Line = 1;
			else if(plcm->value == 2)
				Cur_Line = 2;
			printf("Set Line# = %d\n", Cur_Line);
			break;
		case PLCM_IOCTL_CLEARDISPLAY:
			LCM_Command(0, 0, 0x01, 300, NULL);
			break;
		case PLCM_IOCTL_RETURNHOME:
			LCM_Command(0, 0, 0x02, 300, NULL);
			break;
		case PLCM_IOCTL_ENTRYMODE_ID:
			if(plcm->value == 0)
				Cur_EntryMode &= ~0x02;
			else if(plcm->value == 1)
				Cur_EntryMode |= 0x02;
			LCM_Command(0, 0, Cur_EntryMode, 300, NULL);
			break;
		case PLCM_IOCTL_ENTRYMODE_SH:
			if(plcm->value == 0)
				Cur_EntryMode &= ~0x01;
			else if(plcm->value == 1)
				Cur_EntryMode |= 0x01;
			LCM_Command(0, 0, Cur_EntryMode, 300, NULL);
			break;
		case PLCM_IOCTL_DISPLAY_D:
			if(plcm->value == 0)
				Cur_Display &= ~0x04;
			else if(plcm->value == 1)
				Cur_Display |= 0x04;
			LCM_Command(0, 0, Cur_Display, 300, NULL);
			break;
		case PLCM_IOCTL_DISPLAY_C:
			if(plcm->value == 0)
				Cur_Display &= ~0x02;
			else if(plcm->value == 1)
				Cur_Display |= 0x02;
			LCM_Command(0, 0, Cur_Display, 300, NULL);
			break;
		case PLCM_IOCTL_DISPLAY_B:
			if(plcm->value == 0)
				Cur_Display &= ~0x01;
			else if(plcm->value == 1)
				Cur_Display |= 0x01;
			LCM_Command(0, 0, Cur_Display, 300, NULL);
			break; 
		case PLCM_IOCTL_SHIFT_SC:
			if(plcm->value == 0)
				Cur_Shift &= ~0x08;
			else if(plcm->value == 1)
				Cur_Shift |= 0x08;
			LCM_Command(0, 0, Cur_Shift, 300, NULL);
			break;
		case PLCM_IOCTL_SHIFT_RL:
			if(plcm->value == 0)
				Cur_Shift &= ~0x04;
			else if(plcm->value == 1)
				Cur_Shift |= 0x04;
			LCM_Command(0, 0, Cur_Shift, 300, NULL);
			break;
		case PLCM_IOCTL_GET_KEYPAD:
			return inb(StatusPort);
			break;
		case PLCM_IOCTL_WRITE:
			for(i = 0; i < 40; i++)
			{
				LCM_Message[i] = ' ';
			}
			if(plcm->value > 40)
			{
				printf("plcm->value out of range !!\n");
				return 0;
			}	
			if(Cur_Line == 1)
			{
				LCM_Command(0, 0, 0x80, 300, NULL);
			}
			else if(Cur_Line == 2)
			{
				LCM_Command(0, 0, 0xC0, 300, NULL);
			}
			for(i = 0; i < plcm->value; i++)
			{
				LCM_Command(1, 0, plcm->msg[i], 300, NULL);
			}
			break;
		case PLCM_IOCTL_READ:
			if(length != 40)
			{
				printf("plcm->msg out of range !!\n");
				return 0;
			}
			if(Cur_Line == 1)
			{
				LCM_Command(0, 0, 0x80, 300, NULL);
			}
			else if(Cur_Line == 2)
			{
				LCM_Command(0, 0, 0xC0, 300, NULL);
			}
			memset (LCM_Message, 0, sizeof (LCM_Message) ) ;
			for(i = 0; i < length ; i++)
			{
				LCM_Command(1, 1, 0x00, 300, &plcm->msg[i]); // Read Data
				if((i > 0) && (plcm->msg[i - 1] == ' ') && (plcm->msg[i] == ' '))
				{
					plcm->msg[i] = 0;
				}
			}
			printf("read string is :%s\n",plcm->msg);
			break;
		default:
			error = ENOTTY;
			break;
	}

	return 0;
}

int plcm(lkmtp,cmd,ver)
	struct lkm_table *lkmtp;
	int cmd;
	int ver;
{
	DISPATCH(lkmtp,cmd,ver,plcmhandler,plcmhandler,lkm_nofunc);
}

int plcmhandler(lkmtp,cmd)
	struct lkm_table *lkmtp;
	int cmd;
{
	struct lkm_dev *args = lkmtp->private.lkm_dev;
	if(cmd == LKM_E_LOAD) {
		printf("Loading module %s\n",args->lkm_name);
		LCM_Init();
	}

	if(cmd == LKM_E_UNLOAD) {
		printf("Unloading module %s\n",args->lkm_name);
	}

	return 0; 
}
