/*****************************************
 Copyright © 2001-2003	
 Sigma Designs, Inc. All Rights Reserved
 Proprietary and Confidential
 *****************************************/

/*****************************************************************************/

/*
 *      realmagicdev.c -- NetStream2000 EM8400 (quasar) ``major level'' driver
 *
 *      Copyright (C) 1999-2000  Sigma Designs
 *                    written by Emmanuel Michon <emmanuel_michon@sdesigns.com>
 *                           and Howard G. Page
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*****************************************************************************/

#include <linux/version.h>
#include <linux/module.h>

#include <linux/pci.h>

#include <linux/fs.h>

#include <asm/io.h>

#include "common.h"
#include "quasar.h"
#include "realmagicdev.h"

char kernel_version[] = UTS_RELEASE;

// this table stores up to REALMAGIC_MAX_DEV devices
realmagic_dev_t realmagic_dev_tab[REALMAGIC_MAX_DEV];
int realmagic_dev_idx;

// look in linux/fs.h for types
ssize_t realmagic_read(struct file *, char *, size_t, loff_t *);
int realmagic_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
int realmagic_mmap(struct file *, struct vm_area_struct *);
int realmagic_open(struct inode *, struct file *);
int realmagic_release(struct inode *, struct file *);

// override this default by using: ``insmod anyrealmagic.o quasar_dev_id=0x8450'' for instance.
#ifdef _STREAMMACHINE_
unsigned short quasar_dev_id=0x8475; 
#else
unsigned short quasar_dev_id=0x8400; 
#endif /*_STREAMMACHINE_*/

MODULE_PARM(quasar_dev_id, "h");

struct file_operations realmagic_fops = 
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	owner:THIS_MODULE, // struct module *owner;
#endif
	read:realmagic_read, 
	ioctl:realmagic_ioctl, 
	mmap:realmagic_mmap, 
	open:realmagic_open, 
	release:realmagic_release, 
};

// extract minor number when accessed by struct inode
int realmagic_get_dev(struct inode *i_node,struct file_operations **fops)
{
	int idx = MINOR(i_node->i_rdev);
	realmagic_dev_t *dev;

	ASSERT(DEBUG,(idx<realmagic_dev_idx),
	       -EINVAL,RAS);

	dev=&realmagic_dev_tab[idx];

	ASSERT(DEBUG,dev->fops,-EINVAL,RAS);
	
	*fops=dev->fops;

	return 0;
}

// extract minor number when accessed by struct file
int realmagic_get_dev_file(struct file *file, struct file_operations **fops)
{
	return realmagic_get_dev(file->f_dentry->d_inode,fops);
}

ssize_t realmagic_read(struct file *filp, char *buf, size_t count, loff_t *pos)
{
	struct file_operations *dev_fops=0;
	
	VERBOSECALL(DEBUG,realmagic_get_dev_file(filp,&dev_fops),
		    -EINVAL,RAS);
	
	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->read,-EINVAL,RAS);

	// do not use VERBOSECALL here (returned number is number of char read)
	return (*dev_fops->read)(filp,buf,count,pos);
}

int realmagic_ioctl(struct inode* i_node, struct file* filp,
		    unsigned int arg1, unsigned long arg2)
{
	struct file_operations *dev_fops=0;

	VERBOSECALL(DEBUG,realmagic_get_dev(i_node,&dev_fops),
		    -EINVAL,RAS);
	
	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->ioctl,-EINVAL,RAS);
	
	VERBOSECALL(DEBUG,(*dev_fops->ioctl)(i_node, filp, arg1, arg2),
		    -EINVAL,RAS);

	return 0;
}

int realmagic_mmap(struct file* filp,
		   struct vm_area_struct* vmptr)
{
	struct file_operations *dev_fops=0;

	VERBOSECALL(DEBUG,realmagic_get_dev_file(filp,&dev_fops),
		    -EINVAL,RAS);
	
	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->mmap,-EINVAL,RAS);
	
	VERBOSECALL(DEBUG,(*dev_fops->mmap)(filp, vmptr),
		    -EINVAL,RAS);

	return 0;
}

int realmagic_open(struct inode *i_node, struct file *filp)
{
	struct file_operations *dev_fops=0;

	VERBOSECALL(DEBUG,realmagic_get_dev(i_node,&dev_fops),
		    -EINVAL,RAS);

	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->open,-EINVAL,RAS);

	VERBOSECALL(DEBUG,(*dev_fops->open)(i_node, filp),
		    -EINVAL,RAS);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#else
	MOD_INC_USE_COUNT;
#endif

	return 0;
}

int realmagic_release(struct inode *i_node, struct file *filp)
{
	struct file_operations *dev_fops=0;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#else
	MOD_DEC_USE_COUNT;
#endif

	VERBOSECALL(DEBUG,realmagic_get_dev(i_node,&dev_fops),
		    -EINVAL,RAS);
	
	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->release,-EINVAL,RAS);
	
	VERBOSECALL(DEBUG,(*dev_fops->release)(i_node, filp),
		    -EINVAL,RAS);

	return 0;
}

int init_module(void)
{
	realmagic_dev_idx=0;
	
	// look for hardware
	VERBOSECALL(DEBUG,quasar_init(),
		    -EINVAL,RAS);
	
	// register char devices
	VERBOSECALL(DEBUG,register_chrdev(REALMAGIC_MAJOR,
					  REALMAGIC_DEVICE_NAME,
					  &realmagic_fops),
		    -EINVAL,"ok for major %d",REALMAGIC_MAJOR);
	
	LOGINFO(1,"init_module success");

	return 0;
}

int cleanup_module_with_return_code(void)
{
	VERBOSECALL(DEBUG,quasar_cleanup(),
		    -EINVAL,RAS);
	
	VERBOSECALL(DEBUG,unregister_chrdev(REALMAGIC_MAJOR,
					    REALMAGIC_DEVICE_NAME),
		    -EINVAL,RAS);
	
	return 0;
}

void cleanup_module(void)
{
	int rc;
	
	rc=cleanup_module_with_return_code();

	LOGINFO(1,"cleanup_module over with %d",rc);
}
