diff -urN linux-2.4.26/Documentation/Configure.help linux-2.4.26-panel/Documentation/Configure.help --- linux-2.4.26/Documentation/Configure.help Sun Apr 11 12:05:45 2004 +++ linux-2.4.26-panel/Documentation/Configure.help Sun May 9 23:07:24 2004 @@ -5855,6 +5855,16 @@ transfer modes. Also say Y if you want device ID information to appear in /proc/sys/dev/parport/*/autoprobe*. It is safe to say N. +Parallel port LCD Panel + Keypad +CONFIG_PANEL + Say Y here if you have an HD44780 or KS-0074 LCD connected to your + parallel port. This driver also features a 6-key keypad, and a + smartcard reader. The LCD is accessible through the /dev/lcd char + device (10, 156), the keypad through /dev/keypad (10, 185), and the + smartcard through /dev/smartcard (10, 186). Both require misc device + to be enabled. This code can also be compiled as a module. If you + don't understand what all this is about, say N. + Enable loadable module support CONFIG_MODULES Kernel modules are small pieces of compiled code which can be diff -urN linux-2.4.26/drivers/char/Config.in linux-2.4.26-panel/drivers/char/Config.in --- linux-2.4.26/drivers/char/Config.in Fri Feb 20 07:38:28 2004 +++ linux-2.4.26-panel/drivers/char/Config.in Sun May 9 23:07:24 2004 @@ -179,6 +179,7 @@ fi dep_tristate 'Support for user-space parallel port device drivers' CONFIG_PPDEV $CONFIG_PARPORT dep_tristate 'Texas Instruments parallel link cable support' CONFIG_TIPAR $CONFIG_PARPORT + dep_tristate 'Parallel port LCD/Keypad Panel support' CONFIG_PANEL $CONFIG_PARPORT fi if [ "$CONFIG_PPC64" = "y" ] ; then diff -urN linux-2.4.26/drivers/char/Makefile linux-2.4.26-panel/drivers/char/Makefile --- linux-2.4.26/drivers/char/Makefile Fri Feb 20 07:38:28 2004 +++ linux-2.4.26-panel/drivers/char/Makefile Sun May 9 23:07:24 2004 @@ -229,6 +229,7 @@ obj-$(CONFIG_PRINTER) += lp.o obj-$(CONFIG_TIPAR) += tipar.o obj-$(CONFIG_OBMOUSE) += obmouse.o +obj-$(CONFIG_PANEL) += panel.o ifeq ($(CONFIG_INPUT),y) obj-y += joystick/js.o diff -urN linux-2.4.26/drivers/char/panel.c linux-2.4.26-panel/drivers/char/panel.c --- linux-2.4.26/drivers/char/panel.c Thu Jan 1 01:00:00 1970 +++ linux-2.4.26-panel/drivers/char/panel.c Sun May 9 23:35:41 2004 @@ -0,0 +1,1747 @@ +/* + * Front panel driver for Linux - 20000810 - Willy Tarreau - willy@meta-x.org. + * It includes and LCD display (/dev/lcd), a 4-key keypad (/dev/keypad), and a + * smart card reader (/dev/smartcard). + * + * the driver skeleton has been stolen from nvram.c which was clearly written. + * + * Changes: + * 2000/08/10 + * - keypad now scrolls LCD when not opened + * - released 0.5.1 + * 2000/08/10 + * - bug fixes + * - released 0.5.2 + * 2000/08/10 + * - Reposition LCD when opening /dev/keypad (WIP) + * - Released 0.5.3 + * 2001/02/04 + * - Start of port to kernel 2.4.1 + * 2001/03/11 + * - implementation of a 24-key keyboard scanner with less electronics + * around, thus allowing to release the IRQ line. + * 2001/03/25 + * - the driver now compiles and works with both 2.4.2 and 2.2.18 kernels + * 2001/04/22 + * - implementation of KS0074-based serial LCD (load with lcd_enabled=2 and lcd_hwidth=16) + * 2001/04/29 + * - added back-light support, released 0.7.1 + * 2001/05/01 + * - added charset conversion table for ks0074, released 0.7.2 + * 2001/05/08 + * - start of rewriting towards v0.8 + * 2001/10/21 + * - replaced linux/malloc.h with linux/slab.h to be 2.4 compliant + * - definition of the multi-layer input system with its naming scheme + * - profile support for simplified configuration + * 2001/10/28 + * - smartcard now works for telecards. /dev/smartcard returns the card serial number + * 2001/11/10 + * - fix too short sleep for lcd_clear + * 2004/05/09 + * - add support for hantronix LCD modules (RS on SELECTIN instead of AUTOLF) + * (load with lcd_enabled=3 or profile=3) + * + * TODO: + * - document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs) + * - make the LCD a part of a virtual screen of Vx*Vy + * - make the inputs list smp-safe + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include // previously +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* smartcard length */ +#define SMARTCARD_BYTES 64 +#define LCD_MINOR 156 +#define KEYPAD_MINOR 185 +#define SMARTCARD_MINOR 186 + +#define PANEL_VERSION "0.8.2" + +#define LCD_MAXBYTES 256 /* max burst write */ + +#define SMARTCARD_LOGICAL_DETECTOR "S6" /* D6 wired to SELECT = card inserted */ + +#define KEYPAD_BUFFER 64 +#define INPUT_POLL_TIME (HZ/25) /* poll the keyboard this every second */ +#define KEYPAD_MAX_REL (1) /* ignore it if a key gets released during this times INPUT_POLL_TIME */ +#define KEYPAD_MIN_PRS (1) /* a key needs to be held during this times INPUT_POLL_TIME */ +#define KEYPAD_REP_START (12) /* a key starts to repeat after this times INPUT_POLL_TIME */ +#define KEYPAD_REP_DELAY (2) /* a key repeats this times INPUT_POLL_TIME */ + +#define FLASH_LIGHT_TEMPO (200) /* keep the light on this times INPUT_POLL_TIME for each flash */ + +#define PNL_PBUSY 0x80 /* inverted input, active low */ +#define PNL_PACK 0x40 /* direct input, active low */ +#define PNL_POUTPA 0x20 /* direct input, active high */ +#define PNL_PSELECD 0x10 /* direct input, active high */ +#define PNL_PERRORP 0x08 /* direct input, active low */ + +/* converts an r_str() input to an active high, bits string : 000BAOSE */ +#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) + +#define PNL_PINTEN 0x10 /* high to read data in or-ed with data out */ +#define PNL_PSELECP 0x08 /* inverted output, active low */ +#define PNL_PINITP 0x04 /* direct output, active low */ +#define PNL_PAUTOLF 0x02 /* inverted output, active low */ +#define PNL_PSTROBE 0x01 /* inverted output */ + +#define PNL_PD0 0x01 +#define PNL_PD1 0x02 +#define PNL_PD2 0x04 +#define PNL_PD3 0x08 +#define PNL_PD4 0x10 +#define PNL_PD5 0x20 +#define PNL_PD6 0x40 +#define PNL_PD7 0x80 + +/* some smartcard-specific signals */ +#define PNL_SC_IO PNL_PD1 /* Warning! inverted output, 0=highZ */ +#define PNL_SC_RST PNL_PD2 +#define PNL_SC_CLK PNL_PD3 +#define PNL_SC_RW PNL_PD4 +#define PNL_SC_ENA PNL_PINITP +#define PNL_SC_IOR PNL_PACK +#define PNL_SC_BITS (PNL_SC_IO | PNL_SC_RST | PNL_SC_CLK | PNL_SC_RW) + +#define LCD_FLAG_S 0x0001 +#define LCD_FLAG_ID 0x0002 +#define LCD_FLAG_B 0x0004 /* blink on */ +#define LCD_FLAG_C 0x0008 /* cursor on */ +#define LCD_FLAG_D 0x0010 /* display on */ +#define LCD_FLAG_F 0x0020 /* large font mode */ +#define LCD_FLAG_N 0x0040 /* 2-rows mode */ +#define LCD_FLAG_L 0x0080 /* backlight enabled */ + +#define LCD_ESCAPE_LEN 16 /* 16 chars max for an LCD escape command */ +#define LCD_ESCAPE_CHAR 27 /* use char 27 for escape command */ + +/* macros to simplify use of the parallel port */ +#define r_ctr(x) (parport_read_control((x)->port)) +#define r_dtr(x) (parport_read_data((x)->port)) +#define r_str(x) (parport_read_status((x)->port)) +#define w_ctr(x,y) do { parport_write_control((x)->port, (y)); } while (0) +#define w_dtr(x,y) do { parport_write_data((x)->port, (y)); } while (0) + +/* this defines which bits are to be used and which ones to be ignored */ +static __u8 scan_mask_o = 0; /* logical or of the output bits involved in the scan matrix */ +static __u8 scan_mask_i = 0; /* logical or of the input bits involved in the scan matrix */ + +typedef __u64 pmask_t; + +enum input_type { + INPUT_TYPE_STD, + INPUT_TYPE_KBD, +}; + +enum input_state { + INPUT_ST_LOW, + INPUT_ST_RISING, + INPUT_ST_HIGH, + INPUT_ST_FALLING, +}; + +struct logical_input { + struct list_head list; + pmask_t mask; + pmask_t value; + enum input_type type; + enum input_state state; + __u8 rise_time, fall_time; + __u8 rise_timer, fall_timer, high_timer; + + union { + struct { /* this structure is valid when type == INPUT_TYPE_STD */ + void(*press_fct)(int); + void(*release_fct)(int); + int press_data; + int release_data; + } std; + struct { /* this structure is valid when type == INPUT_TYPE_KBD */ + /* strings can be full-length (ie. non null-terminated) */ + char press_str[sizeof(void *) + sizeof (int)]; + char repeat_str[sizeof(void *) + sizeof (int)]; + char release_str[sizeof(void *) + sizeof (int)]; + } kbd; + } u; +}; + +LIST_HEAD(logical_inputs); /* list of all defined logical inputs */ + +/* physical contacts history + * Physical contacts are a 45 bits string of 9 groups of 5 bits each. + * The 8 lower groups correspond to output bits 0 to 7, and the 9th group + * corresponds to the ground. + * Within each group, bits are stored in the same order as read on the port : + * BAPSE (busy=4, ack=3, paper empty=2, select=1, error=0). + * So, each __u64 (or pmask_t) is represented like this : + * 0000000000000000000BAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSE + * <-----unused------> + */ +static pmask_t phys_read; /* what has just been read from the I/O ports */ +static pmask_t phys_read_prev; /* previous phys_read */ +static pmask_t phys_curr; /* stabilized phys_read (phys_read|phys_read_prev) */ +static pmask_t phys_prev; /* previous phys_curr */ +static char inputs_stable = 0; /* 0 means that at least one logical signal needs be computed */ + +/* these variables are specific to the smartcard */ +static __u8 smartcard_data[SMARTCARD_BYTES]; +static int smartcard_ptr = 0; /* pointer to half bytes in smartcard_data */ + +/* these variables are specific to the keypad */ +static char keypad_buffer[KEYPAD_BUFFER]; +static int keypad_buflen = 0; +static int keypad_start = 0; +static char keypressed = 0; +static wait_queue_head_t keypad_read_wait; +static wait_queue_head_t smartcard_read_wait; + +/* lcd-specific variables */ +static unsigned long int lcd_flags = 0; /* contains the LCD config state */ +static unsigned long int lcd_addr_x = 0; /* contains the LCD X offset */ +static unsigned long int lcd_addr_y = 0; /* contains the LCD Y offset */ +static char lcd_escape[LCD_ESCAPE_LEN+1]; /* current escape sequence, 0 terminated */ +static int lcd_escape_len = -1; /* not in escape state. >=0 = escape cmd len */ + +static int lcd_height = -1; +static int lcd_width = -1; +static int lcd_hwidth = -1; /* hardware buffer width (usually 64) */ +static int lcd_bwidth = -1; /* internal buffer width (usually 40) */ + +/* global variables */ +static int smartcard_open_cnt = 0; /* #times opened */ +static int keypad_open_cnt = 0; /* #times opened */ +static int lcd_open_cnt = 0; /* #times opened */ + +static struct pardevice *pprt = NULL; +static int parport = -1; +static int lcd_enabled = -1; +static int keypad_enabled = -1; +static int smartcard_enabled = -1; +static int profile = 0; + +static int light_tempo = 0; + +static char lcd_must_clear = 0; +static char lcd_left_shift = 0; +static char init_in_progress = 0; + +static void(*lcd_write_cmd)(int) = NULL; +static void(*lcd_write_data)(int) = NULL; +static void(*lcd_clear_fast)(void) = NULL; + +static spinlock_t pprt_lock = SPIN_LOCK_UNLOCKED; +static struct timer_list scan_timer; + +#ifdef MODULE + +MODULE_PARM(parport, "i"); +MODULE_PARM(lcd_height, "i"); +MODULE_PARM(lcd_width, "i"); +MODULE_PARM(lcd_bwidth, "i"); +MODULE_PARM(lcd_hwidth, "i"); +MODULE_PARM(lcd_enabled, "i"); /* 1 = parallel 8 bits, 2 = serial KS0074, 3 = parallel 8 bits, RS on SELECTIN */ +MODULE_PARM(keypad_enabled, "i"); +MODULE_PARM(smartcard_enabled, "i"); +MODULE_PARM(profile, "i"); /* 0=default, 1=//, 2=serial+keypad */ + +#endif + +static unsigned char *lcd_char_conv = NULL; + +/* for some LCD drivers (ks0074) we need a charset conversion table. */ +static unsigned char lcd_char_conv_ks0074[256] = { + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + /* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0xa2, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + /* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + /* 0x40 */ 0xa0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + /* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xfa, 0xfb, 0xfc, 0x1d, 0xc4, + /* 0x60 */ 0x96, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + /* 0x70 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xfd, 0xfe, 0xff, 0xce, 0x20, + /* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + /* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + /* 0xA0 */ 0x20, 0x40, 0xb1, 0xa1, 0x24, 0xa3, 0xfe, 0x5f, 0x22, 0xc8, 0x61, 0x14, 0x97, 0x2d, 0xad, 0x96, + /* 0xB0 */ 0x80, 0x8c, 0x82, 0x83, 0x27, 0x8f, 0x86, 0xdd, 0x2c, 0x81, 0x6f, 0x15, 0x8b, 0x8a, 0x84, 0x60, + /* 0xC0 */ 0xe2, 0xe2, 0xe2, 0x5b, 0x5b, 0xae, 0xbc, 0xa9, 0xc5, 0xbf, 0xc6, 0xf1, 0xe3, 0xe3, 0xe3, 0xe3, + /* 0xD0 */ 0x44, 0x5d, 0xa8, 0xe4, 0xec, 0xec, 0x5c, 0x78, 0xab, 0xa6, 0xe5, 0x5e, 0x5e, 0xe6, 0xaa, 0xbe, + /* 0xE0 */ 0x7f, 0xe7, 0xaf, 0x7b, 0x7b, 0xaf, 0xbd, 0xc8, 0xa4, 0xa5, 0xc7, 0xf6, 0xa7, 0xe8, 0x69, 0x69, + /* 0xF0 */ 0xed, 0x7d, 0xa8, 0xe4, 0xec, 0x5c, 0x5c, 0x25, 0xac, 0xa6, 0xea, 0xef, 0x7e, 0xeb, 0xb2, 0x79, +}; + +char old_keypad_profile[][4][8] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4", "Esc\n", "Esc\n", ""}, + {"S5", "Ret\n", "Ret\n", ""}, + {"","",""} +}; + +char new_keypad_profile[][4][8] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4s5", "", "Esc\n", "Esc\n"}, + {"s4S5", "", "Ret\n", "Ret\n"}, + {"S4S5", "Help\n", "", ""}, + /* FIXME: change this for a normal signal */ + {"","",""} +}; + +static char (*keypad_profile)[4][8] = old_keypad_profile; + +static void init_scan_timer(void); + +/* sleeps that many milliseconds with a reschedule */ +static void long_sleep(int ms) { + + if (in_interrupt()) + mdelay(ms); + else { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout((ms*HZ+999)/1000); + } +} + + +/* send a serial byte to the LCD panel. The caller is responsible for locking if needed. */ +static void lcd_send_serial(int byte) { + int bit; + + /* the data bit is set on D0, and the clock on STROBE. + * LCD reads D0 on STROBE's rising edge. + */ + for (bit = 0; bit < 8; bit++) { + w_ctr(pprt, (r_ctr(pprt) | PNL_PSTROBE)); /* set CLK low */ + /* present the data to the data port */ + w_dtr(pprt, (r_dtr(pprt) & 0xFE) | (byte & 1)); + udelay(2); /* maintain the data during 2 us before CLK up */ + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PSTROBE)); + udelay(1); /* maintain the strobe during 1 us */ + byte >>= 1; + } +} + +/* turn the backlight on or off */ +static void lcd_backlight(int on) { + if (lcd_enabled != 2) /* this needs a serial LCD at the moment */ + return; + + /* The backlight is activated by seting the AUTOFEED line to +5V */ + spin_lock(&pprt_lock); + if (on) + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PAUTOLF)); /* set to +5V (on) */ + else + w_ctr(pprt, (r_ctr(pprt) | PNL_PAUTOLF)); /* set to 0V (off) */ + spin_unlock(&pprt_lock); +} + +/* send a command to the LCD panel in serial mode */ +static void lcd_write_cmd_s(int cmd) { + spin_lock(&pprt_lock); + lcd_send_serial(0x1F); /* R/W=W, RS=0 */ + lcd_send_serial(cmd & 0x0F); + lcd_send_serial((cmd >> 4) & 0x0F); + udelay(40); /* the shortest command takes at least 40 us */ + spin_unlock(&pprt_lock); +} + +/* send data to the LCD panel in serial mode */ +static void lcd_write_data_s(int data) { + spin_lock(&pprt_lock); + lcd_send_serial(0x5F); /* R/W=W, RS=1 */ + lcd_send_serial(data & 0x0F); + lcd_send_serial((data >> 4) & 0x0F); + udelay(40); /* the shortest data takes at least 40 us */ + spin_unlock(&pprt_lock); +} + +/* send a command to the LCD panel in 8 bits parallel mode */ +static void lcd_write_cmd_p8(int cmd) { + spin_lock(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, cmd); + udelay(20); /* maintain the data during 20 us before the strobe */ + + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PSTROBE) | PNL_PAUTOLF); + udelay(40); /* maintain the strobe during 40 us */ + + w_ctr(pprt, (r_ctr(pprt) | PNL_PSTROBE) | PNL_PAUTOLF); + + udelay(120); /* the shortest command takes at least 120 us */ + spin_unlock(&pprt_lock); +} + +/* send data to the LCD panel in 8 bits parallel mode */ +static void lcd_write_data_p8(int data) { + spin_lock(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(20); /* maintain the data during 20 us before the strobe */ + + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PSTROBE) & ~PNL_PAUTOLF); + udelay(40); /* maintain the strobe during 40 us */ + + w_ctr(pprt, (r_ctr(pprt) | PNL_PSTROBE) & ~PNL_PAUTOLF); + udelay(45); /* the shortest data takes at least 45 us */ + spin_unlock(&pprt_lock); +} + +/* send a command to the LCD panel in 8 bits parallel mode, second mode */ +static void lcd_write_cmd_p8b(int cmd) { + spin_lock(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, cmd); + udelay(20); /* maintain the data during 20 us before the strobe */ + + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PSTROBE) | PNL_PSELECP); + udelay(40); /* maintain the strobe during 40 us */ + + w_ctr(pprt, (r_ctr(pprt) | PNL_PSTROBE) | PNL_PSELECP); + + udelay(120); /* the shortest command takes at least 120 us */ + spin_unlock(&pprt_lock); +} + +/* send data to the LCD panel in 8 bits parallel mode, second mode */ +static void lcd_write_data_p8b(int data) { + spin_lock(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(20); /* maintain the data during 20 us before the strobe */ + + w_ctr(pprt, (r_ctr(pprt) & ~PNL_PSTROBE) & ~PNL_PSELECP); + udelay(40); /* maintain the strobe during 40 us */ + + w_ctr(pprt, (r_ctr(pprt) | PNL_PSTROBE) & ~PNL_PSELECP); + udelay(45); /* the shortest data takes at least 45 us */ + spin_unlock(&pprt_lock); +} + +static void lcd_gotoxy(void) { + lcd_write_cmd(0x80 /* set DDRAM address */ + | (lcd_addr_y ? lcd_hwidth : 0) + /* we force the cursor to stay at the end of the line if it wants to go farther */ + | ((lcd_addr_x < lcd_bwidth) ? lcd_addr_x & (lcd_hwidth-1) : lcd_bwidth - 1)); +} + +static void lcd_print(char c) { + if (lcd_addr_x < lcd_bwidth) { + if (lcd_char_conv != NULL) + c = lcd_char_conv[(unsigned char)c]; + lcd_write_data(c); + lcd_addr_x++; + } + /* prevents the cursor from wrapping onto the next line */ + if (lcd_addr_x == lcd_bwidth) { + lcd_gotoxy(); + } +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_s(void) { + int pos; + lcd_addr_x = lcd_addr_y = 0; + lcd_gotoxy(); + + spin_lock(&pprt_lock); + for (pos=0; pos> 4) & 0x0F); + udelay(40); /* the shortest data takes at least 40 us */ + } + spin_unlock(&pprt_lock); + + lcd_addr_x = lcd_addr_y = 0; + lcd_gotoxy(); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_p8(void) { + int pos; + lcd_addr_x = lcd_addr_y = 0; + lcd_gotoxy(); + + spin_lock(&pprt_lock); + for (pos=0; pos 1) ? LCD_FLAG_N : 0) + | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B; + + long_sleep(20); /* wait 20 ms after power-up for the paranoid */ + + lcd_write_cmd(0x30); /* 8bits, 1 line, small fonts */ + long_sleep(10); + lcd_write_cmd(0x30); /* 8bits, 1 line, small fonts */ + long_sleep(10); + lcd_write_cmd(0x30); /* 8bits, 1 line, small fonts */ + long_sleep(10); + + lcd_write_cmd(0x30 /* set font height and lines number */ + | ((lcd_flags & LCD_FLAG_F)?4:0) + | ((lcd_flags & LCD_FLAG_N)?8:0) + ); + long_sleep(10); + + lcd_write_cmd(0x08); /* display off, cursor off, blink off */ + + long_sleep(10); + + lcd_write_cmd(0x08 /* set display mode */ + | ((lcd_flags & LCD_FLAG_D)?4:0) + | ((lcd_flags & LCD_FLAG_C)?2:0) + | ((lcd_flags & LCD_FLAG_B)?1:0) + ); + + lcd_backlight((lcd_flags & LCD_FLAG_L) ? 1 : 0); + + long_sleep(10); + + lcd_write_cmd(0x06); /* entry mode set : increment, cursor shifting */ + + lcd_clear_display(); +} + +/* + * The are the file operation function for user access to /dev/lcd + * This function can also be called from inside the kernel, by + * setting file and ppos to NULL. + * + */ + +static ssize_t lcd_write(struct file * file, + const char * buf, size_t count, loff_t *ppos ) { + + const char *tmp = buf; + char c; + + for( ; count-- > 0; (ppos?(*ppos)++:0), ++tmp ) { + if (!in_interrupt() && ((count & 0x1f) == 0x1f)) { + schedule(); /* let's be a little nice with other processes that need some CPU */ + } + if (ppos == NULL && file == NULL) + c = *tmp; /* let's not use get_user() from the kernel ! */ + else if (get_user( c, tmp )) + return -EFAULT; + + /* first, we'll test if we're in escape mode */ + if ((c != '\n') && lcd_escape_len >= 0) { /* yes, let's add this char to the buffer */ + lcd_escape[lcd_escape_len++] = c; + lcd_escape[lcd_escape_len] = 0; + } + else { + lcd_escape_len = -1; /* aborts any previous escape sequence */ + + switch (c) { + case LCD_ESCAPE_CHAR: /* start of an escape sequence */ + lcd_escape_len = 0; + lcd_escape[lcd_escape_len] = 0; + break; + case '\b': /* go back one char and clear it */ + if (lcd_addr_x > 0) { + if (lcd_addr_x < lcd_bwidth) /* check if we're not at the end of the line */ + lcd_write_cmd(0x10); /* back one char */ + lcd_addr_x--; + } + lcd_write_data(' '); /* replace with a space */ + lcd_write_cmd(0x10); /* back one char again */ + break; + case '\014': /* quickly clear the display */ + lcd_clear_fast(); + break; + case '\n': /* flush the remainder of the current line and go to the + beginning of the next line */ + for (; lcd_addr_x= 2) { /* minimal length for an escape command */ + int processed = 0; /* 1 means the command has been processed */ + + if (!strcmp(lcd_escape,"[2J")) { /* Clear the display */ + lcd_clear_fast(); /* clear display */ + processed = 1; + } + else if (!strcmp(lcd_escape,"[H")) { /* Cursor to home */ + lcd_addr_x = lcd_addr_y = 0; + lcd_gotoxy(); + processed = 1; + } + /* codes starting with ^[[L */ + else if ((lcd_escape_len >= 3) && + (lcd_escape[0]=='[') && (lcd_escape[1]=='L')) { /* LCD special codes */ + + char *esc = lcd_escape + 2; + int oldflags = lcd_flags; + + /* check for display mode flags */ + switch (*esc) { + case 'D' : /* Display ON */ + lcd_flags |= LCD_FLAG_D; + processed = 1; + break; + case 'd' : /* Display OFF */ + lcd_flags &= ~LCD_FLAG_D; + processed = 1; + break; + case 'C' : /* Cursor ON */ + lcd_flags |= LCD_FLAG_C; + processed = 1; + break; + case 'c' : /* Cursor OFF */ + lcd_flags &= ~LCD_FLAG_C; + processed = 1; + break; + case 'B' : /* Blink ON */ + lcd_flags |= LCD_FLAG_B; + processed = 1; + break; + case 'b' : /* Blink OFF */ + lcd_flags &= ~LCD_FLAG_B; + processed = 1; + break; + case '+' : /* Back light ON */ + lcd_flags |= LCD_FLAG_L; + processed = 1; + break; + case '-' : /* Back light OFF */ + lcd_flags &= ~LCD_FLAG_L; + processed = 1; + break; + case '*' : /* flash back light using the keypad timer */ + if (scan_timer.function != NULL) { + if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0)) + lcd_backlight(1); + light_tempo = FLASH_LIGHT_TEMPO; + } + processed = 1; + break; + case 'f' : /* Small Font */ + lcd_flags &= ~LCD_FLAG_F; + processed = 1; + break; + case 'F' : /* Large Font */ + lcd_flags |= LCD_FLAG_F; + processed = 1; + break; + case 'n' : /* One Line */ + lcd_flags &= ~LCD_FLAG_N; + processed = 1; + break; + case 'N' : /* Two Lines */ + lcd_flags |= LCD_FLAG_N; + break; + + case 'l' : /* Shift Cursor Left */ + if (lcd_addr_x > 0) { + if (lcd_addr_x < lcd_bwidth) + lcd_write_cmd(0x10); /* back one char if not at end of line */ + lcd_addr_x--; + } + processed = 1; + break; + + case 'r' : /* shift cursor right */ + if (lcd_addr_x < lcd_width) { + if (lcd_addr_x < (lcd_bwidth - 1)) + lcd_write_cmd(0x14); /* allow the cursor to pass the end of the line */ + lcd_addr_x++; + } + processed = 1; + break; + + case 'L' : /* shift display left */ + lcd_left_shift++; + lcd_write_cmd(0x18); + processed = 1; + break; + + case 'R' : /* shift display right */ + lcd_left_shift--; + lcd_write_cmd(0x1C); + processed = 1; + break; + + case 'k' : { /* kill end of line */ + int x; + for (x=lcd_addr_x; x= LCD_ESCAPE_LEN)) + lcd_escape_len = -1; + } /* escape codes */ + } + + return( tmp - buf ); +} + +static int lcd_open( struct inode *inode, struct file *file ) { + if (lcd_open_cnt) + return( -EBUSY ); /* open only once at a time */ + + if (file->f_mode & FMODE_READ) /* device is write-only */ + return ( -EPERM ); + + if (lcd_must_clear) { + lcd_clear_display(); + lcd_must_clear = 0; + } + lcd_open_cnt++; + MOD_INC_USE_COUNT; + return( 0 ); +} + +static int lcd_release( struct inode *inode, struct file *file ) { + lock_kernel(); + lcd_open_cnt--; + MOD_DEC_USE_COUNT; + unlock_kernel(); + return( 0 ); +} + + +static struct file_operations lcd_fops = { + write: lcd_write, + open: lcd_open, + release: lcd_release, +}; + +static struct miscdevice lcd_dev = { + LCD_MINOR, + "lcd", + &lcd_fops +}; + + + +/* public function usable from the kernel for any purpose */ +void panel_lcd_print(char *s) { + if (lcd_enabled) + lcd_write(NULL, s, strlen(s), NULL); +} + + +/* initialize the LCD driver */ +void lcd_init(void) { + if (lcd_enabled == 2) /* backlight feature needs a serial LCD at the moment */ + init_scan_timer(); + + if (lcd_width <= 0) + lcd_width = 40; /* defaults to 40 columns */ + + if (lcd_bwidth <= 0) + lcd_bwidth = 40; /* defaults to 40 columns */ + + if (lcd_height <= 0) + lcd_height = 2; /* defaults to 2 lines */ + + switch (lcd_enabled) { + case 1 : /* parallel mode, 8 bits */ + lcd_write_cmd = lcd_write_cmd_p8; + lcd_write_data = lcd_write_data_p8; + lcd_clear_fast = lcd_clear_fast_p8; + lcd_char_conv = NULL; + break; + case 2 : /* serial mode, ks0074 */ + lcd_write_cmd = lcd_write_cmd_s; + lcd_write_data = lcd_write_data_s; + lcd_clear_fast = lcd_clear_fast_s; + lcd_char_conv = lcd_char_conv_ks0074; + break; + case 3 : /* parallel mode, 8 bits, hantronix-like */ + default : + lcd_write_cmd = lcd_write_cmd_p8b; + lcd_write_data = lcd_write_data_p8b; + lcd_clear_fast = lcd_clear_fast_p8b; + lcd_char_conv = NULL; + break; + } + + lcd_init_display(); + + /* display a short message */ + panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\nPanel-" PANEL_VERSION); + lcd_addr_x = lcd_addr_y = 0; + lcd_must_clear = 1; /* clear the display on the next device opening */ + lcd_gotoxy(); +} + + +/* + * The are the file operation function for user access to /dev/keypad + */ + +static ssize_t keypad_read(struct file * file, + char * buf, size_t count, loff_t *ppos ) { + + unsigned i = *ppos; + char *tmp = buf; + + if (keypad_buflen == 0) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + interruptible_sleep_on(&keypad_read_wait); + if (signal_pending(current)) + return -EINTR; + } + + for( ; count-- > 0 && (keypad_buflen > 0); ++i, ++tmp, --keypad_buflen ) { + put_user( keypad_buffer[keypad_start], tmp ); + keypad_start = (keypad_start + 1) % KEYPAD_BUFFER; + } + *ppos = i; + + return( tmp - buf ); +} + + +static int keypad_open( struct inode *inode, struct file *file ) { + + if (keypad_open_cnt) + return( -EBUSY ); /* open only once at a time */ + + if (file->f_mode & FMODE_WRITE) /* device is read-only */ + return ( -EPERM ); + + keypad_buflen = 0; /* flush the buffer on opening */ + keypad_open_cnt++; + MOD_INC_USE_COUNT; + return( 0 ); +} + +static int keypad_release( struct inode *inode, struct file *file ) { + lock_kernel(); + keypad_open_cnt--; + MOD_DEC_USE_COUNT; + unlock_kernel(); + return( 0 ); +} + +static struct file_operations keypad_fops = { + read: keypad_read, /* read */ + open: keypad_open, /* open */ + release: keypad_release, /* close */ +}; + +static struct miscdevice keypad_dev = { + KEYPAD_MINOR, + "keypad", + &keypad_fops +}; + +static void keypad_send_key(char *string, int max_len) { + if (init_in_progress) + return; + + /* send the key to the device only if a process is attached to it. */ + if (keypad_open_cnt > 0) { + while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) { + keypad_buffer[(keypad_start + keypad_buflen++) % KEYPAD_BUFFER] = *string++; + } + wake_up_interruptible(&keypad_read_wait); + } +} + + +/* this function scans all the bits involving at least one logical signal, and puts the + * results in the bitfield "phys_read" (one bit per established contact), and sets + * "phys_read_last" to "phys_read". + */ +static void phys_scan_contacts(void) { + int bit, bitval; + char oldval; + char bitmask; + char gndmask; + + phys_prev = phys_curr; + phys_read_prev = phys_read; + phys_read = 0; /* flush all signals */ + + oldval = r_dtr(pprt) | scan_mask_o; /* keep track of old value, with all outputs disabled */ + w_dtr(pprt, oldval & ~scan_mask_o); /* activate all keyboard outputs */ + bitmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + w_dtr(pprt, oldval); /* disable all matrix signals */ + + if (bitmask == 0) { /* no signal was activated */ + phys_curr = phys_read | phys_read_prev; + return; /* nothing else to scan */ + } + + /* now that all outputs are cleared, the only active input bits are + * directly connected to the ground + */ + gndmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; /* 1 for each grounded input */ + + phys_read |= (pmask_t)gndmask << 40; /* grounded inputs are signals 40-44 */ + + for (bit = 0; bit < 8; bit ++) { + bitval = 1 << bit; + + if (!(scan_mask_o & bitval)) /* skip unused bits */ + continue; + + w_dtr(pprt, oldval & ~bitval); /* enable this output */ + bitmask = PNL_PINPUT(r_str(pprt)) & ~gndmask; + phys_read |= (pmask_t) bitmask << (5*bit); + } + w_dtr(pprt, oldval); /* disable all outputs */ + phys_curr = phys_read | phys_read_prev; +} + +static void panel_process_inputs(void) { + struct list_head *item; + struct logical_input *input; + +#if 0 + printk(KERN_DEBUG "entering panel_process_inputs with pp=%016Lx & pc=%016Lx\n", + phys_prev, phys_curr); +#endif + + keypressed = 0; + inputs_stable = 1; + list_for_each(item, &logical_inputs) { + input = list_entry(item, struct logical_input, list); + + switch (input->state) { + case INPUT_ST_LOW: + if ((phys_curr & input->mask) != input->value) + break; + /* if all needed ones were already set previously, this means that + * this logical signal has been activated by the releasing of + * another combined signal, so we don't want to match. + * eg: AB -(release B)-> A -(release A)-> 0 : don't match A. + */ + if ((phys_prev & input->value) == input->value) + break; + input->rise_timer = 0; + input->state = INPUT_ST_RISING; + /* no break here, fall through */ + case INPUT_ST_RISING: + if ((phys_curr & input->mask) != input->value) { + input->state = INPUT_ST_LOW; + break; + } + if (input->rise_timer < input->rise_time) { + inputs_stable = 0; + input->rise_timer++; + break; + } + input->high_timer = 0; + input->state = INPUT_ST_HIGH; + /* no break here, fall through */ + case INPUT_ST_HIGH: + /* try to catch dangerous transitions cases : + * someone adds a bit, so this signal was a false + * positive resulting from a transition. We should invalidate + * the signal immediately and not call the release function. + * eg: 0 -(press A)-> A -(press B)-> AB : don't match A's release. + */ + if (((phys_prev & input->mask) == input->value) + && ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + break; + } + else if ((phys_curr & input->mask) == input->value) { + if ((input->type == INPUT_TYPE_STD) && (input->high_timer == 0)) { + input->high_timer++; + if (input->u.std.press_fct != NULL) + input->u.std.press_fct(input->u.std.press_data); + } + else if (input->type == INPUT_TYPE_KBD) { + keypressed = 1; /* will turn on the light */ + + if (input->high_timer == 0) { + if (input->u.kbd.press_str[0]) + keypad_send_key(input->u.kbd.press_str, sizeof(input->u.kbd.press_str)); + } + + if (input->u.kbd.repeat_str[0]) { + if (input->high_timer >= KEYPAD_REP_START) { + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(input->u.kbd.repeat_str, sizeof(input->u.kbd.repeat_str)); + } + inputs_stable = 0; /* we will need to come back here soon */ + } + + if (input->high_timer < 255) { + input->high_timer++; + } + } + break; + } + else { + /* else signal falling down. Let's fall through. */ + input->state = INPUT_ST_FALLING; + input->fall_timer = 0; + } + /* no break here, fall through */ + case INPUT_ST_FALLING: + if (((phys_prev & input->mask) == input->value) + && ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + break; + } + else if ((phys_curr & input->mask) == input->value) { + if (input->type == INPUT_TYPE_KBD) { + keypressed = 1; /* will turn on the light */ + + if (input->u.kbd.repeat_str[0]) { + if (input->high_timer >= KEYPAD_REP_START) + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(input->u.kbd.repeat_str, sizeof(input->u.kbd.repeat_str)); + inputs_stable = 0; /* we will need to come back here soon */ + } + + if (input->high_timer < 255) { + input->high_timer++; + } + } + input->state = INPUT_ST_HIGH; + break; + } + else if (input->fall_timer >= input->fall_time) { + /* call release event */ + if (input->type == INPUT_TYPE_STD) { + if (input->u.std.release_fct != NULL) + input->u.std.release_fct(input->u.std.release_data); + } + else if (input->type == INPUT_TYPE_KBD) { + if (input->u.kbd.release_str[0]) + keypad_send_key(input->u.kbd.release_str, sizeof(input->u.kbd.release_str)); + } + + input->state = INPUT_ST_LOW; + break; + } + else { + input->fall_timer++; + inputs_stable = 0; + break; + } + } + } +} + +static void panel_scan_timer(void) { + if (spin_trylock(&pprt_lock)) { + phys_scan_contacts(); + spin_unlock(&pprt_lock); /* no need for the parport anymore */ + } + + if (!inputs_stable || phys_curr != phys_prev) { + panel_process_inputs(); + } + + if (keypressed) { + if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0)) + lcd_backlight(1); + light_tempo = FLASH_LIGHT_TEMPO; + } + else if (light_tempo > 0) { + light_tempo--; + if (light_tempo == 0 && ((lcd_flags & LCD_FLAG_L) == 0)) + lcd_backlight(0); + } + + mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); +} + +/* send a high / low clock impulse of microseconds high and low */ +static void smartcard_send_clock(int duration) { + int old; + + w_dtr(pprt, (old = r_dtr(pprt)) | PNL_SC_CLK); + udelay(duration); + w_dtr(pprt, (old & ~PNL_SC_CLK)); + udelay(duration); +} + +static void smartcard_insert(int dummy) { + int ofs; + + spin_lock(&pprt_lock); + w_dtr(pprt, (r_dtr(pprt) & ~PNL_SC_BITS)); + w_ctr(pprt, (r_ctr(pprt) | PNL_SC_ENA)); + + udelay(30); /* ensure the rst is low at least 30 us */ + + smartcard_send_clock(100); /* reset address counter */ + + w_dtr(pprt, r_dtr(pprt) | PNL_SC_RST); + udelay(30); /* ensure the rst is high at least 30 us */ + + for (ofs = 0; ofs < SMARTCARD_BYTES; ofs++) { + int bit, byte; + byte = 0; + for (bit = 128; bit > 0; bit >>= 1) { + if (!(r_str(pprt) & PNL_SC_IOR)) + byte |= bit; + smartcard_send_clock(15); /* 15 us are enough for data */ + } + smartcard_data[ofs] = byte; + // printk(KERN_DEBUG "sc[0x%02x] = 0x%02x\n", ofs, byte); + } + + w_dtr(pprt, (r_dtr(pprt) & ~PNL_SC_BITS)); + w_ctr(pprt, (r_ctr(pprt) & ~PNL_SC_ENA)); + + spin_unlock(&pprt_lock); + + printk(KERN_INFO "Panel: smart card inserted : %02x%02x%02x%02x%1x\n", + smartcard_data[2], smartcard_data[3], smartcard_data[4], + smartcard_data[5], smartcard_data[6] >> 4); + keypad_send_key("CardIn\n", 7); +} + +static void smartcard_remove(int dummy) { + printk(KERN_INFO "Panel: smart card removed : %02x%02x%02x%02x%1x\n", + smartcard_data[2], smartcard_data[3], smartcard_data[4], + smartcard_data[5], smartcard_data[6] >> 4); + memset(smartcard_data, 0, sizeof(smartcard_data)); + keypad_send_key("CardOut\n", 8); +} + +/* + * The are the file operation function for user access to /dev/smartcard + */ + +static ssize_t smartcard_read(struct file * file, + char * buf, size_t count, loff_t *ppos ) { + + unsigned i = *ppos; + char *tmp = buf; + + for( ; count-- > 0 && (smartcard_ptr < 9); ++i, ++tmp, ++smartcard_ptr ) { + if (smartcard_ptr & 1) + put_user( '0' + (smartcard_data[2 + (smartcard_ptr >> 1)] & 0xF), tmp ); + else + put_user( '0' + (smartcard_data[2 + (smartcard_ptr >> 1)] >> 4), tmp ); + } + *ppos = i; + + return( tmp - buf ); +} + + +static int smartcard_open( struct inode *inode, struct file *file ) { + + if (smartcard_open_cnt) + return( -EBUSY ); /* open only once at a time */ + + if (file->f_mode & FMODE_WRITE) /* device is read-only */ + return ( -EPERM ); + + smartcard_ptr = 0; /* flush the buffer on opening */ + smartcard_open_cnt++; + MOD_INC_USE_COUNT; + return( 0 ); +} + +static int smartcard_release( struct inode *inode, struct file *file ) { + lock_kernel(); + smartcard_open_cnt--; + MOD_DEC_USE_COUNT; + unlock_kernel(); + return( 0 ); +} + +static struct file_operations smartcard_fops = { + read: smartcard_read, /* read */ + open: smartcard_open, /* open */ + release: smartcard_release, /* close */ +}; + +static struct miscdevice smartcard_dev = { + SMARTCARD_MINOR, + "smartcard", + &smartcard_fops +}; + +static void init_scan_timer(void) { + if (scan_timer.function != NULL) + return; /* already started */ + + init_timer(&scan_timer); + scan_timer.expires = jiffies + INPUT_POLL_TIME; + scan_timer.data = 0; + scan_timer.function = (void *)&panel_scan_timer; + add_timer(&scan_timer); +} + +/* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits. + * if or are non-null, they will be or'ed with the bits corresponding + * to out and in bits respectively. + * returns 1 if ok, 0 if error (in which case, nothing is written). + */ +static int input_name2mask(char *name, pmask_t *mask, pmask_t *value, char *imask, char *omask) { + static char sigtab[10]="EeSsPpAaBb"; + char im, om; + pmask_t m, v; + + om = im = m = v = 0ULL; + while (*name) { + int in, out, bit, neg; + for (in = 0; (in < sizeof(sigtab)) && (sigtab[in] != *name); in++); + if (in >= sizeof(sigtab)) + return 0; /* input name not found */ + neg = (in & 1); /* odd (lower) names are negated */ + in >>= 1; + im |= (1 << in); + + name++; + if (isdigit(*name)) { + out = *name - '0'; + om |= (1 << out); + } + else if (*name == '-') + out = 8; + else + return 0; /* unknown bit name */ + + bit = (out * 5) + in; + + m |= 1ULL << bit; + if (!neg) + v |= 1ULL << bit; + name++; + } + *mask = m; + *value = v; + if (imask) + *imask |= im; + if (omask) + *omask |= om; + return 1; +} + +/* tries to bind a key to the signal name . The key will send the + * strings , , for these respective events. + * Returns the pointer to the new key if ok, NULL if the key could not be bound. + */ +static struct logical_input *panel_bind_key(char *name, char *press, char *repeat, char *release) { + struct logical_input *key; + + key = (struct logical_input*)kmalloc(sizeof(struct logical_input), GFP_KERNEL); + if (!key) { + printk(KERN_ERR "panel: not enough memory\n"); + return NULL; + } + memset(key, 0, sizeof(struct logical_input)); + if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i, &scan_mask_o)) + return NULL; + key->type = INPUT_TYPE_KBD; + key->state = INPUT_ST_LOW; + key->rise_time = 1; + key->fall_time = 1; + strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str)); + strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str)); + strncpy(key->u.kbd.release_str, release, sizeof(key->u.kbd.release_str)); + list_add(&key->list, &logical_inputs); + return key; +} + +/* tries to bind a callback function to the signal name . The function + * will be called with the arg when the signal is + * activated, and so on for / + * Returns the pointer to the new signal if ok, NULL if the signal could not be bound. + */ +static struct logical_input *panel_bind_callback(char *name, + void (*press_fct)(int), int press_data, + void (*release_fct)(int), int release_data) { + struct logical_input *callback; + + callback = (struct logical_input*)kmalloc(sizeof(struct logical_input), GFP_KERNEL); + if (!callback) { + printk(KERN_ERR "panel: not enough memory\n"); + return NULL; + } + memset(callback, 0, sizeof(struct logical_input)); + if (!input_name2mask(name, &callback->mask, &callback->value, &scan_mask_i, &scan_mask_o)) + return NULL; + callback->type = INPUT_TYPE_STD; + callback->state = INPUT_ST_LOW; + callback->rise_time = 1; + callback->fall_time = 1; + callback->u.std.press_fct = press_fct; + callback->u.std.press_data = press_data; + callback->u.std.release_fct = release_fct; + callback->u.std.release_data = release_data; + list_add(&callback->list, &logical_inputs); + return callback; +} + +static void keypad_init(void) { + int keynum; + init_waitqueue_head(&keypad_read_wait); + keypad_buflen = 0; /* flushes any eventual noisy keystroke */ + + /* Let's create all known keys */ + + for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) { + panel_bind_key(keypad_profile[keynum][0], + keypad_profile[keynum][1], + keypad_profile[keynum][2], + keypad_profile[keynum][3]); + } + + init_scan_timer(); +} + + +static void smartcard_init(void) { + init_waitqueue_head(&smartcard_read_wait); + + panel_bind_callback(SMARTCARD_LOGICAL_DETECTOR, &smartcard_insert, 0, &smartcard_remove, 0); + init_scan_timer(); +} + + +/**************************************************/ +/* device initialization */ +/**************************************************/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +#define INIT_FUNC static int __init panel_init_module +#define CLEANUP_FUNC static void panel_cleanup_module +#else +#define INIT_FUNC int init_module +#define CLEANUP_FUNC int cleanup_module +#endif + +#ifndef MODULE +/* called when compiled into the kernel */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +static int __init panel_setup(char *str) +#else +__initfunc(void panel_setup(char *str, int *ints)) +#endif +{ + int dummy; + int *where; + int helpdisplayed = 0; + + if (!str) + return 0; + + while (*str) { + where = NULL; + + /* let's parse each of the command line parameters of the following form : + panel=[parport:x],[lcd_height:x],[lcd_width:x],[lcd_bwidth:x],[lcd_hwidth:x] + */ + if (!strncmp(str, "parport:", 8)) { + str += 8; + where = &parport; + } + else if (!strncmp(str, "disabled", 8)) { + return 0; + } + else if (!strncmp(str, "lcd_height:", 11)) { + str += 11; + where = &lcd_height; + } + else if (!strncmp(str, "lcd_width:", 10)) { + str += 10; + where = &lcd_width; + } + else if (!strncmp(str, "lcd_bwidth:", 11)) { + str += 11; + where = &lcd_bwidth; + } + else if (!strncmp(str, "lcd_hwidth:", 11)) { + str += 11; + where = &lcd_hwidth; + } + else if (!strncmp(str, "lcd_enabled:", 12)) { + str += 12; + where = &lcd_enabled; + } + else if (!strncmp(str, "keypad_enabled:", 15)) { + str += 15; + where = &keypad_enabled; + } + else if (!strncmp(str, "smartcard_enabled:", 18)) { + str += 18; + where = &smartcard_enabled; + } + else if (!strncmp(str, "profile:", 8)) { + str += 8; + where = &profile; + } + else if (!helpdisplayed) { + helpdisplayed = 1; + printk(KERN_ERR "Panel version " PANEL_VERSION ": invalid argument. Known arguments are :\n" + " parport:, lcd_{height,width,bwidth,enabled}:, keypad_enabled:\n"); + } + + /* see if we need to read a number */ + if (where != NULL) { + dummy = 0; + while (isdigit(*str)) { + dummy = (dummy*10) + (*str - '0'); + str++; + } + *where = dummy; + } + +#if 0 + /* look if there's something to do immediately after parsing */ + if (where == &profile) { + switch (profile) { + case 1: /* 8 bits, 2*16, old keypad */ + parport = 0; + lcd_enabled = 1; + keypad_enabled = 1; + lcd_width = 16; + lcd_hwidth = 16; + lcd_height = 2; + keypad_profile = &old_keypad_profile; + break; + case 2: /* serial, 2*16, new keypad */ + parport = 0; + lcd_enabled = 2; + keypad_enabled = 1; + lcd_width = 16; + lcd_hwidth = 16; + lcd_height = 2; + keypad_profile = &new_keypad_profile; + break; + } + } +#endif + + /* look for next arg */ + while (*str && (*str != ',')) + str++; + while (*str == ',') + str++; + } + return 1; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +__setup("panel=", panel_setup ); +#else +__setup("panel", panel_setup ); +#endif + +#endif /* !MODULE */ + +/* init function */ +int panel_init (void) { + int portnb; + struct parport *port; + + /* take care of an eventual profile */ + switch (profile) { + case 1: /* 8 bits, 2*16, old keypad */ + if (parport < 0) parport = 0; + if (keypad_enabled < 0) keypad_enabled = 1; + if (smartcard_enabled < 0) smartcard_enabled = 0; + if (lcd_enabled < 0) lcd_enabled = 1; + if (lcd_width < 0) lcd_width = 16; + if (lcd_hwidth < 0) lcd_hwidth = 16; + if (lcd_height < 0) lcd_height = 2; + keypad_profile = old_keypad_profile; + break; + case 2: /* serial, 2*16, new keypad */ + if (parport < 0) parport = 0; + if (keypad_enabled < 0) keypad_enabled = 1; + if (smartcard_enabled < 0) smartcard_enabled = 1; + if (lcd_enabled < 0) lcd_enabled = 2; + if (lcd_width < 0) lcd_width = 16; + if (lcd_hwidth < 0) lcd_hwidth = 16; + if (lcd_height < 0) lcd_height = 2; + keypad_profile = new_keypad_profile; + break; + case 3: /* 8 bits, 2*16 hantronix-like, no keypad */ + if (parport < 0) parport = 0; + if (keypad_enabled < 0) keypad_enabled = 0; + if (smartcard_enabled < 0) smartcard_enabled = 0; + if (lcd_enabled < 0) lcd_enabled = 3; + if (lcd_width < 0) lcd_width = 16; + if (lcd_hwidth < 0) lcd_hwidth = 64; + if (lcd_height < 0) lcd_height = 2; + keypad_profile = old_keypad_profile; + break; + default: /* 8 bits, 2*40, old keypad */ + if (parport < 0) parport = 0; + if (keypad_enabled < 0) keypad_enabled = 1; + if (smartcard_enabled < 0) smartcard_enabled = 0; + if (lcd_enabled < 0) lcd_enabled = 1; + if (lcd_width < 0) lcd_width = 40; + if (lcd_hwidth < 0) lcd_hwidth = 64; + if (lcd_height < 0) lcd_height = 2; + keypad_profile = old_keypad_profile; + break; + } + + portnb = parport; + /* tells various subsystems about the fact that we are initializing */ + init_in_progress = 1; + + for (port = parport_enumerate(); port && (portnb-->0); port = port->next); + + if (port == NULL) { + printk(KERN_ERR "Panel: could not find parport%d. Aborting.\n", parport); + return -ENODEV; /* port not found */ + } + + pprt = parport_register_device(port, "panel", + NULL, NULL, /* pf, kf */ + NULL, + /*PARPORT_DEV_EXCL*/ + 0, + (void *)&pprt); + if (pprt == NULL) { + printk(KERN_ERR "Panel: found parport%d, but could not register. Aborting.\n", parport); + return -EIO; + } + + if (parport_claim(pprt)) { + printk(KERN_ERR "Panel: could not claim access to parport%d. Aborting.\n", parport); + parport_unregister_device(pprt); + return -EIO; + } + + /* turns IRQ off */ + // port->ops->disable_irq(port); + + /* must init LCD first, just in case an IRQ from the keypad is generated at keypad init */ + if (lcd_enabled) { + lcd_init(); + misc_register( &lcd_dev ); + } + + if (keypad_enabled) { + keypad_init(); + misc_register( &keypad_dev ); + } + + if (smartcard_enabled) { + smartcard_init(); + misc_register( &smartcard_dev ); + } + + if (!lcd_enabled && !keypad_enabled && !smartcard_enabled) { /* no device enabled, let's release the parport */ + parport_release(pprt); + parport_unregister_device(pprt); + printk(KERN_ERR "Panel driver version " PANEL_VERSION " disabled.\n"); + return -ENODEV; + } + + + printk(KERN_INFO "Panel driver version " PANEL_VERSION " registered on parport%d (io=0x%lx).\n", + parport, pprt->port->base); + /* tells various subsystems about the fact that initialization is finished */ + init_in_progress = 0; + return 0; + +} + + +#if defined(MODULE) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)) +INIT_FUNC (void) { + return panel_init(); +} + +CLEANUP_FUNC (void) { + if (scan_timer.function != NULL) { + del_timer(&scan_timer); + } + + if (keypad_enabled) { + misc_deregister( &keypad_dev ); + } + + if (smartcard_enabled) { + misc_deregister( &smartcard_dev ); + } + + if (lcd_enabled) { + panel_lcd_print("\x0cLCD driver " PANEL_VERSION "\nunloaded.\x1b[Lc\x1b[Lb\x1b[L-"); + misc_deregister( &lcd_dev ); + } + + /* TODO: free all input signals */ + + parport_release(pprt); + parport_unregister_device(pprt); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +module_init(panel_init_module); +module_exit(panel_cleanup_module); +#endif + +/* + * Local variables: + * c-indent-level: 4 + * tab-width: 8 + * End: + */ +