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); }