Index: linux-2.6.30.9/arch/arm/Kconfig
===================================================================
--- linux-2.6.30.9.orig/arch/arm/Kconfig	2009-10-05 17:38:08.000000000 +0200
+++ linux-2.6.30.9/arch/arm/Kconfig	2009-11-24 02:01:42.000000000 +0100
@@ -1407,6 +1407,8 @@
 
 source "drivers/accessibility/Kconfig"
 
+source "drivers/lcd-linux/Kconfig"
+
 source "drivers/leds/Kconfig"
 
 source "drivers/rtc/Kconfig"
Index: linux-2.6.30.9/drivers/Makefile
===================================================================
--- linux-2.6.30.9.orig/drivers/Makefile	2009-10-05 17:38:08.000000000 +0200
+++ linux-2.6.30.9/drivers/Makefile	2009-11-24 02:01:42.000000000 +0100
@@ -106,4 +106,5 @@
 obj-$(CONFIG_SSB)		+= ssb/
 obj-$(CONFIG_VIRTIO)		+= virtio/
 obj-$(CONFIG_STAGING)		+= staging/
+obj-$(CONFIG_LCD_LINUX)		+= lcd-linux/
 obj-y				+= platform/
Index: linux-2.6.30.9/drivers/lcd-linux/Config.in
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/Config.in	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,8 @@
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+   mainmenu_option next_comment
+   comment 'LCD support'
+
+   tristate 'LCD-Linux layer' CONFIG_LCD_LINUX
+   dep_tristate '  HD44780 controller' CONFIG_LCD_HD44780 $CONFIG_LCD_LINUX
+   endmenu
+fi
Index: linux-2.6.30.9/drivers/lcd-linux/Kconfig
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/Kconfig	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,33 @@
+menu "LCD-Linux support"
+	depends on EXPERIMENTAL
+
+config LCD_LINUX
+	tristate "LCD-Linux layer"
+	default m
+	help
+	  LCD-Linux provides an easy way to drive LCD displays under
+	  Linux by creating a character which can be read or written.
+	  It features complete VT102 emulation and recognizes
+	  many escape sequences. If you want to use it you must also
+	  choose an appropriate driver, otherwise it will not be
+	  very useful. For more information see
+	  http://lcd-linux.sourceforge.net/
+
+	  To compile LCD-Linux as a module, choose M here:
+	  the module will be called lcd-linux.
+
+config LCD_HD44780
+	tristate "HD44780 controller"
+	depends on LCD_LINUX && MACH_SIM_ONE
+	default m
+	help
+	  This is a LCD-Linux driver for LCD displays based on the
+	  Hitachi HD44780 (and compatible) controllers connected
+	  to the SimOne LCD port.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called hd44780.
+
+	  If unsure, say N.
+
+endmenu
Index: linux-2.6.30.9/drivers/lcd-linux/Makefile
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/Makefile	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,8 @@
+#
+#
+# Standard Makefile to statically compile LCD-Linux into the kernel
+# Linux 2.6
+
+obj-$(CONFIG_LCD_LINUX)		+= lcd-linux.o
+obj-$(CONFIG_LCD_HD44780)	+= hd44780.o
+
Index: linux-2.6.30.9/drivers/lcd-linux/cgram/default.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/cgram/default.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,37 @@
+/* default.h
+ *
+ *
+ *
+ * Default user defined characters for lcdmod.
+ *
+ * Copyright (C) by Michael McLellan (mikey@cs.auckland.ac.nz)
+ *
+ *  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
+ *
+ *
+ */
+
+static void init_charmap(void)
+{
+}
+
+static unsigned char cg0[] = { 0x1f, 0x1f, 0x11, 0x0f, 0x11, 0x1e, 0x01, 0x1f };
+static unsigned char cg1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f };
+static unsigned char cg2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f };
+static unsigned char cg3[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f };
+static unsigned char cg4[] = { 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f };
+static unsigned char cg5[] = { 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f };
+static unsigned char cg6[] = { 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f };
+static unsigned char cg7[] = { 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f };
Index: linux-2.6.30.9/drivers/lcd-linux/charmap.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/charmap.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,79 @@
+/* charmap.h
+ *
+ *
+ *
+ * Character mapping for HD44780 devices by Mark Haemmerling <mail@markh.de>.
+ *
+ * Translates ISO 8859-1 to HD44780 charset.
+ * HD44780 charset reference: http://markh.de/hd44780-charset.png
+ *
+ * Initial table taken from lcd.o Linux kernel driver by
+ * Nils Faerber <nilsf@users.sourceforge.net>. Thanks!
+ *
+ * This file is released under the GNU General Public License. Refer to the
+ * COPYING file distributed with this package.
+ *
+ * Following translations are being performed:
+ * - maps umlaut accent characters to the corresponding umlaut characters
+ * - maps other accent characters to the characters without accents
+ * - maps beta (=ringel-S), micro and Yen
+ *
+ * Alternative mappings:
+ * - #112 ("p") -> #240 (large "p"), orig. mapped -> #112
+ * - #113 ("q") -> #241 (large "q"), orig. mapped -> #113
+ *
+ * HD44780 misses backslash
+ *
+ */
+
+static unsigned char charmap[] = {
+
+/* 0 - 31 */
+  0,   1,   2,   3,   4,   5,   6,   7,
+  8,   9,  10,  11,  12,  13,  14,  15,
+ 16,  17,  18,  19,  20,  21,  22,  23,
+ 24,  25,  26,  27,  28,  29,  30,  31,
+
+/* 32 - 63 */
+ 32,  33,  34,  35,  36,  37,  38,  39,
+ 40,  41,  42,  43,  44,  45,  46,  47,
+ 48,  49,  50,  51,  52,  53,  54,  55,
+ 56,  57,  58,  59,  60,  61,  62,  63,
+
+/* 64 - 95 */
+ 64,  65,  66,  67,  68,  69,  70,  71,
+ 72,  73,  74,  75,  76,  77,  78,  79,
+ 80,  81,  82,  83,  84,  85,  86,  87,
+ 88,  89,  90,  91,  47,  93,  94,  95,
+
+/* 96 - 127 */
+ 96,  97,  98,  99, 100, 101, 102, 103,
+104, 105, 106, 107, 108, 109, 110, 111,
+112, 113, 114, 115, 116, 117, 118, 119,
+120, 121, 122, 123, 124, 125, 126, 127,
+
+/* 128 - 159 */
+128, 129, 130, 131, 132, 133, 134, 135,
+136, 137, 138, 139, 140, 141, 142, 143,
+144, 145, 146, 147, 148, 149, 150, 151,
+152, 153, 154, 155, 156, 157, 158, 159,
+
+/* 160 - 191 */
+160,  33, 236, 237, 164,  92, 124, 167,
+ 34, 169, 170, 171, 172, 173, 174, 175,
+223, 177, 178, 179,  39, 249, 247, 165,
+ 44, 185, 186, 187, 188, 189, 190,  63,
+
+/* 192 - 223 */
+ 65,  65,  65,  65, 225,  65,  65,  67,
+ 69,  69,  69,  69,  73,  73,  73,  73,
+ 68,  78,  79,  79,  79,  79, 239, 120,
+ 48,  85,  85,  85, 245,  89, 240, 226,
+
+/* 224 - 255 */
+ 97,  97,  97,  97, 225,  97,  97,  99,
+101, 101, 101, 101, 105, 105, 105, 105,
+111, 110, 111, 111, 111, 111, 239, 253,
+ 48, 117, 117, 117, 245, 121, 240, 255
+
+};
Index: linux-2.6.30.9/drivers/lcd-linux/commands.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/commands.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,77 @@
+/* commands.h
+ *
+ *
+ *
+ * LCD-Linux:
+ * Driver for HD44780 compatible displays connected to the parallel port.
+ *
+ * HD44780 commands.
+ *
+ * Copyright (C) 2004 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ *
+ *  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
+ *
+ */
+
+#ifndef HD44780_COMMANDS_H
+#define HD44780_COMMANDS_H
+
+/*** HD44780 Command Set ***/
+
+/* Clear Display*/
+#define CLR_DISP	0x01	/* Clear entire display; cursor at row 0, column 0 */
+
+/* Return Home */
+#define	RET_HOME	0x02	/* Cursor at row 0, column 0; display content doesn't change */
+
+/* Entry Mode Set */
+#define ENTRY_MODE_SET	0x04
+#define DISP_SHIFT_ON	(ENTRY_MODE_SET | 0x01)		/* Shift display, not cursor after data write */
+#define DISP_SHIFT_OFF	(ENTRY_MODE_SET | 0x00)		/* Shift cursor, not display after data write */
+#define CURS_INC	(ENTRY_MODE_SET | 0x02)		/* Shift on the right after data read/write */
+#define CURS_DEC	(ENTRY_MODE_SET | 0x00)		/* Shift on the left after data read/write */
+
+/* Display on/off Control */
+#define DISP_ONOFF_CNTR	0x08
+#define BLINK_ON	(DISP_ONOFF_CNTR | 0x01)	/* Cursor blinking on */
+#define BLINK_OFF	(DISP_ONOFF_CNTR | 0x00)	/* Cursor blinking off */
+#define CURS_ON		(DISP_ONOFF_CNTR | 0x02)	/* Display Cursor */
+#define CURS_OFF	(DISP_ONOFF_CNTR | 0x00)	/* Hide Cursor */
+#define DISP_ON		(DISP_ONOFF_CNTR | 0x04)	/* Turn on display updating */
+#define DISP_OFF	(DISP_ONOFF_CNTR | 0x00)	/* Freeze display content */
+
+/* Cursor or Display Shift */
+#define CURS_DISP_SHIFT	0x10
+#define SHIFT_RIGHT	(CURS_DISP_SHIFT | 0x04)	/* Shift on the right */
+#define SHIFT_LEFT	(CURS_DISP_SHIFT | 0x00)	/* Shift on the left */
+#define SHIFT_DISP	(CURS_DISP_SHIFT | 0x08)	/* Shift display */
+#define SHIFT_CURS	(CURS_DISP_SHIFT | 0x00)	/* Shift cursor */
+
+/* Function Set */
+#define FUNC_SET	0x20
+#define FONT_5X10	(FUNC_SET | 0x04)	/* Select 5x10 dots font */
+#define FONT_5X8	(FUNC_SET | 0x00)	/* Select 5x8 dots font */
+#define DISP_2_LINES	(FUNC_SET | 0x08)	/* Select 2 lines display (only 5x8 font allowed) */
+#define DISP_1_LINE	(FUNC_SET | 0x00)	/* Select 1 line display */
+#define BUS_8_BITS	(FUNC_SET | 0x10)	/* Set 8 data bits */
+#define BUS_4_BITS	(FUNC_SET | 0x00)	/* Set 4 data bits */
+
+/* Set CGRAM Address */
+#define CGRAM_IO	0x40	/* Base CGRAM address */
+
+/* Set DDRAM Address */
+#define DDRAM_IO	0x80	/* Base DDRAM address */
+
+#endif /* commands included */
Index: linux-2.6.30.9/drivers/lcd-linux/config.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/config.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,73 @@
+/* config.h
+ *
+ *
+ *
+ * Configure file for LCD-Linux. Here you must specify your hardware setup and
+ * timings constants. The default values will probably be right for you.
+ *
+ * Copyright (C) 2005 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ *
+ *  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
+ *
+ *
+ */
+
+/* Setup the default user defined characters in CGRAM */
+#include "cgram/default.h"
+
+/* Don't modify the default timing constants
+ * unless you know what you are doing.
+ */
+
+/* Execution times (in microseconds) */
+#define T_READ			60	/* Read execution time (min 43 us) */
+#define T_WRITE			60	/* Write execution time (min 43 us) */
+#define T_BF			2	/* Busy flag polling time (min 1 us) */
+
+/* Timings in nanoseconds */
+#define T_AS			200	/* Address set-up time (min 140 ns) */
+#define T_EH			500	/* Enable high time (min 450 ns) */
+#define T_EL			600	/* Enable low time (min 500 ns) */
+
+/* Various constants */
+#define DFLT_NUM_CNTR		1	/* Default number of controllers the display has */
+#define DFLT_CNTR_ROWS		2	/* Default number of rows per controller */
+#define DFLT_CNTR_COLS		16	/* Default number of columns the display has */
+#define DFLT_VS_ROWS		25	/* Default number of rows for the virtual screen */
+#define DFLT_VS_COLS		80	/* Default number of columns for the virtual screen */
+#define DFLT_TABSTOP		3	/* Default length of tabs */
+#define DFLT_FLAGS		(HD44780_CHECK_BF | HD44780_4BITS_BUS ) /* Default flags */
+
+#define MAX_CNTR_ROWS		4	/* The HD44780 supports up to 4 lines as a fake 2 lines mode */
+#define MAX_CNTR_COLS		80	/* The HD44780 supports up to 80 characters (1*80; 2*40; etc) */
+
+#define SETUP			4
+#define HIGH_NIBBLE_WRITE(x)	(((x) >> (4-SETUP)) & (0x0f << SETUP))
+#define LOW_NIBBLE_WRITE(x)	(((x) << SETUP) & (0x0f << SETUP))
+#define HIGH_NIBBLE_READ(x)	(((x) & (0x0f << SETUP)) << (4-SETUP))
+#define LOW_NIBBLE_READ(x)	(((x) & (0x0f << SETUP)) >> SETUP)
+
+
+#define SIMONE_LCD_RS           EP93XX_GPIO_LINE_A(2) /* OUT */
+#define SIMONE_LCD_RD           EP93XX_GPIO_LINE_A(3) /* OUT */
+#define SIMONE_LCD_EN           EP93XX_GPIO_LINE_B(4) /* EGPIO12 OUT */
+#define SIMONE_LCD_BCKLIGHT     EP93XX_GPIO_LINE_B(5) /* EGPIO13 OUT */
+
+#define SIMONE_LCD_DATA0        EP93XX_GPIO_LINE_A(4)
+#define SIMONE_LCD_DATA1        EP93XX_GPIO_LINE_A(5)
+#define SIMONE_LCD_DATA2        EP93XX_GPIO_LINE_A(6)
+#define SIMONE_LCD_DATA3        EP93XX_GPIO_LINE_A(7)
+
+
Index: linux-2.6.30.9/drivers/lcd-linux/hd44780.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/hd44780.c	2009-11-24 02:05:29.000000000 +0100
@@ -0,0 +1,860 @@
+/* hd44780.c
+ *
+ *
+ *
+ * LCD-Linux:
+ * Driver for HD44780 compatible displays connected to the parallel port.
+ *
+ * Copyright (C) 2005 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ * Adapted to Sim.One Hardware by Nuccio Raciti (raciti.nuccio@gmail.com)
+ *
+ *  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 <linux/autoconf.h>
+
+
+#ifdef CONFIG_PROC_FS
+#define USE_PROC
+#else
+#undef USE_PROC
+#endif
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/delay.h>
+#include <linux/fs.h>
+
+#include <asm/uaccess.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <linux/ioport.h>
+#include <asm/gpio.h>
+
+#ifdef USE_PROC
+#include <linux/proc_fs.h>
+#endif
+
+#define LCD_LINUX_MAIN
+#include <linux/hd44780.h>
+
+#include "charmap.h"
+#include "commands.h"
+#include "config.h"
+
+/** Function prototypes **/
+static void read_display(unsigned char *byte, unsigned char bitmask);
+static void write_display(unsigned char byte, unsigned char bitmask);
+
+/* Initialization */
+static int hd44780_validate_driver(void);
+static int hd44780_init_port(void);
+static int hd44780_cleanup_port(void);
+static int hd44780_init_display(void);
+static int hd44780_cleanup_display(void);
+
+/* Write */
+static void hd44780_address_mode(int);
+static void hd44780_clear_display(void);
+static void hd44780_write_char(unsigned int, unsigned short);
+static void hd44780_write_cgram_char(unsigned char, unsigned char *);
+
+/* Read */
+static void check_bf(unsigned char);
+static void hd44780_read_char(unsigned int, unsigned short *);
+static void hd44780_read_cgram_char(unsigned char, unsigned char *);
+
+/* Input handling */
+static int hd44780_handle_custom_char(unsigned int);
+static int hd44780_handle_custom_ioctl(unsigned int,unsigned long , unsigned int);
+
+/* Proc operations */
+#ifdef USE_PROC
+static void create_proc_entries(void);
+static void remove_proc_entries(void);
+#endif
+
+/* hd44780 access */
+#define ACCESS_TO_READ    0
+#define ACCESS_TO_WRITE   1
+#define ACCESS_TO_DATA    2
+
+/* hd44780_flags */
+#define _CHECK_BF	0	/* Do busy-flag checking */
+#define _4BITS_BUS	1	/* The bus is 4 bits long */
+#define _5X10_FONT	2	/* Use 5x10 font */
+#define CURSOR_BLINK	3	/* Make the cursor blinking */
+#define SHOW_CURSOR	4	/* Make the cursor visible */
+#define DISPLAY_ON	5	/* Display status: on or off */
+#define INC_ADDR	6	/* Increment address after data read/write */
+#define BACKLIGHT	7	/* Display backlight: on or off */
+#define CGRAM_STATE	9	/* Controller status bitmask (bits 9->15): DDRAM or CGRAM access */
+#define ESC_MASK	0x00ff0000
+#define PROC_MASK	0x0f000000
+
+#define SET_STATE(state, mask)	(hd44780_flags = (hd44780_flags & ~(mask)) | ((state) & (mask)))
+#define SET_ESC_STATE(state)	SET_STATE((state) << 16, ESC_MASK)
+#define SET_PROC_LEVEL(level)	SET_STATE((level) << 24, PROC_MASK)
+#define ESC_STATE		((hd44780_flags & ESC_MASK) >> 16)
+#define PROC_LEVEL		((hd44780_flags & PROC_MASK) >> 24)
+
+/* globals */
+static unsigned int disp_size;			/* Display size (rows*columns) */
+static unsigned int disp_offset[1];	/* Physical cursor position on the display */
+static unsigned long hd44780_flags;		/* Driver flags for internal use only */
+
+static struct lcd_parameters par = {
+	.name		= HD44780_STRING,
+	.minor		= HD44780_MINOR,
+	.flags		= DFLT_FLAGS,
+	.tabstop	= DFLT_TABSTOP,
+	.num_cntr	= 1,
+	.cntr_rows	= DFLT_CNTR_ROWS,
+	.cntr_cols	= DFLT_CNTR_COLS,
+	.vs_rows	= DFLT_VS_ROWS,
+	.vs_cols	= DFLT_VS_COLS,
+	.cgram_chars	= 8,
+	.cgram_bytes	= 8,
+	.cgram_char0	= 0,
+};
+/* End of globals */
+
+#ifdef MODULE
+#include <linux/device.h>
+MODULE_ALIAS_CHARDEV(LCD_MAJOR, HD44780_MINOR);
+#include <linux/kmod.h>
+
+static unsigned short flags	= DFLT_FLAGS;
+static unsigned short tabstop	= DFLT_TABSTOP;
+static unsigned short cntr_rows	= DFLT_CNTR_ROWS;
+static unsigned short cntr_cols	= DFLT_CNTR_COLS;
+static unsigned short vs_rows	= DFLT_VS_ROWS;
+static unsigned short vs_cols	= DFLT_VS_COLS;
+static unsigned short minor	= HD44780_MINOR;
+
+MODULE_DESCRIPTION("LCD SimOne driver for HD44780 compatible controllers.");
+MODULE_AUTHOR("Nuccio Raciti (raciti.nuccio@gmail.com)");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+module_param(flags,	ushort, 0444);
+module_param(cntr_rows,	ushort, 0444);
+module_param(cntr_cols,	ushort, 0444);
+module_param(vs_rows,	ushort, 0444);
+module_param(vs_cols,	ushort, 0444);
+module_param(tabstop,	ushort, 0444);
+module_param(minor,	ushort, 0444);
+
+MODULE_PARM_DESC(flags,		"Various flags (see Documentation)");
+MODULE_PARM_DESC(cntr_rows,	"Number of rows per controller on the LCD: 1, 2, 4 (default: " string(DFLT_CNTR_ROWS) ")");
+MODULE_PARM_DESC(cntr_cols,	"Number of columns on the LCD (default: " string(DFLT_CNTR_COLS) ", max: " string(MAX_CNTR_COLS) ")");
+MODULE_PARM_DESC(vs_rows,	"Number of rows of the virtual screen (default: " string(DFLT_VS_ROWS) ")");
+MODULE_PARM_DESC(vs_cols,	"Number of columns of the virtual screen (default: " string(DFLT_VS_COLS) ")");
+MODULE_PARM_DESC(tabstop,	"Tab character length (default: " string(DFLT_TABSTOP) ")");
+MODULE_PARM_DESC(minor,		"Assigned minor number (default: " string(HD44780_MINOR) ")");
+#else
+
+/*
+ * Parse boot command line
+ *
+ * hd44780=cntr_rows,cntr_cols,vs_rows,vs_cols,flags,minor,tabstop
+ */
+static int __init hd44780_boot_init(char *cmdline)
+{
+	char *str = cmdline;
+	int idx = 0;
+	unsigned short *args[] = {
+		&par.cntr_rows,
+		&par.cntr_cols,
+		&par.vs_rows,
+		&par.vs_cols,
+		&par.flags,
+		&par.num_cntr,
+		&par.minor,
+		&par.tabstop,
+	};
+
+	while (*cmdline && idx < (sizeof(args)/sizeof(unsigned short *))) {
+		switch (*str) {
+		case ',':
+			*str++ = 0;
+		case 0:
+			if (strlen(cmdline))
+				*args[idx] = simple_strtoul(cmdline, NULL, 0);
+			++idx;
+			cmdline = str;
+			break;
+		default:
+			++str;
+			break;
+		}
+	}
+
+	return (1);
+}
+
+__setup("hd44780=", hd44780_boot_init);
+#endif /* MODULE */
+
+/* Macros for iterator handling */
+static inline unsigned int iterator_inc_(unsigned int iterator, const unsigned int module)
+{
+    return ((++iterator)%module);
+}
+
+static inline unsigned int iterator_dec_(unsigned int iterator, const unsigned int module)
+{
+    return (iterator ? --iterator : module-1);
+}
+
+#define iterator_inc(iterator, module)		(iterator = iterator_inc_(iterator, module))
+#define iterator_dec(iterator, module)		(iterator = iterator_dec_(iterator, module))
+
+static inline void set_lines(unsigned char bitmask)
+{
+    gpio_set_value(SIMONE_LCD_EN, 0);  /* Disable */
+
+    if(bitmask & ACCESS_TO_WRITE ) {
+        gpio_direction_output (SIMONE_LCD_DATA0   , 0);
+        gpio_direction_output (SIMONE_LCD_DATA1   , 0);
+        gpio_direction_output (SIMONE_LCD_DATA2   , 0);
+        gpio_direction_output (SIMONE_LCD_DATA3   , 0);
+        gpio_set_value(SIMONE_LCD_RD, 0);  /* Write */
+    } else {
+
+        gpio_direction_input (SIMONE_LCD_DATA0);
+        gpio_direction_input (SIMONE_LCD_DATA1);
+        gpio_direction_input (SIMONE_LCD_DATA2);
+        gpio_direction_input (SIMONE_LCD_DATA3);
+        gpio_set_value(SIMONE_LCD_RD, 1);  /* Read */
+    }
+
+    if(bitmask & ACCESS_TO_DATA )
+        gpio_set_value(SIMONE_LCD_RS, 1);  /* Data */
+    else
+        gpio_set_value(SIMONE_LCD_RS, 0);  /* Cmds*/
+
+    if(test_bit(BACKLIGHT, &hd44780_flags))
+        gpio_set_value(SIMONE_LCD_BCKLIGHT, 1);
+    else
+        gpio_set_value(SIMONE_LCD_BCKLIGHT, 0);
+}
+
+
+/* Low level read from the display */
+static inline unsigned char __read_display(unsigned char bitmask)
+{
+    unsigned char byte;
+
+    set_lines (bitmask);
+
+    ndelay(T_AS); /* Address set-up time */
+    gpio_set_value(SIMONE_LCD_EN, 1); /* Enable */
+    ndelay(T_EH);/* Enable high time */
+
+    byte  = (gpio_get_value(SIMONE_LCD_DATA0) << 4);
+    byte |= (gpio_get_value(SIMONE_LCD_DATA1) << 5);
+    byte |= (gpio_get_value(SIMONE_LCD_DATA2) << 6);
+    byte |= (gpio_get_value(SIMONE_LCD_DATA3) << 7);
+
+    gpio_set_value(SIMONE_LCD_EN, 0); /* Disable */
+
+    ndelay(T_EL); /* Enable low time */
+
+
+   return (byte);
+
+}
+
+/* Low level write to the display */
+static inline void __write_display(unsigned char data, unsigned char bitmask)
+{
+    set_lines(bitmask);
+
+    if(data & 0x10)
+        gpio_set_value(SIMONE_LCD_DATA0, 1);
+    else
+        gpio_set_value(SIMONE_LCD_DATA0, 0);
+
+    if(data & 0x20)
+        gpio_set_value(SIMONE_LCD_DATA1, 1);
+    else
+        gpio_set_value(SIMONE_LCD_DATA1, 0);
+
+    if(data & 0x40)
+        gpio_set_value(SIMONE_LCD_DATA2, 1);
+    else
+        gpio_set_value(SIMONE_LCD_DATA2, 0);
+
+    if(data & 0x80)
+        gpio_set_value(SIMONE_LCD_DATA3, 1);
+    else
+        gpio_set_value(SIMONE_LCD_DATA3, 0);
+
+    ndelay(T_AS); /* Address set-up time */
+    gpio_set_value(SIMONE_LCD_EN, 1);
+    ndelay(T_EH); /* Enable high time */
+
+    gpio_set_value(SIMONE_LCD_EN, 0); /* Disable */
+    ndelay(T_EL);  /* Enable low time */
+}
+
+
+/* Read data from the display (4 bit bus, check busy flag) */
+static void read_display (unsigned char *byte,unsigned char bitmask)
+{
+    if(bitmask) check_bf(bitmask);
+    *byte = HIGH_NIBBLE_READ(__read_display(bitmask));
+    if(bitmask) check_bf(bitmask);
+    *byte |= LOW_NIBBLE_READ(__read_display(bitmask));
+}
+
+
+/* Output commands or data to the display (4 bit bus, check busy flag) */
+static void write_display(unsigned char byte,unsigned char bitmask)
+{
+    check_bf(bitmask);
+    __write_display(HIGH_NIBBLE_WRITE(byte),bitmask);
+     check_bf(bitmask);
+    __write_display(LOW_NIBBLE_WRITE(byte),bitmask);
+}
+
+
+/* Read Address Counter AC from the display */
+static unsigned char read_ac(unsigned char bitmask)
+{
+    unsigned char byte;
+
+    read_display(&byte, bitmask);
+
+    return (byte);
+}
+
+
+/* Do busy-flag check */
+static void  check_bf(unsigned char bitmask)
+{
+    unsigned int timeout = 20;
+    static unsigned char do_check_bf = 5;
+    gpio_set_value(SIMONE_LCD_EN, 0);  /* Disable */
+
+    gpio_direction_input (SIMONE_LCD_DATA0);
+    gpio_direction_input (SIMONE_LCD_DATA1);
+    gpio_direction_input (SIMONE_LCD_DATA2);
+    gpio_direction_input (SIMONE_LCD_DATA3);
+
+    gpio_set_value(SIMONE_LCD_RD, 1);  /* Read */
+    gpio_set_value(SIMONE_LCD_RS, 0);  /* Instru */
+
+    ndelay(T_AS); /* Address set-up time */
+    gpio_set_value(SIMONE_LCD_EN, 1); /* Enable */
+    ndelay(T_EH);/* Enable high time */
+
+    do{
+       udelay(T_BF);
+    } while (gpio_get_value(SIMONE_LCD_DATA3) && --timeout);
+
+    if (! timeout) {
+        if (! --do_check_bf) {
+            printk(KERN_NOTICE "hd44780 error: is the LCD connected?\n");
+	}
+    }
+
+    gpio_set_value(SIMONE_LCD_EN, 0); /* Disable */
+
+    ndelay(T_EL); /* Enable low time */
+}
+
+/* Send commands to the display */
+static void write_command(unsigned char command)
+{
+    write_display(command, ACCESS_TO_WRITE);
+
+    if (command <= 0x03)
+        mdelay(2);
+}
+
+static inline void set_cursor(unsigned int offset)
+{
+    unsigned int disp_number = offset/disp_size;
+    unsigned int local_offset = offset%disp_size;
+
+    if (disp_offset[disp_number] != local_offset || test_bit(CGRAM_STATE+disp_number, &hd44780_flags)) {
+        unsigned int disp_row = local_offset/par.cntr_cols;
+        unsigned int disp_column = local_offset%par.cntr_cols;
+
+        write_command(DDRAM_IO | ((disp_row%2)*0x40) | (((disp_row >= 2)*par.cntr_cols)+disp_column));
+        clear_bit(CGRAM_STATE+disp_number, &hd44780_flags);
+		disp_offset[disp_number] = local_offset;
+	}
+}
+
+/* HD44780 DDRAM addresses are consecutive only when
+ * the cursor moves on the same row of the display.
+ * Every time the row of the cursor changes we invalidate
+ * the cursor position to force hardware cursor repositioning.
+ */
+static inline void mov_cursor(unsigned int disp_number)
+{
+    if (test_bit(INC_ADDR, &hd44780_flags)) {
+        iterator_inc(disp_offset[disp_number], disp_size);
+        if (disp_offset[disp_number]%par.cntr_cols == 0)
+			disp_offset[disp_number] = disp_size;
+	} else {
+		iterator_dec(disp_offset[disp_number], disp_size);
+		if (disp_offset[disp_number]%par.cntr_cols == par.cntr_cols-1)
+			disp_offset[disp_number] = disp_size;
+    }
+}
+
+static struct lcd_driver hd44780 = {
+	.read_char		= hd44780_read_char,
+	.read_cgram_char	= hd44780_read_cgram_char,
+	.write_char		= hd44780_write_char,
+	.write_cgram_char	= hd44780_write_cgram_char,
+	.address_mode		= hd44780_address_mode,
+	.clear_display		= hd44780_clear_display,
+	.validate_driver	= hd44780_validate_driver,
+	.init_display		= hd44780_init_display,
+	.cleanup_display	= hd44780_cleanup_display,
+	.init_port		= hd44780_init_port,
+	.cleanup_port		= hd44780_cleanup_port,
+	.handle_custom_char	= hd44780_handle_custom_char,
+	.handle_custom_ioctl	= hd44780_handle_custom_ioctl,
+
+	.charmap		= charmap,
+};
+
+static void hd44780_read_char(unsigned int offset, unsigned short *data)
+{
+    unsigned int disp_number = offset/disp_size;
+    unsigned char tmp;
+
+    set_cursor(offset);
+    read_display(&tmp, ACCESS_TO_DATA);
+    *data = tmp;
+    mov_cursor(disp_number);
+}
+
+static void hd44780_read_cgram_char(unsigned char index, unsigned char *pixels)
+{
+    unsigned int i;
+
+    write_command(CGRAM_IO | (index << 3));
+    set_bit(CGRAM_STATE+0, &hd44780_flags);
+
+    for (i = 0; i < 8; ++i) {
+		read_display(pixels+i, ACCESS_TO_DATA );
+		pixels[i] &= 0x1f;
+    }
+}
+
+static void hd44780_write_char(unsigned int offset, unsigned short data)
+{
+    unsigned int disp_number = offset/disp_size;
+
+    set_cursor(offset);
+    write_display(data & 0xff, ACCESS_TO_WRITE | ACCESS_TO_DATA );
+    mov_cursor(disp_number);
+}
+
+static void hd44780_write_cgram_char(unsigned char index, unsigned char *pixels)
+{
+	unsigned int i;
+
+	/* Move address pointer to index in CGRAM */
+	write_command(CGRAM_IO | (index << 3));
+
+	set_bit(CGRAM_STATE+0, &hd44780_flags);
+
+	for (i = 0; i < 8; ++i) {
+		pixels[i] &= 0x1f;
+		write_display(pixels[i], ACCESS_TO_WRITE | ACCESS_TO_DATA );
+	}
+}
+
+/* Increment/decrement address mode after a data read/write */
+static void hd44780_address_mode(int mode)
+{
+	if (mode > 0 && ! test_bit(INC_ADDR, &hd44780_flags)) {
+		write_command(CURS_INC | DISP_SHIFT_OFF);
+		set_bit(INC_ADDR, &hd44780_flags);
+	} else if (mode < 0 && test_bit(INC_ADDR, &hd44780_flags)) {
+		write_command(CURS_DEC | DISP_SHIFT_OFF);
+		clear_bit(INC_ADDR, &hd44780_flags);
+	}
+}
+
+static void hd44780_clear_display(void)
+{
+	write_command(CLR_DISP);
+	if (! test_bit(INC_ADDR, &hd44780_flags))
+		write_command(CURS_DEC | DISP_SHIFT_OFF);
+	memset(disp_offset, 0, sizeof(disp_offset));
+}
+
+static int hd44780_validate_driver(void)
+{
+	if (par.cntr_rows != 1 && par.cntr_rows != 2 && par.cntr_rows != 4)
+		par.cntr_rows = DFLT_CNTR_ROWS;
+
+	if (par.cntr_rows != 1)
+		par.flags &= ~HD44780_5X10_FONT;
+
+
+	if (! par.cntr_cols || par.cntr_cols > MAX_CNTR_COLS/par.cntr_rows)
+		par.cntr_cols = MAX_CNTR_COLS/par.cntr_rows;
+
+	disp_size = par.cntr_rows*par.cntr_cols;
+
+	/* These parameters depend on the hardware and cannot be changed */
+	par.cgram_chars = 8;
+	par.cgram_bytes = 8;
+	par.cgram_char0 = 0;
+
+	return (0);
+}
+
+/* Send init commands to the display */
+static void write_init_command(void)
+{
+    unsigned char command = 0x30;
+
+    __write_display(HIGH_NIBBLE_WRITE(command), ACCESS_TO_WRITE);
+    mdelay(20);	/* Wait more than 4.1 ms */
+    __write_display(HIGH_NIBBLE_WRITE(command), ACCESS_TO_WRITE);
+    udelay(200);	/* Wait more than 100 us */
+    __write_display(HIGH_NIBBLE_WRITE(command), ACCESS_TO_WRITE);
+    udelay(200);	/* Wait more than 100 us */
+    command = BUS_4_BITS;
+    command |= ((par.cntr_rows == 1) ? DISP_1_LINE : DISP_2_LINES);
+    command |= (test_bit(_5X10_FONT, &hd44780_flags) ? FONT_5X10 : FONT_5X8);
+    write_command(command);
+    /* set_bit(BACKLIGHT, &hd44780_flags); */
+}
+
+static int hd44780_init_display(void)
+{
+	if (par.flags & HD44780_CHECK_BF)
+		set_bit(_CHECK_BF, &hd44780_flags);
+	else
+		clear_bit(_CHECK_BF, &hd44780_flags);
+
+	if (par.flags & HD44780_4BITS_BUS)
+		set_bit(_4BITS_BUS, &hd44780_flags);
+	else
+		clear_bit(_4BITS_BUS, &hd44780_flags);
+
+	if (par.flags & HD44780_5X10_FONT)
+		set_bit(_5X10_FONT, &hd44780_flags);
+	else
+		clear_bit(_5X10_FONT, &hd44780_flags);
+
+	write_init_command();
+
+	hd44780_clear_display();
+	hd44780_address_mode(1);
+	write_command(DISP_ON | CURS_OFF | BLINK_OFF);
+	set_bit(DISPLAY_ON, &hd44780_flags);
+	clear_bit(SHOW_CURSOR, &hd44780_flags);
+	clear_bit(CURSOR_BLINK, &hd44780_flags);
+
+	/* Set the CGRAM to default values */
+	hd44780_write_cgram_char(0, cg0);
+	hd44780_write_cgram_char(1, cg1);
+	hd44780_write_cgram_char(2, cg2);
+	hd44780_write_cgram_char(3, cg3);
+	hd44780_write_cgram_char(4, cg4);
+	hd44780_write_cgram_char(5, cg5);
+	hd44780_write_cgram_char(6, cg6);
+	hd44780_write_cgram_char(7, cg7);
+	init_charmap();
+
+	return (0);
+}
+
+static int hd44780_cleanup_display(void)
+{
+    hd44780_clear_display();
+
+    return (0);
+}
+
+static int hd44780_init_port(void)
+{
+    gpio_direction_output (SIMONE_LCD_BCKLIGHT, 0);
+    gpio_direction_output (SIMONE_LCD_RD      , 0);
+    gpio_direction_output (SIMONE_LCD_RS      , 0);
+    gpio_direction_output (SIMONE_LCD_EN      , 0);
+    gpio_set_value(SIMONE_LCD_EN, 0);           /* Disable */
+
+    return (0);
+}
+
+static int hd44780_cleanup_port(void)
+{
+
+    gpio_direction_input (SIMONE_LCD_BCKLIGHT);
+    gpio_direction_input (SIMONE_LCD_RD);
+    gpio_direction_input (SIMONE_LCD_RS);
+    gpio_direction_input (SIMONE_LCD_EN);
+    gpio_direction_input (SIMONE_LCD_DATA0);
+    gpio_direction_input (SIMONE_LCD_DATA1);
+    gpio_direction_input (SIMONE_LCD_DATA2);
+    gpio_direction_input (SIMONE_LCD_DATA3);
+    return (0);
+}
+
+static void display_attr(unsigned char input)
+{
+	unsigned char command;
+
+	switch (ESC_STATE) {
+	case 'a':	/* Turn on/off the display cursor */
+		if (input == '1')
+			set_bit(SHOW_CURSOR, &hd44780_flags);
+		else if (input == '0')
+			clear_bit(SHOW_CURSOR, &hd44780_flags);
+		break;
+	case 'b':	/* Turn on/off the display cursor blinking */
+		if (input == '1')
+			set_bit(CURSOR_BLINK, &hd44780_flags);
+		else if (input == '0')
+			clear_bit(CURSOR_BLINK, &hd44780_flags);
+		break;
+	case 'h':	/* Turn on/off the display */
+		if (input == '1')
+			set_bit(DISPLAY_ON, &hd44780_flags);
+		else if (input == '0')
+			clear_bit(DISPLAY_ON, &hd44780_flags);
+		break;
+	}
+
+        command = (test_bit(DISPLAY_ON, &hd44780_flags) ? DISP_ON : DISP_OFF);
+        command |= (test_bit(SHOW_CURSOR, &hd44780_flags) ? CURS_ON : CURS_OFF);
+	command |= (test_bit(CURSOR_BLINK, &hd44780_flags) ? BLINK_ON : BLINK_OFF);
+
+	if (ESC_STATE == 'h')
+		write_command(command);
+}
+
+static int hd44780_handle_custom_char(unsigned int _input)
+{
+	unsigned char input = _input & 0xff;
+
+	if (_input & (~0xff)) {
+		switch (ESC_STATE) {
+		case 'a':	/* Turn on/off the display cursor */
+		case 'b':	/* Turn on/off the display cursor blinking */
+		case 'h':	/* Turn on/off the the display */
+			display_attr(input);
+			return (0);
+		case 'l':	/* Turn on/off the backlight */
+			if (input == '1')
+				set_bit(BACKLIGHT, &hd44780_flags);
+			else if (input == '0')
+				clear_bit(BACKLIGHT, &hd44780_flags);
+			read_ac(ACCESS_TO_READ);
+			return (0);
+		}
+	}
+
+	switch (input) {
+	case 'a':	/* Turn on/off the display cursor */
+	case 'b':	/* Turn on/off the display cursor blinking */
+	case 'h':	/* Turn on/off the display */
+	case 'l':	/* Turn on/off the backlight */
+		SET_ESC_STATE(input);
+		return (1);
+	case 'd':	/* Shift display cursor Right */
+		write_command(SHIFT_CURS | SHIFT_RIGHT);
+		return (0);
+	case 'e':	/* Shift display cursor Left */
+		write_command(SHIFT_CURS | SHIFT_LEFT);
+		return (0);
+	case 'f':	/* Shift display Right */
+		write_command(SHIFT_DISP | SHIFT_RIGHT);
+		return (0);
+	case 'g':	/* Shift display Left */
+		write_command(SHIFT_DISP | SHIFT_LEFT);
+		return (0);
+	}
+
+	return (-1);
+}
+
+static int hd44780_handle_custom_ioctl(unsigned int num, unsigned long arg, unsigned int user_space)
+{
+	unsigned char *buffer = (unsigned char *)arg;
+
+	if (num != HD44780_READ_AC)
+		return (-ENOIOCTLCMD);
+
+	if (user_space)
+		put_user(read_ac(ACCESS_TO_READ), buffer);
+	else
+		buffer[0] = read_ac(ACCESS_TO_READ);
+
+	return (0);
+}
+
+#ifdef USE_PROC
+static int hd44780_proc_status(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+
+	/* Print display configuration */
+	temp += sprintf(temp,
+			"Interface:\t%u bits\n"
+			"Display rows:\t%d\n"
+			"Display cols:\t%d\n"
+			"Screen rows:\t%d\n"
+			"Screen cols:\t%d\n"
+			"Read:\t\t%sabled\n"
+			"Busy flag chk:\t%sabled\n"
+			"Assigned minor:\t%u\n",
+			(test_bit(_4BITS_BUS, &hd44780_flags) ? 4 : 8),
+			par.cntr_rows, par.cntr_cols,
+			par.vs_rows, par.vs_cols,
+			(hd44780.read_char ? "En" : "Dis"),
+			(test_bit(_CHECK_BF, &hd44780_flags) ? "En" : "Dis"),
+			par.minor);
+
+	return (temp-buffer);
+}
+
+static int hd44780_proc_cgram(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+	unsigned int i;
+
+	temp += sprintf(temp,	"static void init_charmap(void)\n{\n"
+				"\t/*\n"
+				"\t * charmap[char mapped to cg0] = 0;\n"
+				"\t * charmap[char mapped to cg1] = 1;\n"
+				"\t * charmap[char mapped to cg2] = 2;\n"
+				"\t * charmap[char mapped to cg3] = 3;\n"
+				"\t * charmap[char mapped to cg4] = 4;\n"
+				"\t * charmap[char mapped to cg5] = 5;\n"
+				"\t * charmap[char mapped to cg6] = 6;\n"
+				"\t * charmap[char mapped to cg7] = 7;\n"
+				"\t */\n"
+				"}\n\n");
+
+	for (i = 0; i < 8; ++i) {
+		unsigned char cgram_buffer[8];
+		unsigned int j;
+
+		temp += sprintf(temp, "static unsigned char cg%u[] = { ", i);
+		hd44780_read_cgram_char(i, cgram_buffer);
+		for (j = 0; j < 8; ++j)
+			temp += sprintf(temp, "0x%.2x%s", cgram_buffer[j], (j == 7 ? " };\n" : ", "));
+	}
+
+	return (temp-buffer);
+}
+
+static void create_proc_entries(void)
+{
+	int count = 0;
+
+	SET_PROC_LEVEL(count);
+	if (create_proc_read_entry("status", 0, hd44780.driver_proc_root, hd44780_proc_status, NULL) == NULL) {
+		printk(KERN_ERR "hd44780: cannot create /proc/lcd/%s/status\n", par.name);
+		return;
+	}
+	SET_PROC_LEVEL(++count);
+	if (hd44780.read_cgram_char) {
+		if (create_proc_read_entry("cgram.h", 0, hd44780.driver_proc_root, hd44780_proc_cgram, NULL) == NULL) {
+			printk(KERN_ERR "hd44780: cannot create /proc/lcd/%s/cgram.h\n", par.name);
+			return;
+		}
+		SET_PROC_LEVEL(++count);
+	}
+}
+
+static void remove_proc_entries(void)
+{
+	switch (PROC_LEVEL) {
+	case 2:
+		remove_proc_entry("cgram.h", hd44780.driver_proc_root);
+	case 1:
+		remove_proc_entry("status", hd44780.driver_proc_root);
+	}
+	SET_PROC_LEVEL(0);
+}
+#endif
+
+/* Initialization */
+static int __init hd44780_init_module(void)
+{
+	int ret;
+
+#ifdef MODULE
+#ifdef NOT_DEF
+	if ((ret = request_module("lcd-linux"))) {
+		if (ret != -ENOSYS) {
+			printk(KERN_ERR "hd44780: failure while loading module lcd-linux\n");
+			return (ret);
+		}
+		printk(KERN_ERR "hd44780: your kernel does not have kmod or kerneld support;\n");
+		printk(KERN_ERR "hd44780: remember to load the lcd-linux module before\n");
+		printk(KERN_ERR "hd44780: loading the hd44780 module\n");
+	}
+#endif
+	if (flags	!= DFLT_FLAGS)		par.flags	= flags;
+	if (tabstop	!= DFLT_TABSTOP)	par.tabstop	= tabstop;
+	if (cntr_rows	!= DFLT_CNTR_ROWS)	par.cntr_rows	= cntr_rows;
+	if (cntr_cols	!= DFLT_CNTR_COLS)	par.cntr_cols	= cntr_cols;
+	if (vs_rows	!= DFLT_VS_ROWS)	par.vs_rows	= vs_rows;
+	if (vs_cols	!= DFLT_VS_COLS)	par.vs_cols	= vs_cols;
+	if (minor	!= HD44780_MINOR)	par.minor	= minor;
+#endif
+
+	lcd_driver_setup(&hd44780);
+	if ((ret = lcd_register_driver(&hd44780, &par)))
+		return (ret);
+
+#ifdef USE_PROC
+	if (hd44780.driver_proc_root)
+		create_proc_entries();
+#endif
+
+	printk(KERN_INFO "HD44780 driver (LCD-Linux" HD44780_VERSION ")\n");
+	printk(KERN_INFO "Nuccio Raciti <raciti.nuccio@gmail.com>\n");
+
+
+	return (0);
+}
+
+/* Cleanup */
+
+static void __exit hd44780_cleanup_module(void)
+{
+#ifdef USE_PROC
+	if (hd44780.driver_proc_root)
+		remove_proc_entries();
+#endif
+
+	lcd_unregister_driver(&hd44780, &par);
+}
+
+module_init(hd44780_init_module)
+module_exit(hd44780_cleanup_module)
Index: linux-2.6.30.9/drivers/lcd-linux/lcd-linux.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/drivers/lcd-linux/lcd-linux.c	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,2892 @@
+/* lcd-linux.c
+ *
+ *
+ *
+ * Software layer to drive LCD displays under Linux.
+ *
+ * Copyright (C) 2005 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ *
+ *  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 <linux/version.h>
+
+#ifndef KERNEL_VERSION
+#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
+#endif
+
+#ifndef LINUX_VERSION_CODE
+#error - LINUX_VERSION_CODE undefined in 'linux/version.h'
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+#include <linux/autoconf.h>
+#else
+#include <linux/config.h>
+#endif
+#ifdef CONFIG_PROC_FS
+#define USE_PROC
+#else
+#undef USE_PROC
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+#include <linux/semaphore.h>
+#else
+#include <asm/semaphore.h>
+#endif
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+
+#include <linux/fs.h>
+
+#include <asm/uaccess.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+#include <linux/device.h>
+#endif
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/selection.h>
+#include <linux/vmalloc.h>
+
+#ifdef USE_PROC
+#include <linux/proc_fs.h>
+#endif
+
+#define LCD_LINUX_MAIN
+#include <linux/lcd-linux.h>
+
+/*** struct_flags ***/
+#define NEED_WRAP	0		/* Next char will trigger a newline */
+#define DECIM		1		/* Insert mode */
+#define DECAWM		2		/* Autowrap */
+#define DECSCNM		3		/* Inverted screen */
+#define CRLF		4		/* Follow lf, vt, ff, with a cr */
+#define INC_CURS_POS	5		/* Increment cursor position after data read/write */
+#define QUES		6		/* CSI Esc sequence contains a question mark */
+#define USER_SPACE	7		/* If set, the buffer pointed by arg in do_lcd_ioctl() is
+					 * assumed to be in user space otherwise it is in kernel space */
+#define NULL_CHARMAP	8		/* The driver doesn't provide a charmap so the
+					 * lcd-linux layer provides one*/
+#define CAN_DO_COLOR	9		/* The display is color capable */
+#define WITH_ATTR	10		/* If set, the void * buffer in do_lcd_read/write() contains
+					 * attributes and therefore is an unsigned short * otherwise it
+					 * is an unsigned char *
+					 */
+
+/*** attributes ***/
+#define I_MASK		0x03		/* Intensity (0 = low, 1 = normal, 2 = bright) */
+#define ULINE		0x04		/* Underlined text */
+#define	REVERSE		0x08		/* Reversed video text */
+#define BLINK		0x80		/* Blinking text */
+
+/*** Color attributes ***/
+#define FG_MASK		0x0f		/* Foreground color */
+#define BG_MASK		0xf0		/* Background color */
+
+/* input states */
+#define NORMAL		0x00001000	/* Normal mode */
+#define RAW		0x00002000	/* Raw mode (console emulation disabled) */
+#define SYN		0x00003000	/* Synchronous Idle mode */
+#define ESC		0x00004000	/* Escape mode */
+#define CSI		0x00005000	/* CSI escape mode */
+#define ESC_G0		0x00006000	/* G0 character set */
+#define ESC_G1		0x00007000	/* G1 character set */
+#define ESC_HASH	0x00008000	/* ESC # escape sequence */
+#define ESC_PERCENT	0x00009000	/* ESC % escape sequence */
+#define ARG		0x0000a000	/* Waiting for arguments for the lcd-linux layer */
+#define ARG_DRIVER 	0x0000b000	/* Waiting for arguments for the display driver */
+#define INPUT_MASK	0x0000f000
+#define ESC_MASK	0x00ff0000
+#define INIT_MASK	0x0f000000
+#define PROC_MASK	0xf0000000
+
+#define SET_STATE(p, state, mask)	((p)->struct_flags = ((p)->struct_flags & ~(mask)) | ((state) & (mask)))
+#define SET_INPUT_STATE(p, state)	SET_STATE(p, state, INPUT_MASK)
+#define SET_ESC_STATE(p, state)		SET_STATE(p, (state) << 16, ESC_MASK)
+#define SET_INIT_LEVEL(p, level)	SET_STATE(p, (level) << 24, INIT_MASK)
+#define SET_PROC_LEVEL(p, level)	SET_STATE(p, (level) << 28, PROC_MASK)
+#define INPUT_STATE(p)			((p)->struct_flags & INPUT_MASK)
+#define ESC_STATE(p)			(((p)->struct_flags & ESC_MASK) >> 16)
+#define INIT_LEVEL(p)			(((p)->struct_flags & INIT_MASK) >> 24)
+#define PROC_LEVEL(p)			(((p)->struct_flags & PROC_MASK) >> 28)
+
+#define NPAR	16			/* Max number of parameters in CSI escape sequence */
+#define FLIP_BUF_SIZE	(1 << 6)	/* Flip buffer size (64 bytes) */
+
+struct lcd_struct {
+	struct list_head	lcd_list;		/* Doubly linked list */
+	struct semaphore	lcd_sem;		/* Locks this structure */
+	struct lcd_driver	*driver;		/* The driver associated to this struct */
+	struct lcd_parameters	*par;			/* The parameters associated to this struct */
+	unsigned long		struct_flags;		/* Flags for internal use only */
+	unsigned int		refcount;		/* Number of references to this struct */
+
+	unsigned short		*display;		/* The display buffer */
+
+	unsigned short		*fb;			/* The virtual screen framebuffer */
+	unsigned int		fb_size;		/* Size of the framebuffer */
+	unsigned int		frame_base;		/* Offset of row 0, column 0 of a frame in fb */
+	unsigned int		frame_size;		/* Size of the frame */
+
+	unsigned int		row;			/* Current row in virtual screen */
+	unsigned int		col;			/* Current column in virtual screen */
+	unsigned int		s_offset;		/* Saved cursor position in virtual screen */
+
+	unsigned int		top;			/* Top scroll row in virtual screen */
+	unsigned int		bot;			/* Bottom scroll row in virtual screen */
+
+	int			esc_args;		/* Number of arguments for a normal escape sequence */
+	unsigned int		csi_args[NPAR];		/* CSI parameters */
+	unsigned int		index;			/* Index in csi_args and counter for cgram characters generation */
+	unsigned char		cgram_index;		/* Index of the cgram character to be created */
+	unsigned char		*cgram_buffer;		/* Buffer for cgram operations in this driver */
+
+	unsigned short		erase_char;		/* Character to be used when erasing */
+	unsigned char		attr;			/* Current attributes */
+	unsigned char		color;			/* Color for normal intensity mode */
+	unsigned char		s_color;		/* Saved color for normal intensity mode */
+	unsigned char		defcolor;		/* Default color for normal intensity mode */
+	unsigned char		ulcolor;		/* Color for underline mode */
+	unsigned char		halfcolor;		/* Color for low intensity mode */
+	unsigned char		attributes;		/* Packed attributes */
+	unsigned char		s_attributes;		/* Saved packed attributes */
+
+	unsigned char		*s_charmap;		/* Saved character map for this driver */
+	unsigned char		*flip_buf;		/* High speed flip buffer */
+};
+
+/** Function prototypes **/
+
+/* Init/Cleanup the driver */
+static int init_driver(struct lcd_struct *);
+static int cleanup_driver(struct lcd_struct *);
+
+/* Read from/Write to the driver */
+static void read_data(struct lcd_struct *, unsigned short *);
+static void read_cgram(struct lcd_struct *, unsigned char, unsigned char *);
+static void write_data(struct lcd_struct *, unsigned short);
+static void write_cgram(struct lcd_struct *, unsigned char, unsigned char *);
+
+/* Input handlers */
+static void cr(struct lcd_struct *);
+static void lf(struct lcd_struct *);
+static void control_char(struct lcd_struct *, unsigned char);
+static void handle_csi(struct lcd_struct *, unsigned char);
+static int handle_custom_esc(struct lcd_struct *, unsigned int);
+static int handle_esc(struct lcd_struct *, unsigned char);
+static void handle_input(struct lcd_struct *, unsigned short);
+
+/* Low level file operations */
+static ssize_t do_lcd_read(struct lcd_struct *, void *, size_t);
+static ssize_t do_lcd_write(struct lcd_struct *, const void *, size_t);
+static int do_lcd_open(struct lcd_struct *);
+static int do_lcd_release(struct lcd_struct *);
+static int do_lcd_ioctl(struct lcd_struct *, unsigned int, unsigned long);
+
+/* Proc functions */
+#ifdef USE_PROC
+static void create_driver_proc_entries(struct lcd_struct *);
+static void remove_driver_proc_entries(struct lcd_struct *);
+#endif
+
+/* globals */
+static unsigned int major	= LCD_MAJOR;		/* Major number for LCD-Linux device */
+static unsigned short minors	= LCD_MINORS;		/* Minor numbers allocated for LCD-Linux */
+static LIST_HEAD(lcd_drivers);				/* Registered lcd drivers */
+static struct semaphore drivers_sem;			/* Locks the lcd_drivers list */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+static struct class *lcd_linux_class;
+#endif
+#ifdef USE_PROC
+static struct proc_dir_entry *lcd_proc_root;
+#endif
+/* End of globals */
+
+#ifdef MODULE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+#include <linux/device.h>
+MODULE_ALIAS_CHARDEV_MAJOR(LCD_MAJOR);
+#endif
+MODULE_DESCRIPTION("Software layer to drive LCD displays under Linux.");
+MODULE_AUTHOR("Mattia Jona-Lasinio <mjona@users.sourceforge.net>");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+module_param(minors, ushort, 0444);
+#else
+MODULE_PARM(minors, "h");
+#endif
+MODULE_PARM_DESC(minors, "Minor numbers allocated for LCD-Linux (default: " string(LCD_MINORS) ")");
+#else
+
+/*
+ * Parse boot command line
+ *
+ * lcd=minors
+ */
+static int __init lcd_linux_boot_init(char *cmdline)
+{
+	unsigned short args;
+
+	if ((args = simple_strtoul(cmdline, NULL, 0)))
+		minors = args;
+
+	return (1);
+}
+
+__setup("lcd=", lcd_linux_boot_init);
+#endif /* MODULE */
+
+/* Macros for iterator handling */
+static inline unsigned int iterator_inc_(unsigned int iterator, const unsigned int module)
+{
+	return ((++iterator)%module);
+}
+
+static inline unsigned int iterator_dec_(unsigned int iterator, const unsigned int module)
+{
+	return (iterator ? --iterator : module-1);
+}
+
+#define iterator_inc(iterator, module)		(iterator = iterator_inc_(iterator, module))
+#define iterator_dec(iterator, module)		(iterator = iterator_dec_(iterator, module))
+
+/* Uncomment the following two lines
+ * for non-atomic set_bit and clear_bit
+#define set_bit		__set_bit
+#define clear_bit	__clear_bit
+*/
+
+/************************************
+ * Low level routines and utilities *
+ ************************************/
+/*
+ * Set whether the address counter should be incremented
+ * or decremented after a Read/Write
+ */
+static void address_mode(struct lcd_struct *p, int mode)
+{
+	struct lcd_driver *driver = p->driver;
+
+	if (mode > 0 && ! test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (driver->address_mode)
+			driver->address_mode(mode);
+		set_bit(INC_CURS_POS, &p->struct_flags);
+	} else if (mode < 0 && test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (driver->address_mode)
+			driver->address_mode(mode);
+		clear_bit(INC_CURS_POS, &p->struct_flags);
+	}
+}
+
+/* WARNING!! This function returns an int because if iterator is not
+ * within the visible area of the frame it returns -1
+ */
+static inline int vs_to_frame_(struct lcd_struct *p, unsigned int iterator)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int row = iterator/vs_cols;
+	unsigned int col = iterator%vs_cols;
+	unsigned int frame_base_row = p->frame_base/vs_cols;
+	unsigned int frame_base_col = p->frame_base%vs_cols;
+	unsigned int frame_rows = p->par->cntr_rows*p->par->num_cntr;
+	unsigned int frame_cols = p->par->cntr_cols;
+
+	if (row < frame_base_row || row >= frame_base_row+frame_rows)
+		return (-1);
+	if (col < frame_base_col || col >= frame_base_col+frame_cols)
+		return (-1);
+
+	return ((row-frame_base_row)*frame_cols+(col-frame_base_col));
+}
+
+/* Given 'iterator' in vs, returns the offset in vs corresponding to the nearest
+ * visible offset in vs, or returns 'iterator' if it is already visible.
+ */
+static unsigned int round_vs_(struct lcd_struct *p, unsigned int iterator)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int row = iterator/vs_cols;
+	unsigned int col = iterator%vs_cols;
+	unsigned int frame_base_row = p->frame_base/vs_cols;
+	unsigned int frame_base_col = p->frame_base%vs_cols;
+	unsigned int frame_rows = p->par->cntr_rows*p->par->num_cntr;
+	unsigned int frame_cols = p->par->cntr_cols;
+
+	if (row < frame_base_row)
+		row = frame_base_row;
+	else if (row >= frame_base_row+frame_rows)
+		row = frame_base_row+(frame_rows-1);
+
+	if (col < frame_base_col)
+		col = frame_base_col;
+	else if (col >= frame_base_col+frame_cols)
+		col = frame_base_col+(frame_cols-1);
+
+	return ((row*vs_cols)+col);
+}
+
+#define round_vs(p, iterator)			(iterator = round_vs_(p, iterator))
+
+/*
+ * Sync the frame area starting at offset s, ending at offset e with fb content.
+ */
+static void redraw_screen(struct lcd_struct *p, unsigned int s, unsigned int e)
+{
+	unsigned int len;
+	unsigned int row = p->row, col = p->col;
+	unsigned int inc_set = test_bit(INC_CURS_POS, &p->struct_flags);
+	unsigned int frame_cols = p->par->cntr_cols;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned long flags;
+
+	if (s >= p->fb_size || e >= p->fb_size || e < s || e < p->frame_base)
+		return;
+
+	round_vs(p, s);
+	round_vs(p, e);
+
+	len = 1+e-s;
+
+	if (! inc_set)
+		s = e;
+
+	p->row = s/vs_cols;
+	p->col = s%vs_cols;
+
+	flags = p->struct_flags;
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	clear_bit(DECIM, &p->struct_flags);
+	set_bit(DECAWM, &p->struct_flags);
+	SET_INPUT_STATE(p, RAW);
+	if (inc_set)
+		while (len--)
+			if (vs_to_frame_(p, (p->row*vs_cols)+p->col) < 0) {
+				s += vs_cols-frame_cols;
+				len -= vs_cols-frame_cols-1;
+				p->row = s/vs_cols;
+				p->col = s%vs_cols;
+			} else {
+				write_data(p, p->fb[s++]);
+				if (test_bit(NEED_WRAP, &p->struct_flags)) {
+					cr(p);
+					lf(p);
+				}
+			}
+	else
+		while (len--)
+			if (vs_to_frame_(p, (p->row*vs_cols)+p->col) < 0) {
+				s -= vs_cols-frame_cols;
+				len -= vs_cols-frame_cols-1;
+				p->row = s/vs_cols;
+				p->col = s%vs_cols;
+			} else {
+				write_data(p, p->fb[s--]);
+				if (test_bit(NEED_WRAP, &p->struct_flags)) {
+					cr(p);
+					lf(p);
+				}
+			}
+	p->struct_flags = flags;
+
+	p->row = row; p->col = col;
+}
+
+static int make_cursor_visible(struct lcd_struct *p)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int frame_base, frame_base_row, frame_base_col;
+	unsigned int frame_rows = p->par->cntr_rows*p->par->num_cntr;
+	unsigned int frame_cols = p->par->cntr_cols;
+	unsigned int tmp = frame_cols/2;
+
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+	/* cursor always on the lowest row of the display */
+		frame_base_row = 0;
+		frame_base_col = 0;
+		if (p->row >= frame_rows)
+			frame_base_row = p->row-(frame_rows-1);
+		if (p->col >= frame_cols) {
+			frame_base_col = p->col-(frame_cols-1);
+			if (tmp) {
+				tmp = (tmp-(frame_base_col%tmp))%tmp;
+				if (frame_base_col+tmp <= vs_cols-frame_cols)
+					frame_base_col += tmp;
+			}
+		}
+	} else {
+	/* cursor always on the uppermost row of the display */
+		frame_base_row = vs_rows-frame_rows;
+		frame_base_col = vs_cols-frame_cols;
+		if (p->row < vs_rows-frame_rows)
+			frame_base_row = p->row;
+		if (p->col < vs_cols-frame_cols) {
+			frame_base_col = p->col;
+			if (tmp) {
+				tmp = frame_base_col%tmp;
+				if (frame_base_col >= tmp)
+					frame_base_col -= tmp;
+			}
+		}
+	}
+
+	frame_base = p->frame_base;
+	p->frame_base = (frame_base_row*vs_cols)+frame_base_col;
+
+	return (frame_base != p->frame_base);
+}
+
+/*
+ * Move the visible screen area at user's wish
+ */
+static void browse_screen(struct lcd_struct *p, unsigned char dir)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int frame_base_row = p->frame_base/vs_cols;
+	unsigned int frame_base_col = p->frame_base%vs_cols;
+	unsigned int frame_rows = p->par->cntr_rows*p->par->num_cntr;
+	unsigned int frame_cols = p->par->cntr_cols;
+
+	switch (dir) {
+	case '1':	/* Up */
+		if (! frame_base_row)
+			return;
+		--frame_base_row;
+		break;
+	case '2':	/* Down */
+		if (frame_base_row >= vs_rows-frame_rows)
+			return;
+		++frame_base_row;
+		break;
+	case '3':	/* Left */
+		if (! frame_base_col)
+			return;
+		--frame_base_col;
+		break;
+	case '4':	/* Right */
+		if (frame_base_col >= vs_cols-frame_cols)
+			return;
+		++frame_base_col;
+		break;
+	default:
+		return;
+	}
+
+	p->frame_base = (frame_base_row*vs_cols)+frame_base_col;
+	redraw_screen(p, 0, p->fb_size-1);
+}
+
+static inline void __memset_short(unsigned short *buf, unsigned short c, unsigned int len)
+{
+	while (len--)
+		*buf++ = c;
+}
+
+/*
+ * A memset implementation writing to LCD instead of memory locations.
+ */
+static void lcd_memset(struct lcd_struct *p, unsigned int d, unsigned short c, unsigned int len)
+{
+	unsigned int inc_set = test_bit(INC_CURS_POS, &p->struct_flags);
+
+	if (! len || d >= p->fb_size)
+		return;
+
+	if (inc_set && d+len > p->fb_size)
+		len = p->fb_size-d;
+	else if (! inc_set && len > d+1)
+		len = d+1;
+
+	if (! inc_set)
+		d -= len-1;
+	__memset_short(p->fb+d, c, len);
+
+	if (make_cursor_visible(p))
+		redraw_screen(p, 0, p->fb_size-1);
+	else
+		redraw_screen(p, d, d+(len-1));
+}
+
+static inline void __memcpy_short(unsigned short *d, unsigned short *s, unsigned int len, int dir)
+{
+	if (dir > 0)
+		while (len--)
+			*d++ = *s++;
+	else
+		while (len--)
+			*d-- = *s--;
+}
+
+/*
+ * A memmove implementation writing to LCD instead of memory locations.
+ * Copy is done in a non destructive way. Display regions may overlap.
+ */
+static void lcd_memmove(struct lcd_struct *p, unsigned int d, unsigned int s, unsigned int len)
+{
+	if (! len || d == s || d >= p->fb_size || s >= p->fb_size)
+		return;
+
+	if (d < s) {
+		if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+			if (s+len > p->fb_size)
+				len = p->fb_size-s;
+		} else {
+			if (len > d+1)
+				len = d+1;
+			d -= len-1;
+			s -= len-1;
+		}
+		__memcpy_short(p->fb+d, p->fb+s, len, 1);
+		if (make_cursor_visible(p))
+			redraw_screen(p, 0, p->fb_size-1);
+		else
+			redraw_screen(p, d, d+(len-1));
+	} else {
+		if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+			if (d+len > p->fb_size)
+				len = p->fb_size-d;
+			d += len-1;
+			s += len-1;
+		} else {
+			if (len > s+1)
+				len = s+1;
+		}
+		__memcpy_short(p->fb+d, p->fb+s, len, -1);
+		if (make_cursor_visible(p))
+			redraw_screen(p, 0, p->fb_size-1);
+		else
+			redraw_screen(p, d-(len-1), d);
+	}
+}
+
+static void scrup(struct lcd_struct *p, unsigned int t, unsigned int b, unsigned int nr)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int d, s;
+
+	if (t+nr >= b)
+		nr = b-t-1;
+	if (b > vs_rows || t >= b || nr < 1)
+		return;
+	d = t*vs_cols;
+	s = (t+nr)*vs_cols;
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		lcd_memmove(p, d, s, (b-t-nr)*vs_cols);
+		lcd_memset(p, d+(b-t-nr)*vs_cols, p->erase_char, nr*vs_cols);
+	} else {
+		lcd_memmove(p, d+(b-t-nr)*vs_cols-1, s+(b-t-nr)*vs_cols-1, (b-t-nr)*vs_cols);
+		lcd_memset(p, d+(b-t)*vs_cols-1, p->erase_char, nr*vs_cols);
+	}
+}
+
+static void scrdown(struct lcd_struct *p, unsigned int t, unsigned int b, unsigned int nr)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int d, s;
+
+	if (t+nr >= b)
+		nr = b-t-1;
+	if (b > vs_rows || t >= b || nr < 1)
+		return;
+	s = t*vs_cols;
+	d = (t+nr)*vs_cols;
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		lcd_memmove(p, d, s, (b-t-nr)*vs_cols);
+		lcd_memset(p, s, p->erase_char, nr*vs_cols);
+	} else {
+		lcd_memmove(p, d+(b-t-nr)*vs_cols-1, s+(b-t-nr)*vs_cols-1, (b-t-nr)*vs_cols);
+		lcd_memset(p, s+nr*vs_cols-1, p->erase_char, nr*vs_cols);
+	}
+}
+
+static void lcd_insert_char(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int pos = (p->row*vs_cols)+p->col;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (test_bit(INC_CURS_POS, &p->struct_flags))
+		lcd_memmove(p, pos+nr, pos, vs_cols-p->col-nr);
+	else
+		lcd_memmove(p, pos-nr, pos, p->col-(nr-1));
+	lcd_memset(p, pos, p->erase_char, nr);
+}
+
+static void lcd_delete_char(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int pos = (p->row*vs_cols)+p->col;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		lcd_memmove(p, pos, pos+nr, vs_cols-(p->col+nr));
+		lcd_memset(p, (p->row+1)*vs_cols-nr, p->erase_char, nr);
+	} else {
+		lcd_memmove(p, pos, pos-nr, p->col-(nr-1));
+		lcd_memset(p, (p->row*vs_cols)+(nr-1), p->erase_char, nr);
+	}
+}
+
+
+
+
+
+/******************************************************************************
+ *************************      VT 102 Emulation      *************************
+ ******************************************************************************/
+
+/**********************
+ * Control characters *
+ **********************/
+static void bs(struct lcd_struct *p)
+{
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (p->col)
+			--p->col;
+	} else {
+		if (p->col+1 < p->par->vs_cols)
+			++p->col;
+	}
+}
+
+static void cr(struct lcd_struct *p)
+{
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	p->col = (test_bit(INC_CURS_POS, &p->struct_flags) ? 0 : p->par->vs_cols-1);
+}
+
+static void lf(struct lcd_struct *p)
+{
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (p->row+1 == p->bot && INPUT_STATE(p) != RAW) {
+			make_cursor_visible(p);
+			scrup(p, p->top, p->bot, 1);
+		} else if (p->row+1 < p->par->vs_rows)
+			++p->row;
+	} else {
+		if (p->row == p->top && INPUT_STATE(p) != RAW) {
+			make_cursor_visible(p);
+			scrdown(p, p->top, p->bot, 1);
+		} else if (p->row)
+			--p->row;
+	}
+}
+
+static void ri(struct lcd_struct *p)
+{
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (p->row == p->top) {
+			make_cursor_visible(p);
+			scrdown(p, p->top, p->bot, 1);
+		} else if (p->row)
+			--p->row;
+	} else {
+		if (p->row+1 == p->bot) {
+			make_cursor_visible(p);
+			scrup(p, p->top, p->bot, 1);
+		} else if (p->row+1 < p->par->vs_rows)
+			++p->row;
+	}
+}
+
+static void ff(struct lcd_struct *p)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (p->driver->clear_display) {
+		p->driver->clear_display();
+		__memset_short(p->fb, p->erase_char, p->fb_size);
+		__memset_short(p->display, p->erase_char, p->frame_size);
+		p->frame_base = 0;
+	} else if (test_bit(INC_CURS_POS, &p->struct_flags))
+		lcd_memset(p, 0, p->erase_char, p->fb_size);
+	else
+		lcd_memset(p, p->fb_size-1, p->erase_char, p->fb_size);
+
+	if (test_bit(INC_CURS_POS, &p->struct_flags))
+		p->row = p->col = 0;
+	else {
+		p->row = vs_rows-1;
+		p->col = vs_cols-1;
+	}
+}
+
+static void tab(struct lcd_struct *p)
+{
+	struct lcd_parameters *par = p->par;
+	unsigned int i, vs_cols = par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+
+	if (! par->tabstop)
+		return;
+
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		i = par->tabstop-(p->col%par->tabstop);
+		if (p->col+i < vs_cols)
+			p->col += i;
+	} else {
+		i = p->col%par->tabstop;
+		i = (i == 0 ? par->tabstop : i);
+		if (p->col >= i)
+			p->col -= i;
+	}
+}
+
+/*
+ * Control character handler.
+ */
+static void control_char(struct lcd_struct *p, unsigned char val)
+{
+	switch (val) {
+	case 0x08: 	/* BS: Back Space (^H) */
+	case 0x7f:	/* DEL: Delete */
+		bs(p);
+		return;
+
+	case 0x09: 	/* HT: Horizontal Tab (^I) */
+		tab(p);
+		return;
+
+	case 0x0c: 	/* FF: Form Feed (^L) */
+		ff(p);
+		return;
+
+	case 0x0a: 	/* LF: Line Feed (^J) */
+	case 0x0b: 	/* VT: Vertical Tab (^K) */
+		lf(p);
+		if (! test_bit(CRLF, &p->struct_flags))
+			return;
+
+	case 0x0d: 	/* CR: Carriage Return (^M) */
+		cr(p);
+		return;
+
+	case 0x16: 	/* SYN: Synchronous Idle (^V) */
+		SET_INPUT_STATE(p, SYN);
+		return;
+
+	case 0x1b: 	/* ESC: Start of escape sequence */
+		SET_INPUT_STATE(p, ESC);
+		return;
+
+	case 0x9b: 	/* CSI: Start of CSI escape sequence */
+		memset(p->csi_args, 0, sizeof(p->csi_args));
+		p->index = 0;
+		SET_INPUT_STATE(p, CSI);
+		return;
+	}
+}
+
+static void gotoxy(struct lcd_struct *p, int new_col, int new_row)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (new_row < 0)
+		p->row = 0;
+	else if (new_row >= vs_rows)
+		p->row = vs_rows-1;
+	else
+		p->row = new_row;
+
+	if (new_col < 0)
+		p->col = 0;
+	else if (new_col >= vs_cols)
+		p->col = vs_cols-1;
+	else
+		p->col = new_col;
+
+	if (make_cursor_visible(p))
+		redraw_screen(p, 0, p->fb_size-1);
+}
+
+
+/******************************
+ * ECMA-48 CSI ESC- sequences *
+ ******************************/
+static void csi_at(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+
+	if (p->col+nr > vs_cols)
+		nr = vs_cols-p->col;
+	else if (! nr)
+		++nr;
+	lcd_insert_char(p, nr);
+}
+
+static void csi_J(struct lcd_struct *p, unsigned int action)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int pos = (p->row*vs_cols)+p->col;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	switch (action) {
+	case 0:		/* From cursor to end of display */
+		lcd_memset(p, pos, p->erase_char, p->fb_size-pos);
+		return;
+
+	case 1:		/* From start of display to cursor */
+		lcd_memset(p, 0, p->erase_char, pos+1);
+		return;
+
+	case 2:		/* Whole display */
+		lcd_memset(p, 0, p->erase_char, p->fb_size);
+		return;
+	}
+}
+
+static void csi_K(struct lcd_struct *p, unsigned int action)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int row_start = p->row*vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	switch (action) {
+	case 0:		/* From cursor to end of line */
+		lcd_memset(p, row_start+p->col, p->erase_char, vs_cols-p->col);
+		return;
+
+	case 1:		/* From start of line to cursor */
+		lcd_memset(p, row_start, p->erase_char, p->col+1);
+		return;
+
+	case 2:		/* Whole line */
+		lcd_memset(p, row_start, p->erase_char, vs_cols);
+		return;
+	}
+}
+
+static void csi_L(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (p->row+nr > vs_rows)
+		nr = vs_rows-p->row;
+	else if (! nr)
+		++nr;;
+	lcd_memmove(p, (p->row+nr)*vs_cols, p->row*vs_cols, (vs_rows-p->row-nr)*vs_cols);
+	lcd_memset(p, p->row*vs_cols, p->erase_char, nr*vs_cols);
+}
+
+static void csi_M(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (p->row+nr > vs_rows)
+		nr = vs_rows-p->row;
+	else if (! nr)
+		++nr;;
+	lcd_memmove(p, p->row*vs_cols, (p->row+nr)*vs_cols, (vs_rows-p->row-nr)*vs_cols);
+	lcd_memset(p, (vs_rows-nr)*vs_cols, p->erase_char, nr*vs_cols);
+}
+
+static void csi_P(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+
+	if (p->col+nr > vs_cols)
+		nr = vs_cols-p->col;
+	else if (! nr)
+		++nr;
+	lcd_delete_char(p, nr);
+}
+
+static void csi_X(struct lcd_struct *p, unsigned int nr)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (p->col+nr > vs_cols)
+		nr = vs_cols-p->col;
+	else if (! nr)
+		++nr;
+	lcd_memset(p, (p->row*vs_cols)+p->col, p->erase_char, nr);
+}
+
+static void csi_su(struct lcd_struct *p, unsigned char input)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+
+	clear_bit(NEED_WRAP, &p->struct_flags);
+	if (input == 'u') {
+		p->row = p->s_offset/vs_cols;
+		p->col = p->s_offset%vs_cols;
+		p->color = p->s_color;
+		p->attributes = p->s_attributes;
+		return;
+	}
+	p->s_offset = (p->row*vs_cols)+p->col;
+	p->s_color = p->color;
+	p->s_attributes = p->attributes;
+}
+
+static unsigned char build_attr(struct lcd_struct *p, unsigned char color, unsigned char intensity,
+				unsigned char blink, unsigned char underline, unsigned char reverse)
+{
+	unsigned char attr;
+
+	if (test_bit(CAN_DO_COLOR, &p->struct_flags)) {
+		attr = color;
+		if (underline)
+			attr = (attr & BG_MASK) | p->ulcolor;
+		else if (intensity == 0)
+			attr = (attr & BG_MASK) | p->halfcolor;
+		if (reverse)
+			attr = (attr & 0x88) | ((attr & 0x70) >> 4) | ((attr & 0x07) << 4);
+		if (blink)
+			attr ^= 0x80;
+		if (intensity == 2)
+			attr ^= 0x08;
+	} else {
+		attr = intensity;
+		attr |= (underline ? ULINE : 0x00);
+		attr |= (reverse ? REVERSE : 0x00);
+		attr |= (blink ? BLINK : 0x00);
+	}
+
+	return (attr);
+}
+
+static void update_attr(struct lcd_struct *p)
+{
+	unsigned char intensity = p->attributes & 0x03;
+	unsigned char underline = (p->attributes >> 2) & 0x01;
+	unsigned char reverse = (p->attributes >> 3) & 0x01;
+	unsigned char blink = (p->attributes >> 7) & 0x01;
+	unsigned char decscnm = (p->struct_flags >> DECSCNM) & 0x01;
+
+	p->attr = build_attr(p, p->color, intensity, blink, underline, reverse^decscnm);
+	p->erase_char = (build_attr(p, p->color, 1, blink, 0, decscnm) << 8) | ' ';
+}
+
+static void default_attr(struct lcd_struct *p)
+{
+	p->attributes = 0x01;
+	p->color = p->defcolor;
+}
+
+static void lcd_invert_screen(struct lcd_struct *p, unsigned int offset, unsigned int len)
+{
+	if (test_bit(CAN_DO_COLOR, &p->struct_flags))
+		while (len--) {
+			p->fb[offset] = (p->fb[offset] & 0x88ff) | ((p->fb[offset] & 0x7000) >> 4) | ((p->fb[offset] & 0x0700) << 4);
+			++offset;
+		}
+	else
+		while (len--) {
+			p->fb[offset] ^= 0x0800;
+			++offset;
+		}
+}
+
+static void csi_m(struct lcd_struct *p, unsigned int n)
+{
+	int i, arg;
+
+	for (i = 0; i <= n; ++i)
+		switch ((arg = p->csi_args[i]))
+		{
+			case 0:
+				default_attr(p);
+				break;
+
+			case 1:
+				p->attributes = (p->attributes & ~I_MASK) | 2;
+				break;
+
+			case 2:
+				p->attributes = (p->attributes & ~I_MASK) | 0;
+				break;
+
+			case 4:
+				p->attributes |= ULINE;
+				break;
+
+			case 5:
+				p->attributes |= BLINK;
+				break;
+
+			case 7:
+				p->attributes |= REVERSE;
+				break;
+
+			case 21: case 22:
+				p->attributes = (p->attributes & ~I_MASK) | 1;
+				break;
+
+			case 24:
+				p->attributes &= ~ULINE;
+				break;
+
+			case 25:
+				p->attributes &= ~BLINK;
+				break;
+
+			case 27:
+				p->attributes &= ~REVERSE;
+				break;
+
+			case 38:
+				p->attributes |= ULINE;
+				p->color = (p->color & BG_MASK) | (p->defcolor & FG_MASK);
+				break;
+
+			case 39:
+				p->attributes &= ~ULINE;
+				p->color = (p->color & BG_MASK) | (p->defcolor & FG_MASK);
+				break;
+
+			case 49:
+				p->color = (p->defcolor & BG_MASK) | (p->color & FG_MASK);
+				break;
+
+			default:
+				if (arg >= 30 && arg <= 37)
+					p->color = (p->color & BG_MASK) | color_table[arg-30];
+				else if (arg >= 40 && arg <= 47)
+					p->color = (p->color & FG_MASK) | (color_table[arg-40] << 4);
+				break;
+		}
+
+	update_attr(p);
+}
+
+static void csi_h(struct lcd_struct *p, unsigned char n)
+{
+	switch (n) {
+		case 4:		/* Set insert mode */
+			set_bit(DECIM, &p->struct_flags);
+			return;
+
+		case 5:		/* Inverted screen mode */
+			if (test_bit(QUES, &p->struct_flags) && ! test_bit(DECSCNM, &p->struct_flags)) {
+				set_bit(DECSCNM, &p->struct_flags);
+				lcd_invert_screen(p, 0, p->fb_size);
+				update_attr(p);
+				redraw_screen(p, 0, p->fb_size-1);
+			}
+			return;
+
+		case 7:		/* Set autowrap */
+			if (test_bit(QUES, &p->struct_flags))
+				set_bit(DECAWM, &p->struct_flags);
+			return;
+
+		case 20:	/* Set cr lf */
+			set_bit(CRLF, &p->struct_flags);
+			return;
+	}
+}
+
+static void csi_l(struct lcd_struct *p, unsigned char n)
+{
+	switch (n) {
+		case 4:		/* Reset insert mode */
+			clear_bit(DECIM, &p->struct_flags);
+			return;
+
+		case 5:		/* Normal screen mode */
+			if (test_bit(QUES, &p->struct_flags) && test_bit(DECSCNM, &p->struct_flags)) {
+				clear_bit(DECSCNM, &p->struct_flags);
+				lcd_invert_screen(p, 0, p->fb_size);
+				update_attr(p);
+				redraw_screen(p, 0, p->fb_size-1);
+			}
+			return;
+
+		case 7:		/* Reset autowrap */
+			if (test_bit(QUES, &p->struct_flags))
+				clear_bit(DECAWM, &p->struct_flags);
+			return;
+
+		case 20:	/* Reset cr lf */
+			clear_bit(CRLF, &p->struct_flags);
+			return;
+	}
+}
+
+static void csi_linux(struct lcd_struct *p)
+{
+	switch (p->csi_args[0]) {
+	case 1:
+		if (test_bit(CAN_DO_COLOR, &p->struct_flags) && p->csi_args[1] < 16) {
+			p->ulcolor = color_table[p->csi_args[1]];
+			if (p->attributes & ULINE)
+				update_attr(p);
+		}
+		return;
+
+	case 2:
+		if (test_bit(CAN_DO_COLOR, &p->struct_flags) && p->csi_args[1] < 16) {
+			p->halfcolor = color_table[p->csi_args[1]];
+			if ((p->attributes & I_MASK) == 0)
+				update_attr(p);
+		}
+		return;
+
+	case 8:
+		p->defcolor = p->color;
+		default_attr(p);
+		update_attr(p);
+		return;
+	}
+}
+
+static void csi_r(struct lcd_struct *p, unsigned int top, unsigned int bot)
+{
+	/* Minimum allowed region is 2 lines */
+	if (top < bot) {
+		p->top = top-1;
+		p->bot = bot;
+		gotoxy(p, 0, 0);
+	}
+}
+
+/*
+ * ECMA-48 CSI ESC- sequence handler.
+ */
+static void handle_csi(struct lcd_struct *p, unsigned char input)
+{
+	if (p->index >= NPAR) {
+		SET_INPUT_STATE(p, NORMAL);
+		printk(KERN_NOTICE "LCD: too many parameters in CSI escape sequence\n");
+	} else if (input == '?') {
+		set_bit(QUES, &p->struct_flags);
+	} else if (input == ';') {
+		++p->index;
+	} else if (input >= '0' && input <= '9') {
+		p->csi_args[p->index] = (p->csi_args[p->index]*10)+(input-'0');
+	} else {
+		SET_INPUT_STATE(p, NORMAL);
+		if (! test_bit(INC_CURS_POS, &p->struct_flags))
+			return;
+		switch (input) {
+		case 'h':		/* DECSET sequences and mode switches */
+			csi_h(p, p->csi_args[0]);
+			clear_bit(QUES, &p->struct_flags);
+			return;
+
+		case 'l':		/* DECRST sequences and mode switches */
+			csi_l(p, p->csi_args[0]);
+			clear_bit(QUES, &p->struct_flags);
+			return;
+		}
+		clear_bit(QUES, &p->struct_flags);
+		switch (input) {
+		case '@':		/* Insert # Blank character */
+			csi_at(p, p->csi_args[0]);
+			return;
+
+		case 'G': case '`':	/* Cursor to indicated column in current row */
+			if (p->csi_args[0])
+				--p->csi_args[0];
+			gotoxy(p, p->csi_args[0], p->row);
+			return;
+
+		case 'A':		/* Cursor # rows Up */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, p->col, p->row-p->csi_args[0]);
+			return;
+
+		case 'B': case 'e':	/* Cursor # rows Down */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, p->col, p->row+p->csi_args[0]);
+			return;
+
+		case 'C': case 'a':	/* Cursor # columns Right */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, p->col+p->csi_args[0], p->row);
+			return;
+
+		case 'D':		/* Cursor # columns Left */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, p->col-p->csi_args[0], p->row);
+			return;
+
+		case 'E':		/* Cursor # rows Down, column 1 */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, 0, p->row+p->csi_args[0]);
+			return;
+
+		case 'F':		/* Cursor # rows Up, column 1 */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			gotoxy(p, 0, p->row-p->csi_args[0]);
+			return;
+
+		case 'd':		/* Cursor to indicated row in current column */
+			if (p->csi_args[0])
+				--p->csi_args[0];
+			gotoxy(p, p->col, p->csi_args[0]);
+			return;
+
+		case 'H': case 'f':	/* Cursor to indicated row, column (origin 1, 1) */
+			if (p->csi_args[0])
+				--p->csi_args[0];
+			if (p->csi_args[1])
+				--p->csi_args[1];
+			gotoxy(p, p->csi_args[1], p->csi_args[0]);
+			return;
+
+		case 'J':		/* Erase display */
+			csi_J(p, p->csi_args[0]);
+			return;
+
+		case 'K':		/* Erase line */
+			csi_K(p, p->csi_args[0]);
+			return;
+
+		case 'L':		/* Insert # blank lines */
+			csi_L(p, p->csi_args[0]);
+			return;
+
+		case 'M':		/* Delete # blank lines */
+			csi_M(p, p->csi_args[0]);
+			return;
+
+		case 'P':		/* Delete # characters on the current line */
+			csi_P(p, p->csi_args[0]);
+			return;
+
+		case 'X':		/* Erase # characters on the current line */
+			csi_X(p, p->csi_args[0]);
+			return;
+
+		case 'm':		/* Set video attributes */
+			csi_m(p, p->index);
+			return;
+
+		case 's':		/* Save cursor position */
+		case 'u':		/* Restore cursor position */
+			csi_su(p, input);
+			return;
+
+		case ']':		/* Linux private ESC [ ] sequence */
+			csi_linux(p);
+			return;
+
+		case 'r':		/* Set the scrolling region */
+			if (! p->csi_args[0])
+				++p->csi_args[0];
+			if (! p->csi_args[1] || p->csi_args[1] > p->par->vs_rows)
+		 		p->csi_args[1] = p->par->vs_rows;
+			csi_r(p, p->csi_args[0], p->csi_args[1]);
+			return;
+
+					/* Ignored escape sequences */
+		case 'c':
+		case 'g':
+		case 'n':
+		case 'q':
+			return;
+
+		default:
+			printk(KERN_NOTICE "LCD: unrecognized CSI escape sequence: ESC [ %u\n", input);
+			return;
+		}
+	}
+}
+
+/*
+ * Custom ESC- sequence handler.
+ */
+static int handle_custom_esc(struct lcd_struct *p, unsigned int _input)
+{
+	unsigned char input = _input & 0xff;
+	struct lcd_parameters *par = p->par;
+
+	if (_input & (~0xff)) {
+		switch (ESC_STATE(p)) {
+		case 's':
+			if (p->index++) {
+				unsigned char *buf = p->cgram_buffer+(p->cgram_index-par->cgram_char0)*par->cgram_bytes;
+
+				buf[p->index-2] = input;
+				if (p->index == par->cgram_bytes+1)
+					write_cgram(p, p->cgram_index, buf);
+			} else {
+				if (! p->driver->write_cgram_char) {
+					printk(KERN_ERR "LCD: %s: missing function to write to CGRAM\n", p->par->name);
+					return (-1);
+				}
+				if (input >= par->cgram_char0 && input < par->cgram_char0+par->cgram_chars)
+					p->cgram_index = input;
+				else {
+					printk(KERN_NOTICE "LCD: bad CGRAM index\n");
+					return (-1);
+				}
+			}
+			return (0);
+
+		case 'G':
+			if (input >= par->cgram_char0 && input < par->cgram_char0+par->cgram_chars)
+				write_data(p, (p->attr << 8) | p->driver->charmap[input]);
+			else {
+				SET_INPUT_STATE(p, NORMAL);
+				handle_input(p, (p->attr << 8) | input);
+			}
+			return (0);
+
+		case 'r':
+			if (input == '1')
+				address_mode(p, -1);
+			else if (input == '0')
+				address_mode(p, 1);
+			return (0);
+
+		case 'A':
+			scrup(p, p->top, p->bot, input);
+			return (0);
+
+		case 'B':
+			scrdown(p, p->top, p->bot, input);
+			return (0);
+
+		case 'C':
+			browse_screen(p, input);
+			return (0);
+		}
+	}
+
+	/* These are the custom ESC- sequences */
+	switch (input) {
+	case 's':	/* CGRAM select */
+		if (p->cgram_buffer) {
+			SET_ESC_STATE(p, input);
+			p->index = 0;
+			return (par->cgram_bytes+1);
+		} else {
+			printk(KERN_NOTICE "LCD: driver %s does not support CGRAM chars\n", par->name);
+			return (0);
+		}
+
+	case 'A':	/* Scroll up */
+	case 'B':	/* Scroll down */
+	case 'C':	/* Browse screen */
+	case 'G':	/* Enter cgram mode */
+	case 'r':	/* Decrement counter after data read/write */
+		SET_ESC_STATE(p, input);
+		return (1);
+	}
+
+	return (-1);
+}
+
+/*
+ * ESC- but not CSI sequence handler.
+ */
+static int handle_esc(struct lcd_struct *p, unsigned char input)
+{
+	int ret;
+
+	SET_INPUT_STATE(p, NORMAL);
+	switch (input) {
+	case 'c':	/* Reset */
+		set_bit(DECAWM, &p->struct_flags);
+		set_bit(INC_CURS_POS, &p->struct_flags);
+		ff(p);
+		return (0);
+
+	case 'D':	/* Line Feed */
+		lf(p);
+		return (0);
+
+	case 'E':	/* New Line */
+		cr(p);
+		lf(p);
+		return (0);
+
+	case 'M':	/* Reverse Line Feed */
+		ri(p);
+		return (0);
+
+	case '7':
+	case '8':
+		csi_su(p, (input == '7' ? 's' : 'u'));
+		return (0);
+
+	/* CSI: Start of CSI escape sequence */
+	case '[':
+		memset(p->csi_args, 0, sizeof(p->csi_args));
+		p->index = 0;
+		SET_INPUT_STATE(p, CSI);
+		return (0);
+
+	/* Ignored escape sequences */
+	case '(':
+		SET_INPUT_STATE(p, ESC_G0);
+		return (1);
+
+	case ')':
+		SET_INPUT_STATE(p, ESC_G1);
+		return (1);
+
+	case '#':
+		SET_INPUT_STATE(p, ESC_HASH);
+		return (1);
+
+	case '%':
+		SET_INPUT_STATE(p, ESC_PERCENT);
+		return (1);
+
+	case 'H':
+	case 'Z':
+	case '>':
+	case '=':
+	case ']':
+		return (0);
+	}
+
+	/* These are the custom ESC- sequences */
+	if ((ret = handle_custom_esc(p, input)) > 0) {
+		SET_INPUT_STATE(p, ARG);
+		return (ret);
+	}
+
+	if (ret < 0 && p->driver->handle_custom_char)
+		if ((ret = p->driver->handle_custom_char(input)) > 0) {
+			SET_INPUT_STATE(p, ARG_DRIVER);
+			return (ret);
+		}
+
+	if (ret < 0)
+		printk(KERN_NOTICE "LCD: unrecognized escape sequence: ESC %u\n", input);
+
+	return (0);
+}
+
+/*
+ * Main input handler.
+ */
+static void handle_input(struct lcd_struct *p, unsigned short _input)
+{
+	unsigned char input = _input & 0xff;
+	struct lcd_driver *driver = p->driver;
+
+	switch (INPUT_STATE(p)) {
+	case NORMAL:
+		if (input < 0x20 || input == 0x9b)
+			control_char(p, input);
+		else
+			write_data(p, (_input & 0xff00) | driver->charmap[input]);
+		return;
+
+	case RAW:
+		write_data(p, (_input & 0xff00) | driver->charmap[input]);
+		return;
+
+	case SYN:
+		write_data(p, _input);
+		SET_INPUT_STATE(p, NORMAL);
+		return;
+
+	case ESC:
+		p->esc_args = handle_esc(p, input);
+		return;
+
+	case ESC_G0:
+	case ESC_G1:
+	case ESC_HASH:
+	case ESC_PERCENT:
+		if (! --p->esc_args)
+			SET_INPUT_STATE(p, NORMAL);
+		return;
+
+	case CSI:
+		handle_csi(p, input);
+		return;
+
+	case ARG:
+		if (handle_custom_esc(p, 0x100 | input) || ! --p->esc_args)
+			SET_INPUT_STATE(p, NORMAL);
+		return;
+
+	case ARG_DRIVER:
+		if (driver->handle_custom_char(0x100 | input) || ! --p->esc_args)
+			SET_INPUT_STATE(p, NORMAL);
+		return;
+	}
+}
+
+
+
+
+
+/***************************************
+ * Read from/Write to display routines *
+ ***************************************/
+
+/*
+ * Write character data to the display.
+ */
+static void write_data(struct lcd_struct *p, unsigned short data)
+{
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int pos;
+	int frame_pos;
+
+	if (test_bit(NEED_WRAP, &p->struct_flags)) {
+		cr(p);
+		lf(p);
+	}
+
+	if (test_bit(DECIM, &p->struct_flags))
+		lcd_insert_char(p, 1);
+
+	pos = (p->row*vs_cols)+p->col;
+	if ((frame_pos = vs_to_frame_(p, pos)) < 0) {
+		make_cursor_visible(p);
+		redraw_screen(p, 0, p->fb_size-1);
+		frame_pos = vs_to_frame_(p, pos);
+	}
+
+	if (p->display[frame_pos] != data) {
+		p->driver->write_char(frame_pos, data);
+		p->display[frame_pos] = data;
+	}
+
+	p->fb[pos] = data;
+
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		if (p->col+1 < vs_cols)
+			iterator_inc(p->col, vs_cols);
+		else if (test_bit(DECAWM, &p->struct_flags))
+			set_bit(NEED_WRAP, &p->struct_flags);
+	} else {
+		if (p->col)
+			iterator_dec(p->col, vs_cols);
+		else if (test_bit(DECAWM, &p->struct_flags))
+			set_bit(NEED_WRAP, &p->struct_flags);
+	}
+}
+
+/*
+ * Write an entire CGRAM character to the display.
+ */
+static void write_cgram(struct lcd_struct *p, unsigned char index, unsigned char *pixels)
+{
+	unsigned int inc_set = test_bit(INC_CURS_POS, &p->struct_flags);
+
+	if (! inc_set)
+		address_mode(p, 1);
+
+	p->driver->write_cgram_char(index, pixels);
+
+	if (! inc_set)
+		address_mode(p, -1);
+}
+
+/*
+ * Read character data from the display.
+ */
+static void read_data(struct lcd_struct *p, unsigned short *data)
+{
+	unsigned int vs_rows = p->par->vs_rows;
+	unsigned int vs_cols = p->par->vs_cols;
+	unsigned int pos = (p->row*vs_cols)+p->col;
+	int frame_pos;
+
+	if ((frame_pos = vs_to_frame_(p, pos)) < 0) {
+		make_cursor_visible(p);
+		redraw_screen(p, 0, p->fb_size-1);
+		frame_pos = vs_to_frame_(p, pos);
+	}
+
+	p->driver->read_char(frame_pos, data);
+
+	if (test_bit(INC_CURS_POS, &p->struct_flags)) {
+		iterator_inc(p->col, vs_cols);
+		if (! p->col) {
+			if (p->row+1 < vs_rows)
+				++p->row;
+		}
+	} else {
+		iterator_dec(p->col, vs_cols);
+		if (p->col+1 == vs_cols) {
+			if (p->row)
+				--p->row;
+		}
+	}
+}
+
+/*
+ * Read an entire CGRAM character from the display.
+ */
+static void read_cgram(struct lcd_struct *p, unsigned char index, unsigned char *pixels)
+{
+	unsigned int inc_set = test_bit(INC_CURS_POS, &p->struct_flags);
+
+	if (! inc_set)
+		address_mode(p, 1);
+
+	p->driver->read_cgram_char(index, pixels);
+
+	if (! inc_set)
+		address_mode(p, -1);
+}
+
+
+
+
+
+/****************************
+ * Proc filesystem routines *
+ ****************************/
+#ifdef USE_PROC
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)
+/* create_proc_read_entry is missing in 2.2.x kernels */
+static struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode,
+			struct proc_dir_entry *parent, read_proc_t *read_proc, void *data)
+{
+	struct proc_dir_entry *res = create_proc_entry(name, mode, parent);
+
+	if (res) {
+		res->read_proc = read_proc;
+		res->data = data;
+	}
+
+	return (res);
+}
+#endif
+
+static int proc_fb_read(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+	struct lcd_struct *p = (struct lcd_struct *)data;
+	unsigned int vs_cols;
+	static unsigned int nr, need_wrap;
+	static off_t _offset;
+
+	down(&p->lcd_sem);
+	if (! offset)
+		_offset = 0;
+	if ((*eof = (_offset >= p->fb_size))) {
+		up(&p->lcd_sem);
+		return (0);
+	}
+	vs_cols = p->par->vs_cols;
+	if (size && need_wrap) {
+		need_wrap = 0;
+		temp += sprintf(temp, "\n");
+		--size;
+	}
+	if (! nr)
+		nr = vs_cols;
+	*start = (char *)0;
+	while (size && nr) {
+		unsigned char c = (p->fb[_offset] & 0xff);
+
+		temp += sprintf(temp, "%c", (c < 0x20 ? '·' : c));
+		--size;
+		--nr;
+		++*start;
+		++_offset;
+	}
+	if (! nr) {
+		if (size) {
+			temp += sprintf(temp, "\n");
+			--size;
+		} else
+			need_wrap = 1;
+	}
+	up(&p->lcd_sem);
+
+	return (temp-buffer);
+}
+
+static int proc_display_read(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+	struct lcd_struct *p = (struct lcd_struct *)data;
+	unsigned int i, frame_cols;
+	int frame_pos;
+
+	down(&p->lcd_sem);
+	frame_cols = p->par->cntr_cols;
+	frame_pos = vs_to_frame_(p, (p->row*p->par->vs_cols)+p->col);
+	temp += sprintf(temp, "    ");
+	for (i = 2; i <= frame_cols; i += 2)
+		temp += sprintf(temp, " %d", i%10);
+	temp += sprintf(temp, "\n");
+
+	temp += sprintf(temp, "   +");
+	for (i = 0; i < frame_cols; ++i)
+		temp += sprintf(temp, "-");
+	temp += sprintf(temp, "+\n");
+
+	for (i = 0; i < p->frame_size; ++i) {
+		unsigned char c = (p->display[i] & 0xff);
+
+		if (! (i%frame_cols))
+			temp += sprintf(temp, "%2d |", 1+i/frame_cols);
+		if (frame_pos--)
+			temp += sprintf(temp, "%c", (c < 0x20 ? '·' : c));
+		else
+			temp += sprintf(temp, "_");
+		if (! ((i+1)%frame_cols))
+			temp += sprintf(temp, "|\n");
+	}
+
+	temp += sprintf(temp, "   +");
+	for (i = 0; i < frame_cols; ++i)
+		temp += sprintf(temp, "-");
+	temp += sprintf(temp, "+\n");
+	up(&p->lcd_sem);
+
+	return (temp-buffer);
+}
+
+static int proc_charmap_read(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+	struct lcd_struct *p = (struct lcd_struct *)data;
+	unsigned char *charmap;
+	unsigned int i;
+
+	down(&p->lcd_sem);
+	charmap = p->driver->charmap;
+	temp += sprintf(temp,	"static unsigned char charmap[] = {");
+	for (i = 0; i < 255; ++i) {
+		if (! (i & 7)) {
+			temp += sprintf(temp, "\n");
+			if (! (i & 31))
+				temp += sprintf(temp, "\n/* %d - %d */\n", i, i+31);
+		}
+		temp += sprintf(temp, "0x%.2x, ", *charmap++);
+	}
+	temp += sprintf(temp, "0x%.2x\n\n};\n", *charmap);
+	up(&p->lcd_sem);
+
+	return (temp-buffer);
+}
+
+static int proc_registered_drivers(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
+{
+	char *temp = buffer;
+	struct list_head *entry;
+
+	down(&drivers_sem);
+	temp += sprintf(temp, "Registered drivers:\n");
+	list_for_each(entry, &lcd_drivers) {
+		struct lcd_struct *p = list_entry(entry, struct lcd_struct, lcd_list);
+
+		down(&p->lcd_sem);
+		temp += sprintf(temp, "%3d %s\n", p->par->minor, p->par->name);
+		up(&p->lcd_sem);
+	}
+	up(&drivers_sem);
+
+	return (temp-buffer);
+}
+
+static void create_driver_proc_entries(struct lcd_struct *p)
+{
+	struct proc_dir_entry *driver_proc_root = p->driver->driver_proc_root;
+	int proc_level = 0;
+
+	SET_PROC_LEVEL(p, proc_level);
+	if (create_proc_read_entry("framebuffer", 0, driver_proc_root, proc_fb_read, p) == NULL) {
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/%s/framebuffer\n", p->par->name);
+		return;
+	}
+	SET_PROC_LEVEL(p, ++proc_level);
+	if (create_proc_read_entry("display", 0, driver_proc_root, proc_display_read, p) == NULL) {
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/%s/display\n", p->par->name);
+		return;
+	}
+	SET_PROC_LEVEL(p, ++proc_level);
+	if (create_proc_read_entry("charmap.h", 0, driver_proc_root, proc_charmap_read, p) == NULL) {
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/%s/charmap.h\n", p->par->name);
+		return;
+	}
+	SET_PROC_LEVEL(p, ++proc_level);
+}
+
+static void remove_driver_proc_entries(struct lcd_struct *p)
+{
+	struct proc_dir_entry *driver_proc_root = p->driver->driver_proc_root;
+
+	switch (PROC_LEVEL(p)) {
+	case 3:
+		remove_proc_entry("charmap.h", driver_proc_root);
+	case 2:
+		remove_proc_entry("display", driver_proc_root);
+	case 1:
+		remove_proc_entry("framebuffer", driver_proc_root);
+	}
+	SET_PROC_LEVEL(p, 0);
+}
+#endif
+
+
+
+
+
+/*****************************
+ * Low level file operations *
+ *****************************/
+static ssize_t do_lcd_read(struct lcd_struct *p, void *buffer, size_t length)
+{
+	unsigned int i;
+	unsigned short tmp;
+
+	if (! p->refcount)
+		return (-ENXIO);
+
+	if (! p->driver->read_char) {
+		printk(KERN_NOTICE "LCD: driver %s doesn't support reading\n", p->par->name);
+		return (-ENOSYS);
+	}
+
+	if (test_bit(WITH_ATTR, &p->struct_flags))
+		for (i = 0; i < length; ++i) {
+			read_data(p, &tmp);
+			((unsigned short *)buffer)[i] = tmp;
+		}
+	else
+		for (i = 0; i < length; ++i) {
+			read_data(p, &tmp);
+			((unsigned char *)buffer)[i] = tmp & 0xff;
+		}
+
+	return (length);
+}
+
+static ssize_t do_lcd_write(struct lcd_struct *p, const void *buffer, size_t length)
+{
+	unsigned int i;
+
+	if (! p->refcount)
+		return (-ENXIO);
+
+	if (test_bit(WITH_ATTR, &p->struct_flags))
+		for (i = 0; i < length; ++i)
+			handle_input(p, ((const unsigned short *)buffer)[i]);
+	else
+		for (i = 0; i < length; ++i)
+			handle_input(p, (p->attr << 8) | ((const unsigned char *)buffer)[i]);
+
+	return (length);
+}
+
+static int do_lcd_open(struct lcd_struct *p)
+{
+	if (! p->refcount) {
+		if (p->driver->driver_module) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+			if (! try_module_get(p->driver->driver_module))
+				return (-EBUSY);
+#else
+			if (__MOD_IN_USE(p->driver->driver_module))
+				return (-EBUSY);
+
+			__MOD_INC_USE_COUNT(p->driver->driver_module);
+#endif
+		}
+	}
+
+	++p->refcount;
+
+	return (0);
+}
+
+static int do_lcd_release(struct lcd_struct *p)
+{
+	if (! p->refcount)
+		return (0);
+
+	if (p->refcount == 1) {
+		if (p->driver->driver_module)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+			module_put(p->driver->driver_module);
+#else
+			__MOD_DEC_USE_COUNT(p->driver->driver_module);
+#endif
+	}
+
+	--p->refcount;
+
+	return (0);
+}
+
+static int cgram_ioctl(struct lcd_struct *p, unsigned int cmd, unsigned char *argp)
+{
+	struct lcd_parameters *par = p->par;
+	unsigned int length = par->cgram_bytes;
+	unsigned char index = argp[0];
+	unsigned char *buffer = argp+1;
+	unsigned char *cgram_buffer = p->cgram_buffer+(index-par->cgram_char0)*length;
+
+	if (index < par->cgram_char0 || index >= par->cgram_char0+par->cgram_chars)
+		return (-EINVAL);
+
+	if (cmd == LCDL_SET_CGRAM_CHAR) {
+		if (! p->driver->write_cgram_char) {
+			printk(KERN_ERR "LCD: %s: missing function to write to CGRAM\n", p->par->name);
+			return (-ENOSYS);
+		}
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_from_user(cgram_buffer, buffer, length))
+				return (-EFAULT);
+		} else
+			memcpy(cgram_buffer, buffer, length);
+		write_cgram(p, index, cgram_buffer);
+	} else {
+		if (! p->driver->read_cgram_char) {
+			printk(KERN_ERR "LCD: %s: missing function to read from CGRAM or unable to read\n", p->par->name);
+			return (-ENOSYS);
+		}
+		read_cgram(p, index, cgram_buffer);
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_to_user(buffer, cgram_buffer, length))
+				return (-EFAULT);
+		} else
+			memcpy(buffer, cgram_buffer, length);
+	}
+
+	return (0);
+}
+
+static int do_lcd_ioctl(struct lcd_struct *p, unsigned int cmd, unsigned long arg)
+{
+	int i;
+	struct lcd_driver *driver = p->driver;
+	struct lcd_parameters *par = p->par;
+	unsigned char *argp = (unsigned char *)arg;
+
+	if (! p->refcount)
+		return (-ENXIO);
+
+	switch (cmd) {
+	case LCDL_SET_PARAM:
+		if ((i = cleanup_driver(p)))
+			return (i);
+		i = par->minor;
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_from_user(par, argp, sizeof(struct lcd_parameters)))
+				return (-EFAULT);
+		} else
+			memcpy(par, argp, sizeof(struct lcd_parameters));
+		par->minor = i;
+		return (init_driver(p));
+
+	case LCDL_GET_PARAM:
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_to_user(argp, par, sizeof(struct lcd_parameters)))
+				return (-EFAULT);
+		} else
+			memcpy(argp, par, sizeof(struct lcd_parameters));
+		return (0);
+
+	case LCDL_RESET_CHARMAP:
+		for (i = 0; i < 256; ++i)
+			driver->charmap[i] = i;
+		return (0);
+
+	case LCDL_CHARSUBST:
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			get_user(i, argp);
+			get_user(driver->charmap[i], argp+1);
+		} else {
+			i = argp[0];
+			driver->charmap[i] = argp[1];
+		}
+		return (0);
+
+	case LCDL_SAVE_CHARMAP:
+		memcpy(p->s_charmap, driver->charmap, 256);
+		return (0);
+
+	case LCDL_RESTORE_CHARMAP:
+		memcpy(driver->charmap, p->s_charmap, 256);
+		return (0);
+
+	case LCDL_SWAP_CHARMAP:
+		{
+			unsigned char *tmp;
+
+			tmp = driver->charmap;
+			driver->charmap = p->s_charmap;
+			p->s_charmap = tmp;
+		}
+		return (0);
+
+	case LCDL_RAW_MODE:
+		if (arg) {
+			clear_bit(NEED_WRAP, &p->struct_flags);
+			clear_bit(DECIM, &p->struct_flags);
+			clear_bit(DECAWM, &p->struct_flags);
+			SET_INPUT_STATE(p, RAW);
+		} else {
+			set_bit(DECAWM, &p->struct_flags);
+			SET_INPUT_STATE(p, NORMAL);
+		}
+		return (0);
+
+	case LCDL_CLEAR_DISP:
+		ff(p);
+		return (0);
+
+	case LCDL_SET_CGRAM_CHAR:
+	case LCDL_GET_CGRAM_CHAR:
+		if (p->cgram_buffer)
+			return (cgram_ioctl(p, cmd, argp));
+		else
+			printk(KERN_NOTICE "LCD: driver %s does not support CGRAM chars\n", par->name);
+		return (0);
+
+	case LCDL_SET_CHARMAP:
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_from_user(driver->charmap, argp, 256))
+				return (-EFAULT);
+		} else
+			memcpy(driver->charmap, argp, 256);
+		return (0);
+
+	case LCDL_GET_CHARMAP:
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_to_user(argp, driver->charmap, 256))
+				return (-EFAULT);
+		} else
+			memcpy(argp, driver->charmap, 256);
+		return (0);
+
+	case LCDL_MEMSET:
+	case LCDL_MEMMOVE:
+	{
+		int buf[3];
+
+		if (test_bit(USER_SPACE, &p->struct_flags)) {
+			if (copy_from_user(buf, argp, sizeof(buf)))
+				return (-EFAULT);
+		} else
+			memcpy(buf, argp, sizeof(buf));
+
+		if (cmd == LCDL_MEMSET)
+			lcd_memset(p, buf[0], buf[1], buf[2]);
+		else
+			lcd_memmove(p, buf[0], buf[1], buf[2]);
+
+		return (0);
+	}
+
+	default:
+		if (driver->handle_custom_ioctl)
+			return (driver->handle_custom_ioctl(cmd, arg, test_bit(USER_SPACE, &p->struct_flags)));
+	}
+
+	return (-ENOIOCTLCMD);
+}
+
+
+
+
+
+/**************************************************
+ * Kernel register/unregister lcd driver routines *
+ **************************************************/
+/*
+ * Find a driver in lcd_drivers linked list
+ */
+static struct lcd_struct *find_lcd_struct(unsigned short minor)
+{
+	struct list_head *entry;
+
+	list_for_each(entry, &lcd_drivers) {
+		struct lcd_struct *p = list_entry(entry, struct lcd_struct, lcd_list);
+
+		if (p->par->minor == minor)
+			return (p);
+	}
+
+	return (NULL);
+}
+
+static void list_add_sorted(struct list_head *new)
+{
+	struct list_head *entry;
+	unsigned short new_minor = (list_entry(new, struct lcd_struct, lcd_list))->par->minor;
+
+	list_for_each(entry, &lcd_drivers) {
+		struct lcd_struct *p = list_entry(entry, struct lcd_struct, lcd_list);
+
+		if (p->par->minor > new_minor)
+			break;
+	}
+	list_add_tail(new, entry);
+}
+
+/* Exported function */
+int lcd_register_driver(struct lcd_driver *driver, struct lcd_parameters *par)
+{
+	int ret;
+	struct lcd_struct *p;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+	struct device *lcd_device;
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+	struct class_device *lcd_class_device;
+#endif
+
+	if (! driver || ! par || par->minor >= minors)
+		return (-EINVAL);
+	if (! driver->write_char || ! driver->init_port || ! driver->cleanup_port) {
+		printk(KERN_ERR "LCD: missing functions\n");
+		return (-EINVAL);
+	}
+
+	down(&drivers_sem);
+
+	if (find_lcd_struct(par->minor)) {
+		up(&drivers_sem);
+		return (-EBUSY);
+	}
+
+	if ((p = (struct lcd_struct *)kmalloc(sizeof(struct lcd_struct), GFP_KERNEL)) == NULL) {
+		printk(KERN_ERR "LCD: memory allocation failed (kmalloc)\n");
+		up(&drivers_sem);
+		return (-ENOMEM);
+	}
+	memset(p, 0, sizeof(struct lcd_struct));
+
+	p->driver = driver;
+	p->par = par;
+	p->refcount = 0;
+	SET_INIT_LEVEL(p, 0);
+	SET_INPUT_STATE(p, NORMAL);
+	set_bit(DECAWM, &p->struct_flags);
+	set_bit(INC_CURS_POS, &p->struct_flags);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+	lcd_device = device_create(lcd_linux_class, NULL, MKDEV(major, par->minor), "%s", par->name);
+	if (IS_ERR(lcd_device)) {
+		kfree(p);
+		up(&drivers_sem);
+		return (PTR_ERR(lcd_device));
+	}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 15)
+	lcd_class_device = class_device_create(lcd_linux_class, NULL, MKDEV(major, par->minor), NULL, "%s", par->name);
+	if (IS_ERR(lcd_class_device)) {
+		kfree(p);
+		up(&drivers_sem);
+		return (PTR_ERR(lcd_class_device));
+	}
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+	lcd_class_device = class_device_create(lcd_linux_class, MKDEV(major, par->minor), NULL, "%s", par->name);
+	if (IS_ERR(lcd_class_device)) {
+		kfree(p);
+		up(&drivers_sem);
+		return (PTR_ERR(lcd_class_device));
+	}
+#endif
+
+#ifdef USE_PROC
+	if (lcd_proc_root && (driver->driver_proc_root = proc_mkdir(par->name, lcd_proc_root)) == NULL)
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/%s/\n", par->name);
+#endif
+
+	if ((ret = init_driver(p))) {
+#ifdef USE_PROC
+		if (driver->driver_proc_root)
+			remove_proc_entry(p->par->name, lcd_proc_root);
+#endif
+		kfree(p);
+		up(&drivers_sem);
+		return (ret);
+	}
+
+	init_MUTEX(&p->lcd_sem);
+
+	list_add_sorted(&p->lcd_list);
+
+	up(&drivers_sem);
+
+#ifdef MODULE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+	try_module_get(THIS_MODULE);
+#else
+	MOD_INC_USE_COUNT;
+#endif
+#endif
+
+	return (0);
+}
+EXPORT_SYMBOL(lcd_register_driver);
+
+/* Exported function */
+int lcd_unregister_driver(struct lcd_driver *driver, struct lcd_parameters *par)
+{
+	int ret;
+	struct lcd_struct *p;
+
+	if (! driver || ! par || par->minor >= minors)
+		return (-EINVAL);
+
+	down(&drivers_sem);
+
+	if ((p = find_lcd_struct(par->minor)) == NULL || p->driver != driver) {
+		printk(KERN_ERR "LCD: driver not found; lcd_unregister_driver failed\n");
+		up(&drivers_sem);
+		return (-ENODEV);
+	}
+
+	down(&p->lcd_sem);
+
+	if (p->refcount) {
+		printk(KERN_ERR "LCD: driver busy; lcd_unregister_driver failed\n");
+		up(&p->lcd_sem);
+		up(&drivers_sem);
+		return (-EBUSY);
+	}
+
+	if ((ret = cleanup_driver(p))) {
+		up(&p->lcd_sem);
+		up(&drivers_sem);
+		return (ret);
+	}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+	device_destroy(lcd_linux_class, MKDEV(major, par->minor));
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+	class_device_destroy(lcd_linux_class, MKDEV(major, par->minor));
+#endif
+
+#ifdef USE_PROC
+	if (p->driver->driver_proc_root)
+		remove_proc_entry(p->par->name, lcd_proc_root);
+#endif
+
+	list_del(&p->lcd_list);
+	kfree(p);
+
+	up(&drivers_sem);
+
+#ifdef MODULE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
+	module_put(THIS_MODULE);
+#else
+	MOD_DEC_USE_COUNT;
+#endif
+#endif
+
+	return (0);
+}
+EXPORT_SYMBOL(lcd_unregister_driver);
+
+
+
+
+
+/************************
+ * Kernel I/O interface *
+ ************************/
+/* Exported function */
+int lcd_open(unsigned short minor, struct lcd_struct **pp)
+{
+	int ret;
+	struct lcd_struct *p;
+
+	down(&drivers_sem);
+
+	if (minor >= minors || (*pp = p = find_lcd_struct(minor)) == NULL) {
+		printk(KERN_ERR "LCD: lcd_open failed. Device not found.\n");
+		up(&drivers_sem);
+		return (-ENODEV);
+	}
+
+	down(&p->lcd_sem);
+	up(&drivers_sem);
+
+	ret = do_lcd_open(p);
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+EXPORT_SYMBOL(lcd_open);
+
+/* Exported function */
+int lcd_close(struct lcd_struct **pp)
+{
+	int ret;
+	struct lcd_struct *p;
+
+	if (! pp || ! (p = *pp)) {
+		printk(KERN_ERR "LCD: NULL pointer in lcd_close\n");
+		return (-ENODEV);
+	}
+
+	down(&p->lcd_sem);
+
+	if (! (ret = do_lcd_release(p)))
+		*pp = NULL;
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+EXPORT_SYMBOL(lcd_close);
+
+static inline loff_t offset_to_row_col(struct lcd_struct *p, loff_t offset)
+{
+	unsigned long _offset = offset;
+	unsigned int vs_cols = p->par->vs_cols;
+
+	gotoxy(p, _offset%vs_cols, _offset/vs_cols);
+
+	return ((p->row*vs_cols)+p->col);
+}
+
+/* Exported function */
+ssize_t lcd_read(struct lcd_struct *p, void *bufp, size_t length, loff_t offset, unsigned int with_attr)
+{
+	ssize_t ret = 0;
+
+	if (! p) {
+		printk(KERN_ERR "LCD: NULL pointer in lcd_read\n");
+		return (-ENODEV);
+	}
+	if (! bufp)
+		return (-EFAULT);
+	if (offset < 0 || offset >= p->fb_size)
+		return (-EINVAL);
+
+	if (length+offset > p->fb_size)
+		length = p->fb_size-offset;
+
+	if (with_attr)
+		set_bit(WITH_ATTR, &p->struct_flags);
+
+	offset_to_row_col(p, offset);
+	ret = do_lcd_read(p, bufp, length);
+
+	if (with_attr)
+		clear_bit(WITH_ATTR, &p->struct_flags);
+
+	return (ret);
+}
+EXPORT_SYMBOL(lcd_read);
+
+/* Exported function */
+ssize_t lcd_write(struct lcd_struct *p, const void *bufp, size_t length, loff_t offset, unsigned int with_attr)
+{
+	ssize_t ret;
+
+	if (! p) {
+		printk(KERN_ERR "LCD: NULL pointer in lcd_write\n");
+		return (-ENODEV);
+	}
+	if (! bufp)
+		return (-EFAULT);
+	if (offset < 0 || offset >= p->fb_size)
+		return (-EINVAL);
+
+	if (with_attr)
+		set_bit(WITH_ATTR, &p->struct_flags);
+
+	offset_to_row_col(p, offset);
+	ret = do_lcd_write(p, bufp, length);
+
+	if (with_attr)
+		clear_bit(WITH_ATTR, &p->struct_flags);
+
+	return (ret);
+}
+EXPORT_SYMBOL(lcd_write);
+
+/* Exported function */
+int lcd_ioctl(struct lcd_struct *p, unsigned int cmd, ...)
+{
+	int ret;
+	unsigned long arg;
+	va_list ap;
+
+	if (! p) {
+		printk(KERN_ERR "LCD: NULL pointer in lcd_ioctl\n");
+		return (-ENODEV);
+	}
+
+	down(&p->lcd_sem);
+	va_start(ap, cmd);
+	arg = va_arg(ap, unsigned long);
+	ret = do_lcd_ioctl(p, cmd, arg);
+	va_end(ap);
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+EXPORT_SYMBOL(lcd_ioctl);
+
+
+
+
+
+/*******************
+ * File operations *
+ *******************/
+static loff_t lcd_fops_llseek(struct file *filp, loff_t offset, int orig)
+{
+	struct lcd_struct *p;
+
+	if (! (p = filp->private_data))
+		return (-ENODEV);
+
+	down(&p->lcd_sem);
+
+	switch (orig) {
+	case 0:
+		filp->f_pos = offset;
+		break;
+
+	case 1:
+		filp->f_pos += offset;
+		break;
+
+	default:
+		up(&p->lcd_sem);
+		return (-EINVAL);     /* SEEK_END not supported */
+	}
+
+	filp->f_pos = offset_to_row_col(p, filp->f_pos);
+
+	up(&p->lcd_sem);
+
+	return (filp->f_pos);
+}
+
+static ssize_t lcd_fops_read(struct file *filp, char *buffer, size_t length, loff_t *offp)
+{
+	ssize_t ret = 0;
+	char *bufp = buffer;
+	struct lcd_struct *p;
+
+	if (! bufp)
+		return (-EFAULT);
+	if (! (p = filp->private_data))
+		return (-ENODEV);
+
+	down(&p->lcd_sem);
+
+	if (*offp < 0 || *offp >= p->fb_size) {
+		up(&p->lcd_sem);
+		return (-EINVAL);
+	}
+
+	if (length+(*offp) > p->fb_size)
+		length = p->fb_size-(*offp);
+
+	while (length) {
+		ret = (length > FLIP_BUF_SIZE ? FLIP_BUF_SIZE : length);
+		if ((ret = do_lcd_read(p, p->flip_buf, ret)) < 0)
+			break;
+		*offp = (p->row*p->par->vs_cols)+p->col;
+		if (copy_to_user(bufp, p->flip_buf, ret)) {
+			ret = -EFAULT;
+			break;
+		}
+		length -= ret;
+		bufp += ret;
+		ret = bufp-buffer;
+		if (length)
+			schedule();
+	}
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+
+static ssize_t lcd_fops_write(struct file *filp, const char *buffer, size_t length, loff_t *offp)
+{
+	ssize_t ret = 0;
+	const char *bufp = buffer;
+	struct lcd_struct *p;
+
+	if (! bufp)
+		return (-EFAULT);
+	if (! (p = filp->private_data))
+		return (-ENODEV);
+
+	down(&p->lcd_sem);
+
+	if (*offp < 0 || *offp >= p->fb_size) {
+		up(&p->lcd_sem);
+		return (-EINVAL);
+	}
+
+	while (length) {
+		ret = (length > FLIP_BUF_SIZE ? FLIP_BUF_SIZE : length);
+		if (copy_from_user(p->flip_buf, bufp, ret)) {
+			ret = -EFAULT;
+			break;
+		}
+		if ((ret = do_lcd_write(p, p->flip_buf, ret)) < 0)
+			break;
+		*offp = (p->row*p->par->vs_cols)+p->col;
+		length -= ret;
+		bufp += ret;
+		ret = bufp-buffer;
+		if (length)
+			schedule();
+	}
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+
+static int lcd_fops_open(struct inode *inop, struct file *filp)
+{
+	unsigned short minor;
+	int ret;
+	struct lcd_struct *p;
+
+	down(&drivers_sem);
+
+	if ((minor = MINOR(inop->i_rdev)) >= minors || (filp->private_data = p = find_lcd_struct(minor)) == NULL) {
+		up(&drivers_sem);
+		return (-ENODEV);
+	}
+
+	down(&p->lcd_sem);
+	up(&drivers_sem);
+
+	ret = do_lcd_open(p);
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+
+static int lcd_fops_release(struct inode *inop, struct file *filp)
+{
+	struct lcd_struct *p;
+	int ret;
+
+	if (! (p = filp->private_data))
+		return (-ENODEV);
+
+	down(&p->lcd_sem);
+
+	if (! (ret = do_lcd_release(p)))
+		filp->private_data = NULL;
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+
+static int lcd_fops_ioctl(struct inode *inop, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	struct lcd_struct *p;
+	int ret;
+
+	if (! (p = filp->private_data))
+		return (-ENODEV);
+
+	down(&p->lcd_sem);
+
+	set_bit(USER_SPACE, &p->struct_flags);
+	ret = do_lcd_ioctl(p, cmd, arg);
+	clear_bit(USER_SPACE, &p->struct_flags);
+
+	up(&p->lcd_sem);
+
+	return (ret);
+}
+
+static struct file_operations lcd_linux_fops = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0)
+	.owner		= THIS_MODULE,
+#endif
+	.llseek		= lcd_fops_llseek,
+	.read		= lcd_fops_read,
+	.write		= lcd_fops_write,
+	.open		= lcd_fops_open,
+	.release	= lcd_fops_release,
+	.ioctl		= lcd_fops_ioctl,
+};
+
+
+
+
+
+/********************************
+ * Init/Cleanup driver routines *
+ ********************************/
+static int do_init_driver(struct lcd_struct *p)
+{
+	int ret, init_level;
+	struct lcd_driver *driver = p->driver;
+	struct lcd_parameters *par = p->par;
+	unsigned int frame_rows = par->cntr_rows*par->num_cntr;
+	unsigned int frame_cols = par->cntr_cols;
+
+	switch ((init_level = INIT_LEVEL(p))) {
+	case 0:
+		if (frame_rows == 0 || frame_cols == 0 || ! par->name) {
+			printk(KERN_ERR "LCD: wrong lcd parameters\n");
+			return (-EINVAL);
+		}
+		if (driver->validate_driver) {
+			if ((ret = driver->validate_driver()) < 0) {
+				printk(KERN_ERR "LCD: validate_driver failed\n");
+				return (-EINVAL);
+			} else if (ret > 0) {
+				set_bit(CAN_DO_COLOR, &p->struct_flags);
+				p->defcolor = 0x07;
+				p->ulcolor = 0x0f;
+				p->halfcolor = 0x08;
+			}
+		}
+		default_attr(p);
+		update_attr(p);
+		p->frame_size = frame_rows*frame_cols;
+		if (par->vs_rows < frame_rows)
+			par->vs_rows = frame_rows;
+		if (par->vs_cols < frame_cols)
+			par->vs_cols = frame_cols;
+		p->fb_size = par->vs_rows*par->vs_cols;
+
+		ret = sizeof(short)*p->fb_size;
+		ret += sizeof(short)*p->frame_size;
+		ret += FLIP_BUF_SIZE;
+		ret += (p->driver->charmap ? 256 : 512);
+		ret += par->cgram_chars*par->cgram_bytes;
+		if ((p->fb = (unsigned short *)vmalloc(ret)) == NULL) {
+			printk(KERN_ERR "LCD: memory allocation failed (vmalloc)\n");
+			return (-ENOMEM);
+		}
+		__memset_short(p->fb, p->erase_char, p->fb_size+p->frame_size);
+
+		p->display = p->fb+p->fb_size;
+		p->flip_buf = (unsigned char *)(p->display+p->frame_size);
+
+		if (! p->driver->charmap) {
+			set_bit(NULL_CHARMAP, &p->struct_flags);
+			p->driver->charmap = p->flip_buf+FLIP_BUF_SIZE;
+			for (ret = 0; ret < 256; ++ret)
+				p->driver->charmap[ret] = ret;
+			p->s_charmap = p->driver->charmap+256;
+		} else
+			p->s_charmap = p->flip_buf+FLIP_BUF_SIZE;
+		memset(p->s_charmap, 0, 256);
+
+		if (par->cgram_chars*par->cgram_bytes) {
+			p->cgram_buffer = p->s_charmap+256;
+			memset(p->cgram_buffer, 0, par->cgram_chars*par->cgram_bytes);
+		} else
+			p->cgram_buffer = NULL;
+
+		p->frame_base = 0;
+		p->row = p->col = 0;
+		p->top = 0;
+		p->bot = par->vs_rows;
+		SET_INIT_LEVEL(p, ++init_level);
+
+	case 1:
+		/* Initialize the communication port */
+		if ((ret = driver->init_port())) {
+			printk(KERN_ERR "LCD: failure while initializing the communication port\n");
+			return (ret);
+		}
+		SET_INIT_LEVEL(p, ++init_level);
+
+	case 2:
+		/* Initialize LCD display */
+		if (driver->init_display && (ret = driver->init_display())) {
+			printk(KERN_ERR "LCD: failure while initializing the display\n");
+			return (ret);
+		}
+
+#ifdef USE_PROC
+		/* Create entries in /proc/lcd/"driver" */
+		if (driver->driver_proc_root)
+			create_driver_proc_entries(p);
+#endif
+		SET_INIT_LEVEL(p, ++init_level);
+	}
+
+	return (0);
+}
+
+static int do_cleanup_driver(struct lcd_struct *p)
+{
+	int ret, init_level;
+	struct lcd_driver *driver = p->driver;
+
+	switch ((init_level = INIT_LEVEL(p))) {
+	case 3:
+#ifdef USE_PROC
+		if (driver->driver_proc_root)
+			remove_driver_proc_entries(p);
+#endif
+		if (driver->cleanup_display && (ret = driver->cleanup_display())) {
+			printk(KERN_ERR "LCD: failure while cleaning the display\n");
+			return (ret);
+		}
+		SET_INIT_LEVEL(p, --init_level);
+
+	case 2:
+		if ((ret = driver->cleanup_port())) {
+			printk(KERN_ERR "LCD: failure while cleaning the communication port\n");
+			return (ret);
+		}
+		SET_INIT_LEVEL(p, --init_level);
+
+	case 1:
+		if (test_bit(NULL_CHARMAP, &p->struct_flags)) {
+			p->driver->charmap = NULL;
+			clear_bit(NULL_CHARMAP, &p->struct_flags);
+		}
+		vfree(p->fb);
+		p->fb = NULL;
+		SET_INIT_LEVEL(p, --init_level);
+	}
+
+	return (0);
+}
+
+static int init_driver(struct lcd_struct *p)
+{
+	int ret;
+
+	if ((ret = do_init_driver(p))) {
+		do_cleanup_driver(p);
+		printk(KERN_ERR "LCD: init_driver failed\n");
+	}
+
+	return (ret);
+}
+
+static int cleanup_driver(struct lcd_struct *p)
+{
+	int ret;
+
+	if ((ret = do_cleanup_driver(p))) {
+		do_init_driver(p);
+		printk(KERN_ERR "LCD: cleanup_driver failed\n");
+	}
+
+	return (ret);
+}
+
+
+
+
+
+/********************************
+ * Init/Cleanup module routines *
+ ********************************/
+static int __init lcd_linux_init_module(void)
+{
+	int ret;
+
+	if (! minors || minors > 256)
+		minors = LCD_MINORS;
+
+	init_MUTEX(&drivers_sem);
+
+	if ((ret = register_chrdev(major, LCD_LINUX_STRING, &lcd_linux_fops)) < 0) {
+		printk(KERN_ERR "LCD: register_chrdev failed\n");
+		return (ret);
+	}
+	if (major == 0)
+		major = ret;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+	if (IS_ERR((lcd_linux_class = class_create(THIS_MODULE, "lcd")))) {
+		ret = PTR_ERR(lcd_linux_class);
+		unregister_chrdev(major, LCD_LINUX_STRING);
+		return (ret);
+	}
+#endif
+
+#ifdef USE_PROC
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+	if ((lcd_proc_root = proc_mkdir("lcd", NULL)) == NULL)
+#else
+	if ((lcd_proc_root = proc_mkdir("lcd", &proc_root)) == NULL)
+#endif
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/\n");
+	else if (create_proc_read_entry("drivers", 0, lcd_proc_root, proc_registered_drivers, NULL) == NULL)
+		printk(KERN_ERR "LCD: cannot create /proc/lcd/drivers\n");
+#endif
+
+	printk(KERN_INFO "LCD: --> LCD-Linux " LCD_LINUX_VERSION " <--\n");
+	printk(KERN_INFO "LCD: --> Mattia Jona-Lasinio <mjona@users.sourceforge.net> <--\n" );
+
+
+	return (0);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0)
+static void __exit lcd_linux_cleanup_module(void)
+#else
+/* __exit is not defined in 2.2.x kernels */
+static void lcd_linux_cleanup_module(void)
+#endif
+{
+#ifdef USE_PROC
+	if (lcd_proc_root) {
+		remove_proc_entry("drivers", lcd_proc_root);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+		remove_proc_entry("lcd", NULL);
+#else
+		remove_proc_entry("lcd", &proc_root);
+#endif
+	}
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13)
+	class_destroy(lcd_linux_class);
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23)
+	unregister_chrdev(major, LCD_LINUX_STRING);
+#else
+	if (unregister_chrdev(major, LCD_LINUX_STRING))
+		printk(KERN_ERR "LCD: unregister_chrdev failed\n");
+#endif
+}
+
+module_init(lcd_linux_init_module)
+module_exit(lcd_linux_cleanup_module)
Index: linux-2.6.30.9/include/linux/hd44780.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/include/linux/hd44780.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,46 @@
+/* hd44780.h
+ *
+ *
+ * LCD-Linux:
+ * Driver for HD44780 compatible displays connected to the parallel port.
+ *
+ * HD44780 header file.
+ *
+ * Copyright (C) 2004 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ *
+ *  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
+ *
+ */
+
+#ifndef HD44780_H
+#define HD44780_H
+
+#include <linux/lcd-linux.h>
+
+#define HD44780_VERSION		LCD_LINUX_VERSION	/* Version number */
+#define HD44780_STRING		"hd44780"
+
+#define HD44780_MINOR		0	/* Minor number for the hd44780 driver */
+
+
+/* flags */
+#define HD44780_CHECK_BF	0x00000001	/* Do busy flag checking */
+#define HD44780_4BITS_BUS	0x00000002	/* Set the bus length to 4 bits */
+#define HD44780_5X10_FONT	0x00000004	/* Use 5x10 dots fonts */
+
+/* IOCTLs */
+#define HD44780_READ_AC		_IOR(LCD_MAJOR, 0x00, unsigned char *)
+
+#endif /* HD44780 included */
Index: linux-2.6.30.9/include/linux/lcd-linux.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.30.9/include/linux/lcd-linux.h	2009-11-24 02:01:42.000000000 +0100
@@ -0,0 +1,151 @@
+/* lcd-linux.h
+ *
+ *
+ * Software layer to drive LCD displays under Linux.
+ *
+ * External interface header file.
+ *
+ * Copyright (C) 2005 - 2007  Mattia Jona-Lasinio (mjona@users.sourceforge.net)
+ *
+ *  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
+ *
+ */
+
+#ifndef LCD_LINUX_H
+#define LCD_LINUX_H
+
+#ifndef LCD_LINUX_MAIN
+#warning
+#warning LCD-Linux is still in development stage and
+#warning aims at speed and optimization. For these
+#warning reasons there is no guarantee of backward
+#warning compatibility between different LCD-Linux
+#warning versions. Be sure to use the lcd-linux.h
+#warning file of the same version as the module.
+#warning "http://lcd-linux.sourceforge.net/"
+#warning
+#endif
+
+#define LCD_LINUX_VERSION	"0.13.6"	/* Version number */
+#define LCD_LINUX_STRING	"lcd"
+
+#define LCD_MAJOR		120	/* Major number for this device
+					 * Set this to 0 for dynamic allocation
+					 */
+#define LCD_MINORS		8	/* Minors allocated for LCD-Linux*/
+
+#include <linux/types.h>
+
+#define	str(s) #s
+#define	string(s) str(s)
+
+struct lcd_parameters {
+	const char	*name;		/* Driver's name */
+	unsigned long	flags;		/* Flags (see documentation) */
+	unsigned short	minor;		/* Minor number of the char device */
+	unsigned short	tabstop;	/* Tab character length */
+	unsigned short	num_cntr;	/* Controllers to drive */
+	unsigned short	cntr_rows;	/* Rows per controller */
+	unsigned short	cntr_cols;	/* Display columns */
+	unsigned short	vs_rows;	/* Virtual screen rows */
+	unsigned short	vs_cols;	/* Virtual screen columns */
+	unsigned short	cgram_chars;	/* Number of user definable characters */
+	unsigned short	cgram_bytes;	/* Number of bytes required to define a
+					 * user definable character */
+	unsigned char	cgram_char0;	/* Ascii of first user definable character */
+};
+
+/* IOCTLs */
+#include <asm/ioctl.h>
+#define LCDL_SET_PARAM		_IOW(LCD_MAJOR, 0x80, struct lcd_parameters *)
+#define LCDL_GET_PARAM		_IOR(LCD_MAJOR, 0x81, struct lcd_parameters *)
+#define LCDL_CHARSUBST		_IOW(LCD_MAJOR, 0x82, unsigned char *)
+#define LCDL_RAW_MODE		_IOW(LCD_MAJOR, 0x83, unsigned int)
+#define LCDL_RESET_CHARMAP	_IO(LCD_MAJOR, 0x84)
+#define LCDL_SAVE_CHARMAP	_IO(LCD_MAJOR, 0x85)
+#define LCDL_RESTORE_CHARMAP	_IO(LCD_MAJOR, 0x86)
+#define LCDL_SWAP_CHARMAP	_IO(LCD_MAJOR, 0x87)
+#define LCDL_CLEAR_DISP		_IO(LCD_MAJOR, 0x88)
+#define LCDL_SET_CGRAM_CHAR	_IOW(LCD_MAJOR, 0x89, unsigned char *)
+#define LCDL_GET_CGRAM_CHAR	_IOR(LCD_MAJOR, 0x8a, unsigned char *)
+#define LCDL_SET_CHARMAP	_IOW(LCD_MAJOR, 0x8b, unsigned char *)
+#define LCDL_GET_CHARMAP	_IOR(LCD_MAJOR, 0x8c, unsigned char *)
+#define LCDL_MEMSET		_IOW(LCD_MAJOR, 0x8d, unsigned int *)
+#define LCDL_MEMMOVE		_IOW(LCD_MAJOR, 0x8e, unsigned int *)
+
+
+
+#ifdef __KERNEL__ /* The rest is for kernel only */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+
+struct lcd_driver {
+	void	(*read_char)(unsigned int offset, unsigned short *data);
+	void	(*read_cgram_char)(unsigned char index, unsigned char *pixmap);
+	void	(*write_char)(unsigned int offset, unsigned short data);
+	void	(*write_cgram_char)(unsigned char index, unsigned char *pixmap);
+	void	(*clear_display)(void);
+	void	(*address_mode)(int mode);
+	int	(*validate_driver)(void);
+	int	(*init_display)(void);
+	int	(*cleanup_display)(void);
+	int	(*init_port)(void);
+	int	(*cleanup_port)(void);
+	int	(*handle_custom_char)(unsigned int data);
+	int	(*handle_custom_ioctl)(unsigned int cmd, unsigned long arg, unsigned int arg_in_userspace);
+
+	/* The character map to be used */
+	unsigned char *charmap;
+
+	/* The root where the driver can create its own proc files.
+	 * Will be filled by the lcd-linux layer.
+	 */
+	struct proc_dir_entry *driver_proc_root;
+
+	/* Set this field to 'driver_module_init' or call lcd_driver_setup
+	 * just before registering the driver with lcd_register_driver.
+	 */
+	struct module *driver_module;
+};
+
+#ifdef MODULE
+#define driver_module_init	THIS_MODULE
+#else
+#define driver_module_init	NULL
+#endif
+
+/* Always call lcd_driver_setup just before registering the driver
+ * with lcd_register_driver.
+ */
+static inline void lcd_driver_setup(struct lcd_driver *p)
+{
+	p->driver_module = driver_module_init;
+}
+
+/* External interface */
+struct lcd_struct;
+int lcd_register_driver(struct lcd_driver *drv, struct lcd_parameters *par);
+int lcd_unregister_driver(struct lcd_driver *drv, struct lcd_parameters *par);
+int lcd_open(unsigned short minor, struct lcd_struct **lcd);
+int lcd_close(struct lcd_struct **lcd);
+int lcd_ioctl(struct lcd_struct *lcd, unsigned int cmd, ...);
+ssize_t lcd_write(struct lcd_struct *lcd, const void *buffer, size_t length, loff_t offset, unsigned int);
+ssize_t lcd_read(struct lcd_struct *lcd, void *buffer, size_t length, loff_t offset, unsigned int);
+
+#endif /* __KERNEL__ */
+
+#endif /* External interface included */
