/*
 *  linux/drivers/char/s3c2410_ts.c
 *
 *  Copyright (C) 2002  SAMSUNG ELECTRONICS SW.LEE <hitchcar@sec.samsung.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
 
/*
 * Using the hareware timer1 instead of software timer (struct timer_list)
 * raw device not implemented
 * calibration not implemented
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/init.h>
#include <linux/compiler.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/arch/s3c2410_ts.h>

#ifdef CONFIG_PM
#include <linux/pm.h>
static struct pm_dev * s3c2410_ts_pm_dev;
#endif
/*
 * TSBUF_SIZE must be a power of two
 */
#define S3C2410_TS_MINOR	0
#define H3600_TS_MODULE_NAME    "ts"
#define INCBUF(x,mod)          (((x)+1) & ((mod) - 1))
#define MOUSEBUF_SIZE           32
#define DOWN_PRESS              1
#define UP_PRESS                0
#define NO_PRESS                0
#define TS_FILTER_LENGTH        8
#define XLIMIT                  0x8 
#define YLIMIT                  0x10
#define TOUCH_ORG_OFFSET_X      75
#define TOUCH_ORG_OFFSET_Y      40
#define TOUCH_ADC_MAX_X         880/* org 1000 */
#define TOUCH_ADC_MAX_Y         890/* org  980 */
#define TOUCH_STOP_BUT_QUIVER   3

/*
 * I used  "h3600_ts" name to use the ramdisk image containing x demo
 *  from www.handhelds.org 
 *
 */

static char *g_ts_id = "h3600_ts ChangeMe";             /* global touch screen id  for irq*/
static char *g_ts_timer_id = "touchscreen timer"; /* global touch screen timer id for irq */

struct s3c2410_ts_general {
	unsigned int          head, tail;        /* Position in the event buffer */
	struct fasync_struct *async_queue;       /* Asynchronous notification    */
	wait_queue_head_t     waitq;             /* Wait queue for reading       */
	struct semaphore      lock;              /* Mutex for reading            */
	unsigned int          usage_count;       /* Increment on each open       */
	unsigned int          total;             /* Total events                 */
	unsigned int          processed;
	unsigned int          dropped;  
};


struct s3c2410_ts_device {
	struct s3c2410_ts_general  d;
        struct s3c2410_ts_calibration   cal;          /* ts calibration parameters */
        struct s3c2410_ts_event   buf[MOUSEBUF_SIZE];
        struct s3c2410_ts_event   cur_data, samples[3],last_data;
};


/*  I think..
 *  if multiple open happens, global_tc ==> global_ts[open_count] 
 */
struct s3c2410_ts_device global_ts;

enum pen_state {
	PEN_UP = 0,
	PEN_DOWN ,
	PEN_SAMPLE
};

enum  ts_timer {
        TTIMER_SETUP,
	TTIMER_START,
	TTIMER_STOP
};

struct  ts_pen_data {
	enum pen_state state;
	unsigned short x[TS_FILTER_LENGTH];  // Unfiltered data points
	unsigned short y[TS_FILTER_LENGTH];
	unsigned short count;   // Number of points recorded in this "DOWN" or "DISCARD" series
	unsigned short index;   // Location in ring buffer of last stored data value
	int            last_cal_x;  // Last reported X value to the user
	int            last_cal_y;  // Last reported Y value to the user
};

struct ts_pen_data pen_data;
static void s3c2410_ts_handler(void);
static int ts_timer_operation(enum ts_timer tt);


static int data_processing(void)
{
	struct s3c2410_ts_device * dev = &global_ts;
	int diff0, diff1, diff2;
	int RetVal = 0;		/* default valid  */
	struct s3c2410_ts_event *samples =dev->samples ;

	dev->cur_data.x = samples[0].x;
	dev->cur_data.y = samples[0].y;

	/* 
	 * Check the variance between X samples (discard if not similar), 
	 * then choose the closest pair 
	 */
	diff0 = abs(samples[0].x - samples[1].x);
	diff1 = abs(samples[1].x - samples[2].x);
	diff2 = abs(samples[2].x - samples[0].x);

	if (diff0 > XLIMIT || diff1 > XLIMIT || diff2 > XLIMIT )
		return XLIMIT ;		/* invalid  */

	if (diff1 < diff2)
	{
		if (diff1 < diff0)
			dev->cur_data.x = (samples[1].x + samples[2].x) / 2;
		else
			dev->cur_data.x = (samples[0].x + samples[1].x) / 2;
	}
	else
	{
		if (diff2 < diff0)
			dev->cur_data.x = (samples[2].x + samples[0].x) / 2;
		else
			dev->cur_data.x = (samples[0].x + samples[1].x) / 2;
	}

	/* Do the same for Y */
	diff0 = abs(samples[0].y - samples[1].y);
	diff1 = abs(samples[1].y - samples[2].y);
	diff2 = abs(samples[2].y - samples[0].y);

	if (diff0 > YLIMIT || diff1 > YLIMIT || diff2 > YLIMIT )
		return YLIMIT;

	if (diff1 < diff2)
	{
		if (diff1 < diff0)
			dev->cur_data.y = (samples[1].y + samples[2].y) / 2;
		else
			dev->cur_data.y = (samples[0].y + samples[0].y) / 2;
	}
	else
	{
		if (diff2 < diff0)
			dev->cur_data.y = (samples[2].y + samples[0].y) / 2;
		else
			dev->cur_data.y = (samples[0].y + samples[1].y) / 2;
	}

	dev->cur_data.x   -= TOUCH_ORG_OFFSET_X;
	if ( dev->cur_data.x > 40000) dev->cur_data.x = 0;
	dev->cur_data.y   -= TOUCH_ORG_OFFSET_Y;
	if ( dev->cur_data.y > 40000) dev->cur_data.y = 0;

	dev->cur_data.x  = (dev->cur_data.x*CURRENT_LCD_X)/(TOUCH_ADC_MAX_X);
	dev->cur_data.y  = CURRENT_LCD_Y - 
		(dev->cur_data.y*CURRENT_LCD_Y)/(TOUCH_ADC_MAX_Y);

	if(dev->cur_data.x > CURRENT_LCD_X ) 
		dev->cur_data.x  = CURRENT_LCD_X;

	if(dev->cur_data.y > CURRENT_LCD_Y ) 
		dev->cur_data.y  = CURRENT_LCD_Y;

	if ( (abs(dev->last_data.x - dev->cur_data.x) <= TOUCH_STOP_BUT_QUIVER) && 
			(abs(dev->last_data.y - dev->cur_data.y) <= TOUCH_STOP_BUT_QUIVER) ) {
		dev->cur_data = dev->last_data;
	}
	else {
		dev->last_data = dev->cur_data;
	}

	return RetVal;
}


static void touch_timer_irq(int irq, void *dev_id, struct pt_regs *regs)
{
        if( (rADCDAT0&0x8000)||(rADCDAT1&0x8000)) {
                  ts_timer_operation(TTIMER_STOP);
                  pen_data.state = PEN_UP;
                  s3c2410_ts_handler();
		  return;
	}

	switch(pen_data.state) {
	case PEN_UP:
                     ts_timer_operation(TTIMER_STOP);
		     s3c2410_ts_handler();
		     return;
	case PEN_DOWN:
	             pen_data.state = PEN_SAMPLE;
		     s3c2410_ts_handler();
		     return;
	case PEN_SAMPLE:
	             if( (rADCDAT0&0x8000)||(rADCDAT1&0x8000)) {
		           ts_timer_operation(TTIMER_STOP);
			   pen_data.state = PEN_UP;
			   s3c2410_ts_handler();
			   return;
		     } else {
		           pen_data.state = PEN_SAMPLE;
                           s3c2410_ts_handler();
			   return;
		     }
	default:
	printk("  UNEXPECTED ERROR %d  \n",pen_data.state);
	}
}


/*
 * Interrupt handler and standard file operations
 */
static void ts_down_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	if ( (rADCDAT1&0x8000)|| (rADCDAT0&0x8000) ) {
                 pen_data.state = PEN_UP;
        }
        else {
                 pen_data.state = PEN_DOWN;
        }
        s3c2410_ts_handler();
}


static void s3c2410_ts_handler()
{
	int index_ret;
	struct s3c2410_ts_device * dev = &global_ts;
	unsigned int  nhead            = INCBUF( dev->d.head, MOUSEBUF_SIZE );
	struct s3c2410_ts_event *event = &dev->buf[dev->d.head];

	switch (pen_data.state) {
		case PEN_UP:
			ts_timer_operation(TTIMER_STOP);
			rADCTSC &= 0xff;
			//pen_data.state = PEN_DOWN;
			break;
		case PEN_DOWN:
			ts_timer_operation(TTIMER_START);
			return;
		case PEN_SAMPLE:
			for ( index_ret  = 0; index_ret < 3; index_ret++) {
				rADCTSC = (rADCTSC&~(7))|(4);/* Auto Conversion Mode &  No Operation Mode */
				rADCCON |= 0x1;	          

				while(rADCCON&0x1);         /* sure that Enable_start is low  */
				while(!(rADCCON&0x8000))    /* End of A/D Conversion  */
					/* Reading DATA */
					dev->samples[index_ret].y = 0x3ff&rADCDAT0;
				dev->samples[index_ret].x = 0x3ff&rADCDAT1;
			}
			rADCTSC = (rADCTSC&~(7))|(3); 	/*Normal ADC Conversion & Waiting for Interrupt Mode */
			break;
		default:
	}

	if (pen_data.state==PEN_SAMPLE){
		index_ret = data_processing();
		if ( index_ret) {
			/* discard the data  */
			ts_timer_operation(TTIMER_START);
			return;
		}
		else {
			ts_timer_operation(TTIMER_START);
			/* Store the character only if there is room */
			dev->d.total++;
			if ( nhead != dev->d.tail ) {
				event->x = dev->cur_data.x;
				event->y = dev->cur_data.y;
				event->pressure = DOWN_PRESS;
				dev->d.head = nhead;

				if ( dev->d.async_queue )
					kill_fasync( &dev->d.async_queue, SIGIO, POLL_IN );
				wake_up_interruptible( &dev->d.waitq );
				dev->d.processed++;
			}
			else {
				dev->d.dropped++;
			}
		}
	}

	/*
	 *  conform to Specification for Generic Touch Screen Driver
	 */
	else {			/* pen_data.state == PEN_UP */
		dev->d.total++;
		if ( nhead != dev->d.tail ) {
			event->x = dev->last_data.x;
			event->y = dev->last_data.y;
			event->pressure = UP_PRESS;
			dev->d.head = nhead;
			if ( dev->d.async_queue )
				kill_fasync( &dev->d.async_queue, SIGIO, POLL_IN );
			wake_up_interruptible( &dev->d.waitq );
			dev->d.processed++;
		}
		else {
			dev->d.dropped++;
		}
	}
}


static ssize_t s3c2410_ts_read(struct file *filp, char *buf, size_t count, loff_t *l)
{
	struct s3c2410_ts_device *dev = (struct s3c2410_ts_device *) filp->private_data;

	if (count < sizeof(struct s3c2410_ts_event))
		return -EINVAL;
	do { 
		if (down_interruptible(&dev->d.lock))      
			return -ERESTARTSYS;               
		while ( dev->d.head == dev->d.tail ) {   
			up(&dev->d.lock);                
			if ( filp->f_flags & O_NONBLOCK )
				return -EAGAIN;          
			if ( wait_event_interruptible( dev->d.waitq, (dev->d.head != dev->d.tail) ) )  
				return -ERESTARTSYS;                
			if ( down_interruptible(&dev->d.lock))    
				return -ERESTARTSYS;              
		} 
	} while (0);

	if ( copy_to_user(buf, &dev->buf[dev->d.tail], sizeof(struct s3c2410_ts_event)) ) {
		up(&dev->d.lock);
		return -EFAULT;
	}
	dev->d.tail = INCBUF(dev->d.tail, MOUSEBUF_SIZE);
	up(&dev->d.lock);
	return sizeof(struct s3c2410_ts_event);
}


static unsigned int s3c2410_ts_poll(struct file *filp, poll_table *wait)
{
	struct s3c2410_ts_general *dev = (struct s3c2410_ts_general *) filp->private_data;
	poll_wait(filp, &dev->waitq, wait);
	return (dev->head == dev->tail ? 0 : (POLLIN | POLLRDNORM));
}


static int 
s3c2410_ts_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
       	int RetVal = 0;

        switch (cmd) {
	case TS_GET_CAL:
                if ( copy_to_user((void *)arg, &global_ts.cal, 
				  sizeof(struct s3c2410_ts_calibration)))
			RetVal = -EFAULT;
                break;
	case TS_SET_CAL:
		if ( copy_from_user(&global_ts.cal, (void *) arg, 
				    sizeof(struct s3c2410_ts_calibration)))
			RetVal = -EFAULT;
		break;
        default:
	       printk(__FUNCTION__ ": UNKNOWN IOCTL COMMAND=0x%08X\n",cmd);
	}
	/*
	 * Future ioctl goes here
	 */
	return RetVal;
}


static int s3c2410_ts_open(struct inode *inode, struct file *filp)
{
	struct s3c2410_ts_general *dev ;

	filp->private_data = &global_ts;
	dev = (struct s3c2410_ts_general *) filp->private_data;
	if ( dev->usage_count++ == 0 )  /* We're the first open - clear the buffer */
		dev->tail = dev->head;
	if (0) printk(__FUNCTION__ " usage=%d\n", dev->usage_count);
	MOD_INC_USE_COUNT;
	return 0;
}


/*
 * invoked to add files to or remove files from the list of
 * interested processes when the FASYNC flag changes for an open file
 * 
 */
static int s3c2410_ts_fasync(int fd, struct file *filp, int mode)
{
	/*  Private * dev = filp->private_data;
	 *  fasyhc_helper(fd,filp,on,&dev->async_queue)
	 *
	 */
	struct s3c2410_ts_general *dev = (struct s3c2410_ts_general *) filp->private_data;
	if (0) printk(__FUNCTION__ ": mode %x\n", mode );
	return fasync_helper(fd, filp, mode, &dev->async_queue);
}


static int s3c2410_ts_release(struct inode *inode, struct file *filp)
{
	struct s3c2410_ts_general *dev = (struct s3c2410_ts_general *) filp->private_data;
	
	dev->usage_count--;
	if (0) printk(__FUNCTION__ " usage=%d\n", dev->usage_count);
	filp->f_op->fasync( -1, filp, 0 );  /* Remove ourselves from the async list */
	MOD_DEC_USE_COUNT;
	return 0;
}


static struct file_operations s3c2410_ts_fops = {
	owner:		THIS_MODULE,
	read:		s3c2410_ts_read,
	poll:		s3c2410_ts_poll,
	ioctl:		s3c2410_ts_ioctl,
	open:		s3c2410_ts_open,
	release:	s3c2410_ts_release,
	fasync:		s3c2410_ts_fasync,
};

static void config_adc_touch(void);
#ifdef CONFIG_PM
static int 
s3c2410_ts_pm_callback(struct pm_dev *pm_dev, pm_request_t req, void *data)
{
	switch (req) {
		case PM_SUSPEND:
			ts_timer_operation(TTIMER_STOP);
			break;
		case PM_RESUME:
			config_adc_touch();
			ts_timer_operation(TTIMER_SETUP);
			ts_timer_operation(TTIMER_START);
			break;
	}
	return 0;
}
#endif


static int ts_timer_operation(enum ts_timer tt)
{
	int RetVal=0;

	switch(tt) {
		case TTIMER_SETUP:
			rTCFG0  |= SYS_TIMER01_PRESCALER;
			rTCFG1  = (rTCFG1&~(0xf<<4))|(SYS_TIMER1_MUX<<4);
			rTCNTB1  = (TOUCH_RESCHED_PERIOD*PCLK)/
				((SYS_TIMER01_PRESCALER +1)*(SYS_TIMER1_DIVIDER)*1000);
			rTCMPB1 = 0;
			rTCON   = (rTCON&~(0xf00))|(0xA<<8); /* the auto reload ,manual update for Timer 1 */
			rTCON   = (rTCON&~(0xf00))|(0<<8); /* Stop and clear manual update  for Timer1  */

			if ((RetVal = request_irq(IRQ_TIMER1,touch_timer_irq ,SA_INTERRUPT,g_ts_timer_id,NULL))) {
				printk(KERN_WARNING "s3c2410_ts timer : failed to get IRQ\n");
				return RetVal;
			}
			return RetVal;
		case TTIMER_START:
			rTCON   = (rTCON&~(0xf00))|(0xA<<8); 
			rTCON   = (rTCON&~(0xf00))|(1<<8); /* Start */
			break;
		case TTIMER_STOP:
			rTCON   = (rTCON&~(0xf00))|(0<<8); /* Stop */
			break;
	}
	return RetVal; /* success */
}


static void config_adc_touch(void)
{
	/* 
	 * Down,YM:GND,YP:AIN5,XM:Hi-z,XP:AIN7,XP pullup En,Auto,Waiting for interrupt mode 
         * If auto mode, the SEL_MUX don't care
	 */
	rADCTSC=(0<<8)|(1<<7)|(1<<6)|(0<<5)|(1<<4)|(0<<3)|(0<<2)|(3);
	/* ADC Start or Interval Delay */
	rADCDLY=ADC_DELAY_TIME;	
	/* Enable Prescaler,Prescaler,AIN5/7 fix,Normal,Disable read start,No operation */
	rADCCON = (1<<14)|(ADCPRS<<6)|(7<<3)|(0<<2)|(0<<1)|(0);	
}


static int gMajor;		/* Dynamic major for now */
int __init s3c2410_ts_init_module(void)
{
	int retval;
	struct s3c2410_ts_general *dev;
	dev = (struct s3c2410_ts_general *)&global_ts;
	
	/* Duplicated Code Sorry *^^*
         * Setup GPIOs for touch 
         * GPGCON/GPGUP  see include/asm/arch-s3c2410/uncomress.h
         * GPG15,14,13,12
         */
	// Clear GPG15, 14, 13, 12
        rGPGCON &= ~((0x03 << 30)|(0x03 << 28)|(0x03 << 26)|(0x03 << 24));
	 // Set GPG15 to use as nYPON, GPG14 to use as YMON, GPG13 to use as nXPON, GPG12 to use as XMON
	rGPGCON |= ((0x03 << 30)|(0x03 << 28)|(0x03 << 26)|(0x03 << 24));
	// Disable full up function
	rGPGUP |= ((0x01 << 15)|(0x01 << 14)|(0x01 << 13)|(0x01 << 12));
	config_adc_touch();
	enable_irq(IRQ_ADC);
   	if ((retval = request_irq(IRQ_TC, ts_down_interrupt ,SA_INTERRUPT,g_ts_id, NULL))) {
		printk(KERN_WARNING "s3c2410_ts: failed to get IRQ\n");
		return retval;
	}
        gMajor = devfs_register_chrdev(0, H3600_TS_MODULE_NAME, &s3c2410_ts_fops);
        if (gMajor < 0) {
                printk(__FUNCTION__ ": can't get major number\n");
                return gMajor;
	}
#ifdef CONFIG_PM
	s3c2410_ts_pm_dev =  pm_register(PM_SYS_DEV, 0, s3c2410_ts_pm_callback);
#endif	
	/*----------------------------------------*/
	dev->head = 0;
	dev->tail = 0;
	init_waitqueue_head( &dev->waitq );
	init_MUTEX( &dev->lock );
	dev->async_queue = NULL;
	pen_data.state = PEN_DOWN;
	
	if(ts_timer_operation(TTIMER_SETUP))
             	  printk(KERN_NOTICE "  !!!!! TOUCH TIMER SETUP FAILED   \n");
	printk(KERN_NOTICE "Loading S3C2410 Touch Screen Driver          \n");
	return 0;
}


void __exit s3c2410_ts_cleanup_module(void)
{
	printk(__FUNCTION__ ": S3C2410 Touch Screen Exit !!! \n");
        free_irq(IRQ_TIMER1,g_ts_id);
        free_irq(IRQ_TC,g_ts_timer_id);
	/*
         * Direct Disable IRQ ADC rINTMSK<= IRQ_ADC
         */
	devfs_unregister_chrdev(gMajor, H3600_TS_MODULE_NAME);
}

module_init(s3c2410_ts_init_module);
module_exit(s3c2410_ts_cleanup_module);

MODULE_AUTHOR("SW.LEE <hitchcar@sec.samsung.com>");
MODULE_DESCRIPTION("S3c2410 touchscreen driver");
MODULE_SUPPORTED_DEVICE("touchscreen/s3c2410");
