/*
 * Jornada 720 MCU keyboard/mouse driver
 *
 * Created 1/1/2001 by John Ankcorn, MIT LCS
 *
 * Changelog:
 *
 * This driver is based on linux/drivers/char/sa1111_keyb.c
 *
 */

#include <linux/config.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/mm.h>
#include <linux/signal.h>
#include <linux/init.h>
#include <linux/kbd_ll.h>
#include <linux/delay.h>
#include <linux/random.h>
#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/malloc.h>
#include <linux/kbd_kern.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/pc_keyb.h>
#include <asm/keyboard.h>
#include <linux/h3600_ts.h>

#define MAX_READ_SIZE	256
#define AUX_DEV ((void *)queue)
#define SC_LIM 128

spinlock_t kbd_controller_lock = SPIN_LOCK_UNLOCKED;
static struct aux_queue *queue;        /* Mouse data buffer. */
static int aux_count = 0;
static unsigned char keyboard_data[MAX_READ_SIZE];

static unsigned char e0_keys[128] = {
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x00-0x07 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x08-0x0f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x10-0x17 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x18-0x1f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x20-0x27 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x28-0x2f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x30-0x37 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x38-0x3f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x40-0x47 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x48-0x4f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x50-0x57 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x58-0x5f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x60-0x67 */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x68-0x6f */
  0, 0, 0, 0, 0, 0, 0, 0,            /* 0x70-0x77 */
  0, 0, 0, 0, 0, 0, 0, 0             /* 0x78-0x7f */
};
TS_DEV pTsDev;

void sa1111_leds(unsigned char leds)
{
/* dummy called from keyboard.c (keyb_leds() ) */
}

/* for "kbd-reset" cmdline param */
static int __init kbd_reset_setup(char *str)
{
       return 1;
}

__setup("kbd-reset", kbd_reset_setup);

int sa1111_setkeycode(unsigned int scancode, unsigned int keycode)
{
       if (scancode < SC_LIM || scancode > 255 || keycode > 127)
         return -EINVAL;
         e0_keys[scancode - 128] = keycode;
       return 0;
}

int sa1111_getkeycode(unsigned int scancode)
{
       return (scancode < SC_LIM || scancode > 255) ? -EINVAL :
           e0_keys[scancode - 128];
}

char sa1111_unexpected_up(unsigned char keycode)
{
   if (keycode >= SC_LIM )
       return 0;
   else
       return 0200;
}

int sa1111_translate(unsigned char scancode, unsigned char *keycode,
                   char raw_mode)
{
       static int prev_scancode = 0;

       if (scancode == 0xe0 || scancode == 0xe1) {
               prev_scancode = scancode;
               return 0;
       }
       if (scancode == 0x00 || scancode == 0xff) {
               prev_scancode = 0;
               return 0;
       }
       scancode &= 0x7f;
#if 0
       if (prev_scancode) {
         if (prev_scancode != 0xe0) {
             if (prev_scancode == 0xe1 && scancode == 0x1d) {
                 prev_scancode = 0x100;
                 return 0;
             } else {
                 if (!raw_mode)
                   printk(KERN_INFO "keyboard: unknown e1 escape sequence\n");
                 prev_scancode = 0;
                 return 0;
             }
         } else {
             prev_scancode = 0;
             if (scancode == 0x2a || scancode == 0x36)
               return 0;
             if (e0_keys[scancode])
               *keycode = e0_keys[scancode];
             else {
                 if (!raw_mode)
                   printk(KERN_INFO "keyboard: unknown scancode e0 %02x\n",
                          scancode);
                 return 0;
             }
         }
       } else
#endif
         *keycode = scancode;
       return 1;
}

static void keyboard_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long flags;
int this_size;
unsigned char *p;
unsigned char scancode;

    kbd_pt_regs = regs;
    spin_lock_irqsave(&kbd_controller_lock, flags);
	this_size = jornada720_getkey(keyboard_data, sizeof(keyboard_data));
    p = keyboard_data;
    while (this_size-- > 0) {
		scancode = *p++ & 0xff;
        handle_scancode(scancode, !(scancode & 0x80));
        tasklet_schedule(&keyboard_tasklet);
	}
    spin_unlock_irqrestore(&kbd_controller_lock, flags);
}

int bozox, bozoy, bozop;

static void mouse_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long flags;
union {
	TS_RET ret;
	char c[sizeof(TS_RET)];
} temp;
int i;
int ok_for_fasync;

    kbd_pt_regs = regs;
    spin_lock_irqsave(&kbd_controller_lock, flags);
    temp.ret.x = bozox;
    temp.ret.y = bozoy;
    temp.ret.pressure = bozop;
bozox += 10;
bozoy += 3;
if (bozop++ > 10)
bozop = 0;
	jornada720_gettouch(&temp.ret);
   if (aux_count) {
       int head = queue->head;
       ok_for_fasync = 1;
       for (i = 0; i < sizeof(TS_RET); i++) {
           queue->buf[head] = temp.c[i];
           head = (head + 1) & (AUX_BUF_SIZE-1);
           if (head == queue->tail)
               ok_for_fasync = 0;
       }
       if (ok_for_fasync) {
printk ("new %d fas %p; ", head, queue->fasync);
           queue->head = head;
           if (queue->fasync)
               kill_fasync(&queue->fasync, SIGIO, POLL_IN);
           wake_up_interruptible(&queue->proc_list);
       }
   }
   spin_unlock_irqrestore(&kbd_controller_lock, flags);
}

static unsigned char get_from_queue(void)
{
       unsigned char result;
       unsigned long flags;

printk("[%s:%d]\n", __FUNCTION__, __LINE__);
       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;
} 

static inline int queue_empty(void)
{
       return queue->head == queue->tail;
}

int h3600_ioctl(struct inode * inode, struct file *filp,
        unsigned int cmd , unsigned long arg)
{
    printk("tsIoctl: cmd %04x  arg=%x\n",cmd, arg);
    switch(cmd) {
    case TS_GET_RATE:    /* TODO TODO */
        printk("TS_GET_RATE\n");
        return pTsDev.rate;
        break;
    case TS_SET_RATE:    /* TODO TODO */
        printk("TS_SET_RATE\n");
        pTsDev.rate = arg;
        break;
    case TS_GET_CAL:
        printk("TS_GET_CAL\n");
        __copy_to_user((char *)arg, (char *)&pTsDev.cal, sizeof(TS_CAL) );
        break;
    case TS_SET_CAL:
        printk("\n\nTS_SET_CAL: ");
        __copy_from_user( (char *)&pTsDev.cal,(char *)arg ,sizeof(TS_CAL) );
    
        printk("xscale=%d xtrans=%d yscale=%d ytrans=%d\n\n",
               pTsDev.cal.xscale,pTsDev.cal.xtrans,
               pTsDev.cal.yscale,pTsDev.cal.ytrans);
        break;
    default:
        printk("IOCTL: unknowncmd %04x\n",cmd);
        return -EINVAL;
    }
    return 0;
}

static int fasync_aux(int fd, struct file *filp, int on)
{
       int retval;

printk("[%s:%d]\n", __FUNCTION__, __LINE__);
       retval = fasync_helper(fd, filp, on, &queue->fasync);
       if (retval < 0)
               return retval;
       return 0;
} 

/*
 * Random magic cookie for the aux device
 */
static int release_aux(struct inode * inode, struct file * file)
{
printk("[%s:%d]\n", __FUNCTION__, __LINE__);
       fasync_aux(-1, file, 0);
       if (--aux_count)
               return 0;
       // kbd_write_cmd(AUX_INTS_OFF);  /* Disable controller ints */
       // kbd_write_command_w(KBD_CCMD_MOUSE_DISABLE);
       free_irq(GPIO_JORNADA720_KEYBOARD_IRQ, AUX_DEV);
       free_irq(GPIO_JORNADA720_MOUSE_IRQ, AUX_DEV);
       return 0;
}

/*
 * Install interrupt handler.
 * Enable auxiliary device.
 */
static int jorirq(void)
{
    set_GPIO_IRQ_edge( GPIO_JORNADA720_MOUSE, GPIO_FALLING_EDGE );
    set_GPIO_IRQ_edge( GPIO_JORNADA720_KEYBOARD, GPIO_FALLING_EDGE );
    if (request_irq(GPIO_JORNADA720_KEYBOARD_IRQ, keyboard_interrupt, SA_SHIRQ,
             "Jornada720 Keyboard", AUX_DEV)) {
        aux_count--;
        return -EBUSY;
    }
    if (request_irq(GPIO_JORNADA720_MOUSE_IRQ, mouse_interrupt, SA_SHIRQ,
              "Jornada720 Mouse", AUX_DEV)) {
        aux_count--;
        return -EBUSY;
    }
	return 0;
}

static int open_aux(struct inode * inode, struct file * file)
{
int minor;
kdev_t dev = inode->i_rdev;

printk("[%s:%d] open_aux_start\n", __FILE__, __LINE__);
		printk("open_aux major = %d\n",MAJOR(dev) );
	minor = MINOR(dev);
printk("open_aux minor = %d\n",minor );
       if (aux_count++) {
               return 0;
       }
       mdelay(50);
       queue->head = queue->tail = 0;          /* Flush input queue */
	   jorirq();

       /* aux_write_ack(AUX_ENABLE_DEV); / Enable aux device */
       return 0;
}
/*
 * Put bytes from input queue to buffer.
 */
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;
int minor;
kdev_t dev = file->f_dentry->d_inode->i_rdev;

		printk("read_aux major = %d\n",MAJOR(dev) );
	minor = MINOR(dev);
printk("read_aux minor = %d\n",minor );

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

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

int minor;
kdev_t dev = file->f_dentry->d_inode->i_rdev;

		printk("write_aux major = %d\n",MAJOR(dev) );
	minor = MINOR(dev);
printk("write_aux minor = %d\n",minor );
       if (count) {
               ssize_t written = 0;
               if (count > 32)
                       count = 32; /* Limit to 32 bytes. */
               do {
                       char c;
                       get_user(c, buffer++);
                       printk ("[%s]: write_aux 0x%x\n", __FILE__, c);
                       written++;
               } while (--count);
               retval = -EIO;
               if (written) {
                       retval = written;
                       file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
               }
       }
       return retval;
}

static unsigned int aux_poll(struct file *file, poll_table * wait)
{
int minor;
kdev_t dev = file->f_dentry->d_inode->i_rdev;

	minor = MINOR(dev);
       poll_wait(file, &queue->proc_list, wait);
printk("Pm=%d [%d]\n",minor, !queue_empty() );
       if (!queue_empty())
               return POLLIN | POLLRDNORM;
       return 0;
}

struct file_operations psaux_fops = {
       read:           read_aux,
       poll:           aux_poll,
       ioctl:    h3600_ioctl,
       open:           open_aux,
       release:        release_aux,
       fasync:         fasync_aux,
};

/*
 * Initialize driver.
 */
static struct miscdevice psaux_mouse = {
       PSMOUSE_MINOR, "psaux", &psaux_fops
};

void __init sa1111_init_hw(void)
{
    kbd_request_region();
    queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL);
    memset(queue, 0, sizeof(*queue));
    queue->head = queue->tail = 0;
    pTsDev.penStatus=PEN_DOWN;
    pTsDev.cal.xscale = -93;
    pTsDev.cal.xtrans = 346;
    pTsDev.cal.yscale = -64;
    pTsDev.cal.ytrans = 251;
    init_waitqueue_head(&queue->proc_list);

	jornada720_mcu_init();
jorirq();

    misc_register(&psaux_mouse);
    // kbd_write_command_w(KBD_CCMD_MOUSE_ENABLE); /* Enable Aux. */
    /* Ok, finally allocate the IRQ, and off we go.. */
    kbd_request_irq(keyboard_interrupt);
}
