/*
 *      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
 */

#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/errno.h>
#include <linux/init.h>
#include <linux/pc_keyb.h>
#include <linux/kbd_kern.h>


static struct hp_device *lasi_psaux_keyb;
static void *lasikbd_hpa;


#define KBD_BAT		0xaa		/* in */
#define KBD_SETLEDS	0xed		/* out */
#define KBD_ECHO	0xee		/* in/out */
#define KBD_BREAK	0xf0		/* in */
#define KBD_TYPRATEDLY	0xf3		/* out */
#define KBD_SCANENABLE	0xf4		/* out */
#define KBD_DEFDISABLE	0xf5		/* out */
#define KBD_DEFAULT	0xf6		/* out */
#define KBD_ACK		0xfa		/* in */
#define KBD_DIAGFAIL	0xfd		/* in */
#define KBD_RESEND	0xfe		/* in/out */
#define KBD_RESET	0xff		/* out */

#define	LASI_ID		0x00
#define LASI_RESET	0x00
#define LASI_RCVDATA	0x04
#define LASI_XMTDATA	0x04
#define LASI_CONTROL	0x08
#define LASI_STATUS	0x0c

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

	if (wait)
		printk(KERN_INFO "Lasi PS/2 wait %d\n", wait);
	
	gsc_writeb(val, hpa+LASI_XMTDATA);

	return;
}


static void lasikbd_leds(unsigned char leds)
{
	write_output(KBD_SETLEDS, lasikbd_hpa);
	write_output(leds, lasikbd_hpa);
	write_output(KBD_SCANENABLE, lasikbd_hpa);
#if 0
	printk("%s(%d)\n", __FUNCTION__, leds); 
#endif
}

#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 

int lasi_ps2_reset(void *hpa)
{
	u8 control;

	/* 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);

	return 0;
}

static int inited;

static void lasi_ps2_init_hw(void)
{
	++inited;
}

static u8 handle_lasikbd_event(void *hpa)
{
	u8 status;
	extern void handle_at_scancode(int); /* in drivers/char/keyb_at.c */

	while ((status = read_status(hpa)) & LASI_STAT_RBNE) {
		u8 scancode;

		scancode = read_input(hpa);

		if (inited)
			handle_at_scancode(scancode);
	}

	tasklet_schedule(&keyboard_tasklet);

	return status;
}
	
extern struct pt_regs *kbd_pt_regs;

static void lasikbd_interrupt(int irq, void *dev, struct pt_regs *regs)
{
	lasikbd_hpa = dev; /* save "hpa" for lasikbd_leds() */

	kbd_pt_regs = regs;

	handle_lasikbd_event(lasikbd_hpa);
}


int lasikbd_request_irq(void (*handler)(int, void *, struct pt_regs*))
{
	unsigned int irq;

	cli();
	irq = busdevice_alloc_irq(lasi_psaux_keyb);
	sti();

	if (!irq) {
		printk(KERN_WARNING "IRQ not found for Lasi psaux at 0x%p\n", lasi_psaux_keyb->hpa);
		return -ENODEV;
	}

	return 0;
}

unsigned char lasikbd_sysrq_xlate[128];

extern unsigned char pckbd_sysrq_xlate[128];
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,
	sysrq_key:	0x54,
	sysrq_xlate:	pckbd_sysrq_xlate,
};

static int __init
lasi_ps2_register(struct hp_device *d, struct pa_iodc_driver *dri)
{
	void *hpa = (void *) d->hpa;
	u8 id;
	char *name;

	id = gsc_readb(hpa+LASI_ID) & 0x0f;

	switch(id) {
	case 0:
		name = "keyboard";
		break;
	case 1:
		name = "psaux"; /* "mouse" */;
		break;
	default:
		printk(KERN_WARNING "unknown PS/2 port %d found!  Get famous now by reporting this to parisc-linux@thepuffingroup.com!\n", id);
		name = "unknown";
	}

	if (id==0)
	    printk("Initializing Lasi PS/2-%s port at 0x%p...\n", name, hpa);
	else
	    printk("Support for Lasi PS/2-%s not yet available !\n", name); /* FIXME */
	    
	if (id==0) {
		int err;
		unsigned int irq;

		if ((err = lasi_ps2_reset(hpa)))
			printk("%s: lasi_ps2_reset() failed!\n", __FUNCTION__);

		irq = busdevice_alloc_irq(d);
		if (!irq)
		    return -ENODEV;
		    
		request_irq(irq, lasikbd_interrupt, 0, name, hpa);

		request_region((unsigned long)hpa, LASI_STATUS + 4, name);
		
		register_kbd_ops(&gsc_ps2_kbd_ops);
	}

	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, }
};

int __init gsc_ps2_init (void) 
{
	register_driver(lasi_psaux_drivers_for);
	return 0;
}
