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.2 --- ivtv/driver/ivtv-api.c:1.1 Sun Mar 7 11:26:54 2004 +++ ivtv/driver/ivtv-api.c Sun Apr 25 20:06:27 2004 @@ -31,6 +31,7 @@ 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_default(struct ivtv* ivtv); struct file_operations ivtv_v4l2_fops = { read: ivtv_v4l2_read, @@ -466,6 +467,35 @@ u32 ivtv_audio_mask_generation = 0x10000; +struct v4l2_queryctrl ivtv_ctrl_menu_dualwatch = { + .id = V4L2_CID_IVTV_DUALWATCH, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Update STEREO/DUAL flag", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + .flags = 0, + .reserved = {0,0} +}; + +struct v4l2_querymenu ivtv_ctrl_query_dualwatch[] = { + /* ID, Index, Name, Reserved */ + {V4L2_CID_IVTV_MONO, 0, "Disabled", 0}, + {V4L2_CID_IVTV_MONO, 1, "Dual or Split Stereo", 0}, + {V4L2_CID_IVTV_MONO, 2, "Dual or Joint Stereo", 0} +}; + +u32 ivtv_audio_tbl_dualwatch[] = { + /* setting */ + 0, /* Disabled */ + IVTV_CODEC_AUDIO_DUALWATCH_ENABLED, /* Dual|Split */ + IVTV_CODEC_AUDIO_DUALWATCH_ENABLED + |IVTV_CODEC_AUDIO_DUALWATCH_STEREO_JOINT /* Dual|Joint */ +}; + +u32 ivtv_audio_mask_dualwatch = IVTV_CODEC_AUDIO_DUALWATCH_MASK; + /* 3 stream types: mpeg, yuv, passthru */ struct ivtv_v4l2_stream tmk_mpg_stream = { @@ -1002,12 +1032,20 @@ itv->v4l2.audio_meta[8].setting = ivtv_ctrl_menu_generation.default_value; itv->v4l2.audio_meta[8].table = &ivtv_audio_tbl_generation[0]; + /* V4L2_CID_IVTV_DUALWATCH */ + itv->v4l2.audio_meta[9].ctrl = &ivtv_ctrl_menu_dualwatch; + itv->v4l2.audio_meta[9].menu = ivtv_ctrl_query_dualwatch; + itv->v4l2.audio_meta[9].mask = ivtv_audio_mask_dualwatch; + itv->v4l2.audio_meta[9].setting = ivtv_ctrl_menu_dualwatch.default_value; + itv->v4l2.audio_meta[9].table = &ivtv_audio_tbl_dualwatch[0]; + itv->v4l2.codec.audio_bitmap = 0; for (x = 0; x < IVTV_V4L2_AUDIO_MENUCOUNT; x++) { temp = itv->v4l2.audio_meta[x].setting; itv->v4l2.codec.audio_bitmap |= itv->v4l2.audio_meta[x].table[temp]; } + ivtv_dualwatch_set_default(itv); retval = ivtv_set_audio(itv,tmk_audio_mapping); if (retval) { @@ -1140,10 +1178,11 @@ if (x) IVTV_DEBUG(IVTV_DEBUG_ERR, "init error 11. Code %d\n",x); /*assign audio properties */ - data[0] = itv->v4l2.codec.audio_bitmap; + data[0] = ivtv_mpeg2_audio_bitmap(itv->v4l2.codec.audio_bitmap); 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; @@ -1412,7 +1451,7 @@ } else { data[2] = 480; /* YUV source height*/ } - data[3] = itv->v4l2.codec.audio_bitmap; /* Audio settings to use, + data[3] = itv->v4l2.codec.audio_bitmap&0xffff;; /* Audio settings to use, bitmap. see docs.*/ x = ivtv_api(itv->dec_mbox, &itv->dec_msem, @@ -1481,6 +1520,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++) { @@ -2235,6 +2275,8 @@ if ((v<=itv->v4l2.audio_meta[off].ctrl->maximum) && (v>=itv->v4l2.audio_meta[off].ctrl->minimum)) { itv->v4l2.audio_meta[off].setting = v; + + u32 old_audio_bitmap = itv->v4l2.codec.audio_bitmap; /* presumably value has changed. * we should update the bitmap */ itv->v4l2.codec.audio_bitmap &= @@ -2249,6 +2291,8 @@ * (see ivtv_ctrl_query_freq[]) */ ivtv_call_i2c_client(itv,IVTV_SAA7115_I2C_ADDR,DECODER_SET_AUDIO,&vrate); } + ivtv_dualwatch_set_default(itv); + ivtv_dualwatch_update(itv); } else { IVTV_DEBUG(IVTV_DEBUG_ERR, "ctrl: value out of range\n"); @@ -2606,6 +2650,7 @@ struct v4l2_frequency *vf=(struct v4l2_frequency *)arg; struct msp_matrix mspm; + IVTV_DEBUG(IVTV_DEBUG_INFO,"v4l2 ioctl: set frequency\n"); if ((vf->tuner < 0) || (vf->tuner >= itv->v4l2.tuner.count)) @@ -2621,18 +2666,23 @@ IVTV_DEBUG(IVTV_DEBUG_ERR, "Freq: Error pausing stream\n"); /* Set frequency */ + printk("ivtv-api: set frequency to %ld\n", itv->v4l2.freq.frequency); ivtv_call_i2c_client(itv,IVTV_TUNER_I2C_ADDR, VIDIOCSFREQ,&itv->v4l2.freq.frequency); /* 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,9 +2770,8 @@ 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); break; } @@ -2748,6 +2797,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: { @@ -2797,6 +2848,8 @@ /* Passed the garbage check */ memcpy(&(itv->v4l2.codec), codec, sizeof(struct ivtv_ioctl_codec)); + ivtv_dualwatch_set_default(itv); + ivtv_dualwatch_update(itv); } /* VCD streamtype has some quirks. Handle them here */ @@ -2824,7 +2877,7 @@ memcpy (&itv->v4l2.streams[0].format, vfmt, sizeof(struct v4l2_format)); } - + ivtv_dualwatch_update(itv); break; } case IVTV_IOCTL_GET_DEBUG_LEVEL: { @@ -2869,3 +2922,148 @@ return 0; } +/** + * Change the DUAL flag + */ +static void ivtv_dualwatch_set_dual(struct ivtv *itv) +{ + if(!itv->dualwatch_encoding) + return; + + u32 audio_bitmap = itv->v4l2.codec.audio_bitmap; + int stereo_flag = audio_bitmap&IVTV_CODEC_AUDIO_MPEG_STEREO_MASK; + int goal_stereo_flag; + if(audio_bitmap&IVTV_CODEC_AUDIO_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.audmode!=V4L2_TUNER_MODE_STEREO) { + /* with dual watch, only this audio mode makes sense */ + vt.audmode=V4L2_TUNER_MODE_STEREO; + ivtv_call_i2c_client(itv, + IVTV_MSP3400_I2C_ADDR, + VIDIOC_S_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 + && (audio_bitmap&IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT)) { + goal_stereo_flag = IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT; + } + + if(goal_stereo_flag != stereo_flag) { + u32 data[IVTV_MBOX_MAX_DATA]; + u32 new_bitmap = + goal_stereo_flag + | (itv->v4l2.codec.audio_bitmap & ~IVTV_CODEC_AUDIO_MPEG_STEREO_MASK); + + printk("timer: change stereo flag from 0x%x to 0x%x\n", + stereo_flag, + goal_stereo_flag); + + data[0] = ivtv_mpeg2_audio_bitmap(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_call(itv->enc_mbox, + IVTV_API_ASSIGN_AUDIO_PROPERTIES, + 1, + &data[0])==0) { + itv->v4l2.codec.audio_bitmap = new_bitmap; + printk("timer: changed stereo flag DONE\n"); + } + printk("timer: change stereo flag END\n"); + } +} + +/** + * function called by the timer every second + */ +static void ivtv_dualwatch_timer_cb(unsigned long data) +{ + struct ivtv * itv = (struct ivtv*)data; + + // stop ASAP if not necessary + if(!itv->dualwatch_encoding) { + itv->dualwatch_timer_running=0; + return; + } + + /* remove the DUAL flag if necessary and stop */ + if(!(itv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DUALWATCH_ENABLED)) { + ivtv_dualwatch_set_dual(itv); + itv->dualwatch_timer_running=0; + return; + } + + ivtv_dualwatch_set_dual(itv); + + /* reschedule */ + itv->dualwatch_timer.expires = jiffies+HZ; + add_timer(&itv->dualwatch_timer); +} + +/* start the thread if necessary */ +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->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DUALWATCH_ENABLED + && !itv->dualwatch_timer_running) { + + itv->dualwatch_timer.expires = jiffies+HZ; /* 1 sec. */ + + add_timer(&itv->dualwatch_timer); + itv->dualwatch_timer_running=1; + } +} +/* 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; + init_timer(&itv->dualwatch_timer); + itv->dualwatch_timer.function = ivtv_dualwatch_timer_cb; + itv->dualwatch_timer.data = (unsigned long)itv; + 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; + itv->dualwatch_encoding = 0; + if(itv->dualwatch_timer_running) { + del_timer_sync(&itv->dualwatch_timer); + itv->dualwatch_timer_running=0; + } +} + +/* add the value in ivtv_dynstereo to audio_bitmap if + * the higher bits are null (it's just a temporary hack, + * I hope) + */ +static void ivtv_dualwatch_set_default(struct ivtv* ivtv) { + if( ! (ivtv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DUALWATCH_MASK) ) { + ivtv->v4l2.codec.audio_bitmap|= ivtv_dualwatch << 16 ; + } +} Index: ivtv/driver/ivtv-driver.c diff -u ivtv/driver/ivtv-driver.c:1.2 ivtv/driver/ivtv-driver.c:1.2.2.1 --- ivtv/driver/ivtv-driver.c:1.2 Sun Mar 7 11:41:14 2004 +++ ivtv/driver/ivtv-driver.c Sun Apr 25 20:06:27 2004 @@ -72,6 +72,7 @@ #endif int ivtv_pal = 0; +long ivtv_dualwatch = 0; /* low debugging by default */ @@ -164,6 +165,10 @@ MODULE_PARM(ivtv_pal, "i"); MODULE_PARM_DESC(ivtv_pal, "\nUse PAL as default video mode instead of NTSC"); +MODULE_PARM(ivtv_dualwatch, "i"); +MODULE_PARM_DESC(ivtv_dualwatch, + "\nForce dynamic stereo configuration"); + MODULE_AUTHOR("Kevin Thayer"); MODULE_DESCRIPTION("Alpha iTVC15 driver"); MODULE_SUPPORTED_DEVICE("iTVC15/16 mpg2 encoder (aka WinTV PVR 250/350)"); @@ -1057,6 +1062,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.1 --- ivtv/driver/ivtv.h:1.2 Sun Mar 7 11:41:14 2004 +++ ivtv/driver/ivtv.h Sun Apr 25 20:06:27 2004 @@ -439,8 +439,9 @@ #define V4L2_CID_IVTV_CRC (V4L2_CID_PRIVATE_BASE + 6) #define V4L2_CID_IVTV_COPYRIGHT (V4L2_CID_PRIVATE_BASE + 7) #define V4L2_CID_IVTV_GEN (V4L2_CID_PRIVATE_BASE + 8) +#define V4L2_CID_IVTV_DUALWATCH (V4L2_CID_PRIVATE_BASE + 9) -#define IVTV_V4L2_AUDIO_MENUCOUNT 9 /* # of v4l controls */ +#define IVTV_V4L2_AUDIO_MENUCOUNT 10 /* # of v4l controls */ #define IVTV_DEC_PRIVATE_BASE (V4L2_CID_PRIVATE_BASE + IVTV_V4L2_AUDIO_MENUCOUNT) @@ -527,7 +528,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; /* see IVTV_CODEC_AUDIO */ u32 bframes; u32 bitrate_mode; u32 bitrate; @@ -543,8 +544,26 @@ u32 stream_type; }; +/* audio_bitmap: + * bits 0-15 follow iso11172, mostly, see http://ivtv.sourceforge.net/firmware-api.html. + * bits 16-32 are ivtv driver extensions as defined below: + */ +#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) + +#define IVTV_CODEC_AUDIO_DUALWATCH_ENABLED (0x01 << 16) /* 1=switch between DUAL and STEREO, following bilingual flag */ +#define IVTV_CODEC_AUDIO_DUALWATCH_STEREO_JOINT (0x02 << 16) /* 1=when encoding in stereo, use JOINT 0= use SPLIT*/ +#define IVTV_CODEC_AUDIO_DUALWATCH_MASK (0x3 << 16) /* mask for DUALWATCH settings */ + +/* only the low bits (0:15) are really the mpeg2 paudio bitmap, the rest is extensions */ +#define ivtv_mpeg2_audio_bitmap(bitmap) ((bitmap) & 0xffff) + extern int ivtv_debug; extern int ivtv_pal; +extern long ivtv_dualwatch; /* Scatter-Gather array element, used in DMA transfers */ struct ivtv_SG_element { @@ -748,6 +767,11 @@ struct ivtv_state state; struct ivtv_v4l2 v4l2; struct list_head client_list; + + /* dualwatch */ + struct timer_list dualwatch_timer; + int dualwatch_encoding; + int dualwatch_timer_running; }; /* Globals */ @@ -831,6 +855,10 @@ 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); +extern void ivtv_dualwatch_update(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.4 --- ivtv/driver/msp3400.c:1.1 Sun Mar 7 11:26:54 2004 +++ ivtv/driver/msp3400.c Sun Apr 25 20:06:27 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; @@ -115,11 +120,13 @@ /* ---------------------------------------------------------------------- */ -#define dprintk if (debug) printk +#define dprintk if (debug>=1) printk +#define dprintk_trace if (debug>=16) printk 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"); @@ -157,6 +164,7 @@ { client->addr, I2C_M_RD, 2, read }, }; + dprintk_trace("trace: msp3400c_reset\n"); if ( (1 != i2c_transfer(client->adapter,&reset[0],1)) || (1 != i2c_transfer(client->adapter,&reset[1],1)) || (2 != i2c_transfer(client->adapter,test,2)) ) { @@ -195,7 +203,9 @@ msp3400c_reset(client); return -1; } - return read[0] << 8 | read[1]; + int retval = read[0] << 8 | read[1]; + dprintk_trace("trace: msp3400c_read(0x%x, 0x%x): 0x%x\n", dev, addr, retval); + return retval; } static int @@ -210,6 +220,7 @@ buffer[3] = val >> 8; buffer[4] = val & 0xff; + dprintk_trace("trace: msp3400c_write(0x%x, 0x%x, 0x%x)\n", dev, addr, val); for (err = 0; err < 3;) { if (5 == i2c_master_send(client, buffer, 5)) break; @@ -482,6 +493,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 +635,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 +1034,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 +1114,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 +1263,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 */ + 0x0200); + 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 +1663,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$ 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 +1724,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 +1800,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 +1906,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 +1931,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 +1988,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.1 --- ivtv/utils/test_ioctl.c:1.1 Sun Mar 7 11:26:54 2004 +++ ivtv/utils/test_ioctl.c Sat Apr 3 16:02:23 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(); @@ -935,7 +953,7 @@ else { printf("Codec parameters\n"); printf("aspect : %d\n", codec.aspect); - printf("audio : 0x%04x\n", codec.audio_bitmask); + printf("audio : 0x%08x\n", codec.audio_bitmask); printf("bframes : %d\n", codec.bframes); printf("bitrate_mode: %d\n", codec.bitrate_mode); printf("bitrate : %d\n", codec.bitrate); @@ -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); }