summaryrefslogtreecommitdiffstats
path: root/c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c
diff options
context:
space:
mode:
Diffstat (limited to 'c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c')
-rw-r--r--c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c b/c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c
new file mode 100644
index 0000000000..23e69a933a
--- /dev/null
+++ b/c/src/lib/libbsp/i386/pc386/console/ps2_mouse.c
@@ -0,0 +1,708 @@
+/*
+ * linux/drivers/char/pc_keyb.c
+ * Separation of the PC low-level part by Geert Uytterhoeven, May 1997
+ * See keyboard.c for the whole history.
+ * Major cleanup by Martin Mares, May 1997
+ * Combined the keyboard and PS/2 mouse handling into one file,
+ * because they share the same hardware.
+ * Johan Myreen <jem@iki.fi> 1998-10-08.
+ * Code fixes to handle mouse ACKs properly.
+ * C. Scott Ananian <cananian@alumni.princeton.edu> 1999-01-29.
+ *
+ * RTEMS port: by Rosimildo da Silva.
+ * This module was ported from Linux.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <bsp.h>
+#include <irq.h>
+#include <rtems/libio.h>
+#include <termios.h>
+#include <i386_io.h>
+#include <rtems/mw_uid.h>
+
+#define INITIALIZE_MOUSE
+/* Some configuration switches are present in the include file... */
+#include "ps2_mouse.h"
+#include "mouse_parser.h"
+
+static void kbd_write_command_w(int data);
+static void kbd_write_output_w(int data);
+
+static unsigned char handle_kbd_event(void);
+
+/* used only by send_data - set by keyboard_interrupt */
+static volatile unsigned char reply_expected = 0;
+static volatile unsigned char acknowledge = 0;
+static volatile unsigned char resend = 0;
+
+/*
+ * PS/2 Auxiliary Device
+ */
+static int psaux_init(void);
+
+static struct aux_queue *queue; /* Mouse data buffer. */
+static int aux_count = 0;
+/* used when we send commands to the mouse that expect an ACK. */
+static unsigned char mouse_reply_expected = 0;
+
+#define AUX_INTS_OFF (KBD_MODE_KCC | KBD_MODE_DISABLE_MOUSE | KBD_MODE_SYS | KBD_MODE_KBD_INT)
+#define AUX_INTS_ON (KBD_MODE_KCC | KBD_MODE_SYS | KBD_MODE_MOUSE_INT | KBD_MODE_KBD_INT)
+#define MAX_RETRIES 60 /* some aux operations take long time*/
+
+static void ps2_mouse_interrupt();
+
+static void ( *driver_input_handler_ps2 )( void *, char *, int ) = 0;
+
+/*
+ * This routine sets the handler to handle the characters received
+ * from the serial port.
+ */
+void ps2_set_driver_handler( int port, void ( *handler )( void *, char *, int ) )
+{
+ driver_input_handler_ps2 = handler;
+}
+
+static void mdelay( unsigned long t )
+{
+ Wait_X_ms( t );
+}
+
+
+static void* termios_ttyp_paux = NULL;
+
+static void
+isr_on(const rtems_irq_connect_data *unused)
+{
+ return;
+}
+
+static void
+isr_off(const rtems_irq_connect_data *unused)
+{
+ return;
+}
+
+static int isr_is_on(const rtems_irq_connect_data *irq)
+{
+ return BSP_irq_enabled_at_i8259s( irq->name );
+}
+
+
+static rtems_irq_connect_data ps2_isr_data = { AUX_IRQ,
+ ps2_mouse_interrupt, isr_on, isr_off, isr_is_on };
+
+
+/*
+ * Wait for keyboard controller input buffer to drain.
+ *
+ * Don't use 'jiffies' so that we don't depend on
+ * interrupts..
+ *
+ * Quote from PS/2 System Reference Manual:
+ *
+ * "Address hex 0060 and address hex 0064 should be written only when
+ * the input-buffer-full bit and output-buffer-full bit in the
+ * Controller Status register are set 0."
+ */
+
+static void kb_wait(void)
+{
+ unsigned long timeout = KBC_TIMEOUT;
+
+ do {
+ /*
+ * "handle_kbd_event()" will handle any incoming events
+ * while we wait - keypresses or mouse movement.
+ */
+ unsigned char status = handle_kbd_event();
+
+ if (! (status & KBD_STAT_IBF))
+ return;
+
+ mdelay(1);
+
+ timeout--;
+ } while (timeout);
+#ifdef KBD_REPORT_TIMEOUTS
+ printk( "Keyboard timed out[1]\n");
+#endif
+}
+
+static int do_acknowledge(unsigned char scancode)
+{
+ if (reply_expected) {
+ /* Unfortunately, we must recognise these codes only if we know they
+ * are known to be valid (i.e., after sending a command), because there
+ * are some brain-damaged keyboards (yes, FOCUS 9000 again) which have
+ * keys with such codes :(
+ */
+ if (scancode == KBD_REPLY_ACK) {
+ acknowledge = 1;
+ reply_expected = 0;
+ return 0;
+ } else if (scancode == KBD_REPLY_RESEND) {
+ resend = 1;
+ reply_expected = 0;
+ return 0;
+ }
+ /* Should not happen... */
+#if 0
+ printk( "keyboard reply expected - got %02x\n",
+ scancode);
+#endif
+ }
+ return 1;
+}
+
+
+static inline void handle_mouse_event(unsigned char scancode)
+{
+ if (mouse_reply_expected) {
+ if (scancode == AUX_ACK) {
+ mouse_reply_expected--;
+ return;
+ }
+ mouse_reply_expected = 0;
+ }
+
+ if (aux_count) {
+ int head = queue->head;
+
+ queue->buf[head] = scancode;
+ head = (head + 1) & (AUX_BUF_SIZE-1);
+ if (head != queue->tail) {
+ queue->head = head;
+ }
+ /* if the input queue is active, add to it */
+ if( driver_input_handler_ps2 )
+ {
+ driver_input_handler_ps2( NULL, &scancode, 1 );
+ }
+ else
+ {
+ /* post this byte to termios */
+ rtems_termios_enqueue_raw_characters( termios_ttyp_paux, &scancode, 1 );
+ }
+ }
+}
+
+/*
+ * This reads the keyboard status port, and does the
+ * appropriate action.
+ *
+ * It requires that we hold the keyboard controller
+ * spinlock.
+ */
+static unsigned char handle_kbd_event(void)
+{
+ unsigned char status = kbd_read_status();
+ unsigned int work = 10000;
+
+ while (status & KBD_STAT_OBF) {
+ unsigned char scancode;
+
+ scancode = kbd_read_input();
+
+ if (status & KBD_STAT_MOUSE_OBF) {
+ handle_mouse_event(scancode);
+ } else {
+ do_acknowledge(scancode);
+ printk("pc_keyb: %X ", scancode );
+ }
+ status = kbd_read_status();
+ if(!work--)
+ {
+ printk("pc_keyb: controller jammed (0x%02X).\n",
+ status);
+ break;
+ }
+ }
+
+ return status;
+}
+
+
+static void ps2_mouse_interrupt()
+{
+ handle_kbd_event();
+}
+
+/*
+ * send_data sends a character to the keyboard and waits
+ * for an acknowledge, possibly retrying if asked to. Returns
+ * the success status.
+ *
+ * Don't use 'jiffies', so that we don't depend on interrupts
+ */
+static int send_data(unsigned char data)
+{
+ int retries = 3;
+
+ do {
+ unsigned long timeout = KBD_TIMEOUT;
+
+ acknowledge = 0; /* Set by interrupt routine on receipt of ACK. */
+ resend = 0;
+ reply_expected = 1;
+ kbd_write_output_w(data);
+ for (;;) {
+ if (acknowledge)
+ return 1;
+ if (resend)
+ break;
+ mdelay(1);
+ if (!--timeout) {
+#ifdef KBD_REPORT_TIMEOUTS
+ printk( "Keyboard timeout[2]\n");
+#endif
+ return 0;
+ }
+ }
+ } while (retries-- > 0);
+#ifdef KBD_REPORT_TIMEOUTS
+ printk( "keyboard: Too many NACKs -- noisy kbd cable?\n");
+#endif
+ return 0;
+}
+
+#define KBD_NO_DATA (-1) /* No data */
+#define KBD_BAD_DATA (-2) /* Parity or other error */
+
+static int kbd_read_data(void)
+{
+ int retval = KBD_NO_DATA;
+ unsigned char status;
+
+ status = kbd_read_status();
+ if (status & KBD_STAT_OBF) {
+ unsigned char data = kbd_read_input();
+
+ retval = data;
+ if (status & (KBD_STAT_GTO | KBD_STAT_PERR))
+ retval = KBD_BAD_DATA;
+ }
+ return retval;
+}
+
+static void kbd_clear_input(void)
+{
+ int maxread = 100; /* Random number */
+
+ do {
+ if (kbd_read_data() == KBD_NO_DATA)
+ break;
+ } while (--maxread);
+}
+
+static int kbd_wait_for_input(void)
+{
+ long timeout = KBD_INIT_TIMEOUT;
+
+ do {
+ int retval = kbd_read_data();
+ if (retval >= 0)
+ return retval;
+ mdelay(1);
+ } while (--timeout);
+ return -1;
+}
+
+static void kbd_write_command_w(int data)
+{
+ kb_wait();
+ kbd_write_command(data);
+}
+
+static void kbd_write_output_w(int data)
+{
+ kb_wait();
+ kbd_write_output(data);
+}
+
+static void kbd_write_cmd(int cmd)
+{
+ kb_wait();
+ kbd_write_command(KBD_CCMD_WRITE_MODE);
+ kb_wait();
+ kbd_write_output(cmd);
+}
+
+/*
+ * Check if this is a dual port controller.
+ */
+static int detect_auxiliary_port(void)
+{
+ int loops = 10;
+ int retval = 0;
+
+ /* Put the value 0x5A in the output buffer using the "Write
+ * Auxiliary Device Output Buffer" command (0xD3). Poll the
+ * Status Register for a while to see if the value really
+ * turns up in the Data Register. If the KBD_STAT_MOUSE_OBF
+ * bit is also set to 1 in the Status Register, we assume this
+ * controller has an Auxiliary Port (a.k.a. Mouse Port).
+ */
+ kb_wait();
+ kbd_write_command(KBD_CCMD_WRITE_AUX_OBUF);
+
+ kb_wait();
+ kbd_write_output(0x5a); /* 0x5a is a random dummy value. */
+
+ do {
+ unsigned char status = kbd_read_status();
+
+ if (status & KBD_STAT_OBF) {
+ (void) kbd_read_input();
+ if (status & KBD_STAT_MOUSE_OBF) {
+ printk( "Detected PS/2 Mouse Port.\n");
+ retval = 1;
+ }
+ break;
+ }
+ mdelay(1);
+ } while (--loops);
+ return retval;
+}
+
+/*
+ * Send a byte to the mouse.
+ */
+static void aux_write_dev(int val)
+{
+ kb_wait();
+ kbd_write_command(KBD_CCMD_WRITE_MOUSE);
+ kb_wait();
+ kbd_write_output(val);
+}
+
+/*
+ * Send a byte to the mouse & handle returned ack
+ */
+static void aux_write_ack(int val)
+{
+ kb_wait();
+ kbd_write_command(KBD_CCMD_WRITE_MOUSE);
+ kb_wait();
+ kbd_write_output(val);
+ /* we expect an ACK in response. */
+ mouse_reply_expected++;
+ kb_wait();
+}
+
+static unsigned char get_from_queue(void)
+{
+ unsigned char result;
+ result = queue->buf[queue->tail];
+ queue->tail = (queue->tail + 1) & (AUX_BUF_SIZE-1);
+ return result;
+}
+
+
+static int queue_empty(void)
+{
+ return queue->head == queue->tail;
+}
+
+/*
+ * Random magic cookie for the aux device
+ */
+#define AUX_DEV ((void *)queue)
+
+static int release_aux()
+{
+ if (--aux_count)
+ return 0;
+ kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints */
+ kbd_write_command_w(KBD_CCMD_MOUSE_DISABLE);
+
+ BSP_remove_rtems_irq_handler( &ps2_isr_data );
+ return 0;
+}
+/*
+ * Install interrupt handler.
+ * Enable auxiliary device.
+ */
+
+static int open_aux()
+{
+ rtems_status_code status;
+
+ if (aux_count++) {
+ return 0;
+ }
+ queue->head = queue->tail = 0; /* Flush input queue */
+
+
+ status = BSP_install_rtems_irq_handler( &ps2_isr_data );
+ if( !status )
+ {
+ printk("Error installing ps2-mouse interrupt handler!\n" );
+ rtems_fatal_error_occurred( status );
+ }
+
+ kbd_write_command_w(KBD_CCMD_MOUSE_ENABLE); /* Enable the
+ auxiliary port on
+ controller. */
+ aux_write_ack(AUX_ENABLE_DEV); /* Enable aux device */
+ kbd_write_cmd(AUX_INTS_ON); /* Enable controller ints */
+ return 0;
+}
+
+/*
+ * Put bytes from input queue to buffer.
+ */
+size_t read_aux(char * buffer, size_t count )
+{
+ size_t i = count;
+ unsigned char c;
+
+ if (queue_empty())
+ {
+ return 0;
+ }
+ while (i > 0 && !queue_empty())
+ {
+ c = get_from_queue();
+ *buffer++ = c;
+ i--;
+ }
+ return count-i;
+}
+
+/*
+ * Write to the aux device.
+ */
+
+static int write_aux( int minor, const char * buffer, int count )
+{
+ int retval = 0;
+
+ if (count) {
+ int written = 0;
+
+ if (count > 32)
+ count = 32; /* Limit to 32 bytes. */
+ do {
+ char c;
+ c = *buffer++;
+ aux_write_dev(c);
+ written++;
+ } while (--count);
+ retval = -EIO;
+ if (written) {
+ retval = written;
+ }
+ }
+ return retval;
+}
+
+static unsigned int aux_poll()
+{
+ if( !queue_empty() )
+ return 1;
+ return 0;
+}
+
+static int psaux_init( void )
+{
+ if( !detect_auxiliary_port() )
+ {
+ printk( "PS/2 - mouse not found.\n" );
+ return -EIO;
+ }
+
+ queue = (struct aux_queue *)malloc( sizeof(*queue) );
+ memset(queue, 0, sizeof(*queue));
+ queue->head = queue->tail = 0;
+ queue->proc_list = NULL;
+
+#ifdef INITIALIZE_MOUSE
+ kbd_write_command_w(KBD_CCMD_MOUSE_ENABLE); /* Enable Aux. */
+ aux_write_ack(AUX_SET_SAMPLE);
+ aux_write_ack(100); /* 100 samples/sec */
+ aux_write_ack(AUX_SET_RES);
+ aux_write_ack(3); /* 8 counts per mm */
+ aux_write_ack(AUX_SET_SCALE21); /* 2:1 scaling */
+#endif /* INITIALIZE_MOUSE */
+ kbd_write_command(KBD_CCMD_MOUSE_DISABLE); /* Disable aux device. */
+ kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints. */
+ return 0;
+}
+
+void paux_reserve_resources(rtems_configuration_table *conf)
+{
+ rtems_termios_reserve_resources(conf, 1);
+ return;
+}
+
+/*
+ * paux device driver INITIALIZE entry point.
+ */
+rtems_device_driver
+paux_initialize( rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg)
+{
+ rtems_status_code status;
+
+ /*
+ * Set up TERMIOS
+ */
+ rtems_termios_initialize();
+
+ printk( "PS/2 mouse probe.\n" );
+ if( psaux_init() < 0 )
+ {
+ printk("Error detecting PS/2 mouse --\n");
+
+ /* we might want to finish the application here !!! */
+ }
+ open_aux();
+
+ /*
+ * Register the device
+ */
+ status = rtems_io_register_name ("/dev/mouse", major, 0);
+ if (status != RTEMS_SUCCESSFUL)
+ {
+ printk("Error registering paux device!\n");
+ rtems_fatal_error_occurred (status);
+ }
+ return RTEMS_SUCCESSFUL;
+} /* tty_initialize */
+
+
+static int paux_last_close(int major, int minor, void *arg)
+{
+ release_aux();
+ return 0;
+}
+
+/*
+ * Write to the aux device. This routine is invoked by the
+ * termios framework whenever the "ECHO" feature is on.
+ * It does nothing write now.
+ */
+static int write_aux_echo( int minor, const char * buffer, int count )
+{
+ return 0;
+}
+
+
+/*
+ * Some initialization if necessary
+ */
+static rtems_device_driver
+paux_first_open( rtems_device_minor_number major,
+ rtems_device_minor_number minor,
+ void *arg)
+{
+ return RTEMS_SUCCESSFUL;
+}
+
+
+/*
+ * paux device driver OPEN entry point
+ */
+rtems_device_driver
+paux_open(rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg)
+{
+ rtems_status_code status;
+ static rtems_termios_callbacks cb =
+ {
+ NULL, /* firstOpen */
+ paux_last_close, /* lastClose */
+ NULL, /* poll read */
+ write_aux_echo, /* write */
+ NULL, /* setAttributes */
+ NULL, /* stopRemoteTx */
+ NULL, /* startRemoteTx */
+ 0 /* outputUsesInterrupts */
+ };
+
+ status = rtems_termios_open (major, minor, arg, &cb );
+ termios_ttyp_paux = ( (rtems_libio_open_close_args_t *)arg)->iop->data1;
+ return status;
+}
+
+/*
+ * paux device driver CLOSE entry point
+ */
+rtems_device_driver
+paux_close(rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg)
+{
+ return (rtems_termios_close (arg));
+}
+
+
+/*
+ * paux device driver READ entry point.
+ * Read characters from the PS/2 mouse.
+ */
+rtems_device_driver
+paux_read(rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg)
+{
+ return rtems_termios_read (arg);
+} /* tty_read */
+
+
+/*
+ * paux device driver WRITE entry point.
+ * Write characters to the PS/2 mouse.
+ */
+rtems_device_driver
+paux_write(rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void * arg)
+{
+ rtems_libio_rw_args_t *rw_args = (rtems_libio_rw_args_t *)arg;
+ char *buffer = rw_args->buffer;
+ int maximum = rw_args->count;
+ rw_args->bytes_moved = write_aux( minor, buffer, maximum );
+ return RTEMS_SUCCESSFUL;
+} /* tty_write */
+
+
+/*
+ * Handle ioctl request.
+ */
+rtems_device_driver
+paux_control(rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void * arg
+)
+{
+ rtems_libio_ioctl_args_t *args = arg;
+ switch( args->command )
+ {
+ default:
+ return rtems_termios_ioctl (arg);
+ break;
+
+ case MW_UID_REGISTER_DEVICE:
+ printk( "PS2 Mouse: reg=%s\n", args->buffer );
+ register_mou_msg_queue( args->buffer, -1 );
+ break;
+
+ case MW_UID_UNREGISTER_DEVICE:
+ unregister_mou_msg_queue( -1 );
+ break;
+ }
+ args->ioctl_return = 0;
+ return RTEMS_SUCCESSFUL;
+}
+
+
+