/*******************************************************************************
** Document: w747_host_bus.c
** Copyright (c) 2004 R&D Center, Tailyn Communication Company, Ltd.  
** Created by: Chien Chung Lo
** Date: Dec. 23, 2004
** Description: Wintegra Host bus driver.
** Version: 0.1
*******************************************************************************/

//----------------------------------------------------------------------
//                              Host bus driver
//----------------------------------------------------------------------
#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif
 
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/spinlock.h>	/* spin lock */
#include <asm/io.h>
#include <asm/uaccess.h> /* copy to user and copy form user */
#include <linux/mm.h>

#include <linux/delay.h>

#include <asm/web777.h>	/* The memory map of the wintegra */
#include "w747_host_bus.h"

#include "w747_gpio_function.h"
#include "w747_host_bus_function.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chien Chung Lo");
MODULE_DESCRIPTION("Wintegra host bus driver.");

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
struct device_struct w747_host_bus_info;
extern struct task_struct *current;

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
struct file_operations w747_host_bus_fops = {
	llseek:		w747_host_bus_llseek,
	read:		w747_host_bus_read,
	write:		w747_host_bus_write,
	open:		w747_host_bus_open,
	release:	w747_host_bus_release,
};


//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
void w747_host_bus_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	if(gpio_read(BP_CARD_EQUIP_0))
	{
		//MSG("not own interrupt!!!\n");
		return;
	}
	
	MSG("interrupt!!!\n");
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
loff_t w747_host_bus_llseek(struct file *filp, loff_t off, int whence)
{
	loff_t newpos;

//	MSG("llseek: now pid is %d\n", current->pid);
//	MSG("llseek: offset:0X%x\n", (unsigned int)off);

	if(!(off >= MIN_RANGE) || !(off < MAX_RANGE))
	{
//		MSG("llseek: over bound\n");
		return -EINVAL;
	}

	switch(whence) {
		case 0: /* SEEK_SET */
		newpos = off;
//		MSG("llseek: new offset:0X%x\n", (unsigned int)newpos);
		break;

		default: /* can't happen */
//		MSG("llseek: can't happen!!\n");
		return -EINVAL;
    	}
    	
	if (newpos < 0) 
		return -EINVAL;
        
	spin_lock(&w747_host_bus_info.lock);
	filp->f_pos = KSEG1ADDR(newpos); // new position for file
	spin_unlock(&w747_host_bus_info.lock);
    
	return newpos;
}

//----------------------------------------------------------------------
// 
//----------------------------------------------------------------------
ssize_t w747_host_bus_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	unsigned char *dptr;
	unsigned int i;
	ssize_t result = 0;
	
//	MSG("read: go\n");
	
	dptr = kmalloc(count * sizeof(unsigned char), GFP_KERNEL);
	
	if(down_interruptible(&w747_host_bus_info.sem))
		return -ERESTARTSYS;
		
	up(&w747_host_bus_info.sem); //release semaphore

	for(i = 0; i < count; i++)
	{
//		MSG("read:ADDR %x:%x\n", (unsigned int)(filp->f_pos + i), *(dptr + i));
		udelay(5); // The reading DSP need 0.01us.
		*(dptr + i) = inb_p(filp->f_pos + i);
	}
	
	if(copy_to_user(buf, dptr, count))
	{
//		MSG("read: copy_to_user fail!!\n");
		up(&w747_host_bus_info.sem); // release semaphore!!
		kfree(dptr); // free memory
		return -EFAULT;
	}
	
	result = count;
	kfree(dptr); // free memory
//	MSG("read: success !!\n");
	
	return result;	
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
ssize_t w747_host_bus_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
	unsigned char *dptr;
	unsigned int i;
	ssize_t result = 0;

//	MSG("write: go\n");

	dptr = kmalloc(count * sizeof(unsigned char), GFP_KERNEL);

	if(down_interruptible(&w747_host_bus_info.sem))
		return -ERESTARTSYS;
		
	if(copy_from_user(dptr, buf, count))
	{
//		MSG("write: copy_from_user fail!!\n");
		up(&w747_host_bus_info.sem); // remember release semaphore!!
		kfree(dptr); // free memory
		return -EFAULT;
	}
	
	for(i = 0; i < count; i++)
	{
//		MSG("write:ADDR %x:%x\n", (unsigned int)(filp->f_pos + i), *(dptr + i));
		udelay(5); // The writing DSP need 0.01us.
		outb_p(*(dptr + i), (unsigned int)(filp->f_pos + i));
	}
		
	up(&w747_host_bus_info.sem); //release semaphore
	
	result = count;
	kfree(dptr); // free memory
//	MSG("write: success !!\n");
	
	return result;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int w747_host_bus_open(struct inode *inode, struct file *file)
{
	spin_lock(&w747_host_bus_info.lock);
//	MSG("open: now pid is %d\n", current->pid);

	while (w747_host_bus_info.usag_count)
	{
	    spin_unlock(&w747_host_bus_info.lock);
		if(file->f_flags & O_NONBLOCK) 
		{
//			MSG("open: nonblocking operation!!\n");
			return -EAGAIN; 
		}
	
		interruptible_sleep_on(&w747_host_bus_info.wait_queue);
//		MSG("open: wake up or have a signal!!\n");
		if (signal_pending(current)) /* a signal arrived */
		{
//			MSG("open: a signal arrived!!\n");
			return -ERESTARTSYS; /* tell the fs layer to handle it */	
		}
		/* else, loop */
		spin_lock(&w747_host_bus_info.lock);
	}
	
	w747_host_bus_info.owner = current->uid; /* grab it */
//	MSG("open: get new uid!!\n");
	w747_host_bus_info.usag_count++;
	
//	MSG("open: show usag count=%d!!\n", w747_host_bus_info.usag_count);
	
	spin_unlock(&w747_host_bus_info.lock);
	// increment usage count
	MOD_INC_USE_COUNT;
//	MSG("open: open!!\n");

	return 0;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int w747_host_bus_release(struct inode *inode, struct file *file)
{
//	MSG("release: now pid is %d\n", current->pid);
	w747_host_bus_info.usag_count--;
//	MSG("release: show usag count=%d!!\n", w747_host_bus_info.usag_count);
	
	if (w747_host_bus_info.usag_count == 0)
	{
		wake_up_interruptible(&w747_host_bus_info.wait_queue); /* awake other uid's */
//		MSG("release: wake up!!!!\n");
	}
	
	MOD_DEC_USE_COUNT;
//	MSG("release: release!!\n");

	return 0;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int w747_host_bus_init_module(void)
{
	int result;
	unsigned int temp;
	
	SET_MODULE_OWNER(&w747_host_bus_fops);

/*	result = request_irq(IV_EINT4_VEC, w747_host_bus_interrupt, 
			SA_INTERRUPT | SA_SHIRQ, "host bus interrupu", &w747_host_bus_info);
	if(result < 0)
	{
		MSG("can't register irq!!\n");
		return result;
	}		
*/
	result = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &w747_host_bus_fops);
	if(result < 0)
	{
		MSG("can't register device with kernel\n");
		return result;
	}

	//get driver information
	if(!w747_host_bus_info.major_num)
	{
		w747_host_bus_info.major_num = DEVICE_MAJOR;
	}
	
	w747_host_bus_info.name = DEVICE_NAME;
	w747_host_bus_info.major_num = result;
	w747_host_bus_info.fops = &w747_host_bus_fops;
	w747_host_bus_info.usag_count = 0;

	init_waitqueue_head(&w747_host_bus_info.wait_queue);
	sema_init(&w747_host_bus_info.sem, 1);
	spin_lock_init(&w747_host_bus_info.lock);


	MSG("had register device with kernel. Major number:%d. Module name:%s\n",
		w747_host_bus_info.major_num, w747_host_bus_info.name);
	
	gpio_clr(ALL_BUS_ENABLE); // host bus enable
	
	//-------------------------------
	//---set up UTOPIA clock(50 Mhz)
	temp = 0x0; //BRG1 is disable
	outl_p(temp, W747_BRG1_REG_BASE);
	temp = inl_p(W747_BRG1_REG_BASE);
	MSG("W747_BRG1_REG_BASE %x:%x\n",  (unsigned int)W747_BRG1_REG_BASE,temp);
	temp = 0x40000009; // BRG1 input clock is driven from the BRGI2 input pin clock(100Nhz),
					   // and value of BRG1 division is 2.
	outl_p(temp, W747_BRG1_REG_BASE);
	temp = inl_p(W747_BRG1_REG_BASE);
	MSG("W747_BRG1_REG_BASE %x:%x\n",  (unsigned int)W747_BRG1_REG_BASE,temp);
	temp = temp | 0x80000000; // BRG1 is enable.
	outl_p(temp, W747_BRG1_REG_BASE);
	temp = inl_p(W747_BRG1_REG_BASE);
	MSG("W747_BRG1_REG_BASE %x:%x\n",  (unsigned int)W747_BRG1_REG_BASE,temp);
	//-------------------------------

	return 0;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
void w747_host_bus_cleanup_module(void)
{
	unsigned int temp;

	gpio_set(ALL_BUS_ENABLE); // host bus disable.
	
	temp = inl_p(W747_BRG1_REG_BASE);
	temp = temp & 0x7fffffff; // BRG1 is disable.
	outl_p(temp, W747_BRG1_REG_BASE);
	
//	free_irq(IV_EINT4_VEC, &w747_host_bus_info);
	unregister_chrdev(w747_host_bus_info.major_num, w747_host_bus_info.name);
	MSG("remove module\n");
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
module_init(w747_host_bus_init_module);
module_exit(w747_host_bus_cleanup_module);
//----------------------------------------------------------------------

