/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of ICS 2101 chip and "mixer" in GF1 chip
 *
 *
 *   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/gus.h"

/*

 */

static int snd_gf1_mixer_update(snd_gus_card_t *gus,
				int w_flag, int *value,
				int bit, int invert)
{
	unsigned long flags;
	int old, change = 0;

	spin_lock_irqsave(&gus->reg_lock, flags);
	old = ((gus->mix_cntrl_reg >> bit) & 1) ^ invert;
	if (!w_flag) {
		*value = old;
	} else {
		change = *value != old;
		old = *value ^ invert;
		gus->mix_cntrl_reg &= ~(1 << bit);
		if (old)
			gus->mix_cntrl_reg |= 1 << bit;
		outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
		outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
	}
	spin_unlock_irqrestore(&gus->reg_lock, flags);
	return change;
}

static int snd_gf1_mixer_mic_switch(void *private_data, int w_flag, int *value)
{
	return snd_gf1_mixer_update((snd_gus_card_t *)private_data,
				    w_flag, value, 2, 0);
}

static int snd_gf1_mixer_line_in_switch(void *private_data, int w_flag, int *value)
{
	return snd_gf1_mixer_update((snd_gus_card_t *)private_data,
				    w_flag, value, 0, 1);
}

static int snd_gf1_mixer_line_out_switch(void *private_data, int w_flag, int *value)
{
	return snd_gf1_mixer_update((snd_gus_card_t *)private_data,
				    w_flag, value, 1, 1);
}

/* Not static anymore due to being needed in gus_pcm.c */

int snd_gf1_ics_volume_level(snd_gus_card_t *gus, int w_flag,
				    int *voices, int addr)
{
	unsigned long flags;
	unsigned int tmp, idx, change = 0;
	unsigned left, right;

#if 0
	printk("mixer setup - addr = %i, left = %i, right = %i\n", addr, left, right);
#endif
	idx = addr;
	addr <<= 3;
	spin_lock_irqsave(&gus->reg_lock, flags);
	if (!w_flag) {
		voices[0] = gus->gf1.ics_regs[idx][0];
		voices[1] = gus->gf1.ics_regs[idx][1];
	} else {
		change = voices[0] != gus->gf1.ics_regs[idx][0] ||
			 voices[1] != gus->gf1.ics_regs[idx][1];
		gus->gf1.ics_regs[idx][0] = left = voices[0];
		gus->gf1.ics_regs[idx][1] = right = voices[1];
		if (gus->ics_flag && gus->ics_flipped &&
		    (addr == SND_ICS_GF1_DEV || addr == SND_ICS_MASTER_DEV)) {
			tmp = left;
			left = right;
			right = tmp;
		}
		outb(addr | 0, GUSP(gus, MIXCNTRLPORT));
		outb(1, GUSP(gus, MIXDATAPORT));
		outb(addr | 2, GUSP(gus, MIXCNTRLPORT));
		outb((unsigned char) left, GUSP(gus, MIXDATAPORT));
		outb(addr | 1, GUSP(gus, MIXCNTRLPORT));
		outb(2, GUSP(gus, MIXDATAPORT));
		outb(addr | 3, GUSP(gus, MIXCNTRLPORT));
		outb((unsigned char) right, GUSP(gus, MIXDATAPORT));
	}
	spin_unlock_irqrestore(&gus->reg_lock, flags);
	return change;
}

static int snd_gf1_ics_master(void *private_data, int w_flag, int *voices)
{
	return snd_gf1_ics_volume_level((snd_gus_card_t *)private_data,
					w_flag, voices, SND_ICS_MASTER_DEV);
}

static int snd_gf1_ics_gf1(void *private_data, int w_flag, int *voices)
{
	return snd_gf1_ics_volume_level((snd_gus_card_t *)private_data,
					w_flag, voices, SND_ICS_GF1_DEV);
}

static int snd_gf1_ics_line(void *private_data, int w_flag, int *voices)
{
	return snd_gf1_ics_volume_level((snd_gus_card_t *)private_data,
					w_flag, voices, SND_ICS_LINE_DEV);
}

static int snd_gf1_ics_mic(void *private_data, int w_flag, int *voices)
{
	return snd_gf1_ics_volume_level((snd_gus_card_t *)private_data,
					w_flag, voices, SND_ICS_MIC_DEV);
}

static int snd_gf1_ics_cd(void *private_data, int w_flag, int *voices)
{
	return snd_gf1_ics_volume_level((snd_gus_card_t *)private_data,
					w_flag, voices, SND_ICS_CD_DEV);
}

static int snd_gf1_ics_group_ctrl1(snd_gus_card_t *gus,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup,
				   snd_mixer_volume1_control_t *volume1,
				   snd_kmixer_element_t *volume1_element,
				   int max,
				   snd_mixer_sw2_control_t *sw2,
				   snd_kmixer_element_t *sw2_element)
{
	int voices[2];
	int value;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1 && volume1_element) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(gus, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw2) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE | SND_MIXER_GRPCAP_JOINTLY_MUTE;
			sw2(gus, 0, &value);
			ugroup->mute = 0;
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
		}
	} else {
		if (volume1 && volume1_element) {
			voices[0] = ugroup->volume.names.front_left & max;
			voices[1] = ugroup->volume.names.front_right & max;
			if (volume1(gus, 1, voices) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw2) {
			value = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				value = 1;
			if (sw2(gus, 1, &value) > 0) {
				snd_mixer_element_value_change(file, sw2_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_gf1_ics_group_master(void *private_data,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;

	return snd_gf1_ics_group_ctrl1(gus, file, w_flag, ugroup,
				       snd_gf1_ics_master,
				       gus->me_vol_master,
				       127,
				       snd_gf1_mixer_line_out_switch,
				       gus->me_sw_master);
}

static int snd_gf1_ics_group_line(void *private_data,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;

	return snd_gf1_ics_group_ctrl1(gus, file, w_flag, ugroup,
				       snd_gf1_ics_line,
				       gus->me_vol_line,
				       127,
				       snd_gf1_mixer_line_in_switch,
				       gus->me_sw_line);
}

static int snd_gf1_ics_group_mic(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;

	return snd_gf1_ics_group_ctrl1(gus, file, w_flag, ugroup,
				       snd_gf1_ics_mic,
				       gus->me_vol_mic,
				       127,
				       snd_gf1_mixer_mic_switch,
				       gus->me_sw_mic);
}

static int snd_gf1_ics_group_gf1(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;

	return snd_gf1_ics_group_ctrl1(gus, file, w_flag, ugroup,
				       snd_gf1_ics_gf1,
				       gus->me_vol_mic,
				       127,
				       NULL,
				       NULL);
}

static int snd_gf1_ics_group_cd(void *private_data,
				snd_kmixer_file_t * file,
				int w_flag,
				snd_mixer_group_t * ugroup)
{
	snd_gus_card_t *gus = (snd_gus_card_t *)private_data;

	return snd_gf1_ics_group_ctrl1(gus, file, w_flag, ugroup,
				       snd_gf1_ics_cd,
				       gus->me_vol_cd,
				       127,
				       NULL,
				       NULL);
}

snd_kmixer_t *snd_gf1_new_mixer(snd_gus_card_t * gus)
{
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range ics_range[2] = {
		{0, 127, -9600, 0},
		{0, 127, -9600, 0}
	};

	if (!gus)
		return NULL;
	mixer = snd_mixer_new(gus->card, gus->ics_flag ? "ICS2101" : "GF1");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, mixer->id);
	if ((gus->me_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build line-out */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_gf1_ics_group_master, gus)) == NULL)
		goto __error;
	if ((gus->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((gus->me_sw_master = snd_mixer_lib_sw2(mixer, "Master Switch", 0, snd_gf1_mixer_line_out_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, gus->me_sw_master) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((gus->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, ics_range, snd_gf1_ics_master, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, gus->me_vol_master) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_accu, gus->me_vol_master) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_vol_master, gus->me_out_master) < 0)
			goto __error;
	} else {
		if (snd_mixer_element_route_add(mixer, gus->me_accu, gus->me_sw_master) < 0)
			goto __error;		
		if (snd_mixer_element_route_add(mixer, gus->me_sw_master, gus->me_out_master) < 0)
			goto __error;
	}
	/* --- */
	if (gus->card->type == SND_CARD_TYPE_GUS_ACE)
		goto __end;
 	/* build line-in */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_gf1_ics_group_line, gus)) == NULL)
		goto __error;
	if ((gus->me_in_line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((gus->me_sw_line = snd_mixer_lib_sw2(mixer, "Line Input Switch", 0, snd_gf1_mixer_line_in_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, gus->me_sw_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, gus->me_in_line, gus->me_sw_line) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((gus->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Input Volume", 0, 2, ics_range, snd_gf1_ics_line, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, gus->me_vol_line) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_sw_line, gus->me_vol_line) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_vol_line, gus->me_accu) < 0)
			goto __error;
	} else {
		if (snd_mixer_element_route_add(mixer, gus->me_sw_line, gus->me_accu) < 0)
			goto __error;
	}
 	/* build MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_gf1_ics_group_mic, gus)) == NULL)
		goto __error;
	if ((gus->me_in_mic = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((gus->me_sw_mic = snd_mixer_lib_sw2(mixer, "MIC Input Switch", 0, snd_gf1_mixer_mic_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, gus->me_sw_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, gus->me_in_mic, gus->me_sw_mic) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((gus->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Input Volume", 0, 2, ics_range, snd_gf1_ics_mic, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, gus->me_vol_mic) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_sw_mic, gus->me_vol_mic) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_vol_mic, gus->me_accu) < 0)
			goto __error;
	} else {
		if (snd_mixer_element_route_add(mixer, gus->me_sw_mic, gus->me_accu) < 0)
			goto __error;
	}
	if (gus->ics_flag) {
	 	/* build GF1 */
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_SYNTH, snd_gf1_ics_group_gf1, gus)) == NULL)
			goto __error;
		if ((gus->me_in_gf1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
			goto __error;
		if ((gus->me_vol_gf1 = snd_mixer_lib_volume1(mixer, "Synth Input Volume", 0, 2, ics_range, snd_gf1_ics_gf1, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, gus->me_vol_gf1) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_in_gf1, gus->me_vol_gf1) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_vol_gf1, gus->me_accu) < 0)
			goto __error;
	 	/* build CD */
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_gf1_ics_group_cd, gus)) == NULL)
			goto __error;
		if ((gus->me_in_cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
			goto __error;
		if ((gus->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Input Volume", 0, 2, ics_range, snd_gf1_ics_cd, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, gus->me_vol_cd) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_in_cd, gus->me_vol_cd) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, gus->me_vol_cd, gus->me_accu) < 0)
			goto __error;
	}
      __end:
	mixer->private_data = gus;
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}
