#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"

struct msp3400c {
	struct i2c_client *client;

	int mode;
	int norm;
	int stereo;
	
	int flags;
	
	int features;
	int features_on;

	int sstandard;          /* supported sound standards */
	int sstandard_on;	/* current used sound standard */
	
	int main, second;	/* sound carrier */
	
	int volume, balance;	/* volume */
	int bass, treble;
	
	int audiomute;

	/* thread */
	struct task_struct  *thread;
	struct wait_queue   *wq;
	struct semaphore    *notify;

	int                  rmmod, control;
	struct timer_list    wake_stereo;
};

/* IC subaddressing demodulator/dsp */
#define MSP_DEM   0x10
#define MSP_DSP   0x12

struct MSP_FEATURES {
	u8 rev[5];
	u8 sstandard;
	int features;
};

struct MSP_FEATURE_NAMES {
	int	feature_id;
	char	*desc;
};

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

/* Thread control */
#define MSP_THREAD_NOCHANNEL	0x01
#define MSP_THREAD_NEWCHANNEL	0x02
#define MSP_THREAD_ADJVOL	0x04
#define MSP_THREAD_RMMOD	0x08

/* DEM and DSP capabilities */
#define MSP_FASTMUTE	0x01
#define	MSP_AVC		0x02

/* sound standards, bitwise OR */
#define MSP_NICAM	0x1
#define MSP_BTSC	0x2
#define MSP_OTHER	0x4

#define MSP_INIT1	0x01<<8
#define MSP_INIT2	0x02<<8
#define MSP_INIT3	0x04<<8

struct MSP_INIT_DATA_DEM {
	int fir1[6];
	int fir2[6];
	int cdo1;
	int cdo2;
	int ad_cv;
	int mode_reg;
	int dfp_src;
	int dfp_matrix;
};


/* sound mixer stuff */ 
#include <linux/config.h>

/* insmod parameters */
static int debug = 0;	 /* debug output */
static int avc = 1;     /* automatic volume correction */

static struct i2c_driver driver;
static struct i2c_client client_template;

#define VIDEO_MODE_RADIO 16      /* norm magic for radio mode */

#define MUTE_ON_CHANGE          /* mutes when doing channel change */

/* AVC delay time can be one of 20ms/2s/4s/8s -> 1/2/4/8 */
#define AVC_DELAY 0x02
#define MUTE_DELAY  30    /* milliseconds */

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

#define dprintk     if (debug) printk

#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(simple,"i");
MODULE_PARM(avc,"i");
#endif

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

static int msp_reset(struct i2c_client *client);
static int msp_read(struct i2c_client *client, int dev, int addr);
static int msp_write(struct i2c_client *client, int dev, int addr, int val);

/* ------------------------------------------------------------------------ */
static struct MSP_FEATURE_NAMES f_list[] = {
	{MSP_FASTMUTE,	"Digital Mute"},
	{MSP_AVC,	"Automatic Volume Correction"},
};

/*  This list defines the first revision when a new capability was added
    These can be found at the end of MSP34xx datasheets */

#define C_SERIES	(MSP_INIT1)
#define D_SERIES	(MSP_FASTMUTE | MSP_AVC | MSP_INIT2)
#define G_SERIES	(MSP_FASTMUTE | MSP_AVC | MSP_INIT3)

static struct MSP_FEATURES msp_list[] = {
	{{0, 0, 'C', 'A', 0}, 0,		C_SERIES},
	{{0, 0, 'C', 'C', 6}, 0,		MSP_FASTMUTE},
	{{0, 0, 'C', 'C', 8}, 0,		MSP_AVC},

	{{0, 0, 'D', 'A', 0}, 0,		MSP_FASTMUTE|MSP_INIT2},
	{{0, 0, 'D', 'B', 3}, 0,		MSP_AVC},

	{{1, 0, 'D', 'A', 0}, MSP_NICAM,	MSP_FASTMUTE|MSP_INIT2},
	{{1, 0, 'D', 'B', 3}, 0,		MSP_AVC},

	{{0, 5, 'D', 'A', 0}, 0,		D_SERIES},
	{{1, 5, 'D', 'A', 0}, MSP_NICAM,	D_SERIES},

	{{0, 7, 'D', 'A', 0}, 0,		D_SERIES},	
	{{1, 7, 'D', 'A', 0}, MSP_NICAM,	D_SERIES},
	
	{{1, 0, 'G', 'A', 1}, MSP_NICAM,	G_SERIES},
	{{3, 0, 'G', 'A', 2}, MSP_BTSC,		G_SERIES},
	{{5, 0, 'G', 'A', 1}, MSP_NICAM|MSP_BTSC, G_SERIES},

	{{3, 5, 'G', 'A', 2}, MSP_BTSC,		G_SERIES},
};

#define MSP_COUNT(x) (sizeof(x)/sizeof(struct MSP_FEATURES))
#define MSP_FEATURE_COUNT(x) (sizeof(x)/sizeof(struct MSP_FEATURE_NAMES))

/* ------------------------------------------------------------------------ */
#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24)))

#define MSP_MODE_AM_DETECT1  0
#define MSP_MODE_AM_DETECT2  1
#define MSP_MODE_FM_RADIO    2
#define MSP_MODE_FM_TERRA    3
#define MSP_MODE_FM_SAT      4
#define MSP_MODE_FM_NICAM1   5
#define MSP_MODE_FM_NICAM2   6
#define MSP_MODE_AM_NICAM    7

static struct MSP_INIT_DATA_DEM msp_init_data[] = {
	/* AM (for carrier detect / msp3400) */
	{ { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 },
	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
	  0x00d0, 0x0500,   0x0020, 0x3000},

	/* AM (for carrier detect / msp3410) */
	{ { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 },
	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
	  0x00d0, 0x0100,   0x0020, 0x3000},

	/* FM Radio */
	{ { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 },
	  MSP_CARRIER(10.7), MSP_CARRIER(10.7),
	  0x00d0, 0x0480, 0x0020, 0x3000 },

	/* Terrestial FM-mono + FM-stereo */
	{ {  3, 18, 27, 48, 66, 72 }, {  3, 18, 27, 48, 66, 72 },
	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
	  0x00d0, 0x0480,   0x0030, 0x3000},

	/* Sat FM-mono */
	{ {  1,  9, 14, 24, 33, 37 }, {  3, 18, 27, 48, 66, 72 },
	  MSP_CARRIER(6.5), MSP_CARRIER(6.5),
	  0x00c6, 0x0480,   0x0000, 0x3000},

	/* NICAM/FM --  B/G (5.5/5.85), D/K (6.5/5.85) */
	{ { -2, -8, -10, 10, 50, 86 }, {  3, 18, 27, 48, 66, 72 },
	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
	  0x00d0, 0x0040,   0x0120, 0x3000},

	/* NICAM/FM -- I (6.0/6.552) */
	{ {  2, 4, -6, -4, 40, 94 }, {  3, 18, 27, 48, 66, 72 },
	  MSP_CARRIER(6.0), MSP_CARRIER(6.0),
	  0x00d0, 0x0040,   0x0120, 0x3000},

	/* NICAM/AM -- L (6.5/5.85) */
	{ {  -2, -8, -10, 10, 50, 86 }, {  -4, -12, -9, 23, 79, 126 },
	  MSP_CARRIER(6.5), MSP_CARRIER(6.5),
	  0x00c6, 0x0140,   0x0120, 0x7000},
};

struct CARRIER_DETECT {
	int   cdo;
	char *name;
};

static struct CARRIER_DETECT carrier_detect_main[] = {
	/* main carrier */
	{ MSP_CARRIER(4.5),        "4.5   NTSC"                   }, 
	{ MSP_CARRIER(5.5),        "5.5   PAL B/G"                }, 
	{ MSP_CARRIER(6.0),        "6.0   PAL I"                  },
	{ MSP_CARRIER(6.5),        "6.5   PAL D/K + SAT + SECAM"  }
};

static struct CARRIER_DETECT carrier_detect_55[] = {
	/* PAL B/G */
	{ MSP_CARRIER(5.7421875),  "5.742 PAL B/G FM-stereo"     }, 
	{ MSP_CARRIER(5.85),       "5.85  PAL B/G NICAM"         }
};

static struct CARRIER_DETECT carrier_detect_65[] = {
	/* PAL SAT / SECAM */
	{ MSP_CARRIER(5.85),       "5.85  PAL D/K NICAM" },
	{ MSP_CARRIER(6.2578125),  "6.25  PAL D/K1 FM-stereo" },
	{ MSP_CARRIER(6.7421875),  "6.74  PAL D/K2 FM-stereo" },
	{ MSP_CARRIER(7.02),       "7.02  PAL SAT FM-stereo s/b" },
	{ MSP_CARRIER(7.20),       "7.20  PAL SAT FM-stereo s"   },
	{ MSP_CARRIER(7.38),       "7.38  PAL SAT FM-stereo b"   },
};

/* This macro is allowed for *constants* only, gcc must calculate it
   at compile time.  Remember -- no floats in kernel mode */

#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT))

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

static void msp_c_setcarrier(struct i2c_client *c, int cdo1, int cdo2)
{
	msp_write(c,MSP_DEM, 0x0093, cdo1 & 0xfff);
	msp_write(c,MSP_DEM, 0x009b, cdo1 >> 12);
	msp_write(c,MSP_DEM, 0x00a3, cdo2 & 0xfff);
	msp_write(c,MSP_DEM, 0x00ab, cdo2 >> 12);
	msp_write(c,MSP_DEM, 0x0056, 0); /*LOAD_REG_1/2*/
}

static void msp_c_setmode(struct i2c_client *c, int type)
{
	struct msp3400c *msp = c->data;
	int i;
	
	dprintk("msp34xx: setmode: %d\n",type);
	msp->mode   = type;

	msp_write(c,MSP_DEM, 0x00bb,                     /* ad_cv */
		       msp_init_data[type].ad_cv);
    
	for (i = 5; i >= 0; i--)                         /* fir 1 */
		msp_write(c,MSP_DEM, 0x0001,
			       msp_init_data[type].fir1[i]);
    
	msp_write(c,MSP_DEM, 0x0005, 0x0004);            /* fir 2 */
	msp_write(c,MSP_DEM, 0x0005, 0x0040);
	msp_write(c,MSP_DEM, 0x0005, 0x0000);
	for (i = 5; i >= 0; i--)
		msp_write(c,MSP_DEM, 0x0005,
			       msp_init_data[type].fir2[i]);
    
	msp_write(c,MSP_DEM, 0x0083,			/* MODE REG */
			msp_init_data[type].mode_reg);

}

static void msp_setvolume(struct i2c_client *c, int vol)
{
	struct msp3400c *msp = c->data;
	int val;

	/* this gives voltage scale of -51dB to +12dB */
	val     = (0x400 + vol * 0x3F0 / 65535) << 4;
	
	if (vol==0 || msp->audiomute==1) {
		val = ((msp->features_on & MSP_FASTMUTE) ? 0xFFE0 : 0x0000);
	}

	dprintk("msp34xx: setvolume: %d 0x%04x\n", vol, val);
	
	msp_write(c,MSP_DSP, 0x0000, val);
}

static void msp_setbalance(struct i2c_client *c, int balance)
{
	int val = (balance - 32768) >> 8;;
	msp_write(c,MSP_DSP, 0x0001, val << 8);
}

static void msp_setbass(struct i2c_client *c, int bass)
{
	int val = ((bass-32768) * 0x60 / 65535) << 8;

	dprintk("msp34xx: setbass: %d 0x%02x\n",bass, val>>8);
	msp_write(c,MSP_DSP, 0x0002, val);
}

static void msp_settreble(struct i2c_client *c, int treble)
{
	int val = ((treble-32768) * 0x60 / 65535) << 8;

	dprintk("msp34xx: settreble: %d 0x%02x\n",treble, val>>8);
	msp_write(c,MSP_DSP, 0x0003, val);
}


static void msp_print_mode(struct msp3400c *msp)
{
	if (msp->main == msp->second) {
		printk("msp34xx: mono sound carrier: %d.%03d MHz\n",
		       msp->main/910000,(msp->main/910)%1000);
	} else {
		printk("msp34xx: main sound carrier: %d.%03d MHz\n",
		       msp->main/910000,(msp->main/910)%1000);
	}
	if (msp->mode == MSP_MODE_FM_NICAM1 ||
	    msp->mode == MSP_MODE_FM_NICAM2)
		printk("msp34xx: NICAM/FM carrier   : %d.%03d MHz\n",
		       msp->second/910000,(msp->second/910)%1000);
	if (msp->mode == MSP_MODE_AM_NICAM)
		printk("msp34xx: NICAM/AM carrier   : %d.%03d MHz\n",
		       msp->second/910000,(msp->second/910)%1000);
	if (msp->mode == MSP_MODE_FM_TERRA &&
	    msp->main != msp->second) {
		printk("msp34xx: FM-stereo carrier : %d.%03d MHz\n",
		       msp->second/910000,(msp->second/910)%1000);
	}
}

/* ----------------------------------------------------------------------- */
/* Audio channel selection                                                 */

static void msp_setstereo(struct i2c_client *c, int mode)
{
	struct msp3400c *msp = c->data;
	int mux = 0;
	int nicam=0;
	
	if (! mode)
		return;
	
	/* switch demodulator */
	switch (msp->mode) {
	case MSP_MODE_FM_TERRA:
		dprintk("msp34xx: FM setstereo: %d\n",mode);
		switch (mode) {
		case VIDEO_SOUND_STEREO:
			msp_write(c,MSP_DSP, 0x0e, 0x3001);
			break;
		case VIDEO_SOUND_MONO:
		case VIDEO_SOUND_LANG1:
		case VIDEO_SOUND_LANG2:
			msp_write(c,MSP_DSP, 0x0e, 0x3000);
			break;
		}
		break;
	case MSP_MODE_FM_SAT:
		dprintk("msp34xx: SAT setstereo: %d\n",mode);
		switch (mode) {
		case VIDEO_SOUND_MONO:
			msp_c_setcarrier(c, MSP_CARRIER(6.5), MSP_CARRIER(6.5));
			break;
		case VIDEO_SOUND_STEREO:
			msp_c_setcarrier(c, MSP_CARRIER(7.2), MSP_CARRIER(7.02));
			break;
		case VIDEO_SOUND_LANG1:
			msp_c_setcarrier(c, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
			break;
		case VIDEO_SOUND_LANG2:
			msp_c_setcarrier(c, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
			break;
		}
		break;
	case MSP_MODE_FM_NICAM1:
	case MSP_MODE_FM_NICAM2:
		msp_write(c,MSP_DSP, 0x10, 0x4000);
		msp_write(c,MSP_DSP, 0x0e, 0x3000);
		nicam=0x0100;
		break;
	case MSP_MODE_AM_NICAM:
		msp_write(c, MSP_DSP, 0x0e, 0x7c00);
		msp_write(c, MSP_DSP, 0x10, 0x4000);
		nicam=0x0100;
		break;
	default:
		dprintk("msp34xx: mono setstereo\n");
		return;
	}

	
	/* switch audio */
	switch (mode) {
	case VIDEO_SOUND_LANG1:
		mux = 0x0000 | nicam;
		break;
	case VIDEO_SOUND_LANG2:
		mux = 0x0010 | nicam;
		break;
	case VIDEO_SOUND_STEREO:
		mux = 0x0020 | nicam;
		break;
	case VIDEO_SOUND_MONO:
		mux = 0x0000;   /* Nicam Mono == FM Mono */
		break;
	}

	msp_write(c,MSP_DSP, 0x0008, mux); /* loudspeaker */

	if (msp->features_on & MSP_AVC) {
		msp_write(c,MSP_DSP, 0x0029, 0x00 << 8);
		msp_write(c,MSP_DSP, 0x0029,
			(0x80 | (AVC_DELAY & 0x0f)) << 8);
	}
}

static int msp_getstereo(struct i2c_client *c)
{
	int val;
	struct msp3400c *msp = c->data;
	int newstereo = msp->stereo;
	int newnicam  = msp->sstandard_on & MSP_NICAM;
	int update = 0;

	switch (msp->mode) {
	case MSP_MODE_FM_TERRA:
		val = msp_read(c, MSP_DSP, 0x18) >> 8;
		if (val >= 128) val-=255;
		dprintk("msp34xx: stereo detect register: %d\n",val);
		if (val > 96)
			newstereo = VIDEO_SOUND_STEREO;
		else if (val < -96)
			newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2;
		else
			newstereo = VIDEO_SOUND_MONO;
		newnicam = 0;
		break;
	case MSP_MODE_FM_NICAM1:
	case MSP_MODE_FM_NICAM2:
	case MSP_MODE_AM_NICAM:
		val = msp_read(c, MSP_DEM, 0x23);
		dprintk("msp34xx: nicam sync=%d, mode=%d\n",val & 1, (val & 0x1e) >> 1);

		if (val & 1) {
			/* nicam synced */
			switch ((val>>1) & 7)  {
			case 0:
				newstereo = VIDEO_SOUND_STEREO;
				break;
			case 1:
				newstereo = VIDEO_SOUND_LANG1;
				break;
			case 2:
				newstereo = VIDEO_SOUND_LANG1 |
						VIDEO_SOUND_LANG2;
				break;
			case 3:
				newstereo = 0;
				break;
			default:
				newstereo = VIDEO_SOUND_MONO;
				break;
			}
			/* FM1 channel is different from NICAM A */
			if ( ! ((val>>1) & 0x08))
				newstereo |= VIDEO_SOUND_MONO;
			newnicam = MSP_NICAM;
		}
		break;
	}
	if (newstereo != msp->stereo) {
		update = 1;
		dprintk("msp34xx: watch: stereo %d => %d\n",
			msp->stereo,newstereo);
		msp->stereo = newstereo;
	}
	if (newnicam != (msp->sstandard_on & MSP_NICAM)) {
		update = 1;
		dprintk("msp34xx: watch: nicam %d => %d\n",
			msp->sstandard_on & MSP_NICAM, newnicam);
		msp->sstandard_on = newnicam;
	}
	return update;
}

/* ----------------------------------------------------------------------- */
/* mode detect thread for MSP34xx B/C-series				   */

static void msp_mode_detect_c(struct i2c_client *c)
{
	struct msp3400c *msp = c->data;
	struct CARRIER_DETECT *cd;
	int val1, val2, max1, max2;
	int val, this, count, mode;
	
newchannel:
	msp->control&=~MSP_THREAD_NEWCHANNEL;
	mode = MSP_MODE_AM_DETECT1;	
	msp_c_setmode(c, mode);
	msp_write(c, MSP_DSP, 0x08, msp_init_data[mode].dfp_src);
	msp_write(c, MSP_DSP, 0x0e, msp_init_data[mode].dfp_matrix);
	
	val1 = val2 = 0;
	max1 = max2 = -1;

	/* carrier detect pass #1 -- main carrier */
	cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main);
	for (this = 0; this < count; this++) {
		msp_c_setcarrier(c, cd[this].cdo,cd[this].cdo);
		current->state   = TASK_INTERRUPTIBLE;
		schedule_timeout(HZ/25);

		if (signal_pending(current))
			goto finish;
		if (msp->control & MSP_THREAD_NEWCHANNEL)
			goto newchannel;

		val = msp_read(c, MSP_DSP, 0x1b);
		/* we take the absolute value of a 2's complement */
		if (val >= 32768)
			val = 65536 - val;
				
		if (val1 < val)
			val1 = val, max1 = this;
		dprintk("msp34xx: carrier1 val: %5d / %s\n", val,cd[this].name);
	}

	/* carrier detect pass #2 -- second (stereo) carrier */
	switch (max1) {
	case 1: /* 5.5 */
		cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55);
		break;
	case 3: /* 6.5 */
		cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65);
		break;
	case 0: /* 4.5 */
	case 2: /* 6.0 */
	default:
		cd = NULL; count = 0;
		break;
	}

	for (this = 0; this < count; this++) {
		msp_c_setcarrier(c, cd[this].cdo,cd[this].cdo);
		current->state   = TASK_INTERRUPTIBLE;
		schedule_timeout(HZ/25);

		if (signal_pending(current))
			goto finish;
		if (msp->control & MSP_THREAD_NEWCHANNEL)
			goto newchannel;

		val = msp_read(c, MSP_DSP, 0x1b);
		if (val >= 32768)
			val = 65536 - val;

		if (val2 < val)
			val2 = val, max2 = this;
		dprintk("msp34xx: carrier2 val: %5d / %s\n", val,cd[this].name);
	}

	/* program the msp3400 according to the results */
	msp->main   = carrier_detect_main[max1].cdo;
	switch (max1) {
	case 1: /* 5.5 */
		if (max2 == 0) {
			/* B/G FM-stereo */
			msp->second = carrier_detect_55[max2].cdo;
			msp_c_setmode(c, MSP_MODE_FM_TERRA);
	 		msp_c_setcarrier(c,msp->second,msp->main);
		} else if (max2 == 1 && (msp->sstandard & MSP_NICAM)) {
			/* B/G NICAM */
			msp->second = carrier_detect_55[max2].cdo;
			msp_c_setmode(c, MSP_MODE_FM_NICAM1);
			msp_c_setcarrier(c, msp->second, msp->main);
		} else {
			goto no_second;
		}
		break;
	case 2: /* 6.0 */
		/* PAL I NICAM */
		msp->second = MSP_CARRIER(6.552);
		msp_c_setmode(c, MSP_MODE_FM_NICAM2);
		msp_c_setcarrier(c, msp->second, msp->main);
		break;
	case 3: /* 6.5 */
		if (max2 == 1 || max2 == 2) {
			/* D/K FM-stereo */
			msp->second = carrier_detect_65[max2].cdo;
			msp_c_setmode(c, MSP_MODE_FM_TERRA);
			msp_c_setcarrier(c,msp->second,msp->main);
		}
		else if (max2 == 0 && (msp->sstandard & MSP_NICAM))
		{
			msp->second = carrier_detect_65[max2].cdo;
			if ( msp->norm == VIDEO_MODE_SECAM)	/* L NICAM */
				msp_c_setmode(c, MSP_MODE_AM_NICAM);
			else
				msp_c_setmode(c, MSP_MODE_FM_NICAM1);
			msp_c_setcarrier(c, msp->second, msp->main);
		} else {
			goto no_second;
		}
		break;
	case 0: /* 4.5 */
	default:
	no_second:
		msp->second = carrier_detect_main[max1].cdo;
		msp_c_setmode(c, MSP_MODE_FM_TERRA);
		msp_c_setcarrier(c, msp->second, msp->main);
		break;
	}

finish:
	/* We have new audio */
	msp->control &= ~MSP_THREAD_NOCHANNEL;	
	msp->control |= MSP_THREAD_ADJVOL;

}

/* ----------------------------------------------------------------------- */
/* mode detect thread for MSP34xxD-series				   */

#if 0
struct REGISTER_DUMP {
	int   addr;
	char *name;
};

static struct REGISTER_DUMP d1[] = {
	{ 0x007e, "autodetect" },
	{ 0x0023, "C_AD_BITS " },
	{ 0x0038, "ADD_BITS  " },
	{ 0x003e, "CIB_BITS  " },
	{ 0x0057, "ERROR_RATE" },
};
#endif

static struct MODES {
	int   retval;
	char *name;
} modelist[] = {
	{ 0x0000, "autodetect error" },
	{ 0x0002, "4.5/4.72  M Dual FM-Stereo" },
	{ 0x0003, "5.5/5.74  B/G Dual FM-Stereo" },
	{ 0x0004, "6.5/6.25  D/K1 Dual FM-Stereo" },
	{ 0x0005, "6.5/6.74  D/K2 Dual FM-Stereo" },
	{ 0x0006, "6.5  D/K FM-Mono (HDEV3)" },
	{ 0x0008, "5.5/5.85  B/G NICAM FM" },
	{ 0x0009, "6.5/5.85  L NICAM AM" },
	{ 0x000a, "6.0/6.55  I NICAM FM" },
	{ 0x000b, "6.5/5.85  D/K NICAM FM" },
	{ 0x000c, "6.5/5.85  D/K NICAM FM (HDEV2)" },
	{ 0x0020, "4.5  M BTSC-Stereo" },
	{ 0x0021, "4.5  M BTSC-Mono + SAP" },
	{ 0x0030, "4.5  M EIA-J Japan Stereo" },
	{ 0x0040, "10.7  FM-Stereo Radio" },
	{ 0x0050, "6.5  SAT-Mono" },
	{ 0x0051, "7.02/7.20  SAT-Stereo" },
	{ 0x0060, "7.2  SAT ADR" },
	{     -1, NULL }, /* EOF */
};

static void msp_mode_detect_d(struct i2c_client *c)
{
	struct msp3400c *msp = c->data;
	int val;
	
newchannel:
	msp->control&=~MSP_THREAD_NEWCHANNEL;	
	msp_write(c, MSP_DEM, 0x20, 0x0001);
	for (;;) {
		current->state = TASK_INTERRUPTIBLE;
		schedule_timeout(HZ/10);
		if (signal_pending(current))
			goto finish;
		if (msp->control & MSP_THREAD_NEWCHANNEL)
			goto newchannel;

		/* check results */
		val = msp_read(c, MSP_DEM, 0x7e);
		if (val < 0x07ff)
			break;
		dprintk("msp34xx: detection still in progress\n");
	}
	if (debug) {
		int i;
		for (i = 0; modelist[i].name != NULL; i++)
			if (modelist[i].retval == val)
				break;
		printk("msp34xx: detected mode: %s (0x%04x)\n",
		       modelist[i].name ? modelist[i].name : "unknown",val);
	}
	
	switch (val) {
	case 0x0009:
		msp->mode = MSP_MODE_AM_NICAM;
		break;
	case 0x0008:
		msp->mode = MSP_MODE_FM_NICAM1;
		break;
	case 0x0003:
		msp->mode = MSP_MODE_FM_TERRA;
		break;
	}

finish:
	msp->control &= ~MSP_THREAD_NOCHANNEL;
	msp->control |= MSP_THREAD_ADJVOL;
}

/* ---------------------------------------------------------------------- */
/*  A kernel thread for msp3400 control -- we don't want to block the     */
/*  in the ioctl while doing the sound carrier & stereo detect            */

static void msp_stereo_wake(unsigned long data)
{
	struct msp3400c *msp = (struct msp3400c *)data;
	wake_up_interruptible(&msp->wq);
}

static int msp_thread(void *data)
{
	struct i2c_client *c = data;
	struct msp3400c *msp = c->data;
    
#ifdef __SMP__
	lock_kernel();
#endif
    
	exit_mm(current);
	current->session = 1;
	current->pgrp = 1;
	sigfillset(&current->blocked);
	current->fs->umask = 0;
	strcpy(current->comm,"msp34xx");

	msp->wq     = NULL;
	msp->thread = current;

#ifdef __SMP__
	unlock_kernel();
#endif

	printk("msp34xx: daemon started\n");
	if(msp->notify != NULL)
		up(msp->notify);
		
	for (;;) {
		if (msp->rmmod)
			goto done;
		if (debug > 1)
			printk("msp34xx: thread: sleep\n");

		interruptible_sleep_on(&msp->wq);
		del_timer(&msp->wake_stereo);
		
		if (debug > 1)
			printk("msp34xx: thread: wakeup\n");
		if ((msp->control & MSP_THREAD_RMMOD)
		      || signal_pending(current))
			goto done;
			
		if (msp->control & MSP_THREAD_NEWCHANNEL)
			goto newchannel;
		else if (msp->control & MSP_THREAD_NOCHANNEL)
			continue;

		/* do that stereo/multilang handling */
		if ( msp_getstereo(c) || (msp->control & MSP_THREAD_ADJVOL)) {
			if (msp->stereo & VIDEO_SOUND_LANG1)
				msp_setstereo(c, VIDEO_SOUND_LANG1);
			else if (msp->stereo & VIDEO_SOUND_STEREO)
				msp_setstereo(c, VIDEO_SOUND_STEREO);
			else 
				msp_setstereo(c, VIDEO_SOUND_MONO);
		}

		if (msp->control & MSP_THREAD_NEWCHANNEL)
			goto newchannel;
		else if (msp->control & MSP_THREAD_NOCHANNEL)
			continue;
		
		if (msp->control & MSP_THREAD_ADJVOL)
		{
			msp->control  &= ~MSP_THREAD_ADJVOL;
			msp_setvolume(c,msp->volume);
		}
	
		msp->wake_stereo.expires = jiffies + 5*HZ;
		add_timer(&msp->wake_stereo);
		continue;

newchannel:
		msp_mode_detect_c(c);

		msp->wake_stereo.expires = jiffies + HZ/4;
		add_timer(&msp->wake_stereo);
	}

done:
	dprintk("msp34xx: thread: exit\n");
	msp->thread = NULL;

	if(msp->notify != NULL)
		up(msp->notify);
	return 0;
}

static int msp_command(struct i2c_client *c, unsigned int cmd, void *arg)
{
	struct msp3400c *msp  = c->data;
	int             *iarg = (int*)arg;
        __u16           *sarg = arg;

	switch (cmd) {
	case AUDC_SET_RADIO:
		del_timer(&msp->wake_stereo);
		msp->control|=MSP_THREAD_NOCHANNEL;
		break;

	case AUDC_SET_TVNORM:
		msp->norm = *iarg;
		break;

	case AUDC_SWITCH_MUTE:
		/* channels switching step one -- mute */
		if (msp->control & MSP_THREAD_NOCHANNEL)
			break;
#ifdef MUTE_ON_CHANGE
		msp_setvolume(c, 0);
		mdelay(50);
#endif
		break;
	case AUDC_NEWCHANNEL:
		/* channels switching step two -- trigger sound carrier scan */
		msp->control|=MSP_THREAD_NEWCHANNEL;
		wake_up_interruptible(&msp->wq);
		break;

	case AUDC_GET_STEREO:
		msp_getstereo(c);
		*sarg = msp->stereo;
		break;
	case AUDC_SET_STEREO:
		msp_setstereo(c,*sarg);
		break;

	case AUDC_GET_VOLUME:
		*sarg = msp->volume;
		break;
	case AUDC_SET_VOLUME:
		msp->volume = *sarg;
		msp_setvolume(c, msp->volume);
		break;

        case AUDC_SET_MUTE:
        	msp->audiomute=1;
        	break;
 	case AUDC_SET_UNMUTE:		
 		msp->audiomute=0;
  		break;

	case AUDC_GET_BASS:
		*sarg = msp->bass;
		break;
	case AUDC_SET_BASS:
		if (msp->bass == *sarg) break;
		msp->bass = *sarg;
		msp_setbass(c,msp->bass);
		break;

	case AUDC_GET_TREBLE:
		*sarg = msp->treble;
		break;
	case AUDC_SET_TREBLE:
		if (msp->treble == *sarg) break;
		msp->treble = *sarg;
		msp_settreble(c,msp->treble);
		break;

	case AUDC_GET_DC:
		*sarg = ((int)msp_read(c, MSP_DSP, 0x1b) +
			 (int)msp_read(c, MSP_DSP, 0x1c));
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int msp_detect(struct i2c_client *c)
{
	struct msp3400c *msp = c->data;
	u8 rev[5];
	int i, rev1, rev2;

	rev1 = msp_read(c, MSP_DSP, 0x1e);
	rev2 = msp_read(c, MSP_DSP, 0x1f);

	if (0 == rev1 && 0 == rev2)
		return -1;
	
	rev[0] = (rev2>>8 & 0xff) / 10;
	rev[1] = (rev2>>8 & 0xff) % 10;
	rev[2] = (rev1&0xff) + '@';
	rev[3] = ((rev1>>8)&0xff) + '@';
	rev[4] = (rev2&0x1f);
	
	sprintf(c->name,"MSP34%02d%c-%c%d",
		((10*rev[0])|rev[1]), rev[2], rev[3], rev[4]);
	         
	/* Add features from older revisions */
	msp->features = 0;
	for (i = 0; i < MSP_COUNT(msp_list); i++)
		if ( msp_list[i].rev[0] == rev[0] &&
		     msp_list[i].rev[1] == rev[1] &&
		     msp_list[i].rev[2] == rev[2] &&
		     msp_list[i].rev[3] <= rev[3] &&
		     msp_list[i].rev[4] <= rev[4] )
		{
			msp->features |= msp_list[i].features;
			msp->sstandard |= msp_list[i].sstandard;
		}
		     	
      	dprintk(KERN_INFO "msp34xx: Found %s with:\n", c->name);
	for (i = 0; i < MSP_FEATURE_COUNT(f_list); i++)
		if (msp->features & f_list[i].feature_id)
			dprintk(KERN_INFO "msp34xx:  %s\n", f_list[i].desc);

	msp->features_on = (msp->features & MSP_FASTMUTE);
	if (avc)
		msp->features_on |= (msp->features & MSP_AVC);
		
#if 0
	/* this will turn on a 1kHz beep - might be useful for debugging... */
	msp_write(c,MSP_DSP, 0x0014, 0x1040);
#endif

	return 0;
}	

/* ----------------------------------------------------------------------- */
/* i2c access functions							   */

static int msp_reset(struct i2c_client *client)
{
	static char reset_off[3] = { 0x00, 0x80, 0x00 };
	static char reset_on[3]  = { 0x00, 0x00, 0x00 };

	i2c_master_send(client,reset_off,3);  /* XXX ignore errors here */
	if (3 != i2c_master_send(client,reset_on, 3))
		return -1;
	return 0;
}

static int msp_read(struct i2c_client *client, int dev, int addr)
{
	unsigned char write[3];
	unsigned char read[2];
	struct i2c_msg msgs[2] = {
		{ client->addr, 0,        3, write },
		{ client->addr, I2C_M_RD, 2, read  }
	};

	write[0] = dev+1;
	write[1] = addr >> 8;
	write[2] = addr & 0xff;
	
	
	if (2 != i2c_transfer(client->adapter,msgs,2)) {
		printk(KERN_WARNING
		       "msp34xx: I/O error, trying reset (read %s 0x%x)\n",
		       (dev == MSP_DEM) ? "Demod" : "Audio", addr);
		msp_reset(client);
	}
	return read[0] << 8 | read[1];
}

static int msp_write(struct i2c_client *client, int dev, int addr, int val)
{
	unsigned char buffer[5];

	buffer[0] = dev;
	buffer[1] = addr >> 8;
	buffer[2] = addr &  0xff;
	buffer[3] = val  >> 8;
	buffer[4] = val  &  0xff;

	if (5 != i2c_master_send(client, buffer, 5)) {
		printk(KERN_WARNING
		       "msp34xx: I/O error, trying reset (write %s 0x%x)\n",
		       (dev == MSP_DEM) ? "Demod" : "Audio", addr);
		msp_reset(client);
		return -1;
	}
	return 0;
}

/* ----------------------------------------------------------------------- */
/* i2c client registration                                                 */

static int msp_attach(struct i2c_adapter *adap)
{
	struct semaphore sem = MUTEX_LOCKED;
	struct msp3400c *msp = 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));
	c->data = msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL);

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

	msp->bass = msp->treble = 32768;
	msp->volume = msp->balance = 32768;

	if (-1 == msp_reset(c)) {
		kfree(msp);
		dprintk("msp34xx: no chip found\n");
		return -1;
	}
    
	if (-1 == msp_detect(c)) {
		kfree(msp);
		printk("msp34xx: error while reading chip version\n");
		return -1;
	}

	i2c_attach_client(c);
	
	/* timer for stereo checking */
	msp->wake_stereo.function = msp_stereo_wake;
	msp->wake_stereo.data     = (unsigned long)msp;

	/* hello world :-) */
	printk(KERN_INFO "msp34xx: init: chip=%s",c->name);
	printk("\n");

	/* startup control thread */
	msp->wq     = NULL;
	msp->notify = &sem;
	kernel_thread(msp_thread, (void *)c, 0);
	down(&sem);
	msp->notify = NULL;
	wake_up_interruptible(&msp->wq);

	/* done */
	return 0;
}

static int msp_detach(struct i2c_client *c)
{
	struct semaphore sem = MUTEX_LOCKED;
	struct msp3400c *msp  = c->data;
    
	/* shutdown control thread */
	del_timer(&msp->wake_stereo);
	if (msp->thread) 
	{
		msp->notify = &sem;
		msp->control |= MSP_THREAD_RMMOD;
		wake_up_interruptible(&msp->wq);
		down(&sem);
		msp->notify = NULL;
	}
    
	i2c_detach_client(c);

	msp_reset(c);

	kfree(msp);
	return 0;
}

static struct i2c_driver driver = {
	"i2c msp34xx driver",
	-1,
	DF_NOTIFY,
	msp_attach,
	msp_detach,
	msp_command,
};

static struct i2c_client client_template = 
{
	"unset",
	I2C_DRIVERID_MSP3400,
	0,
	0x80>>1,
	NULL,
	&driver
};

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

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

/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-basic-offset: 8
 * End:
 */
