/* Device driver for 44780 LCD connected to 68EZ328 */
/* by Steven J. Merrifield July 2005 */

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/MC68EZ328.h>
#include "lcd.h"

/* 
	Port C		LCD (4 bit mode)

	7		D7
	6		D6
	5		D5
	4		D4
	3		RS
	2		EN
	1		N/C
	0		N/C
*/


#define RS_0	PCDATA &=0xF7	/* Bit 3 = 0 */
#define RS_1	PCDATA |=0x08	/* Bit 3 = 1 */
#define EN_0	PCDATA &=0xFB	/* Bit 2 = 0 */
#define EN_1	PCDATA |=0x04	/* Bit 2 = 1 */

#define LINE1	0x00
#define LINE2	0x40
#define LINE3	0x10
#define LINE4	0x50


/* Typical execution time is 37us, except for HOME, which is 1.5ms */
#define WAIT	50	/* microseconds */


/************************************************************/
void ClkE(void)
{
	EN_1;
	udelay(1);	/* >450 ns */
	EN_0;
}

/************************************************************/
void SendNibble(unsigned char ch)
{
	PCDATA = (PCDATA & 0x0F) | (ch & 0xF0);
}

/************************************************************/
/* Home'ing the cursor takes a lot longer than all other instructions */
void WaitChar(unsigned char ch)
{
	if ((ch == 0x01) || (ch == 0x02) || (ch == 0x03))
		udelay(1600);
	else
		udelay(WAIT);
}

/************************************************************/
void SendChar(int i, unsigned char ch)
{
	if (i)
		RS_1;
	else
		RS_0;
	SendNibble(ch);
	ClkE();
	WaitChar(ch);
	SendNibble(ch << 4);
	ClkE();
	WaitChar(ch);
}

/************************************************************/
void Line(int i)
{
	switch(i)
	{
		case 1: SendChar(0, 0x80 | LINE1);
			break;
		case 2: SendChar(0, 0x80 | LINE2);
			break;
		case 3: SendChar(0, 0x80 | LINE3);	
			break;
		case 4: SendChar(0, 0x80 | LINE4);	
			break;
	}
}

/************************************************************/
void SendString(char *s)
{
	int i=0;
	while (s[i] != 0)
	{
		SendChar(1,s[i]);
		i++;
	}
}

/************************************************************/
int lcd_open(struct inode *inode, struct file *filp)
{
        MOD_INC_USE_COUNT;
        return(0);
}

/************************************************************/
int lcd_release(struct inode *inode, struct file *filp)
{
        MOD_DEC_USE_COUNT;
        return(0);
}

/************************************************************/
int CheckButton(void)
{
        PDSEL |= 0x40;		/* Port D, pin 6 = IO */
        PDDIR &= 0xBF;		/* Port D, pin 6 = input */
        if (PDDATA & 0x40)
		return(0);	// not pressed
        else
        	return(1);	// pressed
}

/************************************************************/
int lcd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct lcd_display display;
	char *s;
        int err=0;

        if (_IOC_TYPE(cmd) != LCD_IOC_MAGIC) return(-EINVAL);

        if (_IOC_DIR(cmd) & _IOC_READ)
                err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
        else if (_IOC_DIR(cmd) & _IOC_WRITE)
                err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
        if (err) return(-EINVAL);
        switch(cmd)
        {
                case LCD_Line:
                        Line(arg);
                        break;

		case LCD_Clear:
			SendChar(0,0x01);
			break;

		case LCD_String:
			if (copy_from_user(&display, (struct lcd_display*)arg, sizeof(struct lcd_display)))
				return -EFAULT;
			s = display.str;
			SendString(s);
			break;	

		case LCD_Button:
			display.button = CheckButton();
			if (copy_to_user((struct lcd_display*)arg, &display, sizeof(struct lcd_display)))
				return -EFAULT;
			break;

                default:
                        return(-EINVAL);
                        break;
        }
        return(0);
}

/************************************************************/
static struct file_operations lcd_fops = {
        ioctl:          lcd_ioctl,
        open:           lcd_open,
        release:        lcd_release,
};

/************************************************************/
static int __init lcd_init(void)
{
        SET_MODULE_OWNER(&lcd_fops);
        printk(KERN_INFO "lcd: Copyright (c) Steven J. Merrifield 2005\n");
        if (register_chrdev(LCD_MAJOR,"lcd",&lcd_fops) < 0)
        {
                printk("lcd: can't get major number %d\n",LCD_MAJOR);
                return(-EBUSY);
        }

	PCSEL = 0xFF;		/* All bits = IO pins */ 
	PCDIR = 0xFF;		/* All pins = outputs */
	PCDATA = 0x00;

	EN_0;

	udelay(15000);		/* 15ms */
	RS_0;

	SendNibble(0x30);
	ClkE();
	udelay(4100);	/* Wait 4.1ms */

	SendNibble(0x30);
	ClkE();
	udelay(100);	/* Wait 100us */

	SendNibble(0x30);
	ClkE();
	udelay(WAIT);	

	SendNibble(0x20);
	ClkE();
	udelay(WAIT);

	SendChar(0,0x28);
	SendChar(0,0x0C);
	SendChar(0,0x06);
	SendChar(0,0x01);

        return 0 ;
}
    
/************************************************************/
static void __exit lcd_exit(void)
{
	unregister_chrdev(LCD_MAJOR,"lcd");
}

/************************************************************/
module_init(lcd_init);
module_exit(lcd_exit);

MODULE_AUTHOR("Steven J. Merrifield");
MODULE_DESCRIPTION("44780 LCD interface to 68EZ328");
MODULE_LICENSE("GPL");

