/*
 * programming the tda98xx sound processor family
 *
 * (c) 1997,1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
 * 
 * Extracted tda98xx code from bttv.c to a separate module
 *          1999  Kyösti Mälkki <kmalkki@cc.hut.fi>
 *
 */

#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>
#ifdef __SMP__
#include <asm/pgtable.h>
#include <linux/smp_lock.h>
#endif

/* kernel_thread */
#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>

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

#define I2C_TDA9840        0x84
#define I2C_TDA9850        0xb6
#define I2C_TDA8425        0x82

#define TDA9840_SW         0x00
#define TDA9840_LVADJ      0x02
#define TDA9840_STADJ      0x03
#define TDA9840_TEST       0x04

#define TDA9850_CON1       0x04
#define TDA9850_CON2       0x05
#define TDA9850_CON3       0x06
#define TDA9850_CON4       0x07
#define TDA9850_ALI1       0x08
#define TDA9850_ALI2       0x09
#define TDA9850_ALI3       0x0a

struct tda98xx {
	int left, right;	/* volume */
	int bass, treble;
	
	int stereo;
};

static struct i2c_driver driver;
static struct i2c_client client_template;

/* ---------------------------------------------------------------------- */

#if LINUX_VERSION_CODE < 0x020100   /* 2.0.x */
#define signal_pending(current)  (current->signal & ~current->blocked)
#define sigfillset(set)
#define mdelay(x) udelay(1000*x)
#else
MODULE_PARM(debug,"i");
MODULE_PARM(avc,"i");
#endif

#if LINUX_VERSION_CODE < 0x02017f
void schedule_timeout(int j)
{
	current->timeout = jiffies + j;
	schedule();
}
#endif

/* ----------------------------------------------------------------------- */

static int tda_read(struct i2c_client *client, int reg)
{
	unsigned char write;
	unsigned char read;
	struct i2c_msg msgs[2] = {
		{ client->addr, 0,        1, &write },
		{ client->addr, I2C_M_RD, 1, &read  }
	};

	write = reg & 0xff;
		
	if (2 != i2c_transfer(client->adapter,msgs,2)) {
		printk(KERN_WARNING
			"tda98xx: I/O error, (read 0x%x)\n", write);
		return -1;
	}
	return read;
}

static int tda_write(struct i2c_client *client, int reg, int val)
{
	unsigned char buffer[2];

	buffer[0] = reg & 0xff;
	buffer[1] = val & 0xff;

	if (2 != i2c_master_send(client, buffer, 5)) {
		printk(KERN_WARNING
		       "tda98xx: I/O error, (write 0x%x)\n", buffer[0]);
		return -1;
	}
	return 0;
}

/* ----------------------------------------------------------------------- */
static int tda9850_command(struct i2c_client *c, unsigned int cmd, void *arg)
{
	struct tda98xx  *tda  = c->data;
        __u16           *sarg = arg;

	switch (cmd) {

	case AUDIO_GET_VOLUME:
		*sarg = (tda->left > tda->right) ? tda->left : tda->right;
		break;
	case AUDIO_SET_VOLUME:
		tda->left = tda->right = *sarg;
		tda_write(c, TDA9850_CON4, (*sarg>>12) & 0x0f);
		break;

	case AUDIO_GET_BASS:
		*sarg = tda->bass;
		break;
	case AUDIO_SET_BASS:
		tda->bass = *sarg;
		break;

	case AUDIO_GET_TREBLE:
		*sarg = tda->treble;
		break;
	case AUDIO_SET_TREBLE:
		tda->treble = *sarg;
		break;

	case AUDIO_GET_STEREO:
	{
		unsigned char ALR1;
		ALR1 = tda_read(c, TDA9850_CON3); /* FIX register ! */
		*sarg = VIDEO_SOUND_MONO;
		*sarg |= (ALR1 & 32) ? VIDEO_SOUND_STEREO:0;
		*sarg |= (ALR1 & 64) ? VIDEO_SOUND_LANG1:0;
		tda->stereo = *sarg;
		break;
	}	
	case AUDIO_SET_STEREO:
	{
		unsigned char con3 = 0;
		tda->stereo = *sarg;
		if (*sarg & VIDEO_SOUND_LANG1)
			con3 = 0x80;	/* sap */
		if (*sarg & VIDEO_SOUND_STEREO)
			con3 = 0x40;	/* stereo */
		tda_write(c, TDA9850_CON3, con3);
		break;
	}
	default:
		return -EINVAL;

	} /* end switch (cmd) */

	return 0;
}

static int tda9840_command(struct i2c_client *c, unsigned int cmd, void *arg)
{
	struct tda98xx  *tda  = c->data;
        __u16           *sarg = arg;

	switch (cmd) {

	case AUDIO_GET_VOLUME:
		*sarg = (tda->left > tda->right) ? tda->left : tda->right;
		break;
	case AUDIO_SET_VOLUME:
		tda->left = tda->right = *sarg;
		break;

	case AUDIO_GET_BASS:
		*sarg = tda->bass;
		break;
	case AUDIO_SET_BASS:
		tda->bass = *sarg;
		break;

	case AUDIO_GET_TREBLE:
		*sarg = tda->treble;
		break;
	case AUDIO_SET_TREBLE:
		tda->treble = *sarg;
		break;

	case AUDIO_GET_STEREO:
/* begin of Horrible Hack <grin@tolna.net>
	v.flags|=VIDEO_AUDIO_VOLUME;
*/
 		*sarg  = VIDEO_SOUND_MONO;
 		*sarg |= VIDEO_SOUND_STEREO;
 		*sarg |= VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2;
		tda->stereo = *sarg;
/* 		v.step = 4096; */
		break;
	case AUDIO_SET_STEREO:
		tda->stereo = *sarg;
		break;
				
	default:
		return -EINVAL;
	}
	return 0;
}

/* ---------------------------------------------------------------------- */

static int tda98_detect(struct i2c_client *c)
{
	if (i2c_probe(c, I2C_TDA9840>>1, I2C_TDA9840>>1) >=0)
	{
		/* probe sets c->addr */	
		c->driver->command = tda9840_command;
		
	        /* 00 - mute           10 - mono / averaged stereo
	           2a - stereo         12 - dual A
	           1a - dual AB        16 - dual BA
	           1e - dual B         7a - external */
	        tda_write(c, TDA9840_SW, 0x2a);
		return 0;
        }

	if (i2c_probe(c, I2C_TDA9850>>1, I2C_TDA9850>>1) >=0)
	{
		c->driver->command = tda9850_command;
	        tda_write(c, TDA9850_CON1, 0x08);  /* noise threshold st */
		tda_write(c, TDA9850_CON2, 0x08);  /* noise threshold sap */
		tda_write(c, TDA9850_CON3, 0x40);  /* stereo mode */
		tda_write(c, TDA9850_CON4, 0x07);  /* 0 dB input gain?*/
		tda_write(c, TDA9850_ALI1, 0x10);  /* wideband alignment? */
		tda_write(c, TDA9850_ALI2, 0x10);  /* spectral alignment? */
		tda_write(c, TDA9850_ALI3, 0x03);
		return 0;
	}

	return -ENODEV;
}	


/* ----------------------------------------------------------------------- */

static int tda98_attach(struct i2c_adapter *adap)
{
	struct tda98xx *tda = NULL;
	struct i2c_client *c = NULL;
	
	if ((adap->id &~ ALGO_MASK) != HW_B_BT848)
		return 0;
	
	client_template.adapter = adap;
	c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL);

        if (c==NULL)
		return -ENOMEM;

	memcpy(c,&client_template,sizeof(struct i2c_client));
		
	if ( tda98_detect(c) < 0) {
		printk("tda98xx: couldn't find sound processor\n");
		kfree(c);
		return -ENODEV;
	}
					
	c->data = tda = kmalloc(sizeof(struct tda98xx),GFP_KERNEL);

	if (NULL == tda) {
		kfree(c);
		return -ENOMEM;
	}
	memset(tda,0,sizeof(struct tda98xx));

	tda->left   = 65535;
	tda->right  = 65535;
	tda->bass   = 32768;
	tda->treble = 32768;

	i2c_attach_client(c);

	return 0;
}

static int tda98_detach(struct i2c_client *c)
{
	struct tda98xx *tda  = c->data;

    	i2c_detach_client(c);
	kfree(tda);
	return 0;
}

/* ----------------------------------------------------------------------- */

static struct i2c_driver driver = {
	"i2c tda98xx driver",
	-1,
	DF_NOTIFY,
	tda98_attach,
	tda98_detach,
	NULL        /* set once we now which TDA98xx we have */
};

static struct i2c_client client_template = 
{
	"unset",
	I2C_DRIVERID_TDA9840,
	0,
	0,
	NULL,
	&driver
};

#ifdef MODULE
int init_module(void)
#else
int tda98xx_init(void)
#endif
{
	i2c_add_driver(&driver);
	return 0;
}

#ifdef MODULE
void cleanup_module(void)
{
	i2c_del_driver(&driver);
}
#endif
