/*
 * Input driver for Dell Axim X5.
 * Creates input events for the buttons on the device
 *
 * Copyright Matthew Garrett
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive for
 * more details.
 */


#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
#include <asm/arch/irqs.h>
#include <asm/arch/aximx5-gpio.h>


#define MY_GET_AXIMX5_GPIO(gpio) (GPLR(gpio) & GPIO_bit(gpio))

struct input_dev button_dev;
static volatile int joypad_event_count;
static wait_queue_head_t joypad_event;

struct kpad {
  int keyno;
  int irq;
  int gpio;
};

enum joypad_dir {
  NW=0,
  N=1,
  NE=2,
  E=3,
  SE=4,
  S=5,
  SW=6,
  W=7,
  CENTER=8
};

static int joypad_state;

static struct kpad gpiobuttontab[]={
  {KEY_RECORD,AXIMX5_IRQ(BUTTON_RECORD),GPIO_NR_AXIMX5_BUTTON_RECORD},
  {KEY_CALENDAR,AXIMX5_IRQ(BUTTON_CALENDAR),GPIO_NR_AXIMX5_BUTTON_CALENDAR},
  {KEY_BOOKMARKS,AXIMX5_IRQ(BUTTON_CONTACTS),GPIO_NR_AXIMX5_BUTTON_CONTACTS},
  {KEY_MAIL,AXIMX5_IRQ(BUTTON_MAILBOX),GPIO_NR_AXIMX5_BUTTON_MAILBOX},
  {KEY_HOMEPAGE,AXIMX5_IRQ(BUTTON_HOME),GPIO_NR_AXIMX5_BUTTON_HOME},
  {KEY_PAGEUP,AXIMX5_IRQ(SCROLL_DOWN),GPIO_NR_AXIMX5_SCROLL_DOWN},
  {KEY_ENTER,AXIMX5_IRQ(SCROLL_PUSH),GPIO_NR_AXIMX5_SCROLL_PUSH},
  {KEY_PAGEDOWN,AXIMX5_IRQ(SCROLL_UP),GPIO_NR_AXIMX5_SCROLL_UP},
  {-1,-1,-1}
};

static struct kpad gpiojoytab[]={
  {KEY_ENTER,AXIMX5_IRQ(JOY_CENTER),GPIO_NR_AXIMX5_JOY_CENTER},
  {KEY_LEFT,AXIMX5_IRQ(JOY_NW),GPIO_NR_AXIMX5_JOY_NW},
  {KEY_UP,AXIMX5_IRQ(JOY_NE),GPIO_NR_AXIMX5_JOY_NE},
  {KEY_RIGHT,AXIMX5_IRQ(JOY_SE),GPIO_NR_AXIMX5_JOY_SE},
  {KEY_DOWN,AXIMX5_IRQ(JOY_SW),GPIO_NR_AXIMX5_JOY_SW},
  {-1,-1,-1}
};

struct jpad {
  int nw;
  int ne;
  int se;
  int sw;
  int left;
  int right;
  int down;
  int up;
  int pressed;
};

static struct jpad joytab[]={
  {0,0,0,0,0,0,0,0,0},
  {0,0,0,1,1,0,1,0,0},
  {0,0,1,0,0,1,1,0,0},
  {0,1,0,0,0,1,0,1,0},
  {1,0,0,0,1,0,0,1,0},
  {0,0,1,1,0,0,1,0,0},
  {0,1,0,1,1,1,1,1,0},
  {1,0,0,1,1,0,0,0,0},
  {0,1,1,0,0,1,0,0,0},
  {1,1,0,0,0,0,0,1,0},
  {1,0,1,0,1,1,1,1,0},
  {0,1,1,1,1,1,1,1,0},
  {1,0,1,1,1,1,1,1,0},
  {1,1,0,1,1,1,1,1,0},
  {1,1,1,0,1,1,1,1,0},
  {1,1,1,1,1,1,1,1,0},
  {-1,-1,-1,-1,-1,-1,-1,-1,-1}
};

irqreturn_t aximx5_button_handle(int irq, void* dev_id, struct pt_regs *regs)
{
        int button;

	/* look up the keyno */
	for (button = 0; gpiobuttontab[button].irq!=-1; button++)
	       if (gpiobuttontab[button].irq == irq)
	               break;

        if (gpiobuttontab[button].irq == -1)
	{
	        printk("buttons: unhandled button pressed\n");
		return IRQ_HANDLED;
	};

	if (MY_GET_AXIMX5_GPIO(gpiobuttontab[button].gpio)) {
	  input_report_key(&button_dev, gpiobuttontab[button].keyno, 1);
	  input_sync(&button_dev);
	} else {
	  input_report_key(&button_dev, gpiobuttontab[button].keyno, 0);
	  input_sync(&button_dev);
	}
	return IRQ_HANDLED;
}

irqreturn_t aximx5_joy_handle(int irq, void* dev_id, struct pt_regs *regs)
{  
	joypad_event_count++;
	wake_up_interruptible (&joypad_event);	
        return IRQ_HANDLED;
}

void check_joypad_status(void)
{
  int center = GET_AXIMX5_GPIO(JOY_CENTER);
  int nw = GET_AXIMX5_GPIO(JOY_NW);
  int ne = GET_AXIMX5_GPIO(JOY_NE);
  int se = GET_AXIMX5_GPIO(JOY_SE);
  int sw = GET_AXIMX5_GPIO(JOY_SW);
  int button;

  for (button=0; joytab[button].ne!=-1; button++) 
    {
      if (nw!=joytab[button].nw && ne!=joytab[button].ne && se!=joytab[button].se
	  && sw!=joytab[button].sw)
	break;
    }

  /* figure out what the difference between the old state and the new state */

  if (joytab[button].left!=joytab[joypad_state].left)
    {
      input_report_key(&button_dev, KEY_LEFT, joytab[button].left);
    }

  if (joytab[button].right!=joytab[joypad_state].right)
    {
      input_report_key(&button_dev, KEY_RIGHT, joytab[button].right);
    }

  if (joytab[button].down!=joytab[joypad_state].down)
    {
      input_report_key(&button_dev, KEY_DOWN, joytab[button].down);
    }

  if (joytab[button].up!=joytab[joypad_state].up)
    {
      input_report_key(&button_dev, KEY_UP, joytab[button].up);
    }
  
  /* We want to send an enter if only the central button is pressed */

  if (button==0 && joytab[button].pressed==0 && !center)
    {
      input_report_key(&button_dev, KEY_ENTER, 1);
      joytab[button].pressed=1;
    }
  
  /* We need to send a keyup for it if the central button is released or if
     any other button is pressed */

  if (joytab[joypad_state].pressed && (button!=0 || center))
    {
      input_report_key(&button_dev, KEY_ENTER, 0);
      joytab[joypad_state].pressed=0;
    }

  joypad_state=button;

  input_sync(&button_dev);
};

  
static int joypad_thread(void* arg)
{
  daemonize ("kjoypad");
  set_user_nice (current, 5);

  for (;;) {
    int old_count = joypad_event_count;
    wait_event_interruptible(joypad_event, old_count != joypad_event_count);
    do {
      old_count = joypad_event_count;
      set_current_state (TASK_INTERRUPTIBLE);
      schedule_timeout(HZ/20);
    } while (old_count != joypad_event_count);

    check_joypad_status();
  }
}

static int __init aximx5_button_init(void)
{
        int button;
	int totalbuttons=0;

        for (button=0; gpiobuttontab[button].irq!=-1; button++) {
	  request_irq(gpiobuttontab[button].irq, aximx5_button_handle, SA_SAMPLE_RANDOM, "button", NULL);
	  set_irq_type(gpiobuttontab[button].irq, IRQT_BOTHEDGE);
	  button_dev.evbit[totalbuttons] = BIT(EV_KEY);
	  button_dev.keybit[LONG(gpiobuttontab[button].keyno)] = BIT(gpiobuttontab[button].keyno);
	  totalbuttons++;
        }

        for (button=0; gpiojoytab[button].irq!=-1; button++) {
	  request_irq(gpiojoytab[button].irq, aximx5_joy_handle, SA_SAMPLE_RANDOM, "button", NULL);
	  set_irq_type(gpiojoytab[button].irq, IRQT_BOTHEDGE);
	  button_dev.evbit[totalbuttons] = BIT(EV_KEY);
	  button_dev.keybit[LONG(gpiojoytab[button].keyno)] = BIT(gpiojoytab[button].keyno);
	  totalbuttons++;
        }

	button_dev.name = "Dell Axim button device";
	input_register_device(&button_dev);

	init_waitqueue_head (&joypad_event);
        kernel_thread (joypad_thread, NULL, 0);
	joypad_state=0;
	check_joypad_status();

        return 0;
}

module_init (aximx5_button_init);
/* No module_exit function since this driver should be never unloaded */
MODULE_AUTHOR ("Matthew Garrett <mjg59@srcf.ucam.org>");
MODULE_DESCRIPTION ("Button support for Dell Axim X5");
MODULE_LICENSE ("GPL");

