/*****************************************************************************
   MMSOUNDCORE: Legacy OSS register_/unregister_ functions for MMSOUNDCORE

   (c) 2011 by Michael Minn - see www.michaelminn.com for contact info

   MMSOUNDCORE uses the legacy OSS soundcore register* and unregister* 
   functions. Because those functions have been deprecated in newer
   ALSA-only kernels and newer versions of OSS use an entirely different
   architecture, implementations are provided here to permit stand-alone
   operation.
   
   These functions provide a multiplexed central entry functions for all
   OSS legacy devices (dsp, midi, mixer) that permit multiple devices
   to share a single device major number with multiple minor numbers. 
   This requires calls to register_chrdev() and unregister_chrdev() to
   take control of a device minor number. Registered device driver functions
   are then called from the entry functions.
   
   There are separate sets of file_operations functions that call either the 
   audio or midi file_operations functions. The four upper bits of the minor 
   number are the associated unit number and the four lower bits of the minor 
   number indicate the type of device (0 for mixer, 2 for MIDI, 3 for DSP).

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

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/usb.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <asm/uaccess.h>

#include "../sound.h"
#include "../soundcard.h"
#include "../mmusbaudio.h"

//#include <linux/vermagic.h>
//MODULE_INFO(vermagic, VERMAGIC_STRING);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
#error "This version of MMSOUNDCORE can only be used with 2.6.24 kernels or later."
#endif

/************************* Version Info *****************************/

#define DRIVER_AUTHOR "(c) 2011 by Michael Minn"
#define DRIVER_DESC "MMSOUNDCORE: Legacy OSS register/unregister functions"
#define DRIVER_VERSION "v2011.08.23"


#define MMSOUNDCORE_MAX_OSS_UNITS 8
#define MMSOUNDCORE_MAX_OSS_DEVICE_TYPES 4
static const struct file_operations *mmsoundcore_oss_driver_fops
	[MMSOUNDCORE_MAX_OSS_DEVICE_TYPES][MMSOUNDCORE_MAX_OSS_UNITS];

static const char *mmsoundcore_device_names[MMSOUNDCORE_MAX_OSS_DEVICE_TYPES] =
	{ "mixer", "sequencer", "midi", "dsp" };

static int mmsoundcore_oss_major_number = -1;
static struct class *mmsoundcore_oss_device_class = NULL;
struct file_operations mmsoundcore_oss_fops;

static int mmsoundcore_register_oss_chrdev(void)
{
	if (mmsoundcore_oss_major_number >= 0)
		return mmsoundcore_oss_major_number;

	memset(mmsoundcore_oss_driver_fops, 0, sizeof(mmsoundcore_oss_driver_fops));

	mmsoundcore_oss_major_number = register_chrdev(0, "mmsoundcore", &mmsoundcore_oss_fops);

	pr_info("MMSOUNDCORE stand-alone OSS emulation major number %d", mmsoundcore_oss_major_number);

	if ((mmsoundcore_oss_device_class = class_create(THIS_MODULE, "mmsoundcore")) == NULL)
	{
		pr_info("Failure creating device class");
		return -1;
	}

	return mmsoundcore_oss_major_number;
}

static void mmsoundcore_unregister_oss_chrdev(void)
{
	int unit;

	if (mmsoundcore_oss_major_number < 0)
		return;

	pr_info("MMSOUNDCORE stand-alone OSS deregister major number %d", mmsoundcore_oss_major_number);

	if (mmsoundcore_oss_device_class)
	{
		for (unit = 0; unit < MMSOUNDCORE_MAX_OSS_UNITS; ++unit)
		{
			device_destroy(mmsoundcore_oss_device_class, MKDEV(mmsoundcore_oss_major_number, unit * 16));
			device_destroy(mmsoundcore_oss_device_class, MKDEV(mmsoundcore_oss_major_number, (unit * 16) + 2));
			device_destroy(mmsoundcore_oss_device_class, MKDEV(mmsoundcore_oss_major_number, (unit * 16) + 3));
		}

		class_destroy(mmsoundcore_oss_device_class);
	}

	unregister_chrdev(mmsoundcore_oss_major_number, "mmsoundcore");

	mmsoundcore_oss_major_number = -1;
}

static int register_sound_unit(const struct file_operations *driver_file_operations, int device_type, int minor_number)
{
	int scan = 0;
	int unit = -1;
	char name[8] = "dsp";

	if ((device_type < 0) || (device_type >= MMSOUNDCORE_MAX_OSS_DEVICE_TYPES))
		return -1;

	strcpy(name, mmsoundcore_device_names[device_type]);

	if (minor_number >= 0)
		unit = minor_number >> 4;

	if (unit >= MMSOUNDCORE_MAX_OSS_UNITS)
	{
		pr_info("MMSOUNDCORE: Requested sound unit %d beyond %d max available", 
			unit, MMSOUNDCORE_MAX_OSS_UNITS);
		return -1;
	}

	else if ((minor_number >= 0) && (mmsoundcore_oss_driver_fops[device_type][unit] != NULL))
	{
		pr_info("MMSOUNDCORE: Requested unit %d already registered", unit);
		return -1;
	}

	else for (scan = 0; scan < MMSOUNDCORE_MAX_OSS_UNITS; ++scan)
		if (mmsoundcore_oss_driver_fops[device_type][scan] == NULL)
		{
			unit = scan;
			scan = MMSOUNDCORE_MAX_OSS_UNITS;
		}

	if ((unit < 0) || (unit >= MMSOUNDCORE_MAX_OSS_UNITS))
	{
		pr_info("MMSOUNDCORE: No sound units available for registration");
		return -1;
	}

	if (unit > 0)
		sprintf(name + strlen(name), "%d", unit);

	minor_number = (unit << 4) + device_type;

	if (!device_create(mmsoundcore_oss_device_class, NULL,
		MKDEV(mmsoundcore_oss_major_number, minor_number), NULL, name))
	{
		pr_info("MMSOUNDCORE: Unable to create device maj %d, min %d", 
			mmsoundcore_oss_major_number, minor_number);
		return -1;
	}
		
	mmsoundcore_oss_driver_fops[device_type][unit] = driver_file_operations;

	return minor_number;
}

void unregister_sound_unit(int minor_number)
{
	int device_type = minor_number & 0xf;
	int unit = minor_number >> 4;

	if ((device_type < 0) || (device_type >= MMSOUNDCORE_MAX_OSS_DEVICE_TYPES))
		return;

	if ((unit < 0) || (unit >= MMSOUNDCORE_MAX_OSS_UNITS))
		return;

	device_destroy(mmsoundcore_oss_device_class, MKDEV(mmsoundcore_oss_major_number, minor_number));

	mmsoundcore_oss_driver_fops[device_type][unit] = NULL;
}

int register_sound_mixer(const struct file_operations *driver_file_operations, int minor_number)
{
	return register_sound_unit(driver_file_operations, 0, minor_number);
}

int register_sound_midi(const struct file_operations *driver_file_operations, int minor_number)
{
	return register_sound_unit(driver_file_operations, 2, minor_number);
}

int register_sound_dsp(const struct file_operations *driver_file_operations, int minor_number)
{
	return register_sound_unit(driver_file_operations, 3, minor_number);
}

void unregister_sound_mixer(int minor_number)
{
	unregister_sound_unit(minor_number);
}

void unregister_sound_midi(int minor_number)
{
	unregister_sound_unit(minor_number);
}

void unregister_sound_dsp(int minor_number)
{
	unregister_sound_unit(minor_number);
}


EXPORT_SYMBOL(register_sound_dsp);
EXPORT_SYMBOL(register_sound_midi);
EXPORT_SYMBOL(register_sound_mixer);
EXPORT_SYMBOL(unregister_sound_dsp);
EXPORT_SYMBOL(unregister_sound_midi);
EXPORT_SYMBOL(unregister_sound_mixer);

static int mmsoundcore_oss_open(struct inode *inode, struct file *file)
{
	int unit = (MINOR(inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(inode->i_rdev) & 0xf;

	if ((unit < 0) || (unit >= MMSOUNDCORE_MAX_OSS_UNITS) ||
	    (device_type < 0) || (device_type >= MMSOUNDCORE_MAX_OSS_DEVICE_TYPES))
		return -ENODEV;

	if (mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->open)
		return mmsoundcore_oss_driver_fops[device_type][unit]->open(inode, file);

	return -ENODEV;
}

static int mmsoundcore_oss_release(struct inode *inode, struct file *file)
{
	int unit = (MINOR(inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->release)
		return mmsoundcore_oss_driver_fops[device_type][unit]->release(inode, file);

	return -ENODEV;
}

static ssize_t mmsoundcore_oss_write (struct file *file, const char *user_data, size_t length, loff_t *offset)
{
	int unit = (MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->write)
		return mmsoundcore_oss_driver_fops[device_type][unit]->write
			(file, user_data, length, offset);

	return -ENODEV;
}

static ssize_t mmsoundcore_oss_read (struct file *file, char *data, size_t length, loff_t *offset)
{
	int unit = (MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->read)
		return mmsoundcore_oss_driver_fops[device_type][unit]->read
			(file, data, length, offset);

	return -ENODEV;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35)
static int mmsoundcore_oss_ioctl (struct inode *inode, struct file *file, unsigned int command, unsigned long data)
{
	int unit = (MINOR(inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->ioctl)
		return mmsoundcore_oss_driver_fops[device_type][unit]->ioctl
			(inode, file, command, data);

	return -ENODEV;
}
#else
static long mmsoundcore_oss_ioctl (struct file *file, unsigned int command, unsigned long data)
{
	int unit = (MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->unlocked_ioctl)
		return mmsoundcore_oss_driver_fops[device_type][unit]->unlocked_ioctl
			(file, command, data);

	return -ENODEV;
}
#endif

static unsigned int mmsoundcore_oss_poll(struct file *file, struct poll_table_struct *poll_table)
{
	int unit = (MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf0) >> 4;
	int device_type = MINOR(file->f_path.dentry->d_inode->i_rdev) & 0xf;

	if ((unit >= 0) && 
	    (unit < MMSOUNDCORE_MAX_OSS_UNITS) &&
	    (device_type >= 0) &&
	    (device_type < MMSOUNDCORE_MAX_OSS_DEVICE_TYPES) &&
	    mmsoundcore_oss_driver_fops[device_type][unit] && 
	    mmsoundcore_oss_driver_fops[device_type][unit]->poll)
		return mmsoundcore_oss_driver_fops[device_type][unit]->poll
			(file, poll_table);

	return -ENODEV;
}

struct file_operations mmsoundcore_oss_fops =
{
	.read = mmsoundcore_oss_read, /* ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); */
	.write = mmsoundcore_oss_write, /* ssize_t (*write) (struct file *, char __user *, size_t, loff_t *); */

/* http://unix.stackexchange.com/questions/4711/what-is-the-difference-between-ioctl-unlocked-ioctl-and-compat-ioctl */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35)
	.ioctl = mmsoundcore_oss_ioctl, /* int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); */
#else
	.unlocked_ioctl = mmsoundcore_oss_ioctl, /* long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); */
#endif
	.open = mmsoundcore_oss_open, /* int (*open) (struct inode *, struct file *); */
	.release = mmsoundcore_oss_release, /* int (*release) (struct inode *, struct file *); */
	.poll = mmsoundcore_oss_poll, /* unsigned int (*poll) (struct file *, struct poll_table_struct *); */
};

/****************** Modprobe/Hotplugging Identification Info ******************/

//MODULE_DEVICE_TABLE (usb, mmsoundcore_ids);

MODULE_AUTHOR(DRIVER_AUTHOR);

MODULE_DESCRIPTION(DRIVER_DESC);

//#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
//#endif

/************************* Module Entry and Exit *****************************/

static int __init mmsoundcore_init(void)
{
	int status = 0;

	pr_info(DRIVER_DESC " " DRIVER_AUTHOR " " DRIVER_VERSION);

	mmsoundcore_register_oss_chrdev();

	return status;
}

static void __exit mmsoundcore_cleanup(void)
{
	mmsoundcore_unregister_oss_chrdev();

	pr_info("MMSOUNDCORE module deregistered and unloaded");
}

module_init(mmsoundcore_init);

module_exit(mmsoundcore_cleanup);



