/*
 * Driver for the touch screen on the TRAB.
 * Parts of this driver were taken from tsc2000.c
 *
 * This driver cannot be used together with tsc2000.c when STANDALONE
 * is defined !!
 *
 * Copyright (C) 2002 Gary Jennejohn (gj@denx.de)
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <linux/stat.h>
#include <linux/wait.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/arch/hardware.h>
#include <asm/arch/irqs.h>

#include <linux/trab_callout.h>

/* set this for standalone testing, without tsc2000.c */
#undef STANDALONE
/* set this to use the new CPLD software with separate PENIRQ/DAV */
#define NEWCPLD

#if defined(STANDALONE) && defined(CONFIG_TRAB_TSC2000)
#error "This driver cannot be used together with tsc2000.c"
#endif

static struct callout_list *callout_handle = NULL;
#ifdef STANDALONE
static
#endif
static atomic_t trab_touch_eint0;
static atomic_t touch_locked;
static atomic_t read_locked;
static int touch_early_stop;
static unsigned short trab_touch_read (unsigned short);
static void trab_touch_write (unsigned short, unsigned short);
/* the last coordinates read */
/* a signed short is OK here because the coordinates are 0 to 255 */
static short x, y;

void stop_touch(void *);

#define	DRIVER_VERSION		"$Revision: 1.1 $"
#define TOUCH_MAJOR		244
#define TOUCH_NAME		"touch"

#define SET_CS_TOUCH 		(rPDDAT &= 0x5FF)
#define CLR_CS_TOUCH 		(rPDDAT |= 0x200)

#define TSC2000_REG_X		((0 << 11) | (0 << 5))
#define TSC2000_REG_Y		((0 << 11) | (1 << 5))
#define TSC2000_REG_Z1		((0 << 11) | (2 << 5))
#define TSC2000_REG_Z2		((0 << 11) | (3 << 5))
#define TSC2000_REG_DAC		((0 << 11) | (0xB << 5))
#define TSC2000_REG_ZERO	((0 << 11) | (0x10 << 5))
#define TSC2000_REG_ADC		((1 << 11) | (0 << 5))
#define TSC2000_REG_DACCTL	((1 << 11) | (2 << 5))
#define TSC2000_REG_REF		((1 << 11) | (3 << 5))
#define TSC2000_REG_RESET	((1 << 11) | (4 << 5))
#define TSC2000_REG_CONFIG	((1 << 11) | (5 << 5))

/* PSM:0 STS:0 AD[0-3]:0 RS[0-1]:1 AV[0-1]:1 CL[0-1]:0 PV[0-2]:1 */
/*
 * PSM: conversion controlled by host
 * STS: normal operation
 * AD[0-3]: invalid, bits are set in the driver
 * RS[0-1]: 8-bit resolution
 * AV[0-1]: 4 data averages
 * CL[0-1]: 8MHz internal clock - 8-bit only
 * PV[0-2]: 100us stablization time
 */
#define DEFAULT_ADC		((1<<2)|(0<<4)|(1<<6)|(1<<8)|(0<<10)|(0<<15))

#ifdef STANDALONE
#define sleep_until_interruptible(condition)			\
({								\
	int __ret = 0;						\
	unsigned int __i = 0;					\
								\
	for (__i = 0; __i < 500; __i++) {			\
		if (condition) {				\
			break;					\
		}						\
		udelay(1);					\
	}							\
	while (! (condition)) {					\
		if (! signal_pending(current)) {		\
			set_current_state(TASK_INTERRUPTIBLE);	\
			schedule_timeout(1);			\
		} else {					\
			__ret = -ERESTARTSYS;			\
			break;					\
		}						\
	}							\
	set_current_state(TASK_RUNNING);			\
								\
	__ret;							\
})

static DECLARE_WAIT_QUEUE_HEAD(spi_wq);
static int spi_poll = 0;

static int trab_touch_spi_init = 0;
#endif /* STANDALONE */

#ifndef STANDALONE
extern void spi_wait_transmit_done (void);
extern atomic_t tsc2000_eint0;
#endif /* !STANDALONE */

static int trab_touch_chrdev_registered = 0;

static DECLARE_WAIT_QUEUE_HEAD(pwaitq);
static DECLARE_WAIT_QUEUE_HEAD(rwaitq);

/* atomic_set() and atomic_read() are not atomic !!! */
#define my_atomic_read(x, v) { \
	unsigned long flags; \
	save_flags(flags); \
	cli(); \
	(x) = ((v)->counter); \
	restore_flags(flags); \
}

#define my_atomic_set(v, i) { \
	unsigned long flags; \
	save_flags(flags); \
	cli(); \
	((v)->counter) = (i); \
	restore_flags(flags); \
}

#ifdef STANDALONE
static inline void spi_wait_transmit_done (void)
{
	if (spi_poll) {
		sleep_until_interruptible(rSPSTA & 0x01);
	} else {
		unsigned int i = 0;

		for (i = 0; i < 100; i++) {
			if (rSPSTA & 0x01) {
				break;
			}
			udelay(1);
		}
		wait_event_interruptible(spi_wq, rSPSTA & 0x01);
	}
}

static void
spi_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
	wake_up(&spi_wq);
}

static int __init
spi_init (void)
{
	unsigned int i;

	/* Configure I/O ports. */
	rPDCON = (rPDCON & 0xF3FFFF) | 0x040000;
	rPGCON = (rPGCON & 0x0F3FFF) | 0x008000;
	rPGCON = (rPGCON & 0x0CFFFF) | 0x020000;
	rPGCON = (rPGCON & 0x03FFFF) | 0x080000;
	
	CLR_CS_TOUCH;

	/* Baudrate ca. 200kHz. */
	rSPPRE = 0x7F;

	/* SPI-MOSI holds Level after last bit. */
	rSPPIN = 0x01;

	/* Interupt mode, Prescaler, Master, CPOL=0, CPHA=1. */
	rSPCON = 0x3A;

	/* Dummy byte ensures clock to be low. */
	for (i = 0; i < 10; i++) {
		rSPTDAT = 0xFF;
	}

	if (! spi_poll && request_irq(IRQ_SPI, spi_interrupt, 0, "SPI", NULL)) {
		printk("Failed to install an interrupt handler "
		       "for SPI controller IRQ%d\n", IRQ_SPI);

		/* Switch to polling mode. */
		spi_poll = 1;
		rSPCON &= ~0x20;
	}

	spi_wait_transmit_done();

	return 0;
}

static void
spi_exit (void)
{
	spi_wait_transmit_done();

	rSPCON &= ~0x20;

	if (! spi_poll) {
		free_irq(IRQ_SPI, NULL);
	}
}
#endif /* STANDALONE */

#ifndef NEWCPLD /* the CPLD with only EINT3 */
#define cpld_touch  ((unsigned long *)io_p2v(0x04010000))
#define INTR_DAV	(1 << 16)
#define INTR_TOUCH	(1 << 17)
static void
trab_touch_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long intr;
	short lx, ly;
	int res;

	intr = *cpld_touch;

	if (intr & INTR_DAV)
	{
		*cpld_touch = INTR_DAV;
		if (callout_handle != NULL)
		{
			rmv_callout(callout_handle);
			callout_handle = NULL;
		}
		/* not our interrupt */
		my_atomic_read(res, &touch_locked);
		if (!res)
			return;
#ifdef STANDALONE
		printk("D ");
#endif
		lx = trab_touch_read(TSC2000_REG_X);
		ly = trab_touch_read(TSC2000_REG_Y);
		if (touch_early_stop == 1)
		{
			touch_early_stop = 0;
			return;
		}
		my_atomic_read(res, &read_locked);
		if (!res)
		{
			x = lx;
			y = ly;
			wake_up_interruptible(&rwaitq);
			wake_up_interruptible(&pwaitq);
		}
#ifdef STANDALONE
		printk("lx: %d ly: %d ", lx, ly);
#endif
		atomic_set(&touch_locked, 0);
	}
	if (intr & INTR_TOUCH)
	{
		*cpld_touch = INTR_TOUCH;

		my_atomic_read(res, &touch_locked);
		if (res)
			return;
		atomic_set(&touch_locked, 1);
	
		callout_handle = add_callout(stop_touch, (void *)1, 2, 0);
		if (callout_handle == NULL)
		{
			atomic_set(&touch_locked, 0);
			return;
		}
#ifdef STANDALONE
		printk("T ");
#endif
		touch_early_stop = 0;
		/* start the conversion */
		trab_touch_write(TSC2000_REG_ADC, DEFAULT_ADC|(1 << 10));
	}
}
#else /* NEWCPLD */
static void
trab_dav_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
	atomic_t *ap = (atomic_t *)dev_id;
	short lx, ly;
	int res;

	/* not expecting an interrupt */
	my_atomic_read(res, ap);
	if (!res)
	{
		return;
	}
	atomic_set(ap, 0);
	if (callout_handle != NULL)
	{
		rmv_callout(callout_handle);
		callout_handle = NULL;
	}
	lx = trab_touch_read(TSC2000_REG_X);
	ly = trab_touch_read(TSC2000_REG_Y);
	if (touch_early_stop == 1)
	{
		touch_early_stop = 0;
		return;
	}

	/* stop continous conversion */
	trab_touch_write(TSC2000_REG_ADC, DEFAULT_ADC|(1 << 14));

	/* don't overwrite if a read is in progress */
	my_atomic_read(res, &read_locked);
	if (!res)
	{
		x = lx;
		y = ly;
		wake_up_interruptible(&rwaitq);
		wake_up_interruptible(&pwaitq);
	}
	atomic_set(&touch_locked, 0);
}

static void
trab_touch_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
	int res;

	/* conversion already in progress */
	my_atomic_read(res, &touch_locked);
	if (res)
		return;

	/* conversion in tsc2000.c already in progress */
	my_atomic_read(res, &tsc2000_eint0);
	if (res)
		return;

	atomic_set(&touch_locked, 1);
	atomic_set(&trab_touch_eint0, 1);

	callout_handle = add_callout(stop_touch, (void *)1, 2, 0);
	if (callout_handle == NULL)
	{
		atomic_set(&touch_locked, 0);
		atomic_set(&trab_touch_eint0, 0);
		return;
	}
	touch_early_stop = 0;
	/* start the conversion */
	trab_touch_write(TSC2000_REG_ADC, DEFAULT_ADC|(1 << 10));
}
#endif /* NEWCPLD */

static void
trab_touch_write (unsigned short reg, unsigned short data)
{
	unsigned short command;

	SET_CS_TOUCH;
	command = reg;
	rSPTDAT = (command & 0xFF00) >> 8;
	spi_wait_transmit_done();
	rSPTDAT = (command & 0x00FF);
	spi_wait_transmit_done();
	rSPTDAT = (data & 0xFF00) >> 8;
	spi_wait_transmit_done();
	rSPTDAT = (data & 0x00FF);
	spi_wait_transmit_done();
	CLR_CS_TOUCH;
}

static unsigned short
trab_touch_read (unsigned short reg)
{
	unsigned short command, data;

	SET_CS_TOUCH;
	command = 0x8000 | reg;

	rSPTDAT = (command & 0xFF00) >> 8;
	spi_wait_transmit_done();
	rSPTDAT = (command & 0x00FF);
	spi_wait_transmit_done();

	rSPTDAT = 0xFF;
	spi_wait_transmit_done();
	data = rSPRDAT;
	rSPTDAT = 0xFF;
	spi_wait_transmit_done();

	CLR_CS_TOUCH;
	return (rSPRDAT & 0x0FF) | (data << 8);
}

static void __init
trab_touch_reg_init (void)
{
#ifndef CONFIG_TRAB_TSC2000
	/* PSM:0 STS:0 AD[0-3]:1 RS[0-1]:1 AV[0-1]:0 CL[0-1]:0 PV[0-2]:1 */
	trab_touch_write(TSC2000_REG_ADC, DEFAULT_ADC);
	/* INT:1 DL[0-1]:0 PDN:0 RFV:1 */
	trab_touch_write(TSC2000_REG_REF, 0x0011);
	/* DPD:0 */
	trab_touch_write(TSC2000_REG_DACCTL, 0x0000);
#endif
	/* PRE[0-2]:2 (276us) SNS[0-2]:1 (96us) */
	trab_touch_write(TSC2000_REG_CONFIG, 0x0011);
}

static int
trab_touch_devfs_open (struct inode *inode, struct file *file)
{
	MOD_INC_USE_COUNT;
	
	return 0;
}

static int
trab_touch_devfs_release (struct inode *inode, struct file *file)
{
	MOD_DEC_USE_COUNT;

	return 0;
}

static unsigned int
trab_touch_devfs_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;

	poll_wait(file, &pwaitq, wait);

	if (x != -1 && y != -1)
		mask |= POLLPRI;

	return mask;
}

static ssize_t
trab_touch_devfs_read (struct file *file, char *buf,
	size_t size, loff_t *pos)
{
	ssize_t N;
	struct { short xc, yc; } val;

	DECLARE_WAITQUEUE(wait, current);

	if (size == 0)
		return (0);
	if (size != sizeof(val))
		return (-EINVAL);

	__add_wait_queue(&rwaitq, &wait);
	set_current_state(TASK_INTERRUPTIBLE);

	do {
		if (x != -1 && y != -1)
		{
			atomic_set(&read_locked, 1);
			break;
		}

		if (file->f_flags & O_NONBLOCK)
		{
			N = -EAGAIN;
			goto out;
		}
                if (signal_pending(current)) {
			N = -ERESTARTSYS;
			goto out;
		}
		schedule();
	} while (1);

	val.xc = x;
	val.yc = y;

	N = sizeof(val);
	if (copy_to_user (buf, &val, N))
		N = -EFAULT;

	x = y = -1;
  out:
	atomic_set(&read_locked, 0);
	current->state = TASK_RUNNING;
	__remove_wait_queue(&rwaitq, &wait);
	return N;
}

static struct file_operations trab_touch_devfs_fops = {
	owner:		THIS_MODULE,
	read:		trab_touch_devfs_read,
	open:		trab_touch_devfs_open,
	release:	trab_touch_devfs_release,
	poll:		trab_touch_devfs_poll,
};

static void
trab_touch_cleanup (void)
{
	if (trab_touch_chrdev_registered) {
		devfs_unregister_chrdev(TOUCH_MAJOR, TOUCH_NAME);
		trab_touch_chrdev_registered = 0;
	}

#ifdef STANDALONE
	if (trab_touch_spi_init) {
		spi_exit();

		trab_touch_spi_init = 0;
	}
#endif
#ifndef CONFIG_TRAB_TSC2000
	/* disable EINT3 if not used in tsc2000.c */
	rPECON &= ~(3 << 6);
#endif
	free_irq(IRQ_EINT3, &touch_locked);
#ifdef NEWCPLD
	free_irq(IRQ_EINT0, &trab_touch_eint0);
#endif
}

static int __init
trab_touch_init (void)
{
	int err = 0;

#ifdef NEWCPLD
	atomic_set(&trab_touch_eint0, 0);
#endif
	atomic_set(&touch_locked, 0);
	atomic_set(&read_locked, 0);
	touch_early_stop = 0;
	if (! err) {
#ifdef STANDALONE
		spi_init();
		trab_touch_spi_init = 1;
#endif
		trab_touch_reg_init();
		x = y = -1;
	}

	if (request_irq(IRQ_EINT3, trab_touch_interrupt, SA_SHIRQ, "EINT3", &touch_locked)) {
		printk("Failed to install an interrupt handler "
#ifdef NEWCPLD
		       "for TOUCH IRQ%d\n", IRQ_EINT3);
#else
		       "for TOUCH/DAV IRQ%d\n", IRQ_EINT3);
#endif
		err = -EIO;
	}
#ifdef NEWCPLD
	if (request_irq(IRQ_EINT0, trab_dav_interrupt, SA_SHIRQ, "EINT0", &trab_touch_eint0)) {
		printk("Failed to install an interrupt handler "
		       "for DAV IRQ%d\n", IRQ_EINT0);
		err = -EIO;
	}
	/* configure EINT3 as falling edge triggered */
	rEXTINT &= ~(7 << 12);
	rEXTINT |= (2 << 12);
	/* configure port pin as EINT3 */
	rPECON &= ~(3 << 6);
	rPECON |= (2 << 6);
#ifndef CONFIG_TRAB_TSC2000 /* may be sete in tsc2000.c */
	/* configure EINT0 as falling edge triggered */
	rEXTINT &= ~(7 << 0);
	rEXTINT |= (2 << 0);
	/* configure port pin as EINT0 */
	rPECON &= ~(3 << 0);
	rPECON |= (2 << 0);
#endif
#else /* NEWCPLD */
#ifndef CONFIG_TRAB_TSC2000 /* may be sete in tsc2000.c */
	/* configure EINT3 as rising edge triggered */
	rEXTINT &= ~(7 << 12);
	rEXTINT |= (4 << 12);

	/* configure port pin as EINT3 */
	rPECON &= ~(3 << 0);
	rPECON |= (2 << 0);
	*cpld_touch = (INTR_DAV|INTR_TOUCH);
#endif
#endif /* NEWCPLD */

	if (! err) {
		err = devfs_register_chrdev(TOUCH_MAJOR, TOUCH_NAME,
			&trab_touch_devfs_fops);
		
		trab_touch_chrdev_registered = ! err;
		
		if (! trab_touch_chrdev_registered) {
			printk(KERN_ERR "%s: Unable to register device !\n",
				TOUCH_NAME);
			err = -EIO;
		}
	}

	if (err) {
		trab_touch_cleanup();
	} else {
		printk (KERN_INFO "TRAB TOUCH driver "
			DRIVER_VERSION " initialized\n");
	}

	return err;
}

static void __exit
trab_touch_exit (void) 
{
	trab_touch_cleanup();
}

void
stop_touch(void *arg)
{
	unsigned long flags;

	save_flags(flags);
	cli();
#ifdef STANDALONE
	printk("STOP ");
#endif
	/* stop the conversion */
	trab_touch_write(TSC2000_REG_ADC, DEFAULT_ADC|(1 << 14));
	atomic_set(&touch_locked, 0);
	if (callout_handle != NULL)
	{
		rmv_callout(callout_handle);
		callout_handle = NULL;
	}
#ifdef NEWCPLD
	atomic_set(&trab_touch_eint0, 0);
#else
	*cpld_touch = INTR_TOUCH;
#endif
	/* called from tsc2000.c ? */
	if (arg > (void *)1)
		touch_early_stop = 1;
	restore_flags(flags);
}

module_init(trab_touch_init);
module_exit(trab_touch_exit);

MODULE_AUTHOR("Gary jennejohn <gj@denx.de>");
MODULE_DESCRIPTION("TRAB touch screen");
MODULE_LICENSE("GPL");

EXPORT_NO_SYMBOLS;
