/*
 * Driver interface to the Samsung HAMCOP and SAMCOP Companion chips
 * SAMCOP: iPAQ H5xxx
 *	   Specifications: http://www.handhelds.org/platforms/hp/ipaq-h5xxx/
 * HAMCOP: iPAQ H22xx
 *	   Specifications: http://www.handhelds.org/platforms/hp/ipaq-h22xx/
 *
 * Copyright © 2003, 2004 Compaq Computer Corporation.
 *
 * Use consistent with the GNU GPL is permitted,
 * provided that this copyright notice is
 * preserved in its entirety in all copies and derived works.
 *
 * COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
 * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
 * FITNESS FOR ANY PARTICULAR PURPOSE.
 *
 * Author:  Keith Packard <keith.packard@hp.com>
 *          May 2003
 *
 * Updates:
 *
 * 2004-02-11	Michael Opdenacker	Renamed names from samcop to shamcop,
 * 					Goal:support HAMCOP and SAMCOP.
 * 2004-02-14	Michael Opdenacker	Temporary fix for device id handling
 */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/config.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/device.h>
#include <linux/soc-device.h>

#include <asm/arch/hardware.h>
#include <asm/irq.h>
#include <asm/io.h>

#include <asm/arch-pxa/h2200-asic.h>
#include <asm/hardware/ipaq-hamcop.h>

//#include "shamcop_base.h"
//#include "shamcop_adc.h"
#include "ipaq_ts.h"

// Define this to see all suspend/resume init/cleanup messages
#define DEBUG_INIT()  \
        if (0) printk(KERN_NOTICE "enter %s\n", __FUNCTION__)
#define DEBUG_FINI() \
        if (0) printk(KERN_NOTICE "leave %s\n", __FUNCTION__)
#define DEBUG_OUT(f,a...) \
	if (0) printk(KERN_NOTICE "%s:" f, __FUNCTION__, ##a)
#define DEBUG_ISR_INIT()  \
        if (0) printk(KERN_NOTICE "enter %s\n", __FUNCTION__)
#define DEBUG_ISR_FINI() \
        if (0) printk(KERN_NOTICE "leave %s\n", __FUNCTION__)
#define DEBUG_ISR_OUT(f,a...) \
	if (0) printk(KERN_NOTICE "%s:" f, __FUNCTION__, ##a)

#define PDEBUG(format,arg...) printk(KERN_DEBUG __FILE__ ":%s - " format "\n", __FUNCTION__, ## arg)
#define PALERT(format,arg...) printk(KERN_ALERT __FILE__ ":%s - " format "\n", __FUNCTION__, ## arg)
#define PERROR(format,arg...) printk(KERN_ERR __FILE__ ":%s - " format "\n", __FUNCTION__, ## arg)
	
enum touchscreen_state {
	TS_STATE_WAIT_PEN_DOWN,   // Waiting for a PEN interrupt
	TS_STATE_ACTIVE_SAMPLING  // Actively sampling ADC
};

struct touchscreen_data {
	enum touchscreen_state state;
	struct timer_list      timer;
	int		       irq;
	struct input_dev       input;
	struct device	       *spi;
};

#define ADC_SAMPLE_PERIOD	10  /* Sample every 10 milliseconds */
static unsigned long adc_sample_period   = ADC_SAMPLE_PERIOD;
//static unsigned long adc_prescaler       = 19;
static unsigned long adc_delay           = 500;

//static int clock_multiplier = 2;		/* XXX */

MODULE_PARM(adc_sample_period, "i");
MODULE_PARM(adc_delay, "i");

//static void ipaq_touchscreen_start_record (struct touchscreen_data *);
//static void ipaq_touchscreen_record (struct touchscreen_data *);
//static void ipaq_touchscreen_next_record (struct touchscreen_data *touch);


/***********************************************************************************
 *   Touchscreen support (stolen from the H3900 code)
 ***********************************************************************************/
	
static void
report_touchpanel (struct touchscreen_data *ts, int pressure, int x, int y)
{
	input_report_abs (&ts->input, ABS_PRESSURE, pressure);
	input_report_abs (&ts->input, ABS_X, x);
	input_report_abs (&ts->input, ABS_Y, y);
	input_sync (&ts->input);
}

static int
ipaq_pen_isr (int irq, void *dev_id, struct pt_regs *regs)
{
	struct touchscreen_data *ts = dev_id;
	DEBUG_ISR_INIT ();

	ipaq_touchscreen_start_record (ts);

	DEBUG_ISR_FINI ();

	return 1;
}

static void 
ipaq_ts_interrupt_mode (struct touchscreen_data *touch)
{
//	ipaq_spi_write_tscontrol (touch->adc, ASIC_ADC_TSC_WAIT);
}

/* Ask for the next sample */
static void 
ipaq_spi_start_touchscreen (struct touchscreen_data *touch)
{
//	ipaq_spi_write_tscontrol (touch->adc, ASIC_ADC_TSC_SAMPLE);

//	if (ipaq_spi_request_sample (touch->adc, adc_delay, ASIC_ADC_CONTROL_SAMPLE) == 0) {
//		ipaq_touchscreen_record (touch);
//	} else {
//		printk ("touchscreen adc timed out\n");
//		/* try again */
//		ipaq_touchscreen_next_record (touch);
//	}
}

/* Pen is up, stop sample collection process */
static void 
ipaq_touchscreen_stop_record (struct touchscreen_data *touch)
{
	DEBUG_ISR_INIT ();
	touch->state = TS_STATE_WAIT_PEN_DOWN;
	ipaq_ts_interrupt_mode (touch);
	DEBUG_ISR_FINI ();
}

/* Pen is down, start sample collection process */
static void 
ipaq_touchscreen_start_record (struct touchscreen_data *touch)
{
	DEBUG_ISR_INIT ();
	if (touch->state == TS_STATE_WAIT_PEN_DOWN)
	{
		touch->state = TS_STATE_ACTIVE_SAMPLING;

		ipaq_spi_start_touchscreen( touch );		
	}
	DEBUG_ISR_FINI ();
}


static void 
ipaq_touchscreen_timer_callback (unsigned long data)
{
	struct touchscreen_data *ts = (struct touchscreen_data *)data;
	DEBUG_INIT ();
	/* ask for another sample */
	ipaq_spi_start_touchscreen (ts);
	DEBUG_FINI ();
}

static void 
ipaq_touchscreen_next_record (struct touchscreen_data *touch)
{
	unsigned long inc = (adc_sample_period * HZ) / 1000;
	if (!inc) inc = 1;

	/* 
	 * Set touch screen control back to 0xd3 so that the next
	 * sample will have the PEN_UP bit set correctly
	 */
//	ipaq_spi_write_tscontrol (touch->adc, ASIC_ADC_TSC_WAIT);

	/* queue the timer to get another sample */
	mod_timer (&touch->timer, jiffies + inc);
}

/* Read a sample, then queue a timer if the pen is still down */
static void 
ipaq_touchscreen_record (struct touchscreen_data *touch)
{
	u16	Data0, Data1;
//	int	x, y;

	DEBUG_ISR_INIT ();

//	ipaq_spi_read_data (touch->spi, &Data0, &Data1);

//	if (Data0 & ipaq_ADC_DATA0_PEN_UP) {
//		DEBUG_ISR_OUT ("pen up\n");
		report_touchpanel (touch, 0, 0, 0);
//		ipaq_touchscreen_stop_record (touch);
//	} else {
//		x = Data0 & ADC_DATA_MASK;	/* flip X axis */
//		y = Data1 & ADC_DATA_MASK;	/* flip Y axis */
//		DEBUG_ISR_OUT ("pen down %d, %d\n", x, y);
//		report_touchpanel (touch, 1, x, y);
//		ipaq_touchscreen_next_record (touch);
//	}

	DEBUG_ISR_FINI ();
}
	
static int 
ipaq_touchscreen_probe (struct device *dev)
{
	struct soc_device *sdev = to_soc_device (dev);
	struct touchscreen_data *ts;
	DEBUG_INIT ();
	int result;

	ts = kmalloc (sizeof (*ts), GFP_KERNEL);
	memset (ts, 0, sizeof (*ts));

	ts->irq = sdev->resource[0].start;
	
	ts->spi = dev->parent;

	memset(&ts->input, 0, sizeof(struct input_dev));
	init_input_dev (&ts->input);
	ts->input.evbit[0] = BIT(EV_ABS);	
	ts->input.absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
	ts->input.absmin[ABS_X] = 0;   ts->input.absmin[ABS_Y] = 0;
	ts->input.absmax[ABS_X] = 1023; ts->input.absmax[ABS_Y] = 1023;
	ts->input.absmax[ABS_PRESSURE] = 1;

	ts->input.name = "ipaq ts";
	ts->input.private = ts;

	input_register_device (&ts->input);

	ts->timer.function = ipaq_touchscreen_timer_callback;
	ts->timer.data = (unsigned long)ts;
	ts->state = TS_STATE_WAIT_PEN_DOWN;
	init_timer (&ts->timer);

	dev->driver_data = ts;

	ipaq_ts_interrupt_mode (ts);

	result = request_irq (ts->irq, ipaq_pen_isr, SA_SAMPLE_RANDOM, "touchscreen", ts);

	if (result)
	{
		printk(KERN_CRIT "%s: unable to grab ADCTS IRQ %d error=%d\n", __FUNCTION__, 
		       ts->irq, result);
		input_unregister_device (&ts->input);
		kfree (ts);
		return result;
	}

	DEBUG_FINI();
	return 0;
}

static void 
ipaq_touchscreen_remove (struct device *dev)
{
	struct touchscreen_data *ts = dev->driver_data;

	DEBUG_INIT ();
	del_timer_sync (&ts->timer);
	free_irq (ts->irq, ts);
	DEBUG_FINI ();
}

static int
ipaq_touchscreen_suspend (struct device *dev, u32 state, u32 level)
{
	DEBUG_INIT ();
	/* XXX */
	DEBUG_FINI ();
	return 0;
}

static int
ipaq_touchscreen_resume (struct device *dev, u32 level)
{
	struct touchscreen_data *ts = dev->driver_data;
	DEBUG_INIT ();
	/* XXX */
	ts->state = TS_STATE_WAIT_PEN_DOWN;
	DEBUG_FINI ();
	return 0;
}

static soc_device_id ipaq_touchscreen_device_ids[] = {

//#ifdef SAMCOP
// IPAQ_SAMCOP_TS_DEVICE_ID, 0
//#else
// IPAQ_HAMCOP_TS_DEVICE_ID, 0
//#endif
};

struct soc_device_driver ipaq_touchscreen_soc_device_driver = {
	.device_ids = ipaq_touchscreen_device_ids,
	.driver = {
		.name     = "ipaq ts",
		.probe    = ipaq_touchscreen_probe,
		.shutdown = ipaq_touchscreen_remove,
		.suspend  = ipaq_touchscreen_suspend,
		.resume   = ipaq_touchscreen_resume
	}
};

static int
ipaq_touchscreen_init (void)
{
	return soc_driver_register (&ipaq_touchscreen_soc_device_driver);
}

static void
ipaq_touchscreen_cleanup (void)
{
	soc_driver_unregister (&ipaq_touchscreen_soc_device_driver);
}

module_init(ipaq_touchscreen_init);
module_exit(ipaq_touchscreen_cleanup);
