/*-----------------------------------------------------------------------------
 * sonymisc.c
 *
 * a device driver file for sonymisc
 *
 * Copyright 2005,2006 Sony Corporation
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *---------------------------------------------------------------------------*/
#define __SONYMISC_C__

/*-----------------------------------------------------------------------------
 * Include file
 *---------------------------------------------------------------------------*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <asm/uaccess.h>

#include <asm/arch/sonymisc.h>

/*-----------------------------------------------------------------------------
 * Change note
 *
 * 01.00.000    : First release
 * 01.01.000	: In sonymisc_register(), register (block device) add.
 *                In sonymisc_deregister(), deregister (block device) add.
 * 01.01.001	: In sonymisc_register(), duplicate deny.
 * 01.01.002	: In sonymisc_chr_open(), fops_put() add.
 * 01.01.003	: In sonymisc_register/deregister(), spinlock add.
 * 01.01.004	: Change from Proprietary to GPL.
 * 01.01.005    : module_init() is called target_init.c.
 * 01.01.006    : module_init() is commented out.
 * 01.01.007    : version up for kernel 2.6.22
 */

/*-----------------------------------------------------------------------------
 * Macro declaration
 *---------------------------------------------------------------------------*/
#define MYNAME		"sonymisc"
#define MYVERSION	"01.01.007"

#define MAX_SONYMISC_MINOR	256

/*-----------------------------------------------------------------------------
 * Driver infomationin
 *---------------------------------------------------------------------------*/
#define SONYMISC_MAJOR	240
#define SONYMISC_NAME	MYNAME
#define SONYMISC_CHR_NAME	"sonymisc_chr"
#define SONYMISC_BLK_NAME	"sonymisc_blk"

/*-----------------------------------------------------------------------------
 * Module infomation
 *---------------------------------------------------------------------------*/
MODULE_AUTHOR("Sony Corporation");
MODULE_DESCRIPTION("SONYMISC driver");
MODULE_VERSION(MYVERSION);
MODULE_LICENSE("GPL");

/*-----------------------------------------------------------------------------
 * Compile switch
 *---------------------------------------------------------------------------*/
#define DEBUG_LEVEL 0

/*-[DEBUG_LEVEL 1]-------------------------------------------------
 * print value when function entry and exit			*/
#if(DEBUG_LEVEL == 1)
#define PDEBUG1(arg...)		{ printk(arg); }
#define PDEBUG2(arg...)		{}
#define PDEBUG3(arg...)		{}
#define RETURN1(val,arg)	{ printk(arg,val);return(val); }
#define RETURN2(val,arg)	{ return(val); }
/*-[DEBUG_LEVEL 2]-------------------------------------------------
 * print value when all function(include inline) entry and exit	*/
#elif(DEBUG_LEVEL == 2)
#define PDEBUG1(arg...)		{ printk(arg); }
#define PDEBUG2(arg...)		{ printk(arg); }
#define PDEBUG3(arg...)		{}
#define RETURN1(val,arg)	{ printk(arg,val);return(val); }
#define RETURN2(val,arg)	{ printk(arg,val);return(val); }
/*-[DEBUG_LEVEL 3]-------------------------------------------------
 * print value of variable	*/
#elif(DEBUG_LEVEL == 3)
#define PDEBUG1(arg...)		{ printk(arg); }
#define PDEBUG2(arg...)		{ printk(arg); }
#define PDEBUG3(arg...)		{ printk(arg); }
#define RETURN1(val,arg)	{ printk(arg,val);return(val); }
#define RETURN2(val,arg)	{ printk(arg,val);return(val); }
/*-[NO DEBUG]-------------------------------------------------
 * print nothing	*/
#else
#define PDEBUG1(arg...)		{}
#define PDEBUG2(arg...)		{}
#define PDEBUG3(arg...)		{}
#define RETURN1(val,arg)	{ return(val); }
#define RETURN2(val,arg)	{ return(val); }
#endif


/*-----------------------------------------------------------------------------
 * Function prototype declaration
 *---------------------------------------------------------------------------*/
int __init sonymisc_init( void );
static void __exit sonymisc_exit( void );

static int sonymisc_chr_open( struct inode *inode, struct file *file );
static int sonymisc_blk_open( struct inode *inode, struct file *file );
static int sonymisc_blk_release( struct inode *inode, struct file *file );

int sonymisc_register( struct sonymisc_device *device );
int sonymisc_deregister( struct sonymisc_device *device );

/*-----------------------------------------------------------------------------
 * Variable declaration
 *---------------------------------------------------------------------------*/
static struct file_operations chr_fops = 
{
    .owner 	= THIS_MODULE,
    .open 	= sonymisc_chr_open,
};

static struct block_device_operations blk_bdops = 
{
    .owner 	= THIS_MODULE,
    .open 	= sonymisc_blk_open,
    .release 	= sonymisc_blk_release,
};

static struct sonymisc_device chr_dev[MAX_SONYMISC_MINOR]; /* minor max-> 0xFF */
static struct sonymisc_device blk_dev[MAX_SONYMISC_MINOR]; /* minor max-> 0xFF */
static struct gendisk *disk;

/*=============================================================================
 *
 * Main function body
 *
 *===========================================================================*/

/*-----------------------------------------------------------------------------
 * init_module
 *---------------------------------------------------------------------------*/
int __init sonymisc_init( void )
{
    int err;
    PDEBUG1(" "MYNAME"::init() : Entry\n");

    /* register char device */
    if( (err=register_chrdev( SONYMISC_MAJOR, SONYMISC_CHR_NAME, &chr_fops )) <0 ){
        RETURN1( err, " "MYNAME"::init() : register char err.Return(%x)\n");
    }

    /* register block device */
    if( (err=register_blkdev( SONYMISC_MAJOR, SONYMISC_BLK_NAME )) <0 ){
        RETURN1( err, " "MYNAME"::init() : register blk err.Return(%x)\n");
    }

    if((disk = alloc_disk(1)) == NULL) {
        RETURN1( err, " "MYNAME"::init() : alloc disk err.Return(%x)\n");
    }
    disk->major       = SONYMISC_MAJOR;
    disk->first_minor = 0;
    disk->fops        = &blk_bdops;
    snprintf (disk->disk_name, 32, "sonymisc_blk%c", 0+'a');
    add_disk(disk);

    RETURN1( err, " "MYNAME"::init() : Return(%x)\n");
}

/*-----------------------------------------------------------------------------
 * exit_module
 *---------------------------------------------------------------------------*/
static void __exit sonymisc_exit( void )
{
    PDEBUG1(" "MYNAME"::exit() : Entry\n");

    /* unregister char device */
    unregister_chrdev( SONYMISC_MAJOR, SONYMISC_CHR_NAME );

    /* unregister block device */
    if (disk) {
        del_gendisk(disk);
        put_disk(disk);
        disk = NULL;
    }
    unregister_blkdev( SONYMISC_MAJOR, SONYMISC_BLK_NAME );

    PDEBUG1(" "MYNAME"::exit() : Exit\n");
    return;
}

/*-----------------------------------------------------------------------------
 * chr_open
 *---------------------------------------------------------------------------*/
static int sonymisc_chr_open( struct inode *inode, struct file *file )
{
    unsigned int minor;
    int err;
    const struct file_operations *old_fops;

    minor = iminor(inode);
    PDEBUG1(" "MYNAME"::chr_open() : minor = %d\n", minor);

    /* Check minor */
    if( MAX_SONYMISC_MINOR <= minor ){
        RETURN1( -ENODEV, " "MYNAME"::chr_open() : Return(%x)\n");
    }

    if( !chr_dev[minor].fops 		||
        !chr_dev[minor].fops->open	){
        RETURN1( -ENODEV, " "MYNAME"::chr_open() : Return(%x)\n");
    }

    old_fops = file->f_op;
    file->f_op = fops_get(chr_dev[minor].fops);
    err = file->f_op->open(inode,file);
    fops_put(old_fops);

    RETURN1( err, " "MYNAME"::chr_open() : Return(%x)\n");
}

/*-----------------------------------------------------------------------------
 * blk_open
 *---------------------------------------------------------------------------*/
static int sonymisc_blk_open( struct inode *inode, struct file *file )
{
    unsigned int minor;
    minor = iminor(inode);
    PDEBUG1(" "MYNAME"::blk_open() : minor = %d\n", minor);
    /* Nothing to do */
    RETURN1( 0, " "MYNAME"::blk_open() : Return(%x)\n");
}

/*-----------------------------------------------------------------------------
 * blk_release
 *---------------------------------------------------------------------------*/
static int sonymisc_blk_release( struct inode *inode, struct file *file )
{
    unsigned int minor;
    minor = iminor(inode);
    PDEBUG1(" "MYNAME"::blk_release() : minor = %d\n", minor);
    /* Nothing to do */
    RETURN1( 0, " "MYNAME"::blk_release() : Return(%x)\n");
}

/*-----------------------------------------------------------------------------
 * sonymisc_register
 *---------------------------------------------------------------------------*/
int sonymisc_register( struct sonymisc_device *device )
{
    unsigned long flags;
    spinlock_t lock = SPIN_LOCK_UNLOCKED;

    PDEBUG1(" "MYNAME"::register(%d) : Entry\n", device->minor);

    /* Check input parameter */
    if( !device 				|| 
        (MAX_SONYMISC_MINOR <= device->minor) 	){
        RETURN1( -EINVAL, " "MYNAME"::register() : Return(%x)\n");
    }

    /* register char device */
    if( device->class == DEV_CLASS_CHR ){
        spin_lock_irqsave(&lock, flags);
        if( !chr_dev[device->minor].minor ){
            memcpy( &chr_dev[device->minor], device, sizeof(struct sonymisc_device) );
            spin_unlock_irqrestore(&lock, flags);
        }else{/* duplicate*/
            spin_unlock_irqrestore(&lock, flags);
            RETURN1( -EBUSY, " "MYNAME"::register() : Return(%x)\n");
        }
    }
    /* register blk device */
    else{
        if( !device->gd ){
            RETURN1( -EINVAL, " "MYNAME"::register() : Return(%x)\n");
        }else{
            spin_lock_irqsave(&lock, flags);
            if( !blk_dev[device->minor].minor ){
                memcpy( &blk_dev[device->minor], device, sizeof(struct sonymisc_device) );
                spin_unlock_irqrestore(&lock, flags);

                device->gd->major = SONYMISC_MAJOR;
                add_disk(device->gd);
            }else{/* duplicate*/
                spin_unlock_irqrestore(&lock, flags);
                RETURN1( -EBUSY, " "MYNAME"::register() : Return(%x)\n");
            }
        }
    }

    RETURN1( 0, " "MYNAME"::register() : Return(%x)\n");
}

/*-----------------------------------------------------------------------------
 * sonymisc_register
 *---------------------------------------------------------------------------*/
int sonymisc_deregister( struct sonymisc_device *device )
{
    unsigned long flags;
    spinlock_t lock = SPIN_LOCK_UNLOCKED;

    PDEBUG1(" "MYNAME"::deregister(%d) : Entry\n", device->minor);

    /* Check input parameter */
    if( !device 				|| 
        (MAX_SONYMISC_MINOR <= device->minor) 	){
        RETURN1( -EINVAL, " "MYNAME"::deregister() : Return(%x)\n");
    }

    /* deregister char device */
    if( device->class == DEV_CLASS_CHR ){
        spin_lock_irqsave(&lock, flags);
        memset( &chr_dev[device->minor], 0, sizeof(struct sonymisc_device) );
        spin_unlock_irqrestore(&lock, flags);
    }
    /* deregister blk device */
    else{
        if( !device->gd ) {
            RETURN1( -EINVAL, " "MYNAME"::deregister() : Return(%x)\n");
        }else{
            spin_lock_irqsave(&lock, flags);
            memset( &blk_dev[device->minor], 0, sizeof(struct sonymisc_device) );
            spin_unlock_irqrestore(&lock, flags);

            del_gendisk(device->gd);
            put_disk(device->gd);
        }
    }

    RETURN1( 0, " "MYNAME"::deregister() : Return(%x)\n");
}



/*=============================================================================
 * Export symbols
 *===========================================================================*/
EXPORT_SYMBOL( sonymisc_register );
EXPORT_SYMBOL( sonymisc_deregister );

//module_init( sonymisc_init );
module_exit( sonymisc_exit );

