/*******************************************************************************
** Document: w747_gpio.c
** Copyright (c) 2004 R&D Center, Tailyn Communication Company, Ltd.  
** Created by: Chien Chung Lo
** Date: Dec. 6, 2004
** Description: Wintegra GPIO device driver.
** Version: 0.1
*******************************************************************************/

//----------------------------------------------------------------------
//                              Device Driver of GPIO
//----------------------------------------------------------------------
#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 "w747_gpio.h"
#include "w747_gpio_function.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chien Chung Lo");
MODULE_DESCRIPTION("Wintegra GPIO device driver.");

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

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
struct file_operations w747_gpio_fops = {
	llseek:		w747_gpio_llseek,
	read:		w747_gpio_read,
	write:		w747_gpio_write,
	open:		w747_gpio_open,
	release:	w747_gpio_release,
};

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
loff_t w747_gpio_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);

	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_gpio_info.lock);
	filp->f_pos = newpos; // new position for file
	spin_unlock(&w747_gpio_info.lock);
    
	return newpos;
}

//----------------------------------------------------------------------
// GPIO read: using 2 bytes. buf[1]->select pin; buf[0]-> don't care
//----------------------------------------------------------------------
ssize_t w747_gpio_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	ssize_t result = 0;
	unsigned char value;
	
	MSG("read: go\n");
	
	if(down_interruptible(&w747_gpio_info.sem))
		return -ERESTARTSYS;
		
	// check data length, value of buf and gpio_pin num 	
	if(count >= 2 || *f_pos > MAX_GPIO_NUM)
	{
		MSG("read: check data length, value of buf and gpio_pin num fail!!\n");
		up(&w747_gpio_info.sem); //release semaphore
		return -EINVAL;	
	}
	
	value = gpio_read(*f_pos);
	MSG("read: *f_pos:%x  value:%x\n", (unsigned int)*f_pos, value);
	/*
	value = inl_p(W747_GPIO_GPVR_C_REG);
	if(value < 0)
	{
		MSG("read: value fail!!\n");
		up(&w747_gpio_info.sem); //release semaphore
		return -EINVAL;	
	}
	*/	
	
	if(put_user(value, (unsigned char *)buf))
	{
		MSG("read: copy_to_user fail!!\n");
		up(&w747_gpio_info.sem); //release semaphore
		return -EFAULT;
	}
		
	up(&w747_gpio_info.sem); //release semaphore
	
	return result;	
}

//----------------------------------------------------------------------
// GPIO write: 
//----------------------------------------------------------------------
ssize_t w747_gpio_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
	ssize_t result = 0;
	
	unsigned char value;
	
	if(get_user(value, (unsigned char *)buf))
	{
		MSG("write: copy_from_user fail!!\n");
		return -EFAULT;
	}
	
	MSG("write: *f_pos:%x  buffer:%x\n", (unsigned int)*f_pos, value);
	
	if(down_interruptible(&w747_gpio_info.sem))
		return -ERESTARTSYS;
		
	// check data length, value of buf and gpio_pin num 	
	if(count >= 2 || value >= 2 || *f_pos > MAX_GPIO_NUM)
	{
		up(&w747_gpio_info.sem); //release semaphore
		return -EINVAL;	
	}
	
	if(value)
	{
		gpio_set(*f_pos);	
	}
	else
	{
		gpio_clr(*f_pos);	
	}
		
		
	up(&w747_gpio_info.sem); //release semaphore
	
	return result;
}

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

	while (w747_gpio_info.usag_count)
	{
	    spin_unlock(&w747_gpio_info.lock);
		if(file->f_flags & O_NONBLOCK) 
		{
			MSG("open: nonblocking operation!!\n");
			return -EAGAIN; 
		}
	
		interruptible_sleep_on(&w747_gpio_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_gpio_info.lock);
	}
	
	w747_gpio_info.owner = current->uid; /* grab it */
	MSG("open: get new uid!!\n");
	w747_gpio_info.usag_count++;
	MSG("open: show usag count=%d!!\n", w747_gpio_info.usag_count);
	spin_unlock(&w747_gpio_info.lock);
	// increment usage count
	MOD_INC_USE_COUNT;
	MSG("open: open!!\n");

	return 0;
}

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

	return 0;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int w747_gpio_init_module(void)
{
	int result;
	
	SET_MODULE_OWNER(&w747_gpio_fops);
	
	w747_init_gpio();
        
	result = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &w747_gpio_fops);
	if(result < 0)
	{
		MSG("can't register device with kernel\n");
		return result;
	}

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

	init_waitqueue_head(&w747_gpio_info.wait_queue);
	sema_init(&w747_gpio_info.sem, 1);
	spin_lock_init(&w747_gpio_info.lock);
                   
	MSG("had register device with kernel. Major number:%d. Module name:%s\n",
		w747_gpio_info.major_num, w747_gpio_info.name);
		
	return 0; // succeed
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
void w747_gpio_cleanup_module(void)
{
	unregister_chrdev(w747_gpio_info.major_num, w747_gpio_info.name);
	MSG("remove module\n");
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
module_init(w747_gpio_init_module);
module_exit(w747_gpio_cleanup_module);
//----------------------------------------------------------------------

