/*
 *  OSS emulation layer for the mixer interface
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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.
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../include/driver.h"
#include "../include/minors.h"
#include "../include/mixer.h"
#include "../include/info.h"

extern snd_kmixer_t *snd_mixers[];

static int snd_mixer_oss_caps(snd_kmixer_t *mixer);

static int snd_mixer_oss_open(unsigned short minor, int cardnum, int device,
                              struct file *file)
{
	snd_kmixer_t *mixer;

	if (!(mixer = snd_mixers[(cardnum * SND_MINOR_MIXERS) + device]))
		return -ENODEV;
	file->private_data = mixer;
	MOD_INC_USE_COUNT;
	mixer->card->use_inc(mixer->card);
	return 0;
}

static int snd_mixer_oss_release(unsigned short minor, int cardnum, int device,
                                 struct file *file)
{
	snd_kmixer_t *mixer;

	if (file->private_data) {
		mixer = (snd_kmixer_t *) file->private_data;
		mixer->card->use_dec(mixer->card);
		MOD_DEC_USE_COUNT;
	}
	return 0;
}

static int snd_mixer_oss_info(snd_kmixer_t *mixer, struct snd_oss_mixer_info *_info)
{
	struct snd_oss_mixer_info info;
	
	memset(&info, 0, sizeof(info));
	strncpy(info.id, mixer->id, sizeof(info.id) - 1);
	strncpy(info.name, mixer->name, sizeof(info.name) - 1);
	info.modify_counter = mixer->oss_change_count;
	if (copy_to_user(_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_oss_info_obsolete(snd_kmixer_t *mixer, struct snd_oss_mixer_info_obsolete *_info)
{
	struct snd_oss_mixer_info_obsolete info;
	
	memset(&info, 0, sizeof(info));
	strncpy(info.id, mixer->id, sizeof(info.id) - 1);
	strncpy(info.name, mixer->name, sizeof(info.name) - 1);
	if (copy_to_user(_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static void snd_mixer_oss_mfile(snd_kmixer_file_t *mfile, snd_kmixer_t *mixer)
{
	memset(mfile, 0, sizeof(*mfile));
	mfile->mixer = mixer;
	init_waitqueue_head(&mfile->change_sleep);
}

static int snd_mixer_oss_set_recsrc(snd_kmixer_t *mixer, int recsrc)
{
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;
	snd_kmixer_file_t mfile;
	int caps;

	caps = snd_mixer_oss_caps(mixer);
	snd_mixer_oss_mfile(&mfile, mixer);
	snd_mixer_lock(mixer, 0);
	if (caps & SND_MIXER_OSS_CAP_EXCL_INPUT) {
		if (recsrc & ~mixer->oss_recsrc)
			recsrc &= ~mixer->oss_recsrc;
	}
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->control(group->private_data, NULL, 0, &g) < 0)
			continue;
		if (!(g.caps & SND_MIXER_GRPCAP_CAPTURE))
			continue;
		if (recsrc & (1 << group->oss_dev)) {
			g.capture = g.channels;
		} else {
			g.capture = 0;
		}
		if (group->control(group->private_data, &mfile, 1, &g) > 0)
			snd_mixer_group_value_change(NULL, group, 0);
	}
	snd_mixer_lock(mixer, 1);
	return 0;
}

static int snd_mixer_oss_devmask(snd_kmixer_t *mixer)
{
	int result = 0;
	snd_kmixer_group_t *group;

	snd_mixer_lock(mixer, 0);
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		result |= 1 << group->oss_dev;
	}
	snd_mixer_lock(mixer, 1);
	return result;
}

static int snd_mixer_oss_stereodevs(snd_kmixer_t *mixer)
{
	int result = 0;
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;

	snd_mixer_lock(mixer, 0);
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->control(group->private_data, NULL, 0, &g) < 0)
			continue;
		if (((g.channels & SND_MIXER_CHN_MASK_STEREO) == SND_MIXER_CHN_MASK_STEREO) &&
		    !(g.caps & SND_MIXER_GRPCAP_JOINTLY_VOLUME))
			result |= 1 << group->oss_dev;
	}
	snd_mixer_lock(mixer, 1);
	return result;
}

static int snd_mixer_oss_recmask(snd_kmixer_t *mixer)
{
	int result = 0;
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;

	snd_mixer_lock(mixer, 0);
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->control(group->private_data, NULL, 0, &g) < 0)
			continue;
		if (g.caps & SND_MIXER_GRPCAP_CAPTURE)
			result |= 1 << group->oss_dev;
	}
	snd_mixer_lock(mixer, 1);
	return result;
}

static int snd_mixer_oss_caps(snd_kmixer_t *mixer)
{
	int result = 0;
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;

	snd_mixer_lock(mixer, 0);
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->control(group->private_data, NULL, 0, &g) < 0)
			continue;
		if (g.caps & SND_MIXER_GRPCAP_EXCL_CAPTURE)
			result |= SND_MIXER_OSS_CAP_EXCL_INPUT;
	}
	snd_mixer_lock(mixer, 1);
	return result;
}

static int snd_mixer_oss_recsrc(snd_kmixer_t *mixer)
{
	int result = 0;
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;

	snd_mixer_lock(mixer, 0);
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->control(group->private_data, NULL, 0, &g) < 0)
			continue;
		if (g.caps & SND_MIXER_GRPCAP_CAPTURE) {
			if ((g.capture & SND_MIXER_CHN_MASK_STEREO) == SND_MIXER_CHN_MASK_STEREO)
				result |= 1 << group->oss_dev;
		}
	}
	mixer->oss_recsrc = result;
	snd_mixer_lock(mixer, 1);
	return result;
}

static int snd_mixer_oss_conv(int val, int omin, int omax, int nmin, int nmax)
{
	int orange = omax - omin, nrange = nmax - nmin;
	
	if (orange == 0)
		return 0;
	return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin;
}

static int snd_mixer_oss_conv1(int val, int min, int max)
{
	return snd_mixer_oss_conv(val, min, max, 0, 100);
}

static int snd_mixer_oss_conv2(int val, int min, int max)
{
	return snd_mixer_oss_conv(val, 0, 100, min, max);
}

static snd_kmixer_group_t *snd_mixer_oss_group(snd_kmixer_t *mixer, int channel)
{
	snd_kmixer_group_t *group;

	if (channel < 0 || channel >= SND_MIXER_OSS_UNKNOWN)
		return NULL;
	for (group = mixer->groups; group; group = group->next) {
		if (group->oss_dev < 0 || group->oss_dev >= SND_MIXER_OSS_UNKNOWN)
			continue;
		if (group->control == NULL)
			continue;
		if (group->oss_dev == channel)
			return group;
	}
	return NULL;	
}

static int snd_mixer_oss_get_volume(snd_kmixer_t *mixer, int channel)
{
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;
	int left, left1, right, right1;

	snd_mixer_lock(mixer, 0);
	if ((group = snd_mixer_oss_group(mixer, channel)) == NULL) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if (group->control == NULL) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if (group->control(group->private_data, NULL, 0, &g) < 0) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if ((g.channels & SND_MIXER_CHN_MASK_STEREO) == 0) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	left = right = 0;
	if (g.caps & SND_MIXER_GRPCAP_VOLUME) {
		if (g.channels == SND_MIXER_CHN_MASK_MONO)
			g.volume.names.front_right = g.volume.names.front_left;
		left = snd_mixer_oss_conv1(g.volume.names.front_left, g.min, g.max);
		left1 = snd_mixer_oss_conv2(mixer->oss_volume_levels[channel][0], g.min, g.max);
		if (left == left1 || g.volume.names.front_left == left1)
			left = mixer->oss_volume_levels[channel][0];
		right = snd_mixer_oss_conv1(g.volume.names.front_right, g.min, g.max);
		right1 = snd_mixer_oss_conv2(mixer->oss_volume_levels[channel][1], g.min, g.max);
		if (right == right1 || g.volume.names.front_right == right1)
			right = mixer->oss_volume_levels[channel][1];
	}
	if (g.caps & SND_MIXER_GRPCAP_MUTE) {
		if (g.channels == SND_MIXER_CHN_MASK_MONO) {
			g.mute &= ~SND_MIXER_CHN_MASK_FRONT_RIGHT;
			if (g.mute & SND_MIXER_CHN_MASK_FRONT_LEFT)
				g.mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (g.mute & SND_MIXER_CHN_MASK_FRONT_LEFT)
			left = 0;
		if (g.mute & SND_MIXER_CHN_MASK_FRONT_RIGHT)
			right = 0;
	}
	mixer->oss_volume_levels[channel][0] = left;
	mixer->oss_volume_levels[channel][1] = right;
	snd_mixer_lock(mixer, 1);
 	return (left & 0xff) | ((right & 0xff) << 8);
}

static int snd_mixer_oss_set_volume(snd_kmixer_t *mixer, int channel, int volume)
{
	snd_kmixer_group_t *group;
	snd_mixer_group_t g;
	int left, right;
	snd_kmixer_file_t mfile;

	snd_mixer_oss_mfile(&mfile, mixer);
	snd_mixer_lock(mixer, 0);
	if ((group = snd_mixer_oss_group(mixer, channel)) == NULL) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if (group->control == NULL) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if (group->control(group->private_data, NULL, 0, &g) < 0) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	if ((g.channels & SND_MIXER_CHN_MASK_STEREO) == 0) {
		snd_mixer_lock(mixer, 1);
		return -ENXIO;
	}
	left = volume & 0xff;
	right = (volume >> 8) & 0xff;
	if (left > 100)
		left = 100;
	if (right > 100)
		right = 100;
	if (g.channels == SND_MIXER_CHN_MASK_MONO ||
	    (g.caps & SND_MIXER_GRPCAP_JOINTLY_VOLUME))
		right = left;
	if (g.caps & SND_MIXER_GRPCAP_VOLUME) {
		g.volume.names.front_left = snd_mixer_oss_conv2(left, g.min, g.max);
		g.volume.names.front_right = snd_mixer_oss_conv2(right, g.min, g.max);
	}
	if (g.caps & SND_MIXER_GRPCAP_MUTE) {
		if (!(g.caps & SND_MIXER_GRPCAP_JOINTLY_MUTE)) {
			g.mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (left > 0)
				g.mute &= ~SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (g.channels & SND_MIXER_CHN_MASK_FRONT_RIGHT) {
				g.mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
				if (right > 0)
					g.mute &= ~SND_MIXER_CHN_MASK_FRONT_RIGHT;
			}
		} else {
			g.mute |= SND_MIXER_CHN_MASK_STEREO;
			if (left > 0 || right > 0)
				g.mute &= ~SND_MIXER_CHN_MASK_STEREO;
		}
	}
	mixer->oss_volume_levels[channel][0] = left;
	mixer->oss_volume_levels[channel][1] = right;
	if (group->control(group->private_data, &mfile, 1, &g) > 0)
		snd_mixer_group_value_change(NULL, group, 0);
	snd_mixer_lock(mixer, 1);
 	return (left & 0xff) | ((right & 0xff) << 8);
}

int snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	snd_kmixer_t *mixer;
	int tmp;

	mixer = (snd_kmixer_t *) file->private_data;
	if (!mixer)
		return -EIO;
	if (((cmd >> 8) & 0xff) == 'M') {
		switch (cmd) {
		case SND_MIXER_OSS_INFO:
			return snd_mixer_oss_info(mixer, (struct snd_oss_mixer_info *)arg);
		case SND_MIXER_OSS_OLD_INFO:
 			return snd_mixer_oss_info_obsolete(mixer, (struct snd_oss_mixer_info_obsolete *)arg);
		case SND_MIXER_OSS_SET_RECSRC:
			tmp = snd_mixer_oss_set_recsrc(mixer, snd_ioctl_in((long *) arg));
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		case SND_OSS_GETVERSION:
			return snd_ioctl_out((long *) arg, SND_OSS_VERSION);
		case SND_MIXER_OSS_DEVMASK:
			tmp = snd_mixer_oss_devmask(mixer);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		case SND_MIXER_OSS_STEREODEVS:
			tmp = snd_mixer_oss_stereodevs(mixer);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		case SND_MIXER_OSS_RECMASK:
			tmp = snd_mixer_oss_recmask(mixer);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		case SND_MIXER_OSS_CAPS:
			tmp = snd_mixer_oss_caps(mixer);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		case SND_MIXER_OSS_RECSRC:
			tmp = snd_mixer_oss_recsrc(mixer);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		}
	}
	if (cmd & IOC_IN) {
		tmp = snd_mixer_oss_set_volume(mixer, cmd & 0xff, snd_ioctl_in((long *) arg));
		return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
	} else if (cmd & IOC_OUT) {
		tmp = snd_mixer_oss_get_volume(mixer, cmd & 0xff);
		return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
	}
	return -ENXIO;
}

/*
 *  REGISTRATION PART
 */

static snd_minor_t snd_mixer_oss_reg =
{
	"mixer",		/* comment */
	NULL,			/* reserved */

	NULL,			/* unregister */

	NULL,			/* lseek */
	NULL,			/* read */
	NULL,			/* write */
	snd_mixer_oss_open,	/* open */
	snd_mixer_oss_release,	/* release */
	NULL,			/* poll */
	snd_mixer_oss_ioctl,	/* ioctl */
	NULL			/* mmap */
};

/*
 *  INIT PART
 */

static int snd_mixer_oss_register_minor(unsigned short native_minor,
					snd_kmixer_t * mixer)
{
	char name[32];
	
	mixer->ossreg = 0;
	if (mixer->device < 1) {
		sprintf(name, "mixer%i%i", mixer->card->number, mixer->device);
		if (snd_register_oss_device(SND_OSS_DEVICE_TYPE_MIXER,
				mixer->card, mixer->device, &snd_mixer_oss_reg,
				name) < 0) {
			snd_printk("unable to register OSS mixer device %i:%i\n", mixer->card->number, mixer->device);
		} else {
			snd_oss_info_register(SND_OSS_INFO_DEV_MIXERS,
					      mixer->card->number,
					      mixer->name);
			mixer->ossreg = 1;
		}
	}
	return 0;
}

static int snd_mixer_oss_unregister_minor(unsigned short native_minor,
					  snd_kmixer_t * mixer)
{
	if (mixer->ossreg) {
		snd_unregister_oss_device(SND_OSS_DEVICE_TYPE_MIXER,
				mixer->card, mixer->device);
		snd_oss_info_unregister(SND_OSS_INFO_DEV_MIXERS, mixer->device);
		mixer->ossreg = 0;
	}
	return 0;
}

static struct snd_stru_mixer_notify snd_mixer_oss_notify =
{
	snd_mixer_oss_register_minor,
	snd_mixer_oss_unregister_minor,
	NULL
};

int init_module(void)
{
	int err;

	if ((err = snd_mixer_notify(&snd_mixer_oss_notify, 0)) < 0)
		return err;
	return 0;
}

void cleanup_module(void)
{
	snd_mixer_notify(&snd_mixer_oss_notify, 1);
}
