/*
 *  Routines for control of TEA6330T circuit over i2c bus
 *  Sound fader control circuit for car radios by Philips Semiconductors
 *  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/tea6330t.h"

#define TEA6330T_ADDR			0x80	/* fixed address */

#define TEA6330T_SADDR_VOLUME_LEFT	0x00	/* volume left */
#define TEA6330T_SADDR_VOLUME_RIGHT	0x01	/* volume right */
#define TEA6330T_SADDR_BASS		0x02	/* bass control */
#define TEA6330T_SADDR_TREBLE		0x03	/* treble control */
#define TEA6330T_SADDR_FADER		0x04	/* fader control */
#define   TEA6330T_MFN			0x20	/* mute control for selected channels */
#define   TEA6330T_FCH			0x10	/* select fader channels - front or rear */
#define TEA6330T_SADDR_AUDIO_SWITCH	0x05	/* audio switch */
#define   TEA6330T_GMU			0x80	/* mute control, general mute */
#define   TEA6330T_EQN			0x40	/* equalizer switchover (0=equalizer-on) */

int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer)
{
	equalizer = equalizer ? 0 : TEA6330T_EQN;
	/* mute & turn on/off equalizer */
	if (snd_i2c_write(bus, TEA6330T_ADDR, TEA6330T_SADDR_AUDIO_SWITCH, TEA6330T_GMU | equalizer, 1) < 0)
		return -ENODEV;
	/* fader off */
	if (snd_i2c_write(bus, TEA6330T_ADDR, TEA6330T_SADDR_FADER, 0x3f, 1) < 0)
		return -ENODEV;
	return 0;
}

#if 0
static void snd_tea6330t_set(tea6330t_t *tea,
			     unsigned char addr, unsigned char value)
{
#if 0
	printk("set - 0x%x/0x%x\n", addr, value);
#endif
	snd_i2c_write(tea->bus, TEA6330T_ADDR, addr, value, 1);
}
#endif

static int snd_tea6330t_master_volume(void *private_data, int w_flag, int *volume)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	unsigned long flags;
	int change = 0;
	
	spin_lock_irqsave(&tea->reg_lock, flags);
	if (!w_flag) {
		volume[0] = tea->mleft - 0x14;
		volume[1] = tea->mright - 0x14;
	} else {
		change = volume[0] != tea->mleft || volume[1] != tea->mright;
		tea->mleft = volume[0] + 0x14;
		tea->mright = volume[1] + 0x14;
		if (tea->regs[TEA6330T_SADDR_VOLUME_LEFT] != 0)
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_LEFT, tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = tea->mleft, 1);
		if (tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] != 0)
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_RIGHT, tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = tea->mright, 1);
	}
	spin_unlock_irqrestore(&tea->reg_lock, flags);
	return change;
}

static int snd_tea6330t_master_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	unsigned long flags;
	int change = 0, left, right;
	
	spin_lock_irqsave(&tea->reg_lock, flags);
	left = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] != 0 ? 1 : 0;
	right = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] != 0 ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = snd_mixer_get_bit(bitmap, 0) != left ||
		         snd_mixer_get_bit(bitmap, 1) != right;
		tea->regs[TEA6330T_SADDR_VOLUME_LEFT] =
			snd_mixer_get_bit(bitmap, 0) ? tea->mleft : 0;
		tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] =
			snd_mixer_get_bit(bitmap, 1) ? tea->mright : 0;
		snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_LEFT, tea->regs[TEA6330T_SADDR_VOLUME_LEFT], 1);
		snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_RIGHT, tea->regs[TEA6330T_SADDR_VOLUME_RIGHT], 1);
	}
	spin_unlock_irqrestore(&tea->reg_lock, flags);
	return change;
}

static int snd_tea6330t_group_master(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	int voices[2];
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_MUTE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		snd_tea6330t_master_volume(tea, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 43;
		snd_tea6330t_master_switch(tea, 0, &bitmap);
		ugroup->mute = 0;
		if (!snd_mixer_get_bit(&bitmap, 0))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (!snd_mixer_get_bit(&bitmap, 1))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
	} else {
		voices[0] = ugroup->volume.names.front_left % 44;
		voices[1] = ugroup->volume.names.front_right % 44;
		if (snd_tea6330t_master_volume(tea, 1, voices) > 0) {
			snd_mixer_element_value_change(file, tea->me_vol_master, 0);
			change = 1;
		}
		bitmap = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
			snd_mixer_set_bit(&bitmap, 0, 1);
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
			snd_mixer_set_bit(&bitmap, 1, 1);
		if (snd_tea6330t_master_switch(tea, 1, &bitmap) > 0) {
			snd_mixer_element_value_change(file, tea->me_sw_master, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_tea6330t_tone_control(void *private_data,
				     int w_flag,
				     struct snd_mixer_element_tone_control1 *tc1)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	unsigned long flags;
	int change = 0;

	spin_lock_irqsave(&tea->reg_lock, flags);
	if (w_flag) {
		if (tc1->tc & SND_MIXER_TC1_BASS) {
			change |= tea->bass != tc1->bass;
			tea->bass = tc1->bass;
			tea->regs[TEA6330T_SADDR_BASS] = tc1->bass + tea->equalizer ? 7 : 3;
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_BASS, tea->regs[TEA6330T_SADDR_BASS], 1);
		}
		if (tc1->tc & SND_MIXER_TC1_TREBLE) {
			change |= tea->treble != tc1->treble;
			tea->treble = tc1->treble;
			tea->regs[TEA6330T_SADDR_TREBLE] = tc1->treble + 3;
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_TREBLE, tea->regs[TEA6330T_SADDR_TREBLE], 1);
		}
	} else {
		tc1->tc = SND_MIXER_TC1_BASS;
		tc1->bass = tea->bass;
		if (!tea->equalizer) {
			tc1->tc |= SND_MIXER_TC1_TREBLE;
			tc1->treble = tea->treble;
		}
	}
	spin_unlock_irqrestore(&tea->reg_lock, flags);
	return change;
}

static int snd_tea6330t_group_bass(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_tea6330t_tone_control(tea, 0, &tc);
		ugroup->volume.names.front_left = tc.bass;
		ugroup->min = 0;
		ugroup->max = tea->max_bass;
	} else {
		tc.tc = SND_MIXER_TC1_BASS;
		tc.bass = ugroup->volume.names.front_left % (tea->max_bass+1);
		if (snd_tea6330t_tone_control(tea, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, tea->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_tea6330t_group_treble(void *private_data,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	tea6330t_t *tea = (tea6330t_t *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_tea6330t_tone_control(tea, 0, &tc);
		ugroup->volume.names.front_left = tc.treble;
		ugroup->min = 0;
		ugroup->max = tea->max_treble;
	} else {
		tc.tc = SND_MIXER_TC1_TREBLE;
		tc.treble = ugroup->volume.names.front_left % (tea->max_treble+1);
		if (snd_tea6330t_tone_control(tea, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, tea->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

int snd_tea6330t_update_mixer(snd_kmixer_t * mixer,
			      struct snd_i2c_bus *bus,
			      snd_kmixer_element_t *input,
			      snd_kmixer_element_t *output,
			      int equalizer, int fader)
{
	tea6330t_t *tea;
	snd_kmixer_group_t *group1, *group2;
	static struct snd_mixer_element_volume1_range master_range[2] = {
		{0, 43, -6600, 2000},
		{0, 43, -6600, 2000}
	};
	struct snd_mixer_element_tone_control1_info tinfo;

	tea = (tea6330t_t *)snd_kcalloc(sizeof(*tea), GFP_KERNEL);
	if (!tea)
		return -ENOMEM;
	tea->reg_lock = SPIN_LOCK_UNLOCKED;
	tea->bus = bus;
	tea->equalizer = equalizer;
	tea->fader = fader;

	/* turn fader off */
	tea->regs[TEA6330T_SADDR_FADER] = 0x3f;
	if (snd_i2c_write(bus, TEA6330T_ADDR, TEA6330T_SADDR_FADER, 0x3f, 1) < 0) {
		snd_kfree(tea);
		return -ENODEV;
	}
	equalizer = tea->regs[TEA6330T_SADDR_AUDIO_SWITCH] = equalizer ? 0 : TEA6330T_EQN;
	/* mute off & turn on/off equalizer */
	if (snd_i2c_write(bus, TEA6330T_ADDR, TEA6330T_SADDR_AUDIO_SWITCH, equalizer, 1) < 0) {
		snd_kfree(tea);
		return -ENODEV;
	}

	bus->private_data = tea;
	bus->private_free = snd_kfree;
	strcat(mixer->name, " + TEA6330T");

	if ((group1 = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_tea6330t_group_master, tea)) == NULL)
		goto __error;
	if ((tea->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, master_range, snd_tea6330t_master_volume, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group1, tea->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, input, tea->me_vol_master) < 0)
		goto __error;
	if ((tea->me_sw_master = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, snd_tea6330t_master_switch, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group1, tea->me_sw_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tea->me_vol_master, tea->me_sw_master) < 0)
		goto __error;
	memset(&tinfo, 0, sizeof(tinfo));
	tinfo.tc = SND_MIXER_TC1_BASS;
	if (!tea->equalizer) {
		tinfo.max_bass = 9;
		tinfo.min_bass_dB = -1200;
		tinfo.max_bass_dB = 1500;
		tinfo.tc |= SND_MIXER_TC1_TREBLE;
		tinfo.max_treble = 8;
		tinfo.min_treble_dB = -1200;
		tinfo.max_treble_dB = 1200; 
	} else {
		tinfo.max_bass = 5;
		tinfo.min_bass_dB = 0;
		tinfo.max_bass_dB = 1500;
	}
	tea->max_bass = tinfo.max_bass;
	tea->max_treble = tinfo.max_treble;
	if ((group1 = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_BASS, 0, SND_MIXER_OSS_BASS, snd_tea6330t_group_bass, tea)) == NULL)
		goto __error;
	group2 = NULL;
	if (tinfo.max_treble > 0)
		if ((group2 = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_TREBLE, 0, SND_MIXER_OSS_TREBLE, snd_tea6330t_group_treble, tea)) == NULL)
			goto __error;
	if ((tea->me_tone = snd_mixer_lib_tone_control1(mixer, SND_MIXER_ELEMENT_TONE_CONTROL, 0, &tinfo, snd_tea6330t_tone_control, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group1, tea->me_tone) < 0)
		goto __error;
	if (tinfo.max_treble > 0)
		if (snd_mixer_group_element_add(mixer, group2, tea->me_tone) < 0)
			goto __error;
	if (snd_mixer_element_route_add(mixer, tea->me_sw_master, tea->me_tone) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tea->me_tone, output) < 0)
		goto __error;

	return 0;
	
      __error:
      	bus->private_free = NULL;
      	bus->private_data = NULL;
      	snd_kfree(tea);
      	return -ENOMEM;
}

EXPORT_SYMBOL(snd_tea6330t_detect);
EXPORT_SYMBOL(snd_tea6330t_update_mixer);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
