/*
* 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 <string.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;
}