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:
The Makefile:
Sample program:
Header file:
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
}
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
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;
}
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.