Index: ivtv/driver/Makefile
diff -u ivtv/driver/Makefile:1.2 ivtv/driver/Makefile:1.2.2.1
--- ivtv/driver/Makefile:1.2	Sun Mar  7 11:40:38 2004
+++ ivtv/driver/Makefile	Sun Apr 25 20:06:27 2004
@@ -10,7 +10,7 @@
 
 include /boot/config-$(KERNVER)
 
-CFLAGS = -D__KERNEL__ -D__KERNEL_SYSCALLS__ -DMODULE -DMODVERSIONS -I$(KERNELDIR)/include -O2 -fomit-frame-pointer -march=i586 -mcpu=i586 -fno-strict-aliasing -Wno-unused -include $(KERNELDIR)/include/linux/modversions.h
+CFLAGS = -Wall -D__KERNEL__ -D__KERNEL_SYSCALLS__ -DMODULE -DMODVERSIONS -I$(KERNELDIR)/include -O2 -fomit-frame-pointer -march=i586 -mcpu=i586 -fno-strict-aliasing -Wno-unused -include $(KERNELDIR)/include/linux/modversions.h
 
 # uncomment if you use i2c 2.8.0+
 #CFLAGS += -DNEW_I2C
Index: ivtv/driver/ivtv-api.c
diff -u ivtv/driver/ivtv-api.c:1.1 ivtv/driver/ivtv-api.c:1.1.2.4
--- ivtv/driver/ivtv-api.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/driver/ivtv-api.c	Sat May  8 17:50:42 2004
@@ -7,6 +7,7 @@
  */
 
 #include "ivtv.h"
+#include 
 
 /* Fix the v4l2 api breakage - need to define if still using the old api */
 #ifndef VIDIOC_OVERLAY_OLD
@@ -31,6 +32,12 @@
 static int ivtv_v4l2_ioctl(struct inode *inode, struct file *filp,
 			   unsigned int cmd, unsigned long arg);
 static int ivtv_v4l2_pre_init(struct ivtv *itv);
+static void ivtv_dualwatch_set_enabled(struct ivtv* ivtv, int enabled);
+static void ivtv_dualwatch_start_kthread(struct ivtv* ivtv);
+static void ivtv_dualwatch_stop_kthread(struct ivtv* ivtv);
+static void ivtv_dualwatch_init_kthread(struct ivtv *ivtv);
+static void ivtv_dualwatch_exit_kthread(struct ivtv *ivtv);
+static void ivtv_dualwatch_set_dual(struct ivtv *itv);
 
 struct file_operations ivtv_v4l2_fops = {
 	read:           ivtv_v4l2_read,
@@ -1144,6 +1151,7 @@
         x = ivtv_api(itv->enc_mbox, &itv->enc_msem, IVTV_API_ASSIGN_AUDIO_PROPERTIES,
 		&result, 1, &data[0]);
 	if (x) IVTV_DEBUG(IVTV_DEBUG_ERR, "init error 12. Code %d\n",x);
+        ivtv_dualwatch_start_encoding(itv);
 
 	/*assign dnr filter mode */
 	data[0] = itv->v4l2.codec.dnr_mode;
@@ -1481,6 +1489,7 @@
 
 	IVTV_DEBUG(IVTV_DEBUG_INFO,"v4l2 unregister\n");
 
+        ivtv_dualwatch_stop_encoding(itv); /* in case it's still here */
 	if (atomic_read(&itv->capturing) >= 0) ivtv_stop_all_captures(itv);
 	if (itv->v4l2.tuner.table.tuner) kfree(itv->v4l2.tuner.table.tuner);
 	for (x=0; x < itv->v4l2.streamcount; x++) {
@@ -2627,12 +2636,16 @@
 		/* Pause to let sound calm down */
 		ivtv_sleep_timeout(HZ/33);
 
-		if (0 != ivtv_pause_encoder(itv, 1)) 
-			IVTV_DEBUG(IVTV_DEBUG_ERR, "Freq: Error unpausing stream\n");
+		ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR,
+				     VIDIOCSFREQ,&itv->v4l2.freq.frequency);
+
 
 		/* Unmute */
 		ivtv_set_audio(itv,tmk_audio_mapping);
 
+		if (0 != ivtv_pause_encoder(itv, 1)) 
+			IVTV_DEBUG(IVTV_DEBUG_ERR, "Freq: Error unpausing stream\n");
+
 		break;
 	}
 	case VIDIOC_ENUMSTD: {
@@ -2720,10 +2733,11 @@
 
 		if ((vt->index < 0) || (vt->index >= itv->v4l2.tuner.count))
 			return -EINVAL;
-		/* looks like tuner.c doesn't support selection 
-		 * fallback to stereo... */
-		vt->audmode = V4L2_TUNER_MODE_STEREO;
 
+
+ 		/* Regarding audio, the demodulator is our tuner */
+		ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR,VIDIOC_S_TUNER, vt);
+                ivtv_dualwatch_set_enabled(itv, vt->audmode==V4L2_TUNER_MODE_STEREO);
 		break;
 	}
 	case VIDIOC_G_TUNER: {
@@ -2748,6 +2762,8 @@
 		} else {
 			vt->signal = 0;
 		}
+ 		/* ask msp3400 to set audmode and rxsubchans */
+                ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR,VIDIOC_G_TUNER, vt);
 		break;
 	}
 	case MSP_SET_MATRIX: {
@@ -2824,8 +2840,7 @@
 		        memcpy (&itv->v4l2.streams[0].format,
 				vfmt, sizeof(struct v4l2_format));
 		}
-
-		break;
+                break;
 	}
 	case IVTV_IOCTL_GET_DEBUG_LEVEL: {
 		//FIXME copy_from_user needed
@@ -2869,3 +2884,242 @@
 	return 0;
 }
 
+/**
+ * Change the DUAL flag 
+ */
+static void ivtv_dualwatch_set_dual(struct ivtv *itv) 
+{
+        if(!itv->dualwatch.encoding)
+                return;
+
+        u32 audio_bitmap = itv->dualwatch.audio_bitmap;
+        int stereo_flag = audio_bitmap&IVTV_CODEC_AUDIO_MPEG_STEREO_MASK;
+        int goal_stereo_flag;
+        if(itv->dualwatch.enabled) {
+                struct v4l2_tuner vt;
+                memset(&vt, 0, sizeof(vt));
+                ivtv_call_i2c_client(itv,
+                                     IVTV_MSP3400_I2C_ADDR,
+                                     VIDIOC_G_TUNER,
+                                     &vt);
+                if(vt.rxsubchans&V4L2_TUNER_SUB_LANG2) {
+                        goal_stereo_flag = IVTV_CODEC_AUDIO_MPEG_STEREO_DUAL;
+                } else {
+                        goal_stereo_flag = IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO;
+                }
+        } else {
+                if(stereo_flag==IVTV_CODEC_AUDIO_MPEG_STEREO_DUAL) {
+                        goal_stereo_flag = IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO;
+                } else {
+                        goal_stereo_flag = stereo_flag;
+                }
+        }
+
+        if(stereo_flag==IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO
+           && ((itv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_MPEG_STEREO_MASK ) == IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT)) {
+                goal_stereo_flag = IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT;
+        }
+        
+        if(goal_stereo_flag != stereo_flag) {
+		u32 result; /* ignored */
+                u32 data[IVTV_MBOX_MAX_DATA]; /* has to be this size, even if mostly unused */
+                u32 new_bitmap = 
+                        goal_stereo_flag 
+                        | (itv->dualwatch.audio_bitmap & ~IVTV_CODEC_AUDIO_MPEG_STEREO_MASK);
+
+                printk("ivtv_dualwatch: change stereo flag from 0x%x to 0x%x\n",
+                       stereo_flag, 
+                       goal_stereo_flag);
+                
+                data[0] = itv->v4l2.codec.audio_bitmap;
+                /* not ivtv_api, because may be called from
+                 * a timer. It's ok if it fails, we'll try again later.
+                 */
+                if(ivtv_api(itv->enc_mbox, 
+                            &itv->enc_msem,
+                            IVTV_API_ASSIGN_AUDIO_PROPERTIES,
+                            &result,
+                            1, 
+                            &data[0])==0) {
+                        itv->dualwatch.audio_bitmap = new_bitmap;
+                        printk("ivtv_dualwatch: changed stereo flag SUCCESS\n");         
+                }
+                else 
+                        printk("ivtv_dualwatch: change stereo flag FAILURE\n");
+        }
+}
+
+/* start the thread if necessary */
+static void ivtv_dualwatch_update(struct ivtv* itv)
+{
+        ivtv_dualwatch_set_dual(itv);
+
+        /* start timer, but only if necessary (timer stops automatically) */
+        if(itv->dualwatch.encoding 
+           && itv->dualwatch.enabled) {
+                if(!itv->dualwatch.thread)
+                        ivtv_dualwatch_start_kthread(itv);
+        } else {
+                if(itv->dualwatch.thread)
+                        ivtv_dualwatch_stop_kthread(itv);
+        }
+}
+/* enable/disable dualwatch (= enable/disable full stereo (not lang1/lang2 or mono) mode) */
+static void ivtv_dualwatch_set_enabled(struct ivtv* itv, int enabled)
+{
+        itv->dualwatch.enabled=enabled;
+        ivtv_dualwatch_update(itv);
+}
+/* start encoding => start the thread if DUALWATCH is enabled */
+void ivtv_dualwatch_start_encoding(struct ivtv* itv)
+{
+        if(itv->dualwatch.encoding)
+                return;
+        itv->dualwatch.encoding = 1;
+        itv->dualwatch.audio_bitmap = itv->v4l2.codec.audio_bitmap;
+        ivtv_dualwatch_update(itv);
+}
+
+/* stop encoding => stop the thread if DUALWATCH is enabled */
+void ivtv_dualwatch_stop_encoding(struct ivtv* itv)
+{
+        if(!itv->dualwatch.encoding)
+                return;
+        if(itv->dualwatch.thread)
+                ivtv_dualwatch_stop_kthread(itv);
+        itv->dualwatch.encoding = 0; /* after stopping the thread */
+}
+
+/* function executed on the thread */
+static int ivtv_dualwatch_kthread(void *ptr) {
+   struct ivtv* ivtv = (struct ivtv*)ptr;
+   struct ivtv_dualwatch *kthread = &ivtv->dualwatch;
+
+   ivtv_dualwatch_init_kthread(ivtv);
+
+   for(;;) {
+           interruptible_sleep_on_timeout(&kthread->queue, HZ);
+           
+           mb();
+           
+           if (kthread->terminate)
+                   break;    
+           
+           ivtv_dualwatch_set_dual(ivtv);
+   }
+   ivtv_dualwatch_exit_kthread(ivtv);
+   return 0;
+}
+
+static void ivtv_dualwatch_kthread_launcher(void *data)
+{
+        kernel_thread(ivtv_dualwatch_kthread, data, 0);
+}
+
+static void ivtv_dualwatch_start_kthread(struct ivtv* ivtv) {
+        /* adapted from example by Martin Frey 
+         * http://www.scs.ch/~frey/linux/kernelthreads.html
+         */
+        struct ivtv_dualwatch *kthread = &ivtv->dualwatch;
+        if(kthread->thread)
+                return;
+
+        init_MUTEX_LOCKED(&kthread->startstop_sem);
+        kthread->tq.sync = 0;
+        INIT_LIST_HEAD(&kthread->tq.list);
+        kthread->tq.routine =  ivtv_dualwatch_kthread_launcher;
+        kthread->tq.data = ivtv;
+        
+        schedule_task(&kthread->tq);
+        
+        down(&kthread->startstop_sem);
+}
+
+static void ivtv_dualwatch_stop_kthread(struct ivtv* ivtv) {
+   struct ivtv_dualwatch *kthread = &ivtv->dualwatch;
+
+   if (!kthread->thread || kthread->terminate)
+	  return;
+
+   /* this function needs to be protected with the big
+      kernel lock (lock_kernel()). The lock must be
+      grabbed before changing the terminate
+      flag and released after the down() call. */
+   lock_kernel();
+   if(kthread->thread) /* paranoid mode */
+   {
+	  init_MUTEX_LOCKED(&kthread->startstop_sem);
+	  /* We need to do a memory barrier here to be sure that
+		 the flags are visible on all CPUs. 
+	  */
+	  mb();
+	  /* set flag to request thread termination */
+	  kthread->terminate = 1;
+	  
+	  /* We need to do a memory barrier here to be sure that
+		 the flags are visible on all CPUs. 
+	  */
+	  mb();
+          kill_proc(kthread->thread->pid, SIGKILL, 1);
+          down(&kthread->startstop_sem);
+   }
+   /* release the big kernel lock */
+   unlock_kernel();
+
+   /* now we are sure the thread is in zombie state. We
+      notify keventd to clean the process up.
+   */
+   kill_proc(2, SIGCHLD, 1);
+}
+
+/* initialize new created thread. Called by the new thread. */
+static void ivtv_dualwatch_init_kthread(struct ivtv *ivtv)
+{
+   struct ivtv_dualwatch *kthread = &ivtv->dualwatch;
+   /* lock the kernel. A new kernel thread starts without
+	  the big kernel lock, regardless of the lock state
+	  of the creator (the lock level is *not* inheritated)
+        */
+   lock_kernel();
+   {   
+	  /* fill in thread structure */
+	  kthread->thread = current;
+	  
+	  siginitsetinv(¤t->blocked, sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM));
+	  init_waitqueue_head(&kthread->queue);
+	  
+	  kthread->terminate = 0;
+	  sprintf(current->comm, "ivtv_dualwatch");
+   }
+   unlock_kernel();
+   
+   /* tell the creator that we are ready and let him continue */
+   up(&kthread->startstop_sem);
+}
+
+/* cleanup of thread. Called by the exiting thread. */
+static void ivtv_dualwatch_exit_kthread(struct ivtv *ivtv)
+{
+   struct ivtv_dualwatch *kthread = &ivtv->dualwatch;
+
+   /* lock the kernel, the exit will unlock it */
+   lock_kernel();
+   kthread->thread = NULL;
+   mb();
+
+   if(kthread->terminate) {
+           /* notify the stop_kthread() routine that we are terminating. */
+           up(&kthread->startstop_sem);
+   }
+   /* the kernel_thread that called clone() does a do_exit here. */
+
+   /* there is no race here between execution of the "killer" and real termination
+	  of the thread (race window between up and do_exit), since both the
+	  thread and the "killer" function are running with the kernel lock held.
+	  The kernel lock will be freed after the thread exited, so the code
+	  is really not executed anymore as soon as the unload functions gets
+	  the kernel lock back.
+	  The init process may not have made the cleanup of the process here,
+	  but the cleanup can be done safely with the module unloaded.
+   */
+}
Index: ivtv/driver/ivtv-driver.c
diff -u ivtv/driver/ivtv-driver.c:1.2 ivtv/driver/ivtv-driver.c:1.2.2.2
--- ivtv/driver/ivtv-driver.c:1.2	Sun Mar  7 11:41:14 2004
+++ ivtv/driver/ivtv-driver.c	Sat May  8 17:50:42 2004
@@ -1057,6 +1057,8 @@
 
 	IVTV_DEBUG(IVTV_DEBUG_INFO, "Stop Capture\n");
 
+	ivtv_dualwatch_stop_encoding(itv); 
+
 	/* sem_lock must be held */
 	IVTV_ASSERT(ivtv_sem_count(&itv->sem_lock) <= 0);
 
Index: ivtv/driver/ivtv.h
diff -u ivtv/driver/ivtv.h:1.2 ivtv/driver/ivtv.h:1.2.2.3
--- ivtv/driver/ivtv.h:1.2	Sun Mar  7 11:41:14 2004
+++ ivtv/driver/ivtv.h	Sat May  8 17:50:42 2004
@@ -527,7 +527,7 @@
 /* For use with IVTV_IOC_G_CODEC and IVTV_IOC_S_CODEC */
 struct ivtv_ioctl_codec {
 	u32 aspect;
-	u32 audio_bitmap;
+	u32 audio_bitmap; 
 	u32 bframes;
 	u32 bitrate_mode;
 	u32 bitrate;
@@ -543,6 +543,15 @@
 	u32 stream_type;
 };
 
+/* audio_bitmap: 
+ * bits 0-15 follow iso11172, mostly, see http://ivtv.sourceforge.net/firmware-api.html. 
+ */
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_MASK        (0x03 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO      (0x00 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT       (0x01 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_DUAL        (0x02 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_MONO        (0x03 << 8)
+
 extern int ivtv_debug;
 extern int ivtv_pal;
 
@@ -691,6 +700,20 @@
 	dma_addr_t sg_dma_handle;
 };
 
+/* dualwatch thread and flags */
+struct ivtv_dualwatch {
+        int enabled; /* 1=stereo mode enabled */
+        int encoding; /* 1=ivtv currently encoding */
+        u32 audio_bitmap; /* current real value of audio_bitmap */
+
+        /* control thread */
+        struct task_struct *thread;
+        struct tq_struct tq;
+        struct semaphore startstop_sem;
+        wait_queue_head_t queue;
+        int terminate;
+};
+
 /* Stuct to hold info about ivtv cards */
 struct ivtv {
 	int card_type; /* pvr 250 rev1, 250 rev2, 350 are options so far */
@@ -748,6 +771,9 @@
 	struct ivtv_state	state;
 	struct ivtv_v4l2	v4l2;
 	struct list_head	client_list;
+
+        struct ivtv_dualwatch   dualwatch;
+
 };
 
 /* Globals */
@@ -831,6 +857,9 @@
 extern int ivtv_move_queue(struct ivtv *itv, struct ivtv_buffer_list *src,
 			   struct ivtv_buffer_list *dst);
 
+extern void ivtv_dualwatch_stop_encoding(struct ivtv* itv);
+extern void ivtv_dualwatch_start_encoding(struct ivtv* itv);
+
 /* Hardware/IRQ */
 extern void ivtv_set_irq_mask(struct ivtv *itv, unsigned long mask);
 extern void ivtv_clear_irq_mask(struct ivtv *itv, unsigned long mask);
Index: ivtv/driver/msp3400.c
diff -u ivtv/driver/msp3400.c:1.1 ivtv/driver/msp3400.c:1.1.2.6
--- ivtv/driver/msp3400.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/driver/msp3400.c	Sat May  8 17:50:42 2004
@@ -44,6 +44,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -71,6 +72,7 @@
 			      the autoscan seems work well only with FM... */
 static int standard = 1;   /* Override auto detect of audio standard, if needed. */
 static int simple  = -1;   /* use short programming (>= msp3410 only) */
+static int simpler = -1;   /* use short programming for (msp34xxg+) */
 static int dolby   = 0;
 
 #define DFP_COUNT 0x41
@@ -79,8 +81,11 @@
 	0x0b, 0x0d, 0x0e, 0x10
 };
 
+#define IS_MSP34XX_G(msp) ((msp)->simpler)
 struct msp3400c {
 	int simple;
+	int simpler;
+        int source; /* see msp34xxg_set_source */
 	int nicam;
 	int mode;
 	int norm;
@@ -120,6 +125,7 @@
 MODULE_PARM(once,"i");
 MODULE_PARM(debug,"i");
 MODULE_PARM(simple,"i");
+MODULE_PARM(simpler,"i");
 MODULE_PARM(standard,"i");
 MODULE_PARM(amsound,"i");
 MODULE_PARM(dolby,"i");
@@ -195,7 +201,8 @@
 		msp3400c_reset(client);
 		return -1;
 	}
-        return read[0] << 8 | read[1];
+	int retval = read[0] << 8 | read[1];
+	return retval;
 }
 
 static int
@@ -482,6 +489,14 @@
 	int nicam=0; /* channel source: FM/AM or nicam */
 	int src=0;
 
+        if(IS_MSP34XX_G(msp)) {
+                /* this method would break everything, let's make sure 
+                 * it's never called
+                 */
+                dprintk("msp34xxg: DEBUG WARNING setstereo called with mode=%d instead of set_source (ignored)\n", mode);
+                return;
+        }
+
 	/* switch demodulator */
 	switch (msp->mode) {
 	case MSP_MODE_FM_TERRA:
@@ -616,6 +631,17 @@
 	}
 }
 
+/* if the dfp_regs is set, set what's in there. Otherwise, set the default value */
+static int
+msp3400c_write_dfp_with_default(struct i2c_client *client, int addr, int default_value)
+{
+	struct msp3400c *msp = client->data;
+        int value=default_value;
+        if( addr< DFP_COUNT && -1 != msp->dfp_regs[addr] )
+                value=msp->dfp_regs[addr];
+        return msp3400c_write(client, I2C_MSP3400C_DFP, addr, value);
+}
+
 /* ----------------------------------------------------------------------- */
 
 struct REGISTER_DUMP {
@@ -1004,6 +1030,24 @@
 	{ 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2  SAT ADR" },
 	{     -1, 0, 0, NULL }, /* EOF */
 };
+
+static int msp3400c_modus(int norm)
+{
+        switch(norm) {
+        case VIDEO_MODE_PAL:
+                return 0x1003;
+        case VIDEO_MODE_NTSC:  /* BTSC */
+                return 0x2003;
+        case VIDEO_MODE_SECAM: 
+                return 0x0003;
+        case VIDEO_MODE_RADIO: 
+                return  0x0003;
+        case VIDEO_MODE_AUTO:
+                return 0x2003;
+        default:
+                return 0x0003;
+        }
+}
  
 static int msp3410d_thread(void *data)
 {
@@ -1066,29 +1110,24 @@
 		msp3400c_reset(client);
 
 		/* start autodetect */
+                mode = msp3400c_modus(msp->norm);
 		switch (msp->norm) {
 		case VIDEO_MODE_PAL:
-			mode = 0x1003;
 			std  = standard;
 			break;
 		case VIDEO_MODE_NTSC:  /* BTSC */
-			mode = 0x2003;
 			std  = 0x0020;
 			break;
 		case VIDEO_MODE_SECAM: 
-			mode = 0x0003;
 			std  = standard;
 			break;
 		case VIDEO_MODE_RADIO: 
-			mode = 0x0003;
 			std  = 0x0040;
 			break;
 		case VIDEO_MODE_AUTO:
-			mode = 0x2003;
 			std  = standard;
 			break;
 		default:
-			mode = 0x0003;
 			std  = standard;
 			break;
 		}
@@ -1220,6 +1259,309 @@
 	return 0;
 }
 
+
+
+/* ----------------------------------------------------------------------- */
+/* msp34xxG + (simpler no-thread)                                          */
+/* this one uses both automatic standard detection and automatic sound     */
+/* select which are available in the newer G versions                      */
+/* struct msp: only norm, acb and source are really used in this mode      */
+
+static int msp34xxg_reset(struct i2c_client *client);
+static void msp34xxg_channelchange(struct i2c_client *client);
+static void msp34xxg_set_source(struct i2c_client *client, int source);
+static int msp34xxg_get_v4l1_stereo(struct i2c_client *client);
+static void msp34xxg_set_v4l1_stereo(struct i2c_client *client, int stereo);
+static void msp34xxg_get_v4l2_stereo(struct i2c_client *client, int *rxsubchans, int *audmode);
+static void msp34xxg_set_v4l2_stereo(struct i2c_client *client, int audmode);
+
+
+
+/* (re-)initialize the msp34xxg, according to the current norm in msp->norm 
+ * return 0 if it worked, -1 if it failed
+ */
+static int msp34xxg_reset(struct i2c_client *client)
+{
+	struct msp3400c *msp = client->data;
+        int i;
+        int modus;
+
+        if(msp3400c_reset(client))
+                return -1;
+        
+        /* make sure that input/output is muted (paranoid mode) */
+        if(msp3400c_write(client,
+                          I2C_MSP3400C_DFP,
+                          0x13, /* ACB */
+                          0x0f20 /* mute DSP input, mute SCART 1 */))
+                return -1;
+
+        /* step-by-step initialisation, as described in the manual */
+        modus = msp3400c_modus(msp->norm);
+        modus &= ~0x03; /* STATUS_CHANGE=0 */
+        modus |= 0x01;  /* AUTOMATIC_SOUND_DETECTION=1 */
+        if(msp3400c_write(client, 
+                          I2C_MSP3400C_DEM, 
+                          0x30/*MODUS*/, 
+                          modus))
+                return -1;
+        
+        /* write the dfps that may have an influence on standard/audio autodetection right now  */
+        msp34xxg_set_source(client, msp->source);
+        
+        if(msp3400c_write_dfp_with_default(client,
+                                           0x0e, /* AM/FM Prescale */
+                                           0x3000 /* default: [15:8] 75khz deviation */))
+                return -1;
+        
+        if(msp3400c_write_dfp_with_default(client,
+                                           0x10, /* NICAM Prescale */
+                                           0x5a00 /* default: 9db gain (as recommended) */))
+                return -1;
+
+        if(msp3400c_write(client,
+                          I2C_MSP3400C_DEM,
+                          0x20, /* STANDARD SELECT  */
+                          0x01 /* automatic standard select*/))
+                return -1;
+        
+        dprintk("msp34xxg: triggered autodetect, waiting for result\n");        
+        
+        /* triggered autodetect */
+        for (i=0;i<10;i++) {
+                int val;
+                current->state   = TASK_INTERRUPTIBLE;
+                schedule_timeout(HZ/10);
+                if (signal_pending(current))
+                        return -1; /* failed */
+                
+                /* check results */
+                val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e);
+                if (val < 0x07ff)
+                        break;
+                dprintk("msp34xxg: detection still in progress\n");
+        }
+        if(i==10) {
+                dprintk("msp34xxg: detection still in progress after 10 tries. giving up.\n");
+                return -1;
+        }
+        
+        /* unmute: dispatch sound to scart output, set scart volume */
+        dprintk("msp34xxg: unmute\n");        
+        
+        msp3400c_setbass(client, msp->bass); 
+        msp3400c_settreble(client, msp->treble);
+        msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
+        
+        /* restore other dfp's */
+        msp3400c_restore_dfp(client);
+        
+        /* restore ACB */
+        if(msp3400c_write(client,
+                          I2C_MSP3400C_DFP,
+                          0x13, /* ACB */
+                          msp->acb))
+                return -1;
+        
+        return 0/*success*/;
+}
+
+/* tell the msp34xxg that there's been a channel (frequency) change
+ * and that stereo must be updated. norm stays the same.
+ */
+static void msp34xxg_channelchange(struct i2c_client *client) 
+{
+	struct msp3400c *msp = client->data;
+        /* I'm pretty sure it's not necessary, but I feel safer this way (what
+         * if there are signals with different standards, or if autodetection
+         * failed initially because there was no signal at all ?)
+         */
+        msp34xxg_reset(client);
+}
+
+
+/* set the same 'source' for the loudspeaker, scart and quasi-peak detector 
+ * the value for source is the same as bit 15:8 of DFP registers 0x08,
+ * 0x0a and 0x0c: 0=mono, 1=stereo or A|B, 2=SCART, 3=stereo or A, 4=stereo or B
+ *
+ * this function replaces msp3400c_setstereo
+ */
+static void msp34xxg_set_source(struct i2c_client *client, int source)
+{
+	struct msp3400c *msp = client->data;
+
+        /* fix matrix mode to stereo and let the msp choose what
+         * to output according to 'source', as recommended 
+         */
+	int value = (source&0x07)<<8|(source==0 ? 0x00:0x20); 
+        dprintk("msp34xxg: set source to %d (0x%x)\n", source, value);
+        msp3400c_write(client, 
+                       I2C_MSP3400C_DFP,
+                       0x08, /* Loudspeaker Output */
+                       value);
+        msp3400c_write(client, 
+                       I2C_MSP3400C_DFP,
+                       0x0a, /* SCART1 DA Output */
+                       value);
+        msp3400c_write(client, 
+                       I2C_MSP3400C_DFP,
+                       0x0c, /* Quasi-peak detector */
+                       value);
+        /* 
+         * set identification threshold. I set it
+         * to a lower value that the default of 0x190
+         * because sometimes (rarely) bilingual 
+         * broadcasts are not detected on my machine. 
+         * this needs tuning. (recommended range 0x00a0-0x03c0)
+         */
+        msp3400c_write(client,
+                       I2C_MSP3400C_DEM,
+                       0x22, /* a2 threshold for stereo/bilingual */
+                       0x210);
+        msp->source=source;
+}
+
+/* get the current stereo mode and return it as a V4L1 stereo value  (Use V4L2 calls whenever possible) */
+static int msp34xxg_get_v4l1_stereo(struct i2c_client *client) 
+{
+        /* This is not really clear: the API says that the mode should
+         * be the current mode, but the old driver returned what the
+         * mode *could* be (like rxsubchans in v4l2). I'll just
+         * follow what the API says... 
+         *
+         * The most important point is, I think, that if someone does a get
+         * and then a set with this value, nothing should have changed. It
+         * used not to be the case and that was extremely confusing: one would
+         * do a GET, change mute to 1, then a SET with the same pointer and the 
+         * stereo mode would have changed, the watcher thread killed... (see 
+         * handler for VIDIOCSAUDIO)
+         */
+	struct msp3400c *msp = client->data;
+        switch(msp->source) {
+        case 1: /* stereo or A|B */
+                return VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2|VIDEO_SOUND_STEREO;
+        case 3: /* stereo or A */
+                return VIDEO_SOUND_LANG1|VIDEO_SOUND_STEREO;
+        case 4: /* stereo or B */
+                return VIDEO_SOUND_LANG1|VIDEO_SOUND_STEREO;
+        case 0: /* mono */
+                return VIDEO_SOUND_MONO;
+        default: /* scart input */
+                return VIDEO_SOUND_STEREO;
+        }
+}
+
+/* set the current stereo mode given a v4l1 mode (Use V4L2 calls whenever possible) */
+static void msp34xxg_set_v4l1_stereo(struct i2c_client *client, int stereo) 
+{
+        int source;
+        if( (stereo&VIDEO_SOUND_LANG1) && (stereo&VIDEO_SOUND_LANG2) ) 
+                source = 1; /* stereo or A|B */
+        else if(stereo&VIDEO_SOUND_LANG1)
+                source = 3; /* stereo or A */
+        else if(stereo&VIDEO_SOUND_LANG2)
+                source = 4; /* stereo or B */
+        else if(stereo&VIDEO_SOUND_STEREO)
+                source = 3; /* stereo or A */
+        else 
+                source = 0; /* mono only */
+        msp34xxg_set_source(client, source);
+}
+
+static void msp34xxg_get_v4l2_stereo(struct i2c_client *client, int *rxsubchans, int *audmode)
+{
+	struct msp3400c *msp = client->data;
+        
+        if(rxsubchans) {
+                int status = msp3400c_read(client, 
+                                           I2C_MSP3400C_DEM, 
+                                           0x0200 /* STATUS */);
+                int is_bilingual = status&0x100;
+                int is_stereo = status&0x40;
+                int val=0;
+                if(is_stereo)
+                        val |= V4L2_TUNER_SUB_STEREO;
+                else
+                        val |= V4L2_TUNER_SUB_MONO;
+                if(is_bilingual) {
+                        val |= V4L2_TUNER_SUB_LANG1|V4L2_TUNER_SUB_LANG2;
+                        /* I'm supposed to check whether it's SAP or not
+                         * and set only LANG2/SAP in this case. Yet, the MSP
+                         * does a lot of work to hide this and handle everything
+                         * the same way. I don't want to work around it so unless
+                         * this is a problem, I'll handle SAP just like lang1/lang2.
+                         */
+                }
+                dprintk("msp34xxg: status=0x%x, stereo=%d, bilingual=%d -> rxsubchans=%d\n", 
+                        status,
+                        is_stereo,
+                        is_bilingual,
+                        val);
+                *rxsubchans=val;
+        }
+        if(audmode) {
+                int val;
+                switch(msp->source) {
+                case 0: /* mono only */
+                        val = V4L2_TUNER_MODE_MONO;
+                        break;
+
+                case 1: /* stereo or A|B */
+                case 2: /* scart input (stereo) */
+                        /* I'm surprised, but according to v4l2 spec, that
+                         * is what V4L2_TUNER_MODE_STEREO *may* mean: 
+                         * > When the tuner receives bilingual audio it may play 
+                         * > different languages on the left and right channel or 
+                         * > the primary language on both channels. 
+                         * I chose the 2nd interpretation since there's no
+                         * BILINGUAL mode. 
+                         */
+                        val = V4L2_TUNER_MODE_STEREO; 
+                        break;
+
+				default: 
+                case 3: /* stereo or A */
+                        val = V4L2_TUNER_MODE_LANG1;
+                        break;
+                case 4: /* stereo or B */
+                        val = V4L2_TUNER_MODE_LANG2;
+                        break;
+                }
+                dprintk("msp34xxg: source=%d -> audmode=%d\n", 
+                        msp->source,
+                        val);
+                *audmode=val;
+        }
+}
+
+static void msp34xxg_set_v4l2_stereo(struct i2c_client *client, int audmode)
+{
+        int source=0;
+        switch(audmode) {
+        case V4L2_TUNER_MODE_MONO:
+                source=0; /* mono only */
+                break;
+                
+        case V4L2_TUNER_MODE_STEREO:
+                source=1; /* stereo or A|B, see comment in msp34xxg_get_v4l2_stereo() */
+                /* problem: that could also mean 2 (scart input) */
+                break;
+
+        case V4L2_TUNER_MODE_LANG1:
+                source=3; /* stereo or A */
+                break;
+                
+        case V4L2_TUNER_MODE_LANG2:
+                source=4; /* stereo or B */
+                break;
+
+        default: /* doing nothing: a safe, sane default */
+                return;
+        }
+        msp34xxg_set_source(client, source);
+}
+
+
 /* ----------------------------------------------------------------------- */
 
 static int msp_attach(struct i2c_adapter *adap, int addr,
@@ -1317,26 +1659,51 @@
 		/* use insmod option */
 		msp->simple = simple;
 	}
-
-	/* timer for stereo checking */
-	init_timer(&msp->wake_stereo);
-	msp->wake_stereo.function = msp3400c_stereo_wake;
-	msp->wake_stereo.data     = (unsigned long)msp;
-
+        if (simpler == -1 ) {
+                /* This still needs some research: I only have the documentation
+                 * for the 'G' version, maybe it came earlier. Also, I have a 'W'
+                 * version in my Hauppauge PVR350, which is strange because 
+                 * there's no mention of such a beast on macromedia's web site.
+                 */
+                msp->simpler = ((rev1&0xff)+'@' >= 'G');
+        } else {
+                msp->simpler = simpler;
+        }
+        
 	/* hello world :-) */
 	printk(KERN_INFO "msp34xx: init: chip=%s",c->name);
 	if (msp->nicam)
 		printk(", has NICAM support");
+        if (msp->simple)
+                printk(", simple (D) mode");
+        if (msp->simpler)
+                printk(", simpler (G) no-thread mode");
 	printk("\n");
+	printk(KERN_INFO "msp34xx: $Id: msp3400.c,v 1.1.2.5 2004/05/08 14:54:57 stephane Exp $ compiled on: " __DATE__ " " __TIME__ "\n");
 
-	/* startup control thread */
-	MOD_INC_USE_COUNT;
-	msp->notify = &sem;
-	kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread,
-		      (void *)c, 0);
-	down(&sem);
-	msp->notify = NULL;
-	wake_up_interruptible(&msp->wq);
+
+        if(IS_MSP34XX_G(msp)) {
+                msp->thread=NULL;
+                msp->source=3; /* defaults to 'stereo or A', see msp34xxg_set_source */
+                if(msp34xxg_reset(c)==-1) {
+                        dprintk("msp34xxg: initial automatic detection failed: will be re-done at next channel change\n");
+                }
+        } 
+        MOD_INC_USE_COUNT;
+        if(!IS_MSP34XX_G(msp)) {
+                /* timer for stereo checking */
+                init_timer(&msp->wake_stereo);
+                msp->wake_stereo.function = msp3400c_stereo_wake;
+                msp->wake_stereo.data     = (unsigned long)msp;
+                
+                /* startup control thread */
+                msp->notify = &sem;
+                kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread,
+                              (void *)c, 0);
+                down(&sem);
+                msp->notify = NULL;
+                wake_up_interruptible(&msp->wq);
+        } 
 
 	/* update our own array */
 	for (i = 0; i < MSP3400_MAX; i++) {
@@ -1353,20 +1720,22 @@
 
 static int msp_detach(struct i2c_client *client)
 {
-	DECLARE_MUTEX_LOCKED(sem);
 	struct msp3400c *msp  = (struct msp3400c*)client->data;
 	int i;
-	
-	/* shutdown control thread */
-	del_timer(&msp->wake_stereo);
-	if (msp->thread) 
-	{
-		msp->notify = &sem;
-		msp->rmmod = 1;
-		wake_up_interruptible(&msp->wq);
-		down(&sem);
-		msp->notify = NULL;
-	}
+
+        if( !IS_MSP34XX_G(msp) ) {
+                DECLARE_MUTEX_LOCKED(sem);
+                /* shutdown control thread */
+                del_timer(&msp->wake_stereo);
+                if (msp->thread) 
+                        {
+                                msp->notify = &sem;
+                                msp->rmmod = 1;
+                                wake_up_interruptible(&msp->wq);
+                                down(&sem);
+                                msp->notify = NULL;
+                        }
+        }
     	msp3400c_reset(client);
 
         /* update our own array */
@@ -1427,34 +1796,48 @@
 			msp->stereo = VIDEO_SOUND_STEREO;
 			msp3400c_set_scart(client,SCART_IN2,0);
 			msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900);
-			msp3400c_setstereo(client,msp->stereo);
+                        if(!IS_MSP34XX_G(msp)) {
+                                msp3400c_setstereo(client,msp->stereo);
+                        }
 			break;
 		case AUDIO_EXTERN:
 			msp->mode   = MSP_MODE_EXTERN;
 			msp->stereo = VIDEO_SOUND_STEREO;
 			msp3400c_set_scart(client,SCART_IN1,0);
 			msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900);
-			msp3400c_setstereo(client,msp->stereo);
+                        if(!IS_MSP34XX_G(msp)) {
+                                msp3400c_setstereo(client,msp->stereo);
+                        }
 			break;
 		case AUDIO_TUNER:
 			msp->mode   = -1;
-			msp_wake_thread(client);
+                        if( !IS_MSP34XX_G(msp)) {
+                                msp_wake_thread(client);
+                        } else {
+                                /*msp3400c_set_scart(client, SCART_IN1_DA, 1);*/
+                        }
 			break;
 		default:
 			if (*sarg & AUDIO_MUTE)
 				msp3400c_set_scart(client,SCART_MUTE,0);
 			break;
 		}
-		if (msp->active)
+                if(IS_MSP34XX_G(msp)) {
+                        msp34xxg_reset(client);
+		} else if (msp->active)
 			msp->restart = 1;
 		break;
 
 	case AUDC_SET_RADIO:
 		dprintk(KERN_DEBUG "msp34xx: AUDC_SET_RADIO\n");
 		msp->norm = VIDEO_MODE_RADIO;
+		dprintk("msp34xx: switching to radio mode\n");
+                if(IS_MSP34XX_G(msp)) {
+                        msp34xxg_reset(client);
+                        break;
+                } 
 		msp->watch_stereo=0;
 		del_timer(&msp->wake_stereo);
-		dprintk("msp34xx: switching to radio mode\n");
 		if (msp->simple) {
 			/* the thread will do for us */
 			msp_wake_thread(client);
@@ -1519,7 +1902,9 @@
 		va->bass = msp->bass;
 		va->treble = msp->treble;
 
-		if (msp->norm != VIDEO_MODE_RADIO) {
+                if(IS_MSP34XX_G(msp)) {
+                        va->mode = msp34xxg_get_v4l1_stereo(client);
+		} else if (msp->norm != VIDEO_MODE_RADIO) {
 			autodetect_stereo(client);
 			va->mode = msp->stereo;
 		}
@@ -1542,19 +1927,55 @@
 		msp3400c_settreble(client,msp->treble);
 
 		if (va->mode != 0) {
-			msp->watch_stereo=0;
-			del_timer(&msp->wake_stereo);
-			msp->stereo = va->mode;
-			msp3400c_setstereo(client,va->mode);
+                        if(IS_MSP34XX_G(msp)) {
+                                msp34xxg_set_v4l1_stereo(client, va->mode);
+                        } else {
+                                msp->watch_stereo=0;
+                                del_timer(&msp->wake_stereo);
+                                msp->stereo = va->mode;
+                                msp3400c_setstereo(client,va->mode);
+                        }
 		}
 		break;
 	}
+        case VIDIOC_G_TUNER:
+        {
+                struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+                vt->capability|=V4L2_TUNER_CAP_STEREO|V4L2_TUNER_CAP_LANG1|V4L2_TUNER_CAP_LANG2;
+
+                /* get rxsubchans and audmode */
+                if(IS_MSP34XX_G(msp)) {
+                        msp34xxg_get_v4l2_stereo(client, &vt->rxsubchans, &vt->audmode);
+                }
+                break;
+        }
+        case VIDIOC_S_TUNER:
+        {
+                struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+                /* only set audmode */
+                if(vt->audmode!=-1) {
+                        if(IS_MSP34XX_G(msp)) {
+                                msp34xxg_set_v4l2_stereo(client, vt->audmode);
+                        }
+                }
+                break;
+        }
+
 	case VIDIOCSCHAN:
 	{
 		struct video_channel *vc = arg;
 		
 		dprintk(KERN_DEBUG "msp34xx: VIDIOCSCHAN\n");
 		dprintk("msp34xx: switching to TV mode\n");
+                if(IS_MSP34XX_G(msp)) {
+                        if(msp->norm!=vc->norm) {
+                                msp34xxg_channelchange(client);
+                        } else {
+                                msp->norm = vc->norm;
+                                msp34xxg_reset(client);
+                        }
+                        break;
+                }
 		msp->norm = vc->norm;
 		msp_wake_thread(client);
 		break;
@@ -1563,7 +1984,12 @@
 	{
 		/* new channel -- kick audio carrier scan */
 		dprintk(KERN_DEBUG "msp34xx: VIDIOCSFREQ\n");
-		msp_wake_thread(client);
+                if(IS_MSP34XX_G(msp)) {
+                        msp34xxg_channelchange(client);
+                        break;
+                } else {
+                        msp_wake_thread(client);
+                }
 		break;
 	}
 	case MSP_SET_MATRIX:
Index: ivtv/utils/test_ioctl.c
diff -u ivtv/utils/test_ioctl.c:1.1 ivtv/utils/test_ioctl.c:1.1.2.2
--- ivtv/utils/test_ioctl.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/utils/test_ioctl.c	Sat May  8 17:50:42 2004
@@ -40,6 +40,8 @@
 #define OptFrameSync    (1L<<18)
 #define OptSetDebugLevel (1L<<19)
 #define OptGetDebugLevel (1L<<20)
+#define OptSetAudioMode     (1L<<21)
+#define OptGetAudioMode     (1L<<22)
 
 /* Codec's specified */
 #define CAspect			(1L<<1)
@@ -124,6 +126,8 @@
 	printf("      contrast       =<#> Picture contrast or luma gain. [0 - 127]\n");
 	printf("      volume         =<#> Overall audio volume. [0 - 65535]\n");
 	printf("      mute           =<#> Mute audio, i. e. set the volume to zero [boolean]\n");
+	printf("  -z, --set-audiomode= Possible values: 0(mono),1(stereo),2(lang2),3(lang1)\n");
+	printf("  -Z, --get-audiomode\n");
 	printf("  -D --debug=level   sets/gets the module ivtv_debug variable\n");
  	exit(0);
 }
@@ -318,6 +322,7 @@
 	struct v4l2_frequency vf;    /* get_freq/set_freq */
 	struct v4l2_standard vs;     /* list_std */
 	struct msp_matrix mspm;      /* set_io */
+    int mode = V4L2_TUNER_MODE_STEREO; /* set audio mode */
 	int debug_level = 0;
 	struct ivtv_ioctl_framesync frameinfo;
 	int new_debug_level, gdebug_level;
@@ -353,11 +358,13 @@
 			{"set-io", 1, 0, 0},
 			{"list-ctrls", 0, 0, 0},
 			{"set-ctrl", 1, 0, 0},
+			{"set-audiomode", 1, 0, 0},
+			{"get-audiomode", 0, 0, 0},
 			{"sync", 0, 0, 0},
 			{"debug", 1, 0, 0},
 			{0, 0, 0, 0}
 		};
-		ch = getopt_long(argc, argv, ":hc:Cd:ef:g:Gmnop:q:r:stu:v:aYy:D:",
+		ch = getopt_long(argc, argv, ":hc:Cd:ef:g:Gmnop:q:r:stu:v:aYy:D:z:Z",
 				long_options, &option_index);
 		if (ch == -1)
 			break;
@@ -408,6 +415,10 @@
 				ch = 'y';
 			else if (!strcmp(long_options[option_index].name, "debug"))
 				ch = 'D';
+			else if (!strcmp(long_options[option_index].name, "set-audiomode"))
+				ch = 'z';
+			else if (!strcmp(long_options[option_index].name, "get-audiomode"))
+				ch = 'Z';
 		}
 
 		switch (ch) {
@@ -797,6 +808,13 @@
 					}
 				}
 				break;
+			case 'z':
+				set_opts |= OptSetAudioMode;
+				mode = strtol(optarg, 0L, 0);
+				break;
+			case 'Z':
+				set_opts |= OptGetAudioMode;
+				break;
 			case ':':
 				fprintf(stderr, "Option `%s' requires a value\n", argv[optind]);
 				usage();
@@ -1110,6 +1128,43 @@
 		}
 	}
 
+	if (set_opts & OptGetAudioMode) {
+	    struct v4l2_tuner vt;
+		printf("ioctl: VIDIOC_G_TUNER\n");
+	    memset(&vt, 0, sizeof(struct v4l2_tuner));
+	    if (ioctl(fd, VIDIOC_G_TUNER, &vt) < 0) {
+		   fprintf(stderr, "ioctl: VIDIOC_G_TUNER failed\n");
+		   exit(1);
+	    }
+		printf(" current audio mode   : %d\n", vt.audmode);
+		printf("   (stereo %d, lang1 %d, lang2 %d, mono %d)\n",
+			   V4L2_TUNER_MODE_STEREO, 
+			   V4L2_TUNER_MODE_LANG1,
+			   V4L2_TUNER_MODE_LANG2,
+			   V4L2_TUNER_MODE_MONO);
+		printf(" available subchannels: 0x%x\n", vt.rxsubchans);
+		printf("   (stereo 0x%x, lang1 0x%x, lang2 0x%x, mono 0x%x)\n",
+			   V4L2_TUNER_SUB_STEREO,
+			   V4L2_TUNER_SUB_LANG1,
+			   V4L2_TUNER_SUB_LANG2,
+			   V4L2_TUNER_SUB_MONO);
+	}
+	if (set_opts & OptSetAudioMode) {
+	    struct v4l2_tuner vt;
+		printf("ioctl: VIDIOC_S_TUNER\n");
+	    memset(&vt, 0, sizeof(struct v4l2_tuner));
+	    if (ioctl(fd, VIDIOC_G_TUNER, &vt) < 0) {
+		fprintf(stderr, "ioctl: VIDIOC_G_TUNER failed\n");
+		exit(1);
+	    }
+	    vt.audmode=mode;
+	    if (ioctl(fd, VIDIOC_S_TUNER, &vt) < 0) {
+		fprintf(stderr, "ioctl: VIDIOC_S_TUNER failed\n");
+		exit(1);
+	    }
+	}
+   
+
 	close(fd);
 	exit(0);
 }

    Source: geocities.com/szermatt