/*
 * 9-bit serial port driver for Oxford 16550 UARTs.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/tqueue.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/bitops.h>
#include <asm/io.h>

#include <asm-arm/arch-ep93xx/regmap.h>

MODULE_AUTHOR("Ronald Gomes");
MODULE_DESCRIPTION("9-bit serial driver for TS-SER1 PC104 board from Technologic Systems");
MODULE_LICENSE("GPL");

char *name="serial9bit";
int io=-1;
int irq=-1;

MODULE_PARM(name, "1s");
MODULE_PARM_DESC(name,"Name of the 9-bit serial /dev entry.");
MODULE_PARM(io, "1i");
MODULE_PARM_DESC(io,"IO address for 9-bit serial board");
MODULE_PARM(irq, "1i");
MODULE_PARM_DESC(irq,"IRQ line for 9-bit serial board");


#define TSISA_BASE_IO	0x01E00000

#define TSSER1_BASE_IO	0x02E8
#define TSSER2_BASE_IO	0x03E8
#define TSSER3_BASE_IO	0x03EA
#define TSSER_SIZE_IO	0x8

static int users;

typedef struct serial9bit_dev {
	char *name;
	int io;
	int irq;
	unsigned long base_addr;
	int bakACR;
} serial9bit_dev;

static struct serial9bit_dev *dev;

DECLARE_WAIT_QUEUE_HEAD (rqueue);
DECLARE_WAIT_QUEUE_HEAD (wqueue);

#ifdef DECLARE_MUTEX
static DECLARE_MUTEX(sem);
#else
static struct semaphore sem = MUTEX;
#endif

/*
 * OX16C950 REGISTER DEFINES
 */

//Standard 550 Compatible Registers
#define THR_OFFSET				0x00
#define RHR_OFFSET				0x00
#define IER_OFFSET				0x01
#define FCR_OFFSET				0x02
#define ISR_OFFSET				0x02
#define LCR_OFFSET				0x03
#define MCR_OFFSET				0x04
#define LSR_OFFSET				0x05
#define MSR_OFFSET				0x06
#define SPR_OFFSET				0x07
#define DLL_OFFSET				0x00
#define DLM_OFFSET				0x01

//650 Compatible Registers
#define EFR_OFFSET				0x02
#define XON1_OFFSET				0x04
#define XON2_OFFSET				0x05
#define XOFF1_OFFSET				0x06
#define XOFF2_OFFSET				0x07

//950 Specific Registers
#define ASR_OFFSET				0x01
#define RFL_OFFSET				0x03
#define TFL_OFFSET				0x04
#define ICR_OFFSET				0x05

//Indexed Control Register Set
#define ACR_INDEX					0x00
#define CPR_INDEX					0x01
#define TCR_INDEX					0x02
#define CKS_INDEX					0x03
#define TTL_INDEX					0x04
#define RTL_INDEX					0x05
#define FCL_INDEX					0x06
#define FCH_INDEX					0x07
#define ID1_INDEX					0x08
#define ID2_INDEX					0x09
#define ID3_INDEX					0x0A
#define REV_INDEX					0x0B
#define CSR_INDEX					0x0C
#define NMR_INDEX					0x0D
#define MDM_INDEX					0x0E
#define RFC_INDEX					0x0F
#define GDS_INDEX					0x10
#define DMS_INDEX					0x11
#define PIDX_INDEX				0x12
#define CKA_INDEX					0x13

//Misc - Bits
#define RECEIVE_FIFO 			0x00
#define TRANSMIT_FIFO 			0x01
#define LCR_DL_ACCESS_KEY		0x80
#define LCR_650_ACCESS_KEY		0xBF
#define ACR_ICR_READ_EN	 		0x40
#define FCR_FIFO_EN 				0x01
#define EFR_ENHANCED_MODE_EN	0x10
#define NMR_9BIT_MODE_EN		0x01
#define NMR_9BIT_INTERRUPT_EN	0x02
#define ACR_950_READ_EN			0x40
#define LSR_DATA_AVAILABLE 	0x01
#define LSR_FIFO_EMPTY			0x20

//LSR error handler
#define LSR_OVERRUN_ERROR 		0x02
#define LSR_PARITY_ERROR 		0x04
#define LSR_FRAMING_ERROR 		0x08
#define LSR_BREAK 				0x10
#define LSR_DATA_ERROR 			0x80
#define LSR_ERROR_MASK 			(LSR_FRAMING_ERROR | LSR_BREAK | LSR_DATA_ERROR)

//Baud Rate Divisor
#define DIVISOR_BAUD_110 		0x0300
#define DIVISOR_BAUD_300		0x0180
#define DIVISOR_BAUD_600 		0x00C0
#define DIVISOR_BAUD_1200 		0x0060
#define DIVISOR_BAUD_2400 		0x0030
#define DIVISOR_BAUD_4800 		0x0018
#define DIVISOR_BAUD_9600 		0x000C
#define DIVISOR_BAUD_19200 	0x0006
#define DIVISOR_BAUD_28800 	0x0004
#define DIVISOR_BAUD_38400 	0x0003
#define DIVISOR_BAUD_57600 	0x0002
#define DIVISOR_BAUD_115200 	0x0001

//Interrupts
#define IER_RX_INTERRUPT_EN 		0x01
#define IER_TX_INTERRUPT_EN 		0x02
#define IER_LSTAT_INTERRUPT_EN 	0x04
#define IER_MSTAT_INTERRUPT_EN 	0x08
#define IER_CHR_INTERRUPT_EN 		0x20
#define IER_RTS_INTERRUPT_EN 		0x40
#define IER_CTS_INTERRUPT_EN 		0x80

//Type match
#define FALSE	0
#define TRUE	1

//Functions declarations
unsigned char readReg(serial9bit_dev *dev, unsigned char offset);
void writeReg(serial9bit_dev *dev, unsigned char offset, unsigned char value);
unsigned short readDivisor(serial9bit_dev *dev);
void writeDivisor(serial9bit_dev *dev, unsigned short divisor);
unsigned char read650(serial9bit_dev *dev, unsigned char offset);
void write650(serial9bit_dev *dev, unsigned char offset, unsigned char value);
unsigned char readASR(serial9bit_dev *dev);
void writeASRBit(serial9bit_dev *dev, unsigned char bit, unsigned char value);
unsigned char readICR(serial9bit_dev *dev, unsigned char index);
void writeICR(serial9bit_dev *dev, unsigned char index, unsigned char value);
unsigned char readFIFOLevel(serial9bit_dev *dev, unsigned char fifo);
void lockAdditionalStatus(serial9bit_dev *dev);
void unlockAdditionalStatus(serial9bit_dev *dev);


/*
 * Implementation section of basic functions
 */

//read-write basic routines for registers
unsigned char readReg(serial9bit_dev *dev, unsigned char offset)
{
	return inb(dev->base_addr + offset);
}

void writeReg(serial9bit_dev *dev, unsigned char offset, unsigned char value)
{
	outb(value, dev->base_addr + offset);
}

//divisor latch word registers
unsigned short readDivisor(serial9bit_dev *dev)
{
	unsigned short dlldlm;
	unsigned char oldLCR;
	oldLCR = readReg(dev, LCR_OFFSET);
	writeReg(dev, LCR_OFFSET, oldLCR|LCR_DL_ACCESS_KEY);
	dlldlm = (readReg(dev, DLM_OFFSET)<<8);
	dlldlm += readReg(dev, DLL_OFFSET);
	writeReg(dev, LCR_OFFSET, oldLCR);
	return dlldlm;
}

void writeDivisor(serial9bit_dev *dev, unsigned short divisor)
{
	unsigned char oldLCR;
	oldLCR = readReg(dev, LCR_OFFSET);
	writeReg(dev, LCR_OFFSET, oldLCR|LCR_DL_ACCESS_KEY);
	writeReg(dev, DLL_OFFSET, divisor&0x00FF );
	writeReg(dev, DLM_OFFSET, (divisor&0xFF00)>>8 );
	writeReg(dev, LCR_OFFSET, oldLCR);
}

//650 compatible registers.
unsigned char read650(serial9bit_dev *dev, unsigned char offset)
{
	unsigned char result, oldLCR;
	oldLCR = readReg(dev, LCR_OFFSET);
	writeReg(dev, LCR_OFFSET, LCR_650_ACCESS_KEY);
	result = readReg(dev, offset);
	writeReg(dev, LCR_OFFSET, oldLCR);
	return result;
}

void write650(serial9bit_dev *dev, unsigned char offset, unsigned char value)
{
	unsigned char oldLCR;
	oldLCR = readReg(dev, LCR_OFFSET);
	writeReg(dev, LCR_OFFSET, LCR_650_ACCESS_KEY);
	writeReg(dev, offset, value);
	writeReg(dev, LCR_OFFSET, oldLCR);
}

//950 compatible registers.
unsigned char readASR(serial9bit_dev *dev)
{
	unsigned char retVal;
	unlockAdditionalStatus(dev);
	retVal = readReg(dev, ASR_OFFSET);
	lockAdditionalStatus(dev);
	return retVal;
}

void writeASRBit(serial9bit_dev *dev, unsigned char bit, unsigned char value)
{
	unsigned char currentASR;
	if( bit!=0 && bit!=1 )
		return
	unlockAdditionalStatus(dev);
	currentASR = readReg(dev, ASR_OFFSET);
	if(value)
		currentASR |= (1 << bit);
	else
		currentASR &= ~(1 << bit);
	writeReg(dev, ASR_OFFSET, currentASR);
	lockAdditionalStatus(dev);
}

//Reading FIFO Levels
unsigned char readFIFOLevel(serial9bit_dev *dev, unsigned char fifo)
{
	unsigned char level1, offset;
	if (fifo == RECEIVE_FIFO) offset = RFL_OFFSET;
	else offset = TFL_OFFSET;
	unlockAdditionalStatus(dev);
	level1 = readReg(dev, offset);
	lockAdditionalStatus(dev);
	return level1;
}

//950 compatible registers.
void lockAdditionalStatus(serial9bit_dev *dev)
{
	dev->bakACR &= ~ACR_950_READ_EN;
	writeICR(dev, ACR_INDEX, dev->bakACR);
}

void unlockAdditionalStatus(serial9bit_dev *dev)
{
	dev->bakACR |= ACR_950_READ_EN;
	writeICR(dev, ACR_INDEX, dev->bakACR);
}

unsigned char readICR(serial9bit_dev *dev, unsigned char index)
{
	unsigned char retVal;
	writeICR(dev, ACR_INDEX, (unsigned char)(dev->bakACR|ACR_ICR_READ_EN));
	writeReg(dev, SPR_OFFSET, index);
	retVal = readReg(dev, ICR_OFFSET);
	writeICR(dev, ACR_INDEX, (unsigned char)(dev->bakACR&~ACR_ICR_READ_EN));
	return retVal;
}

void writeICR(serial9bit_dev *dev, unsigned char index, unsigned char value)
{
	writeReg(dev, SPR_OFFSET, index);
	writeReg(dev, ICR_OFFSET, value);
	if (index==ACR_INDEX)
		dev->bakACR = value;
}

//Device Detection
unsigned char detectOX16C95x(serial9bit_dev *dev)
{
	unsigned char id1, id2, id3, rev;
	id1 = readICR(dev, ID1_INDEX);
	id2 = readICR(dev, ID2_INDEX);
	id3 = readICR(dev, ID3_INDEX);
	rev = readICR(dev, REV_INDEX);
	return ((id1==0x16)&&(id2==0xC9)&&((id3&0xF0)==0x50));
}

//Enable 9-bit mode
void set9BitModeEnable(serial9bit_dev *dev, unsigned char state, unsigned char bit9int)
{
	unsigned char nmr = readICR(dev, NMR_INDEX);
	if(state)
		if(bit9int) nmr |= (NMR_9BIT_MODE_EN + NMR_9BIT_INTERRUPT_EN);
		else nmr &=~(NMR_9BIT_INTERRUPT_EN);
	else
		nmr &= ~ NMR_9BIT_MODE_EN;
	writeICR(dev, NMR_INDEX, nmr);
}

//Send 9-bit
void send9BitData(serial9bit_dev *dev, unsigned short data)
{
	writeReg(dev, SPR_OFFSET, (data & 0x0100)>>8);
	writeReg(dev, THR_OFFSET, data & 0x00FF);
}

//Receive 9-bit
unsigned short receive9BitData(serial9bit_dev *dev)
{
	unsigned char lsr = readReg(dev, LSR_OFFSET);
	unsigned short data = readReg(dev, RHR_OFFSET);
	if(lsr & LSR_PARITY_ERROR) data |= 0x0100;
	return data;
}


/*
 * Implementation Section of linux device driver basic API functions
 */

static int serial9bit_open(struct inode *inode, struct file *file)
{
	//if(test_and_set_bit(1,&serial9bit_users))
	//	return -EBUSY;
	MOD_INC_USE_COUNT;
	return 0;
}

static int serial9bit_release(struct inode *inode, struct file *file)
{
	//serial9bit_users = 0;
	MOD_DEC_USE_COUNT;
	return 0;
}

static ssize_t serial9bit_write(struct file *filp, char *buf, size_t len, loff_t *offp)
{
	int retval = len;
	unsigned short *kbuf, *ptr;

	if (offp != &filp->f_pos)
		return -ESPIPE;

	if (len&0x1)
		return -EINVAL;

	if (down_interruptible(&sem))
		return -ERESTARTSYS;

	while ( (readReg(dev, LSR_OFFSET)&LSR_FIFO_EMPTY)==0 )
	{
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		up (&sem);
		interruptible_sleep_on(&wqueue);
		if (signal_pending (current))
			return -ERESTARTSYS;
		if (down_interruptible(&sem))
			return -ERESTARTSYS;
	}

	kbuf=(unsigned short *)kmalloc(len, GFP_KERNEL);
	if (!kbuf) {
		retval = -ENOMEM;
		goto err;
	}
	if (copy_from_user(kbuf, buf, len)) {
		retval = -EFAULT;
		goto err;
	}
	ptr=kbuf;

	while (len) {
		if (readFIFOLevel(dev, TRANSMIT_FIFO) == 128)
			break;
		send9BitData(dev, *(ptr++));
		len-=2;
	}

	kfree(kbuf);
	up (&sem);

	return retval-len;

	err:
		kfree(kbuf);
		up (&sem);
		return retval;
}


static ssize_t serial9bit_read(struct file *filp, char *buf, size_t len, loff_t *offp)
{
	int retval=len, i=0;
	unsigned short *kbuf, *ptr;
	unsigned short lsr;

	if (len&0x1)
		return -EINVAL;

	if (down_interruptible(&sem))
		return -ERESTARTSYS;

	while ( (readReg(dev, LSR_OFFSET)&LSR_DATA_AVAILABLE)==0 )
	{
		writeReg(dev, IER_OFFSET, IER_RX_INTERRUPT_EN|IER_TX_INTERRUPT_EN);
		up (&sem);
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		interruptible_sleep_on(&rqueue);
		if (signal_pending (current))
			return -ERESTARTSYS;
		if (down_interruptible(&sem))
			return -ERESTARTSYS;
	}

	kbuf=(unsigned short *)kmalloc(len, GFP_KERNEL);
	if (!kbuf) {
		retval = -ENOMEM;
		goto err;
	}
	ptr=kbuf;

	while (len)
	{
		lsr = readReg(dev, LSR_OFFSET);

		if (i==10) break;
		if ( (lsr&LSR_DATA_AVAILABLE)==0 ) {
			udelay(10);
			i++;
			continue;
		}
		i=0;

		if ( (lsr&LSR_ERROR_MASK) )
			break;

		*(ptr++) = receive9BitData(dev);
		len-=2;
	}

	if ( (retval > 0) && copy_to_user(buf, kbuf, retval-len)) {
		retval = -EFAULT;
		goto err;
	}

	kfree(kbuf);
	up (&sem);

	if ( (lsr&LSR_ERROR_MASK) ) {
		printk(KERN_ERR "serial9bit: Read interrupted due ");
		if (lsr&LSR_FRAMING_ERROR)
			printk("framing ");
		if (lsr&LSR_BREAK)
			printk("break ");
		if (lsr&LSR_DATA_ERROR)
			printk("data ");
		printk("error\n");
	}

	return retval-len;

	err:
		kfree(kbuf);
		up (&sem);
		return retval;
}


static int serial9bit_ioctl(struct inode *inode, struct file *file,
	unsigned int cmd, unsigned long arg)
{
	switch(cmd){
	default:
		return -ENOIOCTLCMD;
	}
}

static void int_handler(int irq, void *dev_id, struct pt_regs * regs)
{
	int isr = readReg(dev, ISR_OFFSET);
	printk("serial9bit: interrupt handler 0x%x\n", isr);

	if (isr&0x4) {
		writeReg(dev, IER_OFFSET, IER_TX_INTERRUPT_EN);
		wake_up_interruptible(&rqueue);
	}

	if (isr&0x2)
		wake_up_interruptible(&rqueue);

	return;
}

static struct file_operations serial9bit_fops=
{
	owner:		THIS_MODULE,
	write:		serial9bit_write,
	read:			serial9bit_read,
	ioctl:		serial9bit_ioctl,
	open:			serial9bit_open,
	release:		serial9bit_release,
};

struct miscdevice serial9bit_miscdev=
{
	MISC_DYNAMIC_MINOR,
	"serial9bit",
	&serial9bit_fops
};

static int __init serial9bit_init(void)
{
	int ret=0;
	printk(KERN_INFO "serial9bit: Serial 9-bit driver init\n");

	//IO module parameter check
	if (io==-1 || irq==-1) {
		printk(KERN_INFO "serial9bit: You must provide the IO address and IRQ line\n");
		return -ENODEV;
	}

	dev = kmalloc(sizeof(serial9bit_dev), GFP_KERNEL);
	if (!dev) {
		printk(KERN_INFO "serial9bit: Error on memory allocation\n");
		return -ENOMEM;
	}
	memset(dev, 0, sizeof(serial9bit_dev));

	//Mapping IO base address
	dev->name = name;
	dev->io = io;
	dev->irq = irq;
	dev->base_addr = (unsigned long)TS7XXX_IOMEM + io;

	if (check_mem_region(dev->base_addr, TSSER_SIZE_IO)) {
		printk(KERN_INFO "serial9bit: Requested memory already in use\n");
		kfree(dev);
		return -ENOMEM;
	}
	request_mem_region(dev->base_addr, TSSER_SIZE_IO, dev->name);

	//Chip detection
	{
		//950 Mode selection
		write650(dev, EFR_OFFSET, EFR_ENHANCED_MODE_EN);
		writeReg(dev, FCR_OFFSET, FCR_FIFO_EN);
		//9-bit chip detection test
		if (!detectOX16C95x(dev)) {
			printk(KERN_INFO "serial9bit: 9-bit chip NOT detected\n");
			release_mem_region(dev->base_addr, TSSER_SIZE_IO);
			kfree(dev);
			return -ENODEV;
		}
	}

	//Configuration for 9-Bit Mode
	{
		writeDivisor(dev, DIVISOR_BAUD_9600);
		writeReg(dev, MCR_OFFSET, readReg(dev, MCR_OFFSET)&~0x10);
		writeReg(dev, IER_OFFSET, IER_RX_INTERRUPT_EN|IER_TX_INTERRUPT_EN);
		set9BitModeEnable(dev, TRUE, TRUE);
	}

	//Registering IRQ
	if ((ret = request_irq(dev->irq, int_handler, SA_SHIRQ, dev->name, dev)) > 0) {
		printk(KERN_INFO "serial9bit: Error on irq %i request\n", dev->irq);
		release_mem_region(dev->base_addr, TSSER_SIZE_IO);
		kfree(dev);
		return ret;
	}

	//Registering the device + devfs
	serial9bit_miscdev.name = name;
	if ((ret = misc_register(&serial9bit_miscdev)) > 0) {
		printk(KERN_INFO "serial9bit: Error on device registration\n");
		free_irq(dev->irq, dev->name);
		release_mem_region(dev->base_addr, TSSER_SIZE_IO);
		kfree(dev);
		return ret;
	}

	printk(KERN_INFO "serial9bit: Driver loaded with I/O address 0x%lx and IRQ %i\n",
		dev->base_addr, dev->irq);

	return 0;
}

static void __exit serial9bit_exit(void)
{
	misc_deregister(&serial9bit_miscdev);
	free_irq(dev->irq, dev->name);
	release_mem_region(dev->base_addr, TSSER_SIZE_IO);
	kfree(dev);
	printk(KERN_INFO "serial9bit: Driver unloaded\n");
}

module_init(serial9bit_init);
module_exit(serial9bit_exit);
