/*
 * linux/arch/arm/drivers/char/gc_keyb.c
 *
 * Copyright 2000 Applied Data Systems
 *
 * Keyboard & Smartio driver for GraphicsClient Plus
 * Graphics Client is SA1110 based single board computer by 
 *    Applied Data Systems (http://www.applieddata.net)
 *
 * Change log:
 * 		12/01/00 Woojung Huh
 * 			Bug fixed
 * 		11/16/00 Woojung Huh [whuh@applieddata.net]
 * 			Added smartio device driver on it
 */

#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/kbd_ll.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/kbd_kern.h>

#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/keyboard.h>
#include <linux/tqueue.h>

#define  ADS_SMARTIO_MAJOR		58	/* For now */

#define ADS_AVR_IRQ				63

#define	SMARTIO_IOCTL_BASES		's'
#define	SMARTIO_KPD_TIMEOUT		_IOW(SMARTIO_IOCTL_BASES, 0, int)
#define	SMARTIO_KPD_SETUP		   _IOW(SMARTIO_IOCTL_BASES, 1, short)
#define	SMARTIO_BL_CONTROL		_IOW(SMARTIO_IOCTL_BASES, 2, char)
#define	SMARTIO_BL_CONTRAST		_IOW(SMARTIO_IOCTL_BASES, 3, char)
#define  SMARTIO_PORT_CONFIG		_IOW(SMARTIO_IOCTL_BASES, 4, char)

static char	*smartio_version = "1.01";
static char *smartio_date = "Dec-01-2000";

static int	sio_reset_flag;

static void send_SSP_msg(unchar *pBuf, int num)
{
	ushort	tmp;
	int		i;

	for (i=0;i<num;i++) {
		while ((Ser4SSSR & SSSR_TNF) == 0);
		tmp = pBuf[i];
		Ser4SSDR = (tmp << 8);
	}
	
	// Throw away Echo
	for (i=0;i<num;i++) {
		while ((Ser4SSSR & SSSR_RNE) == 0);
		tmp = Ser4SSDR;
	}
}

static unchar ReadSSPByte(void)
{
	if (Ser4SSSR & SSSR_ROR) {
		printk("%s() : Overrun\n", __FUNCTION__);
		return 0;
	}

	Ser4SSDR = 0x00;

	while ((Ser4SSSR & SSSR_RNE) == 0);

	return ((unchar) Ser4SSDR);
}

static ulong read_SSP_response(int num)
{
	int		i;
	ulong	ret;
	
	// discard leading 0x00 and command echo 0 (command group value)
	while (ReadSSPByte() == 0);
	// discard command echo 1 (command code value)
	ReadSSPByte();

	// data from SMARTIO
	// It assumes LSB first.
	// NOTE:Some command uses MSB first order
	ret = 0;
	for (i=0;i<num;i++) {
		ret |= ReadSSPByte() << (8*i);
	}

	return ret;
}

typedef	struct	t_SMARTIO_CMD {
	unchar	Group;
	unchar	Code; 
	unchar  Opt[2];
}	SMARTIO_CMD;

static	SMARTIO_CMD	RD_INT_CMD = { 0x83, 0x01, { 0x00, 0x00 } };
static	SMARTIO_CMD	RD_KBD_CMD = { 0x83, 0x02, { 0x00, 0x00 } };
static	SMARTIO_CMD RD_ADC_CMD = { 0x83, 0x28, { 0x00, 0x00 } };
static	SMARTIO_CMD RD_KPD_CMD = { 0x83, 0x04, { 0x00, 0x00 } };

static	volatile ushort	adc_value;
static	volatile unchar	kpd_value;
static	unsigned int	kpd_timeout = 10000;			// 10000 msec

static  ulong kbd_int, kpd_int, adc_int;

static void smartio_interrupt_task(void *data);

static struct tq_struct tq_smartio = {
		{ NULL,	NULL },			// struct list_head
		0,						// unsigned long sync
		smartio_interrupt_task,	// void (*routine)(void *)
		NULL,					// void *data
};

DECLARE_WAIT_QUEUE_HEAD(smartio_queue);
DECLARE_WAIT_QUEUE_HEAD(smartio_adc_queue);
DECLARE_WAIT_QUEUE_HEAD(smartio_kpd_queue);

static spinlock_t smartio_busy_lock = SPIN_LOCK_UNLOCKED;
static atomic_t	smartio_busy = ATOMIC_INIT(0);

static void smartio_interrupt_task(void *arg)
{
	unchar	code;
	unsigned long flags;

	spin_lock_irqsave(&smartio_busy_lock, flags);
	if (atomic_read(&smartio_busy) == 1) {
		spin_unlock_irqrestore(&smartio_busy_lock, flags);
		queue_task(&tq_smartio, &tq_timer);
	}
	else {
		atomic_set(&smartio_busy, 1);
		spin_unlock_irqrestore(&smartio_busy_lock, flags);
	}

	/* Read SMARTIO Interrupt Status to check which Interrupt is occurred 
	 * and Clear SMARTIO Interrupt */
	send_SSP_msg((unchar *) &RD_INT_CMD, 2);
	code = (unchar) (read_SSP_response(1) & 0xFF);

#ifdef CONFIG_VT
	if (code  & 0x04) {					// Keyboard Interrupt
		kbd_int++;
		/* Read Scan code */
		send_SSP_msg((unchar *) &RD_KBD_CMD, 2);
		code = (unchar) (read_SSP_response(1) & 0xFF);
		handle_scancode( code, (code & 0x80) ? 0 : 1 );
	}
#endif
	// ADC resolution is 10bit (0x000 ~ 0x3FF)
	if (code & 0x02) {					// ADC Complete Interrupt
		adc_int++;
		send_SSP_msg((unchar *) &RD_ADC_CMD, 2);
		adc_value = (ushort) (read_SSP_response(2) & 0x3FF);
		wake_up_interruptible(&smartio_adc_queue);
	}

	if (code & 0x08) { 					// Keypad interrupt
		kpd_int++;
		send_SSP_msg((unchar *) &RD_KPD_CMD, 2);
		kpd_value = (unchar) (read_SSP_response(1) & 0xFF);
		wake_up_interruptible(&smartio_kpd_queue);
	}

	spin_lock_irqsave(&smartio_busy_lock, flags);
	atomic_set(&smartio_busy, 0);
	spin_unlock_irqrestore(&smartio_busy_lock, flags);

	enable_irq(ADS_AVR_IRQ);

	wake_up_interruptible(&smartio_queue);
}

static void gc_sio_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
#ifdef CONFIG_VT
	kbd_pt_regs = regs;
#endif

	// *NOTE*
	// ADS SMARTIO interrupt is cleared after reading interrupt status
	// from smartio. 
	// disable SMARTIO IRQ here and re-enable at samrtio_bh.
	// 11/13/00 Woojung
	disable_irq(ADS_AVR_IRQ);

	queue_task(&tq_smartio, &tq_immediate);
	mark_bh(IMMEDIATE_BH);
}

void gc_kbd_disable_irq( void )
{
}

void gc_kbd_enable_irq( void )
{
}

int gc_kbd_translate(unsigned char scancode, unsigned char *keycode_p, char raw_mode)
{
	*keycode_p = scancode & 0x7f;
	return 1;
}

void gc_kbd_leds(unsigned char leds)
{
}

static inline void gc_sio_init(void)
{
	GPDR |= (GPIO_GPIO10 | GPIO_GPIO12 | GPIO_GPIO13); 	// Output
	GPDR &= ~GPIO_GPIO11;

	// Alternative Function
	GAFR |= (GPIO_GPIO10 | GPIO_GPIO11 | GPIO_GPIO12 | GPIO_GPIO13);

	/* ADS boot loader does this already */
#ifdef CONFIG_ANBELBOOT
	PPAR |= PPAR_SPR;
#endif	

	Ser4SSCR0 = 0xA707;
	Ser4SSSR = SSSR_ROR;
	Ser4SSCR1 = 0x0010;
	Ser4SSCR0 = 0xA787;

	// Reset SMARTIO
	ADS_AVR_REG &= 0xFE;
	udelay(10*1000);			// 10 mSec
	ADS_AVR_REG |= 0x01;
	udelay(10*1000);			// 10 mSec

}

void __init gc_kbd_init_hw(void)
{
	printk (KERN_INFO "Graphics Client keyboard driver v1.0\n");

	gc_sio_init();

	if (request_irq(ADS_AVR_IRQ,gc_sio_interrupt,0,"smartio", NULL) != 0)
		printk("Could not allocate SMARTIO IRQ!\n");

	sio_reset_flag = 1;
}

/* SMARTIO ADC Interface */
#define SMARTIO_VERSION				0
#define SMARTIO_PORT_A				1
#define SMARTIO_PORT_B				2
#define SMARTIO_PORT_C				3
#define SMARTIO_PORT_D				4
#define SMARTIO_SELECT_OPTION		5
#define SMARTIO_BACKLITE			6
#define SMARTIO_KEYPAD				7
#define SMARTIO_ADC					8
#define	SMARTIO_VEE_PWM				9
#define SMARTIO_SLEEP				11

static	SMARTIO_CMD CONV_ADC_CMD = { 0x80, 0x28, { 0x00, 0x00 } };
static	SMARTIO_CMD READ_PORT_CMD = { 0x82, 0x00, { 0x00, 0x00 } };

static	SMARTIO_CMD READ_DEVVER_CMD = { 0x82, 0x05, { 0x00, 0x00 } };
static	SMARTIO_CMD READ_DEVTYPE_CMD = { 0x82, 0x06, { 0x00, 0x00 } };
static	SMARTIO_CMD READ_FWLEVEL_CMD = { 0x82, 0x07, { 0x00, 0x00 } };

static int lock_smartio(unsigned long *flags)
{
	spin_lock_irqsave(&smartio_busy_lock, *flags);
	if (atomic_read(&smartio_busy) == 1) {
		spin_unlock_irqrestore(&smartio_busy_lock, *flags);
		interruptible_sleep_on(&smartio_queue);
	}
	else {
		atomic_set(&smartio_busy, 1);
		spin_unlock_irqrestore(&smartio_busy_lock, *flags);
	}

	return 1;
}

static int unlock_smartio(unsigned long *flags)
{
	spin_lock_irqsave(&smartio_busy_lock, *flags);
	atomic_set(&smartio_busy, 0);
	spin_unlock_irqrestore(&smartio_busy_lock, *flags);

	return 1;
}

static ushort read_sio_adc(int channel)
{
	unsigned long	flags;

	if ((channel < 0) || (channel > 7)) 
		return 0xFFFF;

	CONV_ADC_CMD.Opt[0] = (unchar) channel;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &CONV_ADC_CMD, 3);
	unlock_smartio(&flags);

	interruptible_sleep_on(&smartio_adc_queue);

	return adc_value & 0x3FF;
}

static ushort read_sio_port(int port)
{
	unsigned long	flags;
	ushort			ret;

	if ((port < SMARTIO_PORT_B) || (port > SMARTIO_PORT_D)) 
		return 0xFFFF;

	READ_PORT_CMD.Code = (unchar) port;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &READ_PORT_CMD, 2);
	ret = read_SSP_response(1);
	unlock_smartio(&flags);

	return ret;
}

static ushort read_sio_kpd(void)
{
	long	timeout;

	// kpd_timeout is mSec order
	// interrupt_sleep_on_timeout is based on 10msec timer tick
	if (kpd_timeout == -1) {
		interruptible_sleep_on(&smartio_kpd_queue);
	}
	else {
		timeout = interruptible_sleep_on_timeout(&smartio_kpd_queue, 
												kpd_timeout/10);
		if (timeout == 0) {
			// timeout without keypad input
			return 0xFFFF;
		}
	}
	return kpd_value;
}

static struct sio_ver {
	uint	DevVer;
	uint	DevType;
	uint	FwLevel;
};

static ushort read_sio_version(struct sio_ver *ptr)
{
	unsigned long	flags;
	ushort			ret;

	// Read Device Version 
	lock_smartio(&flags);
	send_SSP_msg((unchar *) &READ_DEVVER_CMD, 2);
	ret = read_SSP_response(1);
	unlock_smartio(&flags);
	ptr->DevVer = (uint)ret;
	// Read Device Type
	lock_smartio(&flags);
	send_SSP_msg((unchar *) &READ_DEVTYPE_CMD, 2);
	ret = read_SSP_response(2);
	unlock_smartio(&flags);
	// swap MSB & LSB
	ret = ((ret & 0xFF) << 8) | ((ret & 0xFF00) >> 8);
	ptr->DevType = (uint)ret;
	// Read Firmware Level
	lock_smartio(&flags);
	send_SSP_msg((unchar *) &READ_FWLEVEL_CMD, 2);
	ret = read_SSP_response(2);
	unlock_smartio(&flags);
	// swap MSB & LSB
	ret = ((ret & 0xFF) << 8) | ((ret & 0xFF00) >> 8);
	ptr->FwLevel = (uint)ret;

	return 0;
}

static ssize_t sio_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
	struct inode *inode = file->f_dentry->d_inode;
	unsigned int minor = MINOR(inode->i_rdev);
	ushort	*ret = (ushort *)buf;

	switch (minor) {
	case	SMARTIO_ADC:
			if ((*ret = read_sio_adc(buf[0])) != 0xFFFF)
				return sizeof(ushort);					// 2 bytes
	case	SMARTIO_PORT_B:
	case	SMARTIO_PORT_C:
	case	SMARTIO_PORT_D:
			if ((*ret = read_sio_port(minor)) != 0xFFFF)
				return sizeof(ushort);
	case	SMARTIO_VERSION:
			if ((read_sio_version((struct sio_ver *)buf)) != 0xFFFF)
				return sizeof(struct sio_ver);
	case	SMARTIO_KEYPAD:
			if ((*ret = read_sio_kpd()) != 0xFFFF)
				return sizeof(ushort);
	default :
			return -ENXIO;
	}
}

static	SMARTIO_CMD WRITE_PORT_CMD = { 0x81, 0x00, { 0x00, 0x00 } };
static	SMARTIO_CMD SELECT_OPT_CMD = { 0x80, 0x00, { 0x00, 0x00 } };
static	SMARTIO_CMD CONTROL_BL_CMD = { 0x80, 0x00, { 0x00, 0x00 } };
static	SMARTIO_CMD CONTRAST_BL_CMD = { 0x80, 0x21, { 0x00, 0x00 } };
static	SMARTIO_CMD CONTROL_KPD_CMD = { 0x80, 0x27, { 0x00, 0x00 } };
static	SMARTIO_CMD	CONTROL_VEE_CMD = { 0x80, 0x22, { 0x00, 0x00 } };

static ushort write_sio_port(int port, unchar value)
{
	unsigned long	flags;

	if ((port < SMARTIO_PORT_B) || (port > SMARTIO_PORT_D)) 
		return 0xFFFF;

	WRITE_PORT_CMD.Code = (unchar) port;
	WRITE_PORT_CMD.Opt[0] = (unchar) value;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &WRITE_PORT_CMD, 3);
	unlock_smartio(&flags);

	return 0;
}

static ushort write_sio_select(unchar select)
{
	unsigned long	flags;

	if ((select < 1) || (select > 2)) 
		return 0xFFFF;

	SELECT_OPT_CMD.Code = (unchar) (select + 0x28);

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &SELECT_OPT_CMD, 2);
	unlock_smartio(&flags);

	return 0;
}

static ushort control_sio_backlite(int cmd, int value)
{
	unsigned long	flags;

	if (cmd == SMARTIO_BL_CONTRAST) {
		value &= 0xFF;
		CONTRAST_BL_CMD.Opt[0] = (unchar) value;

		lock_smartio(&flags);
		send_SSP_msg((unchar *) &CONTRAST_BL_CMD, 3);
		unlock_smartio(&flags);
	}
	else if (cmd == SMARTIO_BL_CONTROL) {
		if (value == 0x00) {
			// Backlite OFF
			CONTROL_BL_CMD.Code = 0x24;
		}
		else {
			// Backlite ON
			CONTROL_BL_CMD.Code = 0x23;
		}
		lock_smartio(&flags);
		send_SSP_msg((unchar *) &CONTROL_BL_CMD, 2);
		unlock_smartio(&flags);
	}
	else 
		return 0xFFFF;

	return 0;
}

static ushort control_sio_keypad(int x, int y)
{
	unsigned long	flags;

	if ( (x<1) || (x>8) || (y<1) || (y>8)) {
		return 0xFFFF;
	}

	CONTROL_KPD_CMD.Opt[0] = (unchar) x;
	CONTROL_KPD_CMD.Opt[1] = (unchar) y;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &CONTROL_KPD_CMD, 4);
	unlock_smartio(&flags);

	return 0;
}

static ushort control_sio_vee(int value)
{
	unsigned long	flags;

	value &= 0xFF;
	CONTROL_VEE_CMD.Opt[0] = (unchar) value;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &CONTROL_VEE_CMD, 3);
	unlock_smartio(&flags);
									   
	return 0;
}

static ssize_t sio_write(struct file *file, const char *buf, size_t cont, loff_t *ppos)
{
	struct inode *inode = file->f_dentry->d_inode;
	unsigned int minor = MINOR(inode->i_rdev);

	switch (minor) {
	case	SMARTIO_PORT_B:
	case	SMARTIO_PORT_C:
	case	SMARTIO_PORT_D:
			if (write_sio_port(minor, buf[0]) != 0xFFFF)
				return 1;
	case	SMARTIO_SELECT_OPTION:
			if (write_sio_select(buf[0]) != 0xFFFF)
				return 1;
	case	SMARTIO_BACKLITE:
			if (control_sio_backlite(SMARTIO_BL_CONTROL, buf[0]) != 0xFFFF)
				return 1;
	case	SMARTIO_KEYPAD:
			if (control_sio_keypad(buf[0], buf[1]) != 0xFFFF)
				return 2;
	case	SMARTIO_VEE_PWM:
			if (control_sio_vee(buf[0]) != 0xFFFF)
				return 1;
	default:
		return -ENXIO;
	}
}

static unsigned int sio_poll(struct file *file, struct poll_table_struct *wait)
{
	return 0;
}

static	SMARTIO_CMD IOCTL_PORT_CMD = { 0x81, 0x00, { 0x00, 0x00 } };

static ushort ioctl_sio_port(int port, unchar value)
{
	unsigned long	flags;

	if ((port < SMARTIO_PORT_B) || (port > SMARTIO_PORT_D)) 
		return 0xFFFF;

	IOCTL_PORT_CMD.Code = (unchar) port + 0x04;		// 0x05 ~ 0x08
	if (port == SMARTIO_PORT_B) {
		// Port B has 4 bits only
		IOCTL_PORT_CMD.Opt[0] = (unchar) value & 0x0F;
	}
	else
		IOCTL_PORT_CMD.Opt[0] = (unchar) value;

	lock_smartio(&flags);
	send_SSP_msg((unchar *) &IOCTL_PORT_CMD, 3);
	unlock_smartio(&flags);

	return 0;
}

static int sio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned int minor = MINOR(inode->i_rdev);
	unchar	*buf = (unchar *)arg;

	switch (minor) {
	case	SMARTIO_PORT_B:
	case	SMARTIO_PORT_C:
	case	SMARTIO_PORT_D:
			if (cmd == SMARTIO_PORT_CONFIG) {
				if (ioctl_sio_port(minor, buf[0]) != 0xFFFF)
					return 0;
			}
			return -EINVAL;
	case	SMARTIO_SELECT_OPTION:
			if (write_sio_select(buf[0]) != 0xFFFF)
				return 0;
	case	SMARTIO_BACKLITE:
			if (cmd == SMARTIO_BL_CONTROL) {
			if (control_sio_backlite(SMARTIO_BL_CONTROL, buf[0]) != 0xFFFF)
				return 0;
			}
			else if (cmd == SMARTIO_BL_CONTRAST) {
			if (control_sio_backlite(SMARTIO_BL_CONTRAST, buf[0]) != 0xFFFF)
				return 0;
			}
			else
				return -EINVAL;
	case	SMARTIO_KEYPAD:
			if (cmd == SMARTIO_KPD_TIMEOUT) {
				kpd_timeout = *(long*)buf;
			}
			else if (cmd == SMARTIO_KPD_SETUP) {
				if (control_sio_keypad(buf[0], buf[1]) != 0xFFFF)
					return 0;
			}
			return -EINVAL;
	case	SMARTIO_VEE_PWM:
			if (control_sio_vee(buf[0]) != 0xFFFF)
				return 0;
	default:
		return -ENXIO;
	}
}

static struct file_operations sio_fops = {
	read: 	sio_read,
	write:	sio_write,
	poll:	sio_poll,
	ioctl:	sio_ioctl,
};


void __init sio_init(void)
{
	if (register_chrdev(ADS_SMARTIO_MAJOR, "sio", &sio_fops)) {
		printk("smartio : unable to register driver\n");
		return;
	}
	else {
		printk("ADS GraphicsClient Plus smartio driver initialized. version %s, date:%s\n", 
				smartio_version, smartio_date);
		
		if (sio_reset_flag != 1) {
			gc_sio_init();
			if (request_irq(ADS_AVR_IRQ, gc_sio_interrupt, 0, "sio", NULL) != 0)
				printk("smartio : Could not allocate IRQ!\n");
		}
	}
}

__initcall (sio_init);
