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

//----------------------------------------------------------------------
//                              Flash 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 <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h> /* copy to user and copy form user */

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alfred");
MODULE_DESCRIPTION("Wintegra flash driver.");

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

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
struct file_operations w747_flash_fops = {
	llseek:		w747_flash_llseek,
	read:		w747_flash_read,
	write:		w747_flash_write,
	open:		w747_flash_open,
	release:	w747_flash_release,
};

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

	MSG("llseek: go\n");
   	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_flash_info.lock);
	filp->f_pos = newpos & 0xfffffffe; // new position for file
	spin_unlock(&w747_flash_info.lock);

	return newpos;
}

//----------------------------------------------------------------------
//        count size is 2 bytes
//----------------------------------------------------------------------
ssize_t w747_flash_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	unsigned short *dptr;
	unsigned int i,nextbank = 0;
	ssize_t result = 0;
	
	MSG("read: go\n");
	
	if(down_interruptible(&w747_flash_info.sem))
		return -ERESTARTSYS;
	
	dptr = kmalloc(count * sizeof(unsigned short), GFP_KERNEL);
	
	for(i=0;i<count;i++){
		if(((*f_pos + i * 2) >= 0xc0000000) && (nextbank == 0)){
			outb_p(0xff, CPLD_HADD23_HIGH);
			nextbank = 1;
		}
		*(unsigned short* )(dptr + i) = inw_p(*f_pos + i * 2 - (nextbank * 0x800000));
	}
	if(nextbank == 1){
		outb_p(0xff, CPLD_HADD23_LOW);
	}
	
	if(copy_to_user(buf, dptr, count * sizeof(unsigned short)))
	{
		MSG("read: copy_to_user fail!!\n");
		up(&w747_flash_info.sem); // remember release semaphore!!
		kfree(dptr); // free memory
		return -EFAULT;
	}
	
	result = count;
	kfree(dptr);
	up(&w747_flash_info.sem); //release semaphore
	
	return result;	
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
ssize_t w747_flash_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{	
	unsigned int flash_pos;
	volatile unsigned short *pos;
	struct flash_option_info *dptr;
	unsigned int i,nextbank = 0;
	ssize_t result = 0;
	
	MSG("write: go\n");
	
	if(down_interruptible(&w747_flash_info.sem))
		return -ERESTARTSYS;
		
	dptr = kmalloc(sizeof(struct flash_option_info), GFP_KERNEL);
	
	if(copy_from_user(dptr, buf, sizeof(struct flash_option_info)))
	{
		MSG("write: copy_from_user fail!!\n");
		up(&w747_flash_info.sem);
		kfree(dptr); // free memory
		return -EFAULT;
	}
	
	flash_pos = *f_pos;
	pos = (unsigned short*)flash_pos;
	i = 0;
	if((*dptr).word_size > WORD_SIZE)
	{
		up(&w747_flash_info.sem);
		kfree(dptr); // free memory
		return -EINVAL;
	}
	MSG("type:%d\n",(*dptr).type);
	switch((*dptr).type){
		case SECTOR_ERASE:
			do{
				if((((*dptr).sector_offset + SECTOR_SIZE * i) >= 0x800000) && (nextbank == 0)){
					outb_p(0xff, CPLD_HADD23_HIGH);
					nextbank = 1;
				}
				cli();  //stop all interrupt and task
				pos[0x555] = 0xAA;
				//udelay(0);
				pos[0x2AA] = 0x55;
				//udelay(0);
				pos[0x555] = 0x80;
				//udelay(0);
				pos[0x555] = 0xAA;
				//udelay(0);
				pos[0x2AA] = 0x55;
				//udelay(0);
				pos[((*dptr).sector_offset + SECTOR_SIZE * i - (nextbank * 0x800000)) / 2] = 0x30;
				sti();  //start all interrupt and task
				i++;
			}while((i < (*dptr).sector_size) && (flash_status((unsigned int)*f_pos) == 0));
			break;
		case SECTOR_PROGRAM:
			do{
				if((((*dptr).addr_offset + i) >= 0x400000) && (nextbank == 0)){
					outb_p(0xff, CPLD_HADD23_HIGH);
					nextbank = 1;
				}
				cli();  //stop all interrupt and task
				pos[0x555] = 0xAA;
				//udelay(0);
				pos[0x2AA] = 0x55;
				//udelay(0);
				pos[0x555] = 0xA0;
				//udelay(0);
				pos[(*dptr).addr_offset + i - (nextbank * 0x400000)] = (*dptr).data[i];
				sti();	//start all interrupt and task
				i++;
			}while((i < (*dptr).word_size) && (flash_status((unsigned int)*f_pos) == 0));
			break;
		default:
			printk("Error option type\n");
			break;
	}//switch
	//pos[0] = 0xf0;//for reset flash
	if(nextbank == 1){
		outb_p(0xff, CPLD_HADD23_LOW);
	}
	
	result = count;
	kfree(dptr);
	up(&w747_flash_info.sem);
	
	return result;
}
//----------------------------------------------------------------------
//                  flash status
//----------------------------------------------------------------------
int flash_status(unsigned int addr)
{
	//unsigned int i;
	unsigned short d,t;
	
	//i = 0;
	do{       //check toggled for busy
		d = inw_p(addr);
		t = d ^ inw_p(addr);
	//	i++;
	}while((t & 0x40));// && (i != TIMEOUT));
	//if(i == TIMEOUT)
	//	return -1;
	//else
		return 0;
}

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

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

	return 0;
}

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

	return 0;
}

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

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

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

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

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
module_init(w747_flash_init_module);
module_exit(w747_flash_cleanup_module);
//----------------------------------------------------------------------

