CFAH1602Y-YYH-ET Linux Kernel Module

ayergo

New member
Hello all,

wrote a kernel module for the CFAH1602Y-YYH-ET to allow you to just write to the device file and have it work. Can also seek to a location or use IOCTL to send raw commands. Likely have to change some things for your hardware but hopefully this code is a good starting point at least for someone out there.
The Module:
Code:
/********************************************************
***
*** JK Microsystems   http://www.jkmicro.com
*** CFAH1602Y-YYH-ET LCD Driver
*** Adam Yergovich 
*** 09-03-2008
***
*** This module will allow you to access the LCD from a 
*** device file by writing to it.  It only has write, 
*** seek, and IOCTL implemented.  It will automatically
*** go to the next line when you just write to it, and 
*** will wrap back around to the beginning after
*** reaching the last character.  Also note that if you
*** want to seek to an address that the first line is
*** address 0-15, and the second line 40 through 55.
*** If you change the display to shift you'll likely 
*** need to change this implementation.
***
*** This module was written for a custom ARM board I
*** designed (JK Microsystems OmniFlash-EP) but it 
*** could be adapted to anything fairly easily.  You
*** would have to change at least the setA, tickE, 
*** clearA, and setup functions to match your hardware
***
*** After you insert the module, don't forget to create
*** the device file as instructed by the printk.  From
*** there you can simply cat to the device or seek to
*** the location, or use IOCTL to access it.
***
*** Connection Diagram
***
*** LCD		|	IO
*** -----------------------------
*** 1 (GND)	|	GND
*** 2 (VCC)	|	+5V
*** 3 (Con)	|	Between 2K pull down and 10K pull up
*** 4 (RS)	|	A.6 J1.8
*** 5 (R/W)	|	A.5 J1.6
*** 6 (E)	|	A.4 J1.4
*** 7 		|	NC
*** 8		|	NC
*** 9		|	NC
*** 10		|	NC
*** 11 (DB7)	|	A.0 J1.1
*** 12 (DB6)	|	A.1 J1.3
*** 13 (DB5)	|	A.2 J1.5
*** 14 (DB4)	|	A.3 J1.7
*** 15 (BKLT+)	|	300 Ohm pull up
*** 16 (BKLT-)	|	GND
***
*** Much of this was derived from the wonderful example 
*** by Peter Salzman found here: 
*** http://www.faqs.org/docs/kernel/index.html
***
***
********************************************************/


/* The necessary header files */
/* Standard in kernel modules */

#include <linux/kernel.h>               
#include <linux/module.h>              
#include <linux/tty.h>     
#include <linux/sched.h>
#include <asm/io.h>

/* System call stuff */

#include <asm/uaccess.h>
#include <asm/unistd.h>
#include "../LCD_test/LCD.h"

#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif

#define DRIVER_AUTHOR "Adam Yergovich www.jkmicro.com"
#define DRIVER_DESC "CFAH1602Y-YYH-ET LCD Driver"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("DRIVER_AUTHOR");
MODULE_DESCRIPTION("DRIVER_DESC");


/* Global variables we will need */

int status = 0;
void *virt_addr_dir, *virt_addr_data;
static int Major;
static int Device_Open = 0;
static struct file_operations fops;
char position = 0;   		//Position on the LCD



/********************
*** Declared in Kernel code
********************/

extern void __udelay(unsigned long n);


static int device_open(struct inode *inode, struct file *file){

   if(Device_Open) return -EBUSY;
   Device_Open++;
   //MOD_INC_USE_COUNT;
   return 0;
}

static int device_release(struct inode *inode, struct file *file){
   Device_Open--;
   //MOD_DEC_USE_COUNT;
   return 0;

}

void lcd_prep(void){
   unsigned long check;
   unsigned long temp;

   virt_addr_dir  = __ioremap(0x80840010,32,0);		//map dir register
   temp = *((unsigned long *) virt_addr_dir);
   temp |= 0x7F;   					//set 0-6 for output
   *((unsigned long *) virt_addr_dir) = temp;   

   virt_addr_data = __ioremap(0x80840000,32,0);		//map data register
   check = *((unsigned long *) virt_addr_data);
   check &= 0x80;					//clear 0-6 for output
   *((unsigned long *) virt_addr_data) = check;		

}

void setA(char mask){

   *((unsigned long *) virt_addr_data) |= mask;

}

void clearA(int mask){

   *((unsigned long *) virt_addr_data) &= mask;

}

void tickE(void){
   unsigned long temp = 0;
   temp = *((unsigned long *) virt_addr_data);
   temp |= 0x10;
   *((unsigned long *) virt_addr_data) = temp;
   __udelay(1);
   
   temp = *((unsigned long *) virt_addr_data);
   temp &= 0xEF;
   *((unsigned long *) virt_addr_data) = temp;
   __udelay(1);

}

void sendcom(char mask){

   setA(mask);
   __udelay(1);
   tickE();
   __udelay(1);
   clearA(~mask);
   __udelay(45);
}


void sendchar(char c){
  
  char low = (c & 0x0F);
  char high = c >> 4;

  high |= 0x40;
  low |= 0x40;

  sendcom(high);
  sendcom(low);

}

/*******************************
*** This is called whenever the device is written to.
*******************************/

ssize_t device_write(struct file *file, const char *buff, size_t len, loff_t *off){

  int i = 0;

  for(i=0;i<(len-1);i++){
    sendchar(buff[i]);
    position++;
    if(position >= 16 && position < 40){
      sendcom(0x0A); 			//move to next line, position 0x28 | 0x80
      sendcom(0x08); 
      position = 40;
    }
    if(position >= 56){
      sendcom(0x08); 			//move back to beginning
      sendcom(0x00);
      position = 0;
    } 
  } 
  return len;
}

/*********************************
*** Seek function.  Whence is not implemented
*********************************/

loff_t device_llseek(struct file *file, loff_t offset, int whence){
   int temp = 0;
   char low = 0;
   char high = 0;

   position = offset;
   temp = offset;
   low = (char)temp & 0xF;
   temp >>= 4;
   high = (char)temp;
   high |= 0x08;

   sendcom(high);
   sendcom(low);    

   return offset;
}

int device_clear(void){
   sendcom(0x00);
   sendcom(0x01);
   position = 0;
   __udelay(1500);
   return 0;
}

/****************************************
*** ioctl function can give you access to 
*** all of the above as well as pass general
*** commands.  This can be dangerous if you
*** send a bad code, it could break the driver.
*****************************************/

int device_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param){
   int temp = 0;
   char low = 0;
   char high = 0;


   temp = (char) ioctl_param;
   low = temp;
   high = temp >> 4;

   if(ioctl_num == IOCTL_COM){
     sendcom(high);
     sendcom(low);
   }
   else if(ioctl_num == IOCTL_WR){
     device_write(NULL, &temp,2,0);
   }
   else if(ioctl_num == IOCTL_SK){
     device_llseek(NULL,ioctl_param,0);
   }
   else if(ioctl_num == IOCTL_CL){
     device_clear();
   }
   else ;

   return 0;
}



void lcd_init(void){

    sendcom(0x02);	//function set 4-bit
    __udelay(1000);

   sendcom(0x02);
   __udelay(1000);

   sendcom(0x02);
   __udelay(1000);

   sendcom(0x02);	//2 line display, font type 5x11
   __udelay(40);

   sendcom(0x02);	
   __udelay(40);


   sendcom(0x02);
   sendcom(0x08);
   __udelay(40);

   sendcom(0x00);	//Display on, blinking cursor
   sendcom(0x0F);   
   __udelay(40);

   sendcom(0x00);	//Clear screen
   sendcom(0x01);
   __udelay(1531);

}



/* Initialize the module  */
int init_module()
{ 
   fops.llseek = device_llseek;
   fops.write = device_write;
   fops.open = device_open;
   fops.release = device_release;
   fops.ioctl = device_ioctl;

   Major = register_chrdev(0, "LCD", &fops);
   if(Major < 0) {
     printk("Registering the character device failed with %d\n", Major);
     return Major;
   }
   lcd_prep();
   __udelay(4000);
   lcd_init();
   __udelay(44);

   printk("Assigned major number %d\n", Major);
   printk("Remember to create a dev file with \n");
   printk("mknod /dev/LCD c %d 0\n", Major);

   return(0);
}




/* Cleanup.  Note there is no check to see if anyone else has messed with the functioncall. */
void cleanup_module()
{
   printk("Removing Module \n");
   unregister_chrdev(Major, "LCD");  //make sure to unregister the device

}
The Makefile:
Code:
#
# Makefile for hello.c file
#
KDIR := /OmniEP_zImage/linux-2.6.20.4
PWD := $(shell pwd)

obj-m:=LCD.o

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
	rm -rf .*.cmd *.mod.c *.o *.ko
Sample program:

Code:
#include <stdio.h>
#include <sys/types.h>
#include "LCD.h"


int main(void){
  int fd = 0;
  char c[12] = "HELLO WORLD";
  int i = 0;
  int retval = 0;

  fd = open("/dev/LCD", "O_WRONLY");
  
  for(i=0;i<11;i++){
  retval = ioctl(fd, IOCTL_WR, c[i]);
  }
  sleep(1);


  i = ioctl(fd, IOCTL_SK, 40);
  sleep(3);

  i = ioctl(fd, IOCTL_CL, 0x00);

  close(fd);

  return 0;
}
Header file:

Code:
#include <linux/ioctl.h>

#define MAJOR_NUM 253

#define IOCTL_COM _IOR(MAJOR_NUM, 0, char*)

#define IOCTL_WR _IOR(MAJOR_NUM, 1, char*)

#define IOCTL_SK _IOR(MAJOR_NUM, 2, char*)

#define IOCTL_CL _IOR(MAJOR_NUM, 3, char*)

#define DEVICE_FILE_NAME "LCD"
Looking for additional LCD resources? Check out our LCD blog for the latest developments in LCD technology.
 
Top