/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>,
 *                   Takashi Iwai <tiwai@suse.de>
 *                   Creative Labs, Inc.
 *  Routines for control of EMU10K1 chips / mixer routines
 *
 *  BUGS:
 *    --
 *
 *  TODO:
 *    --
 *
 *   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__
#include "../../include/driver.h"
#include "../../include/emu10k1.h"

#define VOL_RANGE_MAX	31

static void update_rear(emu10k1_t *emu, ac97_t *ac97)
{
	int reg = FXGPREGBASE + 0x10;
	unsigned int val;

	if (emu->rear_active & SND_MIXER_CHN_MASK_FRONT_LEFT) {
		val = emu->vol_rear[0] * (0x7fffffff / VOL_RANGE_MAX);
	} else {
		val = 0;
	}
	snd_emu10k1_ptr_write(emu, reg, 0, val);
	if (emu->rear_active & SND_MIXER_CHN_MASK_FRONT_RIGHT) {
		val = emu->vol_rear[1] * (0x7fffffff / VOL_RANGE_MAX);
	} else {
		val = 0;
	}
	snd_emu10k1_ptr_write(emu, reg + 1, 0, val);
}
	
static int snd_emu10k1_volume_rear(snd_kmixer_element_t *element, int w_flag, int *volume)
{
	emu10k1_t *emu;
	ac97_t *ac97;
	int change = 0;
	unsigned long flags;

	emu = snd_magic_cast(emu10k1_t, element->private_data, -ENXIO);
	ac97 = emu->ac97;
	snd_debug_check(ac97 == NULL, -ENXIO);
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	if (w_flag) {
		change = (volume[0] != emu->vol_rear[0] ||
			  volume[1] != emu->vol_rear[1]);
		emu->vol_rear[0] = volume[0];
		emu->vol_rear[1] = volume[1];
		update_rear(emu, ac97);
	} else {
		volume[0] = emu->vol_rear[0];
		volume[1] = emu->vol_rear[1];
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_emu10k1_mute_rear(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	emu10k1_t *emu = snd_magic_cast(emu10k1_t, element->private_data, -ENXIO);
	ac97_t *ac97 = emu->ac97;
	int change = 0;
	unsigned int old = 0;
	unsigned long flags;

	snd_debug_check(ac97 == NULL, -ENXIO);
	spin_lock_irqsave(&ac97->reg_lock, flags);
	if (emu->rear_active & SND_MIXER_CHN_MASK_FRONT_LEFT)
		snd_mixer_set_bit(&old, 0, 1);
	if (emu->rear_active & SND_MIXER_CHN_MASK_FRONT_RIGHT)
		snd_mixer_set_bit(&old, 1, 1);
	if (w_flag) {
		change = old != *bitmap;
		emu->rear_active = 0;
		if (snd_mixer_get_bit(bitmap, 0))
			emu->rear_active |= SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (snd_mixer_get_bit(bitmap, 1))
			emu->rear_active |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		update_rear(emu, ac97);
	} else {
		*bitmap = old;
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_emu10k1_mixer_group_fx(snd_kmixer_group_t *group,
				      snd_kmixer_file_t *file, int w_flag,
				      snd_mixer_group_t *ugroup)
{
	emu10k1_t *emu;
	int voices[2];
	unsigned int sw;
	int change = 0;

	emu = snd_magic_cast(emu10k1_t, group->private_data, -EINVAL);

	if (!w_flag) {
		ugroup->channels = SND_MIXER_CHN_MASK_FRONT_LEFT | SND_MIXER_CHN_MASK_FRONT_RIGHT;
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_MUTE;
		ugroup->min = 0;
		ugroup->max = VOL_RANGE_MAX;
		ugroup->mute = 0;
		snd_emu10k1_mute_rear(emu->me_sw_rear, 0, &sw);
		if (!snd_mixer_get_bit(&sw, 0))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (!snd_mixer_get_bit(&sw, 1))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		snd_emu10k1_volume_rear(emu->me_vol_rear, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
	} else {
		sw = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
			snd_mixer_set_bit(&sw, 0, 1);
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
			snd_mixer_set_bit(&sw, 1, 1);
		if (snd_emu10k1_mute_rear(emu->me_sw_rear, 1, &sw)) {
			snd_mixer_element_value_change_all_file(file, emu->me_sw_rear, 0);
			change = 1;
		}
		voices[0] = ugroup->volume.names.front_left;
		voices[1] = ugroup->volume.names.front_right;
		if (snd_emu10k1_volume_rear(emu->me_vol_rear, 1, voices)) {
			snd_mixer_element_value_change_all_file(file, emu->me_vol_rear, 0);
			change = 1;
		}
	}
	return change;
}

static void snd_emu10k1_mixer_free_ac97(void *private_data)
{
	emu10k1_t *emu = snd_magic_cast(emu10k1_t, private_data, );
	emu->mixer = NULL;
}

int snd_emu10k1_mixer(emu10k1_t *emu, int device, snd_pcm_t *pcm, snd_kmixer_t **rmixer)
{
	ac97_t ac97;
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range table13_range[2] = {
		{0, VOL_RANGE_MAX, -3450, 1200},
		{0, VOL_RANGE_MAX, -3450, 1200}
	};
	int err, dev;

	*rmixer = NULL;
	memset(&ac97, 0, sizeof(ac97));
	ac97.write = snd_emu10k1_ac97_write;
	ac97.read = snd_emu10k1_ac97_read;
	ac97.private_data = emu;
	ac97.private_free = snd_emu10k1_mixer_free_ac97;
	dev = 0;
	if ((err = snd_ac97_mixer(emu->card, device, &ac97, 1, &dev /* &pcm->device */, &mixer)) < 0)
		return err;
	emu->ac97 = snd_magic_cast(ac97_t, mixer->private_data, -ENXIO);

	emu->rear_active = 0;
	emu->vol_rear[0] = 0;
	emu->vol_rear[1] = 0;

	/* build rear speaker control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "Rear", 0, SND_MIXER_OSS_DIGITAL1, snd_emu10k1_mixer_group_fx, emu)) == NULL)
		goto __error;
	if ((emu->me_vol_rear = snd_mixer_lib_volume1(mixer, "Rear", 0, 2,
						      table13_range, snd_emu10k1_volume_rear, emu)) == NULL)
		goto __error;
	if ((emu->me_sw_rear = snd_mixer_lib_sw1(mixer, "Rear Switch", 0, 2, snd_emu10k1_mute_rear, emu)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, emu->me_vol_rear) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, emu->me_sw_rear) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, emu->me_vol_rear, emu->me_sw_rear))
		goto __error;
	if (snd_mixer_element_route_add(mixer, emu->me_sw_rear, emu->ac97->me_accu))
		goto __error;

	*rmixer = emu->mixer = mixer;
	return 0;

__error:
	snd_device_free(emu->card, mixer);
	return -ENOMEM;
}
