/*
 * File      : touch.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2010 - 2012, RT-Thread Develop Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-01-01     Yi.Qiu      first version
 */
 
#include <rthw.h>
#include <rtthread.h>
#include <s3c24x0.h>

#ifdef RT_USING_RTGUI
#include <rtgui/rtgui_system.h>
#include <rtgui/rtgui_server.h>
#include <rtgui/event.h>
#endif

#include "lcd.h"
#include "touch.h"

/* ADCCON Register Bits */
#define S3C2410_ADCCON_ECFLG			(1<<15)
#define S3C2410_ADCCON_PRSCEN			(1<<14)
#define S3C2410_ADCCON_PRSCVL(x)		(((x)&0xFF)<<6)
#define S3C2410_ADCCON_PRSCVLMASK		(0xFF<<6)
#define S3C2410_ADCCON_SELMUX(x)		(((x)&0x7)<<3)
#define S3C2410_ADCCON_MUXMASK			(0x7<<3)
#define S3C2410_ADCCON_STDBM			(1<<2)
#define S3C2410_ADCCON_READ_START		(1<<1)
#define S3C2410_ADCCON_ENABLE_START		(1<<0)
#define S3C2410_ADCCON_STARTMASK		(0x3<<0)

/* ADCTSC Register Bits */
#define S3C2410_ADCTSC_UD_SEN			(1<<8) /* ghcstop add for s3c2440a */
#define S3C2410_ADCTSC_YM_SEN			(1<<7)
#define S3C2410_ADCTSC_YP_SEN			(1<<6)
#define S3C2410_ADCTSC_XM_SEN			(1<<5)
#define S3C2410_ADCTSC_XP_SEN			(1<<4)
#define S3C2410_ADCTSC_PULL_UP_DISABLE	(1<<3)
#define S3C2410_ADCTSC_AUTO_PST			(1<<2)
#define S3C2410_ADCTSC_XY_PST(x)		(((x)&0x3)<<0)

/* ADCDAT0 Bits */
#define S3C2410_ADCDAT0_UPDOWN			(1<<15)
#define S3C2410_ADCDAT0_AUTO_PST		(1<<14)
#define S3C2410_ADCDAT0_XY_PST			(0x3<<12)
#define S3C2410_ADCDAT0_XPDATA_MASK		(0x03FF)

/* ADCDAT1 Bits */
#define S3C2410_ADCDAT1_UPDOWN			(1<<15)
#define S3C2410_ADCDAT1_AUTO_PST		(1<<14)
#define S3C2410_ADCDAT1_XY_PST			(0x3<<12)
#define S3C2410_ADCDAT1_YPDATA_MASK		(0x03FF)

#define WAIT4INT(x)  (((x)<<8) | \
		     S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
		     S3C2410_ADCTSC_XY_PST(3))

#define AUTOPST	     (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
		     S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))

#define X_MIN		74
#define X_MAX		934
#define Y_MIN		920
#define Y_MAX		89

struct s3c2410ts
{
	long xp;
	long yp;
	int count;
	int shift;

	int delay;
	int presc;

	char phys[32];
};
static struct s3c2410ts ts;

struct rtgui_touch_device
{
	struct rt_device parent;

	rt_timer_t poll_timer;
	rt_uint16_t x, y;

	rt_bool_t calibrating;
	rt_touch_calibration_func_t calibration_func;

	rt_touch_eventpost_func_t eventpost_func;
	void *eventpost_param;

	rt_uint16_t min_x, max_x;
	rt_uint16_t min_y, max_y;

	rt_uint16_t width;
	rt_uint16_t height;
	
	rt_bool_t first_down_report;
};
static struct rtgui_touch_device *touch = RT_NULL;

#ifdef RT_USING_RTGUI
static void report_touch_input(int updown)
{
	struct rtgui_event_mouse emouse;

	RTGUI_EVENT_MOUSE_BUTTON_INIT(&emouse);
	emouse.wid = RT_NULL;

	/* set emouse button */
	emouse.button = RTGUI_MOUSE_BUTTON_LEFT;
	emouse.parent.sender = RT_NULL;
	
	if (updown)
	{
		ts.xp = ts.xp / ts.count;
		ts.yp = ts.yp / ts.count;;

		if ((touch->calibrating == RT_TRUE) && (touch->calibration_func != RT_NULL))
		{
			touch->x = ts.xp;
			touch->y = ts.yp;
		}
		else
		{
			if (touch->max_x > touch->min_x)
			{
				touch->x = touch->width * (ts.xp-touch->min_x)/(touch->max_x-touch->min_x);
			}
			else
			{
				touch->x = touch->width * ( touch->min_x - ts.xp ) / (touch->min_x-touch->max_x);
			}

			if (touch->max_y > touch->min_y)
			{
				touch->y = touch->height * ( ts.yp - touch->min_y ) / (touch->max_y-touch->min_y);
			}
			else
			{
				touch->y = touch->height * ( touch->min_y - ts.yp ) / (touch->min_y-touch->max_y);
			}
		}

		emouse.x = touch->x;
		emouse.y = touch->y;
		if (touch->first_down_report == RT_TRUE)
		{
			emouse.parent.type = RTGUI_EVENT_MOUSE_BUTTON;
			emouse.button |= RTGUI_MOUSE_BUTTON_DOWN;
		}
		else
		{	
			emouse.parent.type = RTGUI_EVENT_MOUSE_MOTION;
			emouse.button = 0;
		}
	}
	else
	{
		emouse.x = touch->x;
		emouse.y = touch->y;	
		emouse.parent.type = RTGUI_EVENT_MOUSE_BUTTON;
		emouse.button |= RTGUI_MOUSE_BUTTON_UP;
		if ((touch->calibrating == RT_TRUE) && (touch->calibration_func != RT_NULL))
		{
			/* callback function */
			touch->calibration_func(emouse.x, emouse.y);
		}
	}

	/* rt_kprintf("touch %s: ts.x: %d, ts.y: %d\n", updown? "down" : "up",
	touch->x, touch->y); */	
	
	/* send event to server */
	if (touch->calibrating != RT_TRUE)
	{	
		rtgui_server_post_event((&emouse.parent), sizeof(emouse));
	}
}
#else
static void report_touch_input(int updown)
{
	struct rt_touch_event touch_event;

	if (updown)
	{
		ts.xp = ts.xp / ts.count;
		ts.yp = ts.yp / ts.count;

		if ((touch->calibrating == RT_TRUE) && (touch->calibration_func != RT_NULL))
		{
			touch->x = ts.xp;
			touch->y = ts.yp;
		}
		else
		{
			if (touch->max_x > touch->min_x)
			{
				touch->x = touch->width * ( ts.xp - touch->min_x ) / (touch->max_x-touch->min_x);
			}
			else
			{
				touch->x = touch->width * ( touch->min_x - ts.xp ) / (touch->min_x-touch->max_x);
			}

			if (touch->max_y > touch->min_y)
			{
				touch->y = touch->height * ( ts.yp - touch->min_y ) / (touch->max_y-touch->min_y);
			}
			else
			{
				touch->y = touch->height * ( touch->min_y - ts.yp ) / (touch->min_y-touch->max_y);
			}
		}

		touch_event.x = touch->x;
		touch_event.y = touch->y;
		touch_event.pressed = 1;

		if (touch->first_down_report == RT_TRUE)
		{
			if (touch->calibrating != RT_TRUE && touch->eventpost_func)
			{
				touch->eventpost_func(touch->eventpost_param, &touch_event); 
			}
		}
	}
	else
	{
		touch_event.x = touch->x;
		touch_event.y = touch->y;
		touch_event.pressed = 0;
		
		if ((touch->calibrating == RT_TRUE) && (touch->calibration_func != RT_NULL))
		{
			/* callback function */
			touch->calibration_func(touch_event.x, touch_event.y);
		}

		if (touch->calibrating != RT_TRUE && touch->eventpost_func)
		{	
			touch->eventpost_func(touch->eventpost_param, &touch_event); 
		}
	}
}
#endif

static void touch_timer_fire(void *parameter)
{
	rt_uint32_t data0;
	rt_uint32_t data1;
	int updown;

	data0 = ADCDAT0;
	data1 = ADCDAT1;

	updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

	if (updown)
	{
		if (ts.count != 0)
		{
			report_touch_input(updown);
		}

		ts.xp = 0;
		ts.yp = 0;
		ts.count = 0;

		ADCTSC = S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST;
		ADCCON |= S3C2410_ADCCON_ENABLE_START;
	}
}

static void s3c2410_adc_stylus_action(void)
{
	rt_uint32_t data0;
	rt_uint32_t data1;

	data0 = ADCDAT0;
	data1 = ADCDAT1;
	
	ts.xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
	ts.yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
	ts.count ++;

	if (ts.count < (1<<ts.shift))
	{
		ADCTSC = S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST;
		ADCCON |= S3C2410_ADCCON_ENABLE_START;
	}
	else
	{
		if (touch->first_down_report)
		{
			report_touch_input(1);
			ts.xp = 0;
			ts.yp = 0;
			ts.count = 0;
			touch->first_down_report = 0;
		}
		/* start timer */
		rt_timer_start(touch->poll_timer);
		ADCTSC = WAIT4INT(1);
	}

	SUBSRCPND |= BIT_SUB_ADC;
}

static void s3c2410_intc_stylus_updown(void)
{
	rt_uint32_t data0;
	rt_uint32_t data1;
	int updown;

	data0 = ADCDAT0;
	data1 = ADCDAT1;

	updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

	/* rt_kprintf("stylus: %s\n", updown? "down" : "up"); */

	if (updown) 
	{
		touch_timer_fire(0);
	}	
	else
	{
		/* stop timer */
		rt_timer_stop(touch->poll_timer);
		touch->first_down_report = RT_TRUE;
		if (ts.xp >= 0 && ts.yp >= 0)
		{
			report_touch_input(updown);
		}
		ts.count = 0;
		ADCTSC = WAIT4INT(0);
	}

	SUBSRCPND |= BIT_SUB_TC;
}

static void rt_touch_handler(int irqno)
{
	if (SUBSRCPND & BIT_SUB_ADC)
	{
		/* INT_SUB_ADC */
		s3c2410_adc_stylus_action();
	}

	if (SUBSRCPND & BIT_SUB_TC)
	{
		/* INT_SUB_TC */
		s3c2410_intc_stylus_updown();
	}

	/* clear interrupt */
	INTPND |= (1ul << INTADC);
}

/* RT-Thread Device Interface */
static rt_err_t rtgui_touch_init(rt_device_t dev)
{
	/* init touch screen structure */
	rt_memset(&ts, 0, sizeof(struct s3c2410ts));

	ts.delay = 50000;
	ts.presc = 9;
	ts.shift = 2;
	ts.count = 0;
	ts.xp = ts.yp = 0;

	ADCCON = S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(ts.presc);
	ADCDLY = ts.delay;

	ADCTSC = WAIT4INT(0);

	rt_hw_interrupt_install(INTADC, rt_touch_handler, RT_NULL);
	rt_hw_interrupt_umask(INTADC);

	/* clear interrupt */
	INTPND |= (1ul << INTADC);

	SUBSRCPND |= BIT_SUB_TC;
	SUBSRCPND |= BIT_SUB_ADC;

	/* install interrupt handler */
	INTSUBMSK &= ~BIT_SUB_ADC;
	INTSUBMSK &= ~BIT_SUB_TC;

	touch->first_down_report = RT_TRUE;

	return RT_EOK;
}

static rt_err_t rtgui_touch_control(rt_device_t dev, rt_uint8_t cmd, void *args)
{
	switch (cmd)
	{
	case RT_TOUCH_CALIBRATION:
		touch->calibrating = RT_TRUE;
		touch->calibration_func = (rt_touch_calibration_func_t)args;
		break;

	case RT_TOUCH_NORMAL:
		touch->calibrating = RT_FALSE;
		break;

	case RT_TOUCH_CALIBRATION_DATA:
	{
		struct calibration_data *data;

		data = (struct calibration_data *)args;

		/* update */
		touch->min_x = data->min_x;
		touch->max_x = data->max_x;
		touch->min_y = data->min_y;
		touch->max_y = data->max_y;

		/*
			rt_kprintf("min_x = %d, max_x = %d, min_y = %d, max_y = %d\n",
				touch->min_x, touch->max_x, touch->min_y, touch->max_y);
		*/		
	}
		break;

	case RT_TOUCH_EVENTPOST:
		touch->eventpost_func = (rt_touch_eventpost_func_t)args;
		break;

	case RT_TOUCH_EVENTPOST_PARAM:
		touch->eventpost_param = args;
		break;
	}

	return RT_EOK;
}

void rtgui_touch_hw_init(void)
{
	rt_err_t result = RT_FALSE;
	rt_device_t device = RT_NULL;
	struct rt_device_graphic_info info;

	touch = (struct rtgui_touch_device *)rt_malloc(sizeof(struct rtgui_touch_device));
	if (touch == RT_NULL)
		return; /* no memory yet */

	/* clear device structure */
	rt_memset(&(touch->parent), 0, sizeof(struct rt_device));
	touch->calibrating = RT_FALSE;
	touch->min_x = X_MIN;
	touch->max_x = X_MAX;
	touch->min_y = Y_MIN;
	touch->max_y = Y_MAX;
	touch->eventpost_func  = RT_NULL;
	touch->eventpost_param = RT_NULL;

	/* init device structure */
	touch->parent.type = RT_Device_Class_Unknown;
	touch->parent.init = rtgui_touch_init;
	touch->parent.control = rtgui_touch_control;
	touch->parent.user_data = RT_NULL;

	device = rt_device_find("lcd");
	if (device == RT_NULL)
		return; /* no this device */	

	/* get graphic device info */
	result = rt_device_control(device, RTGRAPHIC_CTRL_GET_INFO, &info);
	if (result != RT_EOK)
	{

		/* get device information failed */

		return;
	}

	touch->width = info.width;
	touch->height = info.height;
	
	/* create 1/8 second timer */
	touch->poll_timer = rt_timer_create("touch", touch_timer_fire, RT_NULL,
	                                    RT_TICK_PER_SECOND/8, RT_TIMER_FLAG_PERIODIC);

	/* register touch device to RT-Thread */
	rt_device_register(&(touch->parent), "touch", RT_DEVICE_FLAG_RDWR);
}
