/*
 *  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 "driver.h"
#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(int w_flag, int *volume, tea6330t_t *tea)
{
	unsigned long flags;
	int change = 0;
	
	snd_spin_lock(tea, access, &flags);
	if (!w_flag) {
		volume[0] = tea->mleft;
		volume[1] = tea->mright;
	} else {
		change = volume[0] != tea->mleft || volume[1] != tea->mright;
		if (tea->regs[TEA6330T_SADDR_VOLUME_LEFT] != 0) {
			tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = volume[0] + 0x14;
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_LEFT, tea->regs[TEA6330T_SADDR_VOLUME_LEFT], 1);
		}
		if (tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] != 0) {
			tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = volume[1] + 0x14;
			snd_i2c_write(tea->bus, TEA6330T_ADDR, TEA6330T_SADDR_VOLUME_RIGHT, tea->regs[TEA6330T_SADDR_VOLUME_RIGHT], 1);
		}
	}
	snd_spin_unlock(tea, access, &flags);
	return change;
}

static int snd_tea6330t_master_switch(int w_flag, unsigned int *bitmap, tea6330t_t *tea)
{
	unsigned long flags;
	int change = 0, left, right;
	
	snd_spin_lock(tea, access, &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);
	}
	snd_spin_unlock(tea, access, &flags);
	return change;
}

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

	snd_spin_lock(tea, access, &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;
		}
	}
	snd_spin_unlock(tea, access, &flags);
	return change;
}

static void snd_tea6330t_free(void *private_data)
{
	snd_free(private_data, sizeof(tea6330t_t));
}

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 *group;
	snd_kmixer_element_t *mvolume, *mswitch, *tone;
	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_calloc(sizeof(*tea));
	if (!tea)
		return -ENOMEM;
	snd_spin_prepare(tea, access);
	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_free(tea, sizeof(*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_free(tea, sizeof(*tea));
		return -ENODEV;
	}

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

	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
	if ((mvolume = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, master_range, (snd_mixer_volume1_control_t *)snd_tea6330t_master_volume, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, mvolume) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, input, mvolume) < 0)
		goto __error;
	if ((mswitch = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_tea6330t_master_switch, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, mswitch) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mvolume, mswitch) < 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;
	}
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_TONE_CONTROL, 0)) == NULL)
		goto __error;
	if ((tone = snd_mixer_lib_tone_control1(mixer, SND_MIXER_GRP_TONE_CONTROL, 0, &tinfo, (snd_mixer_tone_control1_control_t *)snd_tea6330t_tone_control, tea)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, tone) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mswitch, tone) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, tone, output) < 0)
		goto __error;

	return 0;
	
      __error:
      	bus->private_free = NULL;
      	bus->private_data = NULL;
      	snd_free(tea, sizeof(*tea));
      	return -ENOMEM;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_tea6330t_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_tea6330t_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
