patch-2.4.15 linux/drivers/char/hp_psaux.c

Next file: linux/drivers/char/isicom.c
Previous file: linux/drivers/char/hp_keyb.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.14/linux/drivers/char/hp_psaux.c linux/drivers/char/hp_psaux.c
@@ -0,0 +1,551 @@
+/*
+ *      LASI PS/2 keyboard/psaux driver for HP-PARISC workstations
+ *      
+ *      (c) Copyright 1999 The Puffin Group Inc.
+ *      by Alex deVries <adevries@thepuffingroup.com>
+ *	Copyright 1999, 2000 Philipp Rumpf <prumpf@tux.org>
+ *
+ *	2000/10/26	Debacker Xavier (debackex@esiee.fr)
+ *			Marteau Thomas (marteaut@esiee.fr)
+ * 			Djoudi Malek (djoudim@esiee.fr)
+ *	fixed leds control
+ *	implemented the psaux and controlled the mouse scancode based on pc_keyb.c
+ */
+
+#include <linux/config.h>
+
+#include <asm/hardware.h>
+#include <asm/keyboard.h>
+#include <asm/gsc.h>
+
+#include <linux/types.h>
+#include <linux/ptrace.h>	/* interrupt.h wants struct pt_regs defined */
+#include <linux/interrupt.h>
+#include <linux/sched.h>	/* for request_irq/free_irq */
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/pc_keyb.h>
+#include <linux/kbd_kern.h>
+
+/* mouse includes */
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include <linux/poll.h>
+
+/* HP specific LASI PS/2 keyboard and psaux constants */
+#define	AUX_REPLY_ACK	0xFA	/* Command byte ACK. */
+#define	AUX_RECONNECT	0xAA	/* scancode when ps2 device is plugged (back) in */
+
+#define	LASI_PSAUX_OFFSET 0x0100 /* offset from keyboard to psaux port */
+
+#define	LASI_ID		0x00	/* ID and reset port offsets */
+#define	LASI_RESET	0x00
+#define	LASI_RCVDATA	0x04	/* receive and transmit port offsets */
+#define	LASI_XMTDATA	0x04
+#define	LASI_CONTROL	0x08	/* see: control register bits */
+#define	LASI_STATUS	0x0C	/* see: status register bits */
+
+/* control register bits */
+#define LASI_CTRL_ENBL	0x01	/* enable interface */
+#define LASI_CTRL_LPBXR	0x02	/* loopback operation */
+#define LASI_CTRL_DIAG	0x20	/* directly control clock/data line */
+#define LASI_CTRL_DATDIR 0x40	/* data line direct control */
+#define LASI_CTRL_CLKDIR 0x80	/* clock line direct control */
+
+/* status register bits */
+#define LASI_STAT_RBNE	0x01
+#define LASI_STAT_TBNE	0x02
+#define LASI_STAT_TERR	0x04
+#define LASI_STAT_PERR	0x08
+#define LASI_STAT_CMPINTR 0x10
+#define LASI_STAT_DATSHD 0x40
+#define LASI_STAT_CLKSHD 0x80
+
+static void *lasikbd_hpa;
+static void *lasips2_hpa;
+
+
+static inline u8 read_input(void *hpa)
+{
+	return gsc_readb(hpa+LASI_RCVDATA);
+}
+
+static inline u8 read_control(void *hpa)
+{
+        return gsc_readb(hpa+LASI_CONTROL);
+}
+
+static inline void write_control(u8 val, void *hpa)
+{
+	gsc_writeb(val, hpa+LASI_CONTROL);
+}
+
+static inline u8 read_status(void *hpa)
+{
+        return gsc_readb(hpa+LASI_STATUS);
+}
+
+static int write_output(u8 val, void *hpa)
+{
+	int wait = 0;
+
+	while (read_status(hpa) & LASI_STAT_TBNE) {
+		wait++;
+		if (wait>10000) {
+			/* printk(KERN_WARNING "Lasi PS/2 transmit buffer timeout\n"); */
+			return 0;
+		}
+	}
+
+	if (wait)
+		printk(KERN_DEBUG "Lasi PS/2 wait %d\n", wait);
+	
+	gsc_writeb(val, hpa+LASI_XMTDATA);
+
+	return 1;
+}
+
+/* This function is the PA-RISC adaptation of i386 source */
+
+static inline int aux_write_ack(u8 val)
+{
+      return write_output(val, lasikbd_hpa+LASI_PSAUX_OFFSET);
+}
+
+static void lasikbd_leds(unsigned char leds)
+{
+	write_output(KBD_CMD_SET_LEDS, lasikbd_hpa);
+	write_output(leds, lasikbd_hpa);
+	write_output(KBD_CMD_ENABLE, lasikbd_hpa);
+}
+
+#if 0
+/* this might become useful again at some point.  not now  -prumpf */
+int lasi_ps2_test(void *hpa)
+{
+	u8 control,c;
+	int i, ret = 0;
+
+	control = read_control(hpa);
+	write_control(control | LASI_CTRL_LPBXR | LASI_CTRL_ENBL, hpa);
+
+	for (i=0; i<256; i++) {
+		write_output(i, hpa);
+
+		while (!(read_status(hpa) & LASI_STAT_RBNE))
+		    /* just wait */;
+		    
+		c = read_input(hpa);
+		if (c != i)
+			ret--;
+	}
+
+	write_control(control, hpa);
+
+	return ret;
+}
+#endif 
+
+static int __init lasi_ps2_reset(void *hpa, int id)
+{
+	u8 control;
+	int ret = 1;
+
+	/* reset the interface */
+	gsc_writeb(0xff, hpa+LASI_RESET);
+	gsc_writeb(0x0 , hpa+LASI_RESET);		
+
+	/* enable it */
+	control = read_control(hpa);
+	write_control(control | LASI_CTRL_ENBL, hpa);
+
+        /* initializes the leds at the default state */
+        if (id==0) {
+           write_output(KBD_CMD_SET_LEDS, hpa);
+	   write_output(0, hpa);
+	   ret = write_output(KBD_CMD_ENABLE, hpa);
+	}
+
+	return ret;
+}
+
+static int inited;
+
+static void lasi_ps2_init_hw(void)
+{
+	++inited;
+}
+
+
+/* Greatly inspired by pc_keyb.c */
+
+/*
+ * 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."
+ */
+#ifdef CONFIG_PSMOUSE
+
+static struct aux_queue	*queue;
+static spinlock_t	kbd_controller_lock = SPIN_LOCK_UNLOCKED;
+static unsigned char	mouse_reply_expected;
+static int 		aux_count;
+
+static int fasync_aux(int fd, struct file *filp, int on)
+{
+	int retval;
+	
+	retval = fasync_helper(fd, filp, on, &queue->fasync);
+	if (retval < 0)
+		return retval;
+	
+	return 0;
+}
+
+
+
+static inline void handle_mouse_scancode(unsigned char scancode)
+{
+	if (mouse_reply_expected) {
+		if (scancode == AUX_REPLY_ACK) {
+			mouse_reply_expected--;
+			return;
+		}
+		mouse_reply_expected = 0;
+	}
+	else if (scancode == AUX_RECONNECT) {
+		queue->head = queue->tail = 0;  /* Flush input queue */
+		return;
+	}
+
+	add_mouse_randomness(scancode);
+	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;
+			kill_fasync(&queue->fasync, SIGIO, POLL_IN);
+			wake_up_interruptible(&queue->proc_list);
+		}
+	}
+}
+
+static inline int queue_empty(void)
+{
+	return queue->head == queue->tail;
+}
+
+static unsigned char get_from_queue(void)
+{
+	unsigned char result;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kbd_controller_lock, flags);
+	result = queue->buf[queue->tail];
+	queue->tail = (queue->tail + 1) & (AUX_BUF_SIZE-1);
+	spin_unlock_irqrestore(&kbd_controller_lock, flags);
+
+	return result;
+}
+
+
+/*
+ * Write to the aux device.
+ */
+
+static ssize_t write_aux(struct file * file, const char * buffer,
+			 size_t count, loff_t *ppos)
+{
+	ssize_t retval = 0;
+
+	if (count) {
+		ssize_t written = 0;
+
+		if (count > 32)
+			count = 32; /* Limit to 32 bytes. */
+		do {
+			char c;
+			get_user(c, buffer++);
+			written++;
+		} while (--count);
+		retval = -EIO;
+		if (written) {
+			retval = written;
+			file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
+		}
+	}
+
+	return retval;
+}
+
+
+
+static ssize_t read_aux(struct file * file, char * buffer,
+			size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t i = count;
+	unsigned char c;
+
+	if (queue_empty()) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		add_wait_queue(&queue->proc_list, &wait);
+repeat:
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (queue_empty() && !signal_pending(current)) {
+			schedule();
+			goto repeat;
+		}
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&queue->proc_list, &wait);
+	}
+	while (i > 0 && !queue_empty()) {
+		c = get_from_queue();
+		put_user(c, buffer++);
+		i--;
+	}
+	if (count-i) {
+		file->f_dentry->d_inode->i_atime = CURRENT_TIME;
+		return count-i;
+	}
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return 0;
+}
+
+
+static int open_aux(struct inode * inode, struct file * file)
+{
+	if (aux_count++) 
+		return 0;
+
+	queue->head = queue->tail = 0;	/* Flush input queue */
+	aux_count = 1;
+	aux_write_ack(AUX_ENABLE_DEV);	/* Enable aux device */
+	
+	return 0;
+}
+
+
+/* No kernel lock held - fine */
+static unsigned int aux_poll(struct file *file, poll_table * wait)
+{
+
+	poll_wait(file, &queue->proc_list, wait);
+	if (!queue_empty())
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+
+static int release_aux(struct inode * inode, struct file * file)
+{
+	lock_kernel();
+	fasync_aux(-1, file, 0);
+	if (--aux_count) {
+	   unlock_kernel();
+		return 0;
+	}
+	unlock_kernel();
+	return 0;
+}
+
+static struct file_operations psaux_fops = {
+	read:		read_aux,
+	write:		write_aux,
+	poll:		aux_poll,
+	open:		open_aux,
+	release:	release_aux,
+	fasync:		fasync_aux,
+};
+
+static struct miscdevice psaux_mouse = {
+	minor:		PSMOUSE_MINOR,
+	name:		"psaux",
+	fops:		&psaux_fops,
+};
+
+#endif /* CONFIG_PSMOUSE */
+
+
+/* This function is looking at the PS2 controller and empty the two buffers */
+
+static u8 handle_lasikbd_event(void *hpa)
+{
+        u8 status_keyb,status_mouse,scancode,id;
+        extern void handle_at_scancode(int); /* in drivers/char/keyb_at.c */
+        
+        /* Mask to get the base address of the PS/2 controller */
+        id = gsc_readb(hpa+LASI_ID) & 0x0f;
+        
+        if (id==1) 
+           hpa -= LASI_PSAUX_OFFSET; 
+        lasikbd_hpa = hpa;
+        
+
+        status_keyb = read_status(hpa);
+        status_mouse = read_status(hpa+LASI_PSAUX_OFFSET);
+
+        while ((status_keyb|status_mouse) & LASI_STAT_RBNE){
+           
+           while (status_keyb & LASI_STAT_RBNE) {
+	      
+              scancode = read_input(hpa);
+
+	      /* XXX don't know if this is a valid fix, but filtering
+	       * 0xfa avoids 'unknown scancode' errors on, eg, capslock
+	       * on some keyboards.
+	       */
+	      if (inited && scancode != 0xfa)
+		 handle_at_scancode(scancode); 
+	      
+	      status_keyb =read_status(hpa);
+           }
+	   
+#ifdef CONFIG_PSMOUSE
+           while (status_mouse & LASI_STAT_RBNE) {
+	      scancode = read_input(hpa+LASI_PSAUX_OFFSET);
+	      handle_mouse_scancode(scancode);
+              status_mouse = read_status(hpa+LASI_PSAUX_OFFSET);
+	   }
+           status_mouse = read_status(hpa+LASI_PSAUX_OFFSET);
+#endif /* CONFIG_PSMOUSE */
+           status_keyb = read_status(hpa);
+        }
+
+        tasklet_schedule(&keyboard_tasklet);
+        return (status_keyb|status_mouse);
+}
+
+
+
+	
+extern struct pt_regs *kbd_pt_regs;
+
+static void lasikbd_interrupt(int irq, void *dev, struct pt_regs *regs)
+{
+	lasips2_hpa = dev; /* save "hpa" for lasikbd_leds() */
+	kbd_pt_regs = regs;
+	handle_lasikbd_event(lasips2_hpa);
+}
+
+
+extern int pckbd_translate(unsigned char, unsigned char *, char);
+
+static struct kbd_ops gsc_ps2_kbd_ops = {
+	translate:	pckbd_translate,
+	init_hw:	lasi_ps2_init_hw,
+	leds:		lasikbd_leds,
+#ifdef CONFIG_MAGIC_SYSRQ
+	sysrq_key:	0x54,
+	sysrq_xlate:	hp_ps2kbd_sysrq_xlate,
+#endif
+};
+
+static int __init
+lasi_ps2_register(struct hp_device *d, struct pa_iodc_driver *dri)
+{
+	void *hpa = (void *) d->hpa;
+	unsigned int irq;
+	char *name;
+	int device_found;
+	u8 id;
+
+	id = gsc_readb(hpa+LASI_ID) & 0x0f;
+
+	switch (id) {
+	case 0:
+		name = "keyboard";
+		lasikbd_hpa = hpa;
+		break;
+	case 1:
+		name = "psaux";
+		break;
+	default:
+		printk(KERN_WARNING "%s: Unknown PS/2 port (id=%d) - ignored.\n",
+			__FUNCTION__, id );
+		return 0;
+	}
+	
+	/* reset the PS/2 port */
+	device_found = lasi_ps2_reset(hpa,id);
+
+	/* allocate the irq and memory region for that device */
+	if (!(irq = busdevice_alloc_irq(d)))
+		return -ENODEV;
+		    
+	if (request_irq(irq, lasikbd_interrupt, 0, name, hpa))
+		return -ENODEV;
+	
+	if (!request_mem_region((unsigned long)hpa, LASI_STATUS + 4, name))
+		return -ENODEV;
+
+	switch (id) {
+	case 0:	
+		register_kbd_ops(&gsc_ps2_kbd_ops);
+		break;
+	case 1:
+#ifdef CONFIG_PSMOUSE
+		queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL);
+		if (!queue)
+			return -ENOMEM;
+
+		memset(queue, 0, sizeof(*queue));
+		queue->head = queue->tail = 0;
+		init_waitqueue_head(&queue->proc_list);
+		
+		misc_register(&psaux_mouse);
+
+		aux_write_ack(AUX_ENABLE_DEV);
+		/* try it a second time, this will give status if the device is
+		 * available */
+		device_found = aux_write_ack(AUX_ENABLE_DEV);
+		break;
+#else
+		/* return without printing any unnecessary and misleading info */
+		return 0;	
+#endif
+	} /* of case */
+	
+	printk(KERN_INFO "PS/2 %s controller at 0x%08lx (irq %d) found, "
+			 "%sdevice attached.\n",
+			name, (unsigned long)hpa, irq,
+			device_found ? "":"no ");
+
+	return 0;
+}
+
+
+static struct pa_iodc_driver lasi_psaux_drivers_for[] __initdata = {
+	{HPHW_FIO, 0x0, 0,0x00084, 0, 0,
+		DRIVER_CHECK_HWTYPE + DRIVER_CHECK_SVERSION,
+		"Lasi psaux", "generic", (void *) lasi_ps2_register},
+	{ 0, }
+};
+
+static int __init gsc_ps2_init(void) 
+{
+	return pdc_register_driver(lasi_psaux_drivers_for);
+}
+
+module_init(gsc_ps2_init);
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)