/*
 * for the TEA6300 chip (only found on Gateway STB TV/FM cards tho the best
 * of my knowledge)
 * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF THE WRONG
 * CHIP (i.e., an MSP3400) IS ON I2C ADDRESS 0x80 (it relies on i2c to
 * make sure that there is a device acknowledging that address).  This
 * is a potential problem because the MSP3400 is very popular and does
 * use this address!  You have been warned!
 *
 * Copyright (c) 1998 Greg Alexander <galexand@acm.org>
 * This code is placed under the terms of the GNU General Public License
 * Code liberally copied from msp3400.c, which is by Gerd Knorr
 *
 * All of this should work, though it would be nice to eventually support
 * balance (different left,right values) and, if someone ever finds a card
 * with the support (or if you're careful with a soldering iron), fade
 * (front/back).
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/malloc.h>

#include "i2c.h"
#include "bttv.h"
#include "videodev.h"
#include "audiochip.h"

static int debug = 0; /* insmod parameter */

struct tea6300 {
	struct i2c_bus *bus;
	int mode;		/* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */
	int stereo;
	__u16 left,right;
	__u16 bass,treble;
};

#define dprintk  if (debug) printk

/* I don't understand this part (I haven't investigated), it's just there
 * because that's how MSP3400 did it. */
#if LINUX_VERSION_CODE >= 0x020117 
MODULE_PARM(debug,"i");
#endif

#define I2C_TEA6300	0x80

                                 /* registers (subaddresses): */
#define TEA6300_VL         0x00  /* volume left */
#define TEA6300_VR         0x01  /* volume right */
#define TEA6300_BA         0x02  /* bass */
#define TEA6300_TR         0x03  /* treble */
#define TEA6300_FA         0x04  /* fader control */
#define TEA6300_S          0x05  /* switch register */
                                 /* values for those registers: */
#define TEA6300_S_SA       0x01  /* stereo A input */
#define TEA6300_S_SB       0x02  /* stereo B */
#define TEA6300_S_SC       0x04  /* stereo C */
#define TEA6300_S_GMU      0x80  /* general mute */


/* ******************************** *
 * functions for talking to TEA6300 *
 * ******************************** */

static int tea6300_write(struct i2c_bus *bus, int addr, int val)
{
	int ret = 0;
    
	i2c_start(bus);
	if (i2c_sendbyte(bus, I2C_TEA6300,  0) ||
	    i2c_sendbyte(bus, addr,         0)   ||
	    i2c_sendbyte(bus, val,          0))
		ret = -1;
	i2c_stop(bus);
	if (-1 == ret) {
		printk(KERN_WARNING "tea6300: I/O error, trying (write %d 0x%x)\n",
		       addr, val);
	}
	return ret;
}

static void tea6300_set(struct tea6300 *tea)
{	/* mode is ignored today */
	dprintk(KERN_DEBUG "tea6300_set(%04x,%04x,%04x,%04x)\n",tea->left>>10,tea->right>>10,tea->bass>>12,tea->treble>>12);
	tea6300_write(tea->bus, TEA6300_VL, tea->left>>10  );
	tea6300_write(tea->bus, TEA6300_VR, tea->right>>10 );
	tea6300_write(tea->bus, TEA6300_BA, tea->bass>>12  );
	tea6300_write(tea->bus, TEA6300_TR, tea->treble>>12);
}

static void tea6300_init(struct tea6300 *tea)
{
	tea->left=tea->right =49152;  /* -10dB (loud enough, but not beyond
	                                 normal line levels - so as to avoid
	                                 clipping */
	tea->bass=tea->treble=28672;  /* 0dB */
	tea->mode=AUDIO_OFF;
	tea->stereo=1;
	/* left=right=0x27<<10, bass=treble=0x07<<12 */
	tea6300_write(tea->bus, TEA6300_FA, 0x3f         ); /* fader off */
	tea6300_write(tea->bus, TEA6300_S , TEA6300_S_GMU); /* mute */
	tea6300_set(tea);
}

static void tea6300_audio(struct tea6300 *tea, int mode)
{		/* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */
	dprintk(KERN_DEBUG "tea6300_audio:%d (T,R,E,I,O)\n",mode);
	tea->mode=mode;
	if (mode==AUDIO_OFF) {	/* just mute it */
		tea6300_write(tea->bus, TEA6300_S, TEA6300_S_GMU);
		return;
	}
	switch(mode) {
		case AUDIO_TUNER:
			tea6300_write(tea->bus, TEA6300_S, TEA6300_S_SA);
			break;
		case AUDIO_RADIO:
			tea6300_write(tea->bus, TEA6300_S, TEA6300_S_SB);
			break;
		case AUDIO_EXTERN:
			tea6300_write(tea->bus, TEA6300_S, TEA6300_S_SC);
			break;
	}
}


/* *********************** *
 * i2c interface functions *
 * *********************** */

static int tea6300_attach(struct i2c_device *device)
{
	struct tea6300 *tea;
	LOCK_FLAGS;

	device->data = tea = kmalloc(sizeof *tea,GFP_KERNEL);
	if (!tea)
		return -ENOMEM;
	memset(tea,0,sizeof *tea);
	tea->bus = device->bus;
	LOCK_I2C_BUS(tea->bus);
	tea6300_init(tea);
	UNLOCK_I2C_BUS(tea->bus);
	MOD_INC_USE_COUNT;
	strcpy(device->name,"TEA6300T");
	printk(KERN_INFO "tea6300: initialized\n");
	return 0;
}

static int tea6300_detach(struct i2c_device *device)
{
	struct tea6300 *tea  = (struct tea6300 *)device->data;
	LOCK_FLAGS;
    
	LOCK_I2C_BUS(tea->bus);
	tea6300_init(tea);
	UNLOCK_I2C_BUS(tea->bus);

	kfree(tea);
	MOD_DEC_USE_COUNT;
	return 0;
}

static int tea6300_command(struct i2c_device *device,
		 unsigned int cmd, void *arg)
{
	struct tea6300 *tea = (struct tea6300 *)device->data;
        __u16 *sarg = arg;
	LOCK_FLAGS;

	switch (cmd) {
	case AUDC_SET_RADIO:
		LOCK_I2C_BUS(tea->bus);
		tea6300_audio(tea,AUDIO_RADIO);
		UNLOCK_I2C_BUS(tea->bus);
		break;
	case AUDC_GET_VOLUME_LEFT:
		*sarg = tea->left;
		break;
	case AUDC_GET_VOLUME_RIGHT:
		*sarg = tea->right;
		break;
	case AUDC_SET_VOLUME_LEFT:
		tea->left = *sarg;
		LOCK_I2C_BUS(tea->bus);
		tea6300_set(tea);
		UNLOCK_I2C_BUS(tea->bus);
		break;
	case AUDC_SET_VOLUME_RIGHT:
		tea->right = *sarg;
		LOCK_I2C_BUS(tea->bus);
		tea6300_set(tea);
		UNLOCK_I2C_BUS(tea->bus);
		break;

	case AUDC_GET_BASS:
		*sarg = tea->bass;
		break;
	case AUDC_SET_BASS:
		tea->bass = *sarg;
		LOCK_I2C_BUS(tea->bus);
		tea6300_set(tea);
		UNLOCK_I2C_BUS(tea->bus);
		break;

	case AUDC_GET_TREBLE:
		*sarg = tea->treble;
		break;
	case AUDC_SET_TREBLE:
		tea->treble = *sarg;
		LOCK_I2C_BUS(tea->bus);
		tea6300_set(tea);
		UNLOCK_I2C_BUS(tea->bus);
		break;

	case AUDC_GET_STEREO:
		*sarg = tea->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO;
		break;
	case AUDC_SET_STEREO:
		tea->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1;
		/* TODO: make this write to the TDA9850? */
		break;
	case AUDC_SET_INPUT:
		LOCK_I2C_BUS(tea->bus);
		tea6300_audio(tea,*sarg);
		UNLOCK_I2C_BUS(tea->bus);
		break;

/*	case AUDC_SWITCH_MUTE:	someday, maybe -- not a lot of point to
	case AUDC_NEWCHANNEL:	it and it would require preserving state
	case AUDC_GET_DC:	huh?? (not used by bttv.c)
*/
	default:
		return -EINVAL;
	}
	return 0;
}

static struct i2c_driver i2c_driver_tea = {
	"tea6300",                    /* name       */
	I2C_DRIVERID_AUDIOCHIP,       /* ID         */
	I2C_TEA6300, I2C_TEA6300,     /* addr range */

	tea6300_attach,
	tea6300_detach,
	tea6300_command
};

#ifdef MODULE
int init_module(void)
#else
int tea6300_init(void)
#endif
{
	i2c_register_driver(&i2c_driver_tea);
	return 0;
}

#ifdef MODULE
void cleanup_module(void)
{
	i2c_unregister_driver(&i2c_driver_tea);
}
#endif
