/*
 *  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 "driver.h"
#include "gus.h"

/*

 */

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

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

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

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

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

static int snd_gf1_ics_volume_level(int w_flag, int *voices, snd_gus_card_t * gus,
			 	    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;
	snd_spin_lock(gus, reg, &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));
	}
	snd_spin_unlock(gus, reg, &flags);
	return change;
}

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

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

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

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

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

snd_kmixer_t *snd_gf1_new_mixer(snd_gus_card_t * gus)
{
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *accu;
	snd_kmixer_element_t *element1, *element2, *element3;
	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 ((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(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw2(mixer, "Master Switch", 0, (snd_mixer_sw2_control_t *)snd_gf1_mixer_line_out_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element1) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((element3 = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, ics_range, (snd_mixer_volume1_control_t *)snd_gf1_ics_master, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element3) < 0)
			goto __error;
	} else {
		element3 = element2;
	}
	if (snd_mixer_element_route_add(mixer, accu, element3) < 0)
		goto __error;
	/* --- */
	if (gus->card->type == SND_CARD_TYPE_GUS_ACE)
		goto __end;
 	/* build line-in */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_LINE, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw2(mixer, "Line Input Switch", 0, (snd_mixer_sw2_control_t *)snd_gf1_mixer_line_in_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((element3 = snd_mixer_lib_volume1(mixer, "Line Input Volume", 0, 2, ics_range, (snd_mixer_volume1_control_t *)snd_gf1_ics_line, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element3) < 0)
			goto __error;
	} else {
		element3 = element2;
	}
	if (snd_mixer_element_route_add(mixer, element3, accu) < 0)
		goto __error;
 	/* build MIC */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw2(mixer, "MIC Input Switch", 0, (snd_mixer_sw2_control_t *)snd_gf1_mixer_mic_switch, gus)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (gus->ics_flag) {
		if ((element3 = snd_mixer_lib_volume1(mixer, "MIC Input Volume", 0, 2, ics_range, (snd_mixer_volume1_control_t *)snd_gf1_ics_mic, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element3) < 0)
			goto __error;
	} else {
		element3 = element2;
	}
	if (snd_mixer_element_route_add(mixer, element3, accu) < 0)
		goto __error;
	if (gus->ics_flag) {
	 	/* build GF1 */
		if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_SYNTHESIZER, 0)) == NULL)
			goto __error;
		if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
			goto __error;
		if ((element2 = snd_mixer_lib_volume1(mixer, "Synth Input Volume", 0, 2, ics_range, (snd_mixer_volume1_control_t *)snd_gf1_ics_gf1, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element2) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
			goto __error;
	 	/* build CD */
		if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
			goto __error;
		if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
			goto __error;
		if ((element2 = snd_mixer_lib_volume1(mixer, "CD Input Volume", 0, 2, ics_range, (snd_mixer_volume1_control_t *)snd_gf1_ics_cd, gus)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element2) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
			goto __error;
	}
      __end:
	mixer->private_data = gus;
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}
