/* lcd_module.c * Copyright 2002 Bjorn Bringert */ /* * Some parts of this driver are based on the lcd driver available at * http://lcd.sourceforge.net/, which is * copyright 1998-2001 Nils Faerber */ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #ifdef CONFIG_MODVERSIONS #include #endif #include #include #include #include #include #include #include #include "sysdep.h" #include "c_table.h" #include "lcd_module.h" #define LCD_DRIVER_NAME "lcd" #define LCD LCD_DRIVER_NAME ": " /* time to delay when writing */ #define DELAY_TIME 50 /* LCD control bits */ #define LCD_RW 0x01 #define LCD_RS 0x02 /* LCD functions */ #define LCD_CLEAR 0x01 #define LCD_HOME 0x02 #define LCD_SET_MODE 0x04 #define LCD_INC 0x02 /* with LCD_MODESET*/ #define LCD_SET_ONOFF 0x08 #define LCD_ON 0x04 /* with LCD_SET_ONOFF */ #define LCD_CURS_ON 0x02 /* with LCD_SET_ONOFF */ #define LCD_CURS_BLINK 0x01 /* with LCD_SET_ONOFF */ #define LCD_SET_SHIFT 0x10 #define LCD_DISP_SHIFT 0x08 /* with LCD_SET_SHIFT */ #define LCD_SHIFT_RIGHT 0x04 /* with LCD_SET_SHIFT */ #define LCD_SET_FUNC 0x20 #define LCD_8BIT 0x10 /* with LCD_SET_FUNC */ #define LCD_2LINES 0x08 /* with LCD_SET_FUNC */ #define LCD_LARGE_FONT 0x04 /* with LCD_SET_FUNC */ #define LCD_SET_CGRAM 0x40 #define LCD_SET_DDRAM 0x80 #define FILE_DISP(filp) ((struct display *)(filp)->private_data) #define DEFAULT_CURSOR LCD_CURS_ON #define CGRAM_SIZE 8 #define CHAR_HEIGHT 8 #define MAX_ESC_PARAMS (CHAR_HEIGHT+1) EXPORT_NO_SYMBOLS; struct display { int cols; int rows; int x; int y; int cursor_state; int esc_seen; /* 0 = no ESC, 1 = ESC seen */ int esc_first; /* first character after ESC */ int esc_curr_param; /* index into esc_params */ int esc_params[MAX_ESC_PARAMS]; char esc_op; int start_line; int in_use; /* access protected by sem */ char * buf; struct semaphore sem; struct parport * port; struct pardevice * dev; }; /* * Static variables */ static struct display * displays[MAX_DISPLAYS] = { [0 ... MAX_DISPLAYS-1] = NULL }; static int major = DEFAULT_LCD_MAJOR; static char * port[MAX_DISPLAYS] = { LCD_DEFAULT_PORT, [1 ... MAX_DISPLAYS-1] = "none" }; static int cols[MAX_DISPLAYS] = { [0 ... MAX_DISPLAYS-1] = DEFAULT_COLS }; static int rows[MAX_DISPLAYS] = { [0 ... MAX_DISPLAYS-1] = DEFAULT_ROWS }; /* * Module info and parameters */ MODULE_LICENSE ("GPL"); MODULE_DESCRIPTION ("LCD display driver"); MODULE_AUTHOR ("Bjorn Bringert "); MODULE_PARM (major, "i"); MODULE_PARM_DESC (major, "Major number for the driver"); MODULE_PARM (port, "1-" __MODULE_STRING(MAX_DISPLAYS) "l"); MODULE_PARM_DESC (port, "port=none|parportN[,none|parportM]..., ports to use"); MODULE_PARM (cols, "1-" __MODULE_STRING(MAX_DISPLAYS) "i"); MODULE_PARM_DESC (cols, "Number of columns for each display"); MODULE_PARM (rows, "1-" __MODULE_STRING(MAX_DISPLAYS) "i"); MODULE_PARM_DESC (rows, "Number of rows for each display"); /* * Prototypes */ static struct display * create_display (struct parport *, int, int); static void destroy_display (struct display *); /* * Hardware stuff */ /* * Sleep for 'n' uS * If n is less that the time between ticks, call udelay else sleep */ void sleep_delay (unsigned long n) { int d = n / (1000000 / HZ); if (!d) { udelay(n); } else { /* Yield CPU time */ unsigned long x = jiffies; while((jiffies-x) <= d) schedule(); } } static void write_command (struct display * d, char command) { DPRINTK(KERN_DEBUG LCD "write_command(%d)\n", command); parport_write_control(d->port, LCD_RS); parport_write_data(d->port, command); parport_write_control(d->port, LCD_RS | LCD_RW);/* start write */ sleep_delay(DELAY_TIME); parport_write_control(d->port, LCD_RS); /* end write */ sleep_delay(DELAY_TIME); if (command == LCD_CLEAR || command == LCD_HOME) sleep_delay(2000); } static void write_data (struct display * d, char data) { DPRINTK(KERN_DEBUG LCD "write_data(%d)\n", data); parport_write_control(d->port, 0x00); parport_write_data(d->port, c_table[(unsigned char)data]); parport_write_control(d->port, LCD_RW); /* start write */ sleep_delay(DELAY_TIME); parport_write_control(d->port, 0x00); /* end write */ sleep_delay(DELAY_TIME); } static inline int claim_port (struct display * d) { DPRINTK(KERN_DEBUG LCD "claiming %s\n", d->port->name); return parport_claim(d->dev); } static inline void release_port (struct display * d) { DPRINTK(KERN_DEBUG LCD "releasing %s\n", d->port->name); parport_release(d->dev); } static int lcd_init_hardware (struct display * d) { int result; d->dev = parport_register_device(d->port, LCD_DRIVER_NAME, NULL, NULL, NULL, 0, d); if (!d->dev) { printk(KERN_ERR LCD "parport_register_device(%s, ...) failed\n", d->port->name); return -EIO; } result = claim_port(d); if (result < 0) { printk(KERN_ERR LCD "could not claim %s\n", d->port->name); return result; } sleep_delay(15000); write_command(d, LCD_SET_FUNC | LCD_8BIT); sleep_delay(4200); write_command(d, LCD_SET_FUNC | LCD_8BIT); sleep_delay(200); write_command(d, LCD_SET_FUNC | LCD_8BIT); sleep_delay(4200); write_command(d, LCD_SET_FUNC | LCD_8BIT | LCD_2LINES); sleep_delay(240); write_command(d, LCD_SET_ONOFF); sleep_delay(240); write_command(d, LCD_SET_ONOFF | LCD_ON | DEFAULT_CURSOR); sleep_delay(1740); write_command(d, LCD_SET_MODE | LCD_INC); sleep_delay(240); release_port(d); return 0; } static void lcd_release_hardware (struct display * d) { parport_unregister_device(d->dev); } static void setup_cursor(struct display * d, int cursor_state) { write_command(d, LCD_SET_ONOFF | LCD_ON | cursor_state); } static void pos_cursor (struct display * d, int col, int line) { unsigned char offset; /* weird line offsets */ offset = 20 * (line / 2) + 64 * (line % 2); write_command(d, LCD_SET_DDRAM | (offset + col)); } static void lcd_reset (struct display * d) { write_command(d, LCD_CLEAR); } /* lines should be an array of length CHAR_HEIGHT. */ static void define_char (struct display * d, unsigned char c, unsigned char lines[]) { int i; if (c >= CGRAM_SIZE) return; write_command(d, LCD_SET_CGRAM | CHAR_HEIGHT * c); for (i = 0; i < CHAR_HEIGHT; i++) { write_data(d, lines[i]); } pos_cursor(d, d->x, d->y); } /* * Hardware independent stuff */ static int inline xy_to_index (struct display * d, int x, int y) { return d->cols*((y + d->start_line) % d->rows) + x; } static void update_display_settings(struct display * d) { setup_cursor(d, d->cursor_state); } static void update_disp (struct display * d) { int offset, x, y; for (y = 0; y < d->rows; y++) { pos_cursor(d, 0, y); offset = d->cols * ((y + d->start_line) % d->rows); for (x = 0; x < d->cols; x++) { write_data(d, d->buf[offset + x]); } } pos_cursor(d, d->x, d->y); } static void clear_esc (struct display * d) { d->esc_seen = 0; d->esc_first = '\0'; d->esc_curr_param = 0; memset(d->esc_params, 0, MAX_ESC_PARAMS * sizeof(int)); d->esc_op = '\0'; } static void lcd_init (struct display * d) { memset(d->buf, ' ', d->cols*d->rows*sizeof(char)); d->start_line = 0; d->x = 0; d->y = 0; d->cursor_state = DEFAULT_CURSOR; clear_esc(d); lcd_reset(d); } static void lcd_gotoxy (struct display * d, int x, int y) { DPRINTK(KERN_DEBUG LCD "lcd_gotoxy(%d, %d)\n", x, y); d->x = x; d->y = y; pos_cursor(d, d->x, d->y); } static void lcd_scroll (struct display * d, int dy) { DPRINTK(KERN_DEBUG LCD "lcd_scroll(%d)\n", dy); for (; dy > 0; dy--) { memset(&d->buf[d->start_line * d->cols], ' ', d->cols); /* clear row 0 */ d->start_line = (d->start_line + 1) % d->rows; /* scroll 1 line */ } update_disp(d); } static void lcd_putchar (struct display * d, char c) { d->buf[xy_to_index(d, d->x, d->y)] = c; write_data(d, c); } static void lcd_fill (struct display * d, char c, int length) { int i, start, size; start = xy_to_index(d, d->x, d->y); size = d->cols * d->rows; for (i = 0; i < length; i++) { d->buf[(start + i) % size] = c; } update_disp(d); } static void lcd_insert_chars (struct display * d, char c, int length) { int here, left, size, i; here = xy_to_index(d, d->x, d->y); left = d->cols * (d->rows - d->y) - d->x; size = d->cols * d->rows; for (i = left - (length + 1); i >= 0; i--) { d->buf[(length + here + i) % size] = d->buf[(here + i) % size]; } for (i = min(length, left) - 1; i >= 0; i--) { d->buf[(here + i) % size] = c; } update_disp(d); } static void lcd_delete_chars (struct display * d, char c, int length) { int here, copy, size, i; here = xy_to_index(d, d->x, d->y); copy = d->cols * (d->rows - d->y) - d->x - length; size = d->cols * d->rows; for (i = 0; i < copy; i++) { d->buf[(here + i) % size] = d->buf[(length + here + i) % size]; } for (i = 0; i < length; i++) { d->buf[(copy + here + i) % size] = c; } update_disp(d); } static void setpos (struct display * d, int x, int y) { int n; DPRINTK(KERN_DEBUG LCD "setpos(%d, %d)\n", x, y); if (y < 0) { lcd_scroll(d, y); y = 0; } else if (y >= d->rows) { lcd_scroll(d, y - (d->rows - 1)); y = d->rows - 1; } if (x < 0) { n = 1 - x / d->cols; x += n * d->cols; y -= n; } else if (x >= d->cols) { n = x / d->cols; x -= n * d->cols; y += n; } lcd_gotoxy(d, x, y); } static void putchar (struct display * d, char c) { lcd_putchar(d, c); d->x++; if (d->x >= d->cols) { d->x = 0; d->y++; if (d->y >= d->rows) { d->y--; lcd_scroll(d, 1); } lcd_gotoxy(d, d->x, d->y); } } /* * Escape sequence handling * For a reference on VT100 escape sequences see: * http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.3 */ static void do_esc(struct display * d) { int n, m, old_x, old_y; n = d->esc_params[0]; m = d->esc_params[1]; old_x = d->x; old_y = d->y; switch (d->esc_first) { case '\0': switch (d->esc_op) { case 'c': /* reset display */ lcd_init(d); return; case 'D': /* index */ setpos(d, d->x, d->y + 1); return; case 'E': /* next line */ setpos(d, 0, d->y + 1); return; case 'M': /* reverse index */ setpos(d, d->x, d->y - 1); return; default: /* unknown operation, do nothing */ return; } case '?': switch (d->esc_op) { case 'h': /* set mode */ switch (n) { case 25: /* cursor on */ d->cursor_state |= LCD_CURS_ON; update_display_settings(d); return; default: /* unknown setting, do nothing */ return; } case 'l': /* reset mode */ switch (n) { /* cursor off */ case 25: d->cursor_state &= ~LCD_CURS_ON; update_display_settings(d); return; default: /* unknown setting, do nothing */ return; } default: /* unknown operation, do nothing */ return; } case '[': switch (d->esc_op) { case 'A': /* move up n lines */ n = max(n, 1); setpos(d, d->x, d->y - n); return; case 'B': /* move down n lines */ n = max(n, 1); setpos(d, d->x, d->y + n); return; case 'C': /* move right n characters */ n = max(n, 1); setpos(d, d->x + n, d->y); return; case 'D': /* move left n characters */ n = max(n, 1); setpos(d, d->x - n, d->y); return; case 'f': /* move to pos n m */ case 'H': /* H and f are equivalent */ n = max(n, 0); m = max(m, 0); setpos(d, n, m); return; case 'J': /* clear screen */ n = max(n, 0); switch (n) { case 0: /* (default) from cursor */ lcd_fill(d, ' ', d->cols * (d->rows - d->y) - d->x); break; case 1: /* to cursor */ lcd_gotoxy(d, 0, 0); lcd_fill(d, ' ', d->cols * old_y + old_x); break; case 2: /* entire screen, and home cursor */ lcd_gotoxy(d, 0, 0); lcd_fill(d, ' ', d->cols * d->rows); old_x = old_y = 0; break; default: /* unknown, do nothing */ return; } lcd_gotoxy(d, old_x, old_y); return; case 'K': /* clear line */ n = max(n, 0); switch (n) { case 0: /* (default) from cursor */ lcd_fill(d, ' ', d->cols - d->x); break; case 1: /* to cursor */ lcd_gotoxy(d, 0, d->y); lcd_fill(d, ' ', old_x); break; case 2: /* entire line */ lcd_gotoxy(d, 0, d->y); lcd_fill(d, ' ', d->cols); break; default: /* unknown, do nothing */ return; } lcd_gotoxy(d, old_x, old_y); return; case 'L': /* insert n lines */ n = max(n, 1); lcd_insert_chars(d, ' ', n * d->cols); return; case 'M': /* delete n lines */ n = max(n, 1); lcd_delete_chars(d, ' ', n * d->cols); return; case 'P': /* delete n chars */ n = max(n, 1); lcd_delete_chars(d, ' ', n); return; case 'R': /* define character */ { int i; unsigned char lines[CHAR_HEIGHT]; for (i = 0; i < CHAR_HEIGHT; i++) { lines[i] = (unsigned char)d->esc_params[i+1]; } define_char(d, (unsigned char)n, lines); } return; case '@': /* insert n chars */ n = max(n, 1); lcd_insert_chars(d, ' ', n); return; default: /* unknown operation, ignore */ return; } default: /* unknown initial character, do nothing */ return; } } static void parse_esc (struct display * d, char c) { if (c >= '0' && c <= '9') { d->esc_params[d->esc_curr_param] = 10 * d->esc_params[d->esc_curr_param] + (c - '0'); } else if (c == ';') { if (d->esc_curr_param < MAX_ESC_PARAMS-1) { /* ignore extra parameters if too many */ d->esc_curr_param++; d->esc_params[d->esc_curr_param] = 0; } } else { d->esc_op = c; do_esc(d); clear_esc(d); } } static void display_write_char (struct display * d, char c) { if (!d->esc_seen) { switch (c) { case '\010': /* BS - backspace */ setpos(d, d->x-1, d->y); putchar(d, ' '); setpos(d, d->x-1, d->y); return; case '\011': /* HT - horizontal tab */ setpos(d, d->x + TAB_SIZE - (d->x % TAB_SIZE), d->y); return; case '\012': /* LF - newline */ case '\013': /* VT - vertical tab */ case '\014': /* FF - form feed */ setpos(d, 0, d->y+1); return; case '\015': /* CR - to beginning of line */ setpos(d, 0, d->y); return; case '\033': /* ESC */ d->esc_seen = 1; return; default: putchar(d, c); return; } } else { switch (c) { case '?': case '[': d->esc_first = c; return; default: parse_esc(d, c); return; } } } /* * Device setup and cleanup */ /* allocates a display structure, registers and initializes the hardware */ static struct display * create_display (struct parport * p, int cols, int rows) { char * buf; struct display * d; int buf_size, result; buf_size = cols*rows*sizeof(char); buf = (char *)kmalloc(buf_size, GFP_KERNEL); if (!buf) { printk(KERN_ERR LCD "Could not allocate buffer for display on %s\n", p->name); return NULL; } d = (struct display *)kmalloc(sizeof(struct display), GFP_KERNEL); if (!d) { printk(KERN_ERR LCD "Could not allocate structure for display on %s\n", p->name); kfree(buf); return NULL; } d->cols = cols; d->rows = rows; d->in_use = 0; d->buf = buf; sema_init(&d->sem, 1); d->port = p; result = lcd_init_hardware(d); if (result < 0) { kfree(buf); kfree(d); return NULL; } result = claim_port(d); if (result < 0) { printk(KERN_ERR LCD "Could not claim %s\n", d->port->name); kfree(buf); kfree(d); return NULL; } lcd_init(d); release_port(d); return d; } /* frees a display structure and unregisters the hardware */ static void destroy_display (struct display * d) { printk(KERN_INFO LCD "releasing display connected to %s\n", d->port->name); lcd_release_hardware(d); kfree(d->buf); kfree(d); } static int init_displays (void) { struct parport * p; int i, displays_set_up = 0; for (p = parport_enumerate(); p != NULL; p = p->next) { for (i = 0; i < MAX_DISPLAYS; i++) { if (strcmp(port[i], "none") == 0) { /* do nothing */ } else if (strcmp(port[i], p->name) == 0) { displays[i] = create_display(p, cols[i], rows[i]); if (displays[i] == NULL) { printk(KERN_ERR LCD "Could not initialize display %i on %s\n", i, p->name); return -EIO; } printk(KERN_INFO LCD "display %d (%d x %d) set up on %s\n", i, displays[i]->cols, displays[i]->rows, p->name); displays_set_up++; } } } return displays_set_up; } static void release_displays (void) { int i; for (i = 0; i < MAX_DISPLAYS; i++) { if (displays[i] != NULL) { destroy_display(displays[i]); displays[i] = NULL; } } } /* * Character device stuff */ static int lcd_disp_open (struct inode * inode, struct file * filp) { struct display * d; int num; num = MINOR(inode->i_rdev); DPRINTK(KERN_DEBUG LCD "open minor: %d\n", num); if (num >= MAX_DISPLAYS) { printk(KERN_ERR LCD "only %d displays supported, tried to open display %d\n", MAX_DISPLAYS, num); return -ENODEV; } d = displays[num]; if (d == NULL) { printk(KERN_ERR LCD "display %d not set up\n", num); return -ENODEV; } filp->private_data = d; /* do this before maybe sleeping on the semaphore */ MOD_INC_USE_COUNT; if (down_interruptible(&d->sem)) { MOD_DEC_USE_COUNT; return -ERESTARTSYS; } if (d->in_use) { up(&d->sem); MOD_DEC_USE_COUNT; printk(KERN_INFO LCD "display already in use\n"); return -EBUSY; } d->in_use = 1; up(&d->sem); DPRINTK(KERN_DEBUG LCD "display opened\n"); return 0; } static int lcd_disp_release (struct inode * inode, struct file * filp) { struct display * d = FILE_DISP(filp); if (down_interruptible(&d->sem)) return -ERESTARTSYS; d->in_use = 0; up(&d->sem); MOD_DEC_USE_COUNT; DPRINTK(KERN_DEBUG LCD "display closed\n"); return 0; } /* write() for display devices */ static ssize_t lcd_disp_write (struct file * filp, const char * buf, size_t length, loff_t *offp) { struct display * d = FILE_DISP(filp); char c; int i, result; result = claim_port(d); if (result < 0) { printk(KERN_ERR LCD "Could not claim %s\n", d->port->name); return result; } for (i = 0; i < length; i++) { get_user(c, buf + i); display_write_char(d, c); } release_port(d); return length; } struct file_operations lcd_disp_fops = { open: lcd_disp_open, release: lcd_disp_release, write: lcd_disp_write, }; /* * Module init and cleanup */ #define ENFORCE_ARR_VALUE(arr,i,op,val) \ if ((arr)[(i)] op (val)) { \ printk(KERN_INFO LCD #arr "[%d] = %d " #op \ " %d, set to %d\n", (i), (arr)[(i)], (val), (val)); \ (arr)[(i)] = (val); \ } void validate_parameters(void) { int i; for (i = 0; i < MAX_DISPLAYS; i++) { ENFORCE_ARR_VALUE(cols, i, <, MIN_COLS); ENFORCE_ARR_VALUE(cols, i, >, MAX_COLS); ENFORCE_ARR_VALUE(rows, i, <, MIN_ROWS); ENFORCE_ARR_VALUE(rows, i, >, MAX_ROWS); } } int init_module(void) { int result; printk(KERN_INFO LCD "driver loading...\n"); SET_MODULE_OWNER(&lcd_disp_fops); validate_parameters(); result = register_chrdev(major, LCD_DRIVER_NAME, &lcd_disp_fops); if (result < 0) { printk(KERN_ERR LCD "could not register major number %d\n", major); return result; } if (major == 0) { major = result; /* dynamic major */ } result = init_displays(); if (result < 0) { printk(KERN_ERR LCD "could not initialize displays\n"); unregister_chrdev(major, LCD_DRIVER_NAME); return result; } else if (result == 0) { printk(KERN_ERR LCD "no displays found\n"); return -ENODEV; } printk(KERN_INFO LCD "driver loaded with major number %d\n", major); return 0; } void cleanup_module(void) { int result; release_displays(); result = unregister_chrdev(major, LCD_DRIVER_NAME); if (result < 0) { printk(KERN_ERR LCD "could not unregister major number %d\n", major); } printk(KERN_INFO LCD "driver unloaded\n"); }