msp3400-driver.c

来自「trident tm5600的linux驱动」· C语言 代码 · 共 1,036 行 · 第 1/2 页

C
1,036
字号
	/* --- v4l ioctls --- */	/* take care: bttv does userspace copying, we'll get a	   kernel pointer here... */#ifdef CONFIG_VIDEO_ALLOW_V4L1	case VIDIOCGAUDIO:	{		struct video_audio *va = arg;		va->flags |= VIDEO_AUDIO_VOLUME | VIDEO_AUDIO_MUTABLE;		if (state->has_sound_processing)			va->flags |= VIDEO_AUDIO_BALANCE |				VIDEO_AUDIO_BASS |				VIDEO_AUDIO_TREBLE;		if (state->muted)			va->flags |= VIDEO_AUDIO_MUTE;		va->volume = state->volume;		va->balance = state->volume ? state->balance : 32768;		va->bass = state->bass;		va->treble = state->treble;		if (state->radio)			break;		if (state->opmode == OPMODE_AUTOSELECT)			msp_detect_stereo(client);		va->mode = msp_mode_v4l2_to_v4l1(state->rxsubchans, state->audmode);		break;	}	case VIDIOCSAUDIO:	{		struct video_audio *va = arg;		state->muted = (va->flags & VIDEO_AUDIO_MUTE);		state->volume = va->volume;		state->balance = va->balance;		state->bass = va->bass;		state->treble = va->treble;		msp_set_audio(client);		if (va->mode != 0 && state->radio == 0 &&		    state->audmode != msp_mode_v4l1_to_v4l2(va->mode)) {			state->audmode = msp_mode_v4l1_to_v4l2(va->mode);			msp_set_audmode(client);		}		break;	}	case VIDIOCSCHAN:	{		struct video_channel *vc = arg;		int update = 0;		v4l2_std_id std;		if (state->radio)			update = 1;		state->radio = 0;		if (vc->norm == VIDEO_MODE_PAL)			std = V4L2_STD_PAL;		else if (vc->norm == VIDEO_MODE_SECAM)			std = V4L2_STD_SECAM;		else			std = V4L2_STD_NTSC;		if (std != state->v4l2_std) {			state->v4l2_std = std;			update = 1;		}		if (update)			msp_wake_thread(client);		break;	}	case VIDIOCSFREQ:	{		/* new channel -- kick audio carrier scan */		msp_wake_thread(client);		break;	}#endif	case VIDIOC_S_FREQUENCY:	{		/* new channel -- kick audio carrier scan */		msp_wake_thread(client);		break;	}	/* --- v4l2 ioctls --- */	case VIDIOC_S_STD:	{		v4l2_std_id *id = arg;		int update = state->radio || state->v4l2_std != *id;		state->v4l2_std = *id;		state->radio = 0;		if (update)			msp_wake_thread(client);		return 0;	}	case VIDIOC_INT_G_AUDIO_ROUTING:	{		struct v4l2_routing *rt = arg;		*rt = state->routing;		break;	}	case VIDIOC_INT_S_AUDIO_ROUTING:	{		struct v4l2_routing *rt = arg;		int tuner = (rt->input >> 3) & 1;		int sc_in = rt->input & 0x7;		int sc1_out = rt->output & 0xf;		int sc2_out = (rt->output >> 4) & 0xf;		u16 val, reg;		int i;		int extern_input = 1;		if (state->routing.input == rt->input &&		    state->routing.output == rt->output)			break;		state->routing = *rt;		/* check if the tuner input is used */		for (i = 0; i < 5; i++) {			if (((rt->input >> (4 + i * 4)) & 0xf) == 0)				extern_input = 0;		}		state->mode = extern_input ? MSP_MODE_EXTERN : MSP_MODE_AM_DETECT;		state->rxsubchans = V4L2_TUNER_SUB_STEREO;		msp_set_scart(client, sc_in, 0);		msp_set_scart(client, sc1_out, 1);		msp_set_scart(client, sc2_out, 2);		msp_set_audmode(client);		reg = (state->opmode == OPMODE_AUTOSELECT) ? 0x30 : 0xbb;		val = msp_read_dem(client, reg);		msp_write_dem(client, reg, (val & ~0x100) | (tuner << 8));		/* wake thread when a new input is chosen */		msp_wake_thread(client);		break;	}	case VIDIOC_G_TUNER:	{		struct v4l2_tuner *vt = arg;		if (state->radio)			break;		if (state->opmode == OPMODE_AUTOSELECT)			msp_detect_stereo(client);		vt->audmode    = state->audmode;		vt->rxsubchans = state->rxsubchans;		vt->capability |= V4L2_TUNER_CAP_STEREO |			V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;		break;	}	case VIDIOC_S_TUNER:	{		struct v4l2_tuner *vt = (struct v4l2_tuner *)arg;		if (state->radio)  /* TODO: add mono/stereo support for radio */			break;		if (state->audmode == vt->audmode)			break;		state->audmode = vt->audmode;		/* only set audmode */		msp_set_audmode(client);		break;	}	case VIDIOC_INT_I2S_CLOCK_FREQ:	{		u32 *a = (u32 *)arg;		v4l_dbg(1, msp_debug, client, "Setting I2S speed to %d\n", *a);		switch (*a) {		case 1024000:			state->i2s_mode = 0;			break;		case 2048000:			state->i2s_mode = 1;			break;		default:			return -EINVAL;		}		break;	}	case VIDIOC_QUERYCTRL:	{		struct v4l2_queryctrl *qc = arg;		switch (qc->id) {		case V4L2_CID_AUDIO_VOLUME:		case V4L2_CID_AUDIO_MUTE:			return v4l2_ctrl_query_fill_std(qc);		default:			break;		}		if (!state->has_sound_processing)			return -EINVAL;		switch (qc->id) {		case V4L2_CID_AUDIO_LOUDNESS:		case V4L2_CID_AUDIO_BALANCE:		case V4L2_CID_AUDIO_BASS:		case V4L2_CID_AUDIO_TREBLE:			return v4l2_ctrl_query_fill_std(qc);		default:			return -EINVAL;		}	}	case VIDIOC_G_CTRL:		return msp_get_ctrl(client, arg);	case VIDIOC_S_CTRL:		return msp_set_ctrl(client, arg);	case VIDIOC_LOG_STATUS:	{		const char *p;		if (state->opmode == OPMODE_AUTOSELECT)			msp_detect_stereo(client);		v4l_info(client, "%s rev1 = 0x%04x rev2 = 0x%04x\n",				client->name, state->rev1, state->rev2);		v4l_info(client, "Audio:    volume %d%s\n",				state->volume, state->muted ? " (muted)" : "");		if (state->has_sound_processing) {			v4l_info(client, "Audio:    balance %d bass %d treble %d loudness %s\n",					state->balance, state->bass,					state->treble,					state->loudness ? "on" : "off");		}		switch (state->mode) {		case MSP_MODE_AM_DETECT: p = "AM (for carrier detect)"; break;		case MSP_MODE_FM_RADIO: p = "FM Radio"; break;		case MSP_MODE_FM_TERRA: p = "Terrestial FM-mono/stereo"; break;		case MSP_MODE_FM_SAT: p = "Satellite FM-mono"; break;		case MSP_MODE_FM_NICAM1: p = "NICAM/FM (B/G, D/K)"; break;		case MSP_MODE_FM_NICAM2: p = "NICAM/FM (I)"; break;		case MSP_MODE_AM_NICAM: p = "NICAM/AM (L)"; break;		case MSP_MODE_BTSC: p = "BTSC"; break;		case MSP_MODE_EXTERN: p = "External input"; break;		default: p = "unknown"; break;		}		if (state->mode == MSP_MODE_EXTERN) {			v4l_info(client, "Mode:     %s\n", p);		} else if (state->opmode == OPMODE_MANUAL) {			v4l_info(client, "Mode:     %s (%s%s)\n", p,				(state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono",				(state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : "");		} else {			if (state->opmode == OPMODE_AUTODETECT)				v4l_info(client, "Mode:     %s\n", p);			v4l_info(client, "Standard: %s (%s%s)\n",				msp_standard_std_name(state->std),				(state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono",				(state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : "");		}		v4l_info(client, "Audmode:  0x%04x\n", state->audmode);		v4l_info(client, "Routing:  0x%08x (input) 0x%08x (output)\n",				state->routing.input, state->routing.output);		v4l_info(client, "ACB:      0x%04x\n", state->acb);		break;	}	case VIDIOC_G_CHIP_IDENT:		return v4l2_chip_ident_i2c_client(client, arg, state->ident,				(state->rev1 << 16) | state->rev2);	default:		/* unknown */		return -EINVAL;	}	return 0;}static int msp_suspend(struct i2c_client *client, pm_message_t state){	v4l_dbg(1, msp_debug, client, "suspend\n");	msp_reset(client);	return 0;}static int msp_resume(struct i2c_client *client){	v4l_dbg(1, msp_debug, client, "resume\n");	msp_wake_thread(client);	return 0;}/* ----------------------------------------------------------------------- */static int msp_probe(struct i2c_client *client, const struct i2c_device_id *id){	struct msp_state *state;	int (*thread_func)(void *data) = NULL;	int msp_hard;	int msp_family;	int msp_revision;	int msp_product, msp_prod_hi, msp_prod_lo;	int msp_rom;#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)	snprintf(client->name, sizeof(client->name) - 1, "msp3400");#else	if (!id)		strlcpy(client->name, "msp3400", sizeof(client->name));#endif	if (msp_reset(client) == -1) {		v4l_dbg(1, msp_debug, client, "msp3400 not found\n");		return -ENODEV;	}	state = kzalloc(sizeof(*state), GFP_KERNEL);	if (!state)		return -ENOMEM;	i2c_set_clientdata(client, state);	state->v4l2_std = V4L2_STD_NTSC;	state->audmode = V4L2_TUNER_MODE_STEREO;	state->volume = 58880;	/* 0db gain */	state->balance = 32768;	/* 0db gain */	state->bass = 32768;	state->treble = 32768;	state->loudness = 0;	state->input = -1;	state->muted = 0;	state->i2s_mode = 0;	init_waitqueue_head(&state->wq);	/* These are the reset input/output positions */	state->routing.input = MSP_INPUT_DEFAULT;	state->routing.output = MSP_OUTPUT_DEFAULT;	state->rev1 = msp_read_dsp(client, 0x1e);	if (state->rev1 != -1)		state->rev2 = msp_read_dsp(client, 0x1f);	v4l_dbg(1, msp_debug, client, "rev1=0x%04x, rev2=0x%04x\n",			state->rev1, state->rev2);	if (state->rev1 == -1 || (state->rev1 == 0 && state->rev2 == 0)) {		v4l_dbg(1, msp_debug, client,				"not an msp3400 (cannot read chip version)\n");		kfree(state);		return -ENODEV;	}#if 0	/* this will turn on a 1kHz beep - might be useful for debugging... */	msp_write_dsp(client, 0x0014, 0x1040);#endif	msp_set_audio(client);	msp_family = ((state->rev1 >> 4) & 0x0f) + 3;	msp_product = (state->rev2 >> 8) & 0xff;	msp_prod_hi = msp_product / 10;	msp_prod_lo = msp_product % 10;	msp_revision = (state->rev1 & 0x0f) + '@';	msp_hard = ((state->rev1 >> 8) & 0xff) + '@';	msp_rom = state->rev2 & 0x1f;#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)	snprintf(client->name, sizeof(client->name), "MSP%d4%02d%c-%c%d",			msp_family, msp_product,			msp_revision, msp_hard, msp_rom);#endif	/* Rev B=2, C=3, D=4, G=7 */	state->ident = msp_family * 10000 + 4000 + msp_product * 10 +			msp_revision - '@';	/* Has NICAM support: all mspx41x and mspx45x products have NICAM */	state->has_nicam =		msp_prod_hi == 1 || msp_prod_hi == 5;	/* Has radio support: was added with revision G */	state->has_radio =		msp_revision >= 'G';	/* Has headphones output: not for stripped down products */	state->has_headphones =		msp_prod_lo < 5;	/* Has scart2 input: not in stripped down products of the '3' family */	state->has_scart2 =		msp_family >= 4 || msp_prod_lo < 7;	/* Has scart3 input: not in stripped down products of the '3' family */	state->has_scart3 =		msp_family >= 4 || msp_prod_lo < 5;	/* Has scart4 input: not in pre D revisions, not in stripped D revs */	state->has_scart4 =		msp_family >= 4 || (msp_revision >= 'D' && msp_prod_lo < 5);	/* Has scart2 output: not in stripped down products of	 * the '3' family */	state->has_scart2_out =		msp_family >= 4 || msp_prod_lo < 5;	/* Has scart2 a volume control? Not in pre-D revisions. */	state->has_scart2_out_volume =		msp_revision > 'C' && state->has_scart2_out;	/* Has a configurable i2s out? */	state->has_i2s_conf =		msp_revision >= 'G' && msp_prod_lo < 7;	/* Has subwoofer output: not in pre-D revs and not in stripped down	 * products */	state->has_subwoofer =		msp_revision >= 'D' && msp_prod_lo < 5;	/* Has soundprocessing (bass/treble/balance/loudness/equalizer):	 *  not in stripped down products */	state->has_sound_processing =		msp_prod_lo < 7;	/* Has Virtual Dolby Surround: only in msp34x1 */	state->has_virtual_dolby_surround =		msp_revision == 'G' && msp_prod_lo == 1;	/* Has Virtual Dolby Surround & Dolby Pro Logic: only in msp34x2 */	state->has_dolby_pro_logic =		msp_revision == 'G' && msp_prod_lo == 2;	/* The msp343xG supports BTSC only and cannot do Automatic Standard	 * Detection. */	state->force_btsc =		msp_family == 3 && msp_revision == 'G' && msp_prod_hi == 3;	state->opmode = opmode;	if (state->opmode == OPMODE_AUTO) {		/* MSP revision G and up have both autodetect and autoselect */		if (msp_revision >= 'G')			state->opmode = OPMODE_AUTOSELECT;		/* MSP revision D and up have autodetect */		else if (msp_revision >= 'D')			state->opmode = OPMODE_AUTODETECT;		else			state->opmode = OPMODE_MANUAL;	}	/* hello world :-) */	v4l_info(client, "MSP%d4%02d%c-%c%d found @ 0x%x (%s)\n",			msp_family, msp_product,			msp_revision, msp_hard, msp_rom,			client->addr << 1, client->adapter->name);	v4l_info(client, "%s ", client->name);	if (state->has_nicam && state->has_radio)		printk(KERN_CONT "supports nicam and radio, ");	else if (state->has_nicam)		printk(KERN_CONT "supports nicam, ");	else if (state->has_radio)		printk(KERN_CONT "supports radio, ");	printk(KERN_CONT "mode is ");	/* version-specific initialization */	switch (state->opmode) {	case OPMODE_MANUAL:		printk(KERN_CONT "manual");		thread_func = msp3400c_thread;		break;	case OPMODE_AUTODETECT:		printk(KERN_CONT "autodetect");		thread_func = msp3410d_thread;		break;	case OPMODE_AUTOSELECT:		printk(KERN_CONT "autodetect and autoselect");		thread_func = msp34xxg_thread;		break;	}	printk(KERN_CONT "\n");	/* startup control thread if needed */	if (thread_func) {		state->kthread = kthread_run(thread_func, client, "msp34xx");		if (IS_ERR(state->kthread))			v4l_warn(client, "kernel_thread() failed\n");		msp_wake_thread(client);	}	return 0;}static int msp_remove(struct i2c_client *client){	struct msp_state *state = i2c_get_clientdata(client);	/* shutdown control thread */	if (state->kthread) {		state->restart = 1;		kthread_stop(state->kthread);	}	msp_reset(client);	kfree(state);	return 0;}/* ----------------------------------------------------------------------- */#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)static const struct i2c_device_id msp_id[] = {	{ "msp3400", 0 },	{ }};MODULE_DEVICE_TABLE(i2c, msp_id);#endifstatic struct v4l2_i2c_driver_data v4l2_i2c_data = {	.name = "msp3400",	.driverid = I2C_DRIVERID_MSP3400,	.command = msp_command,	.probe = msp_probe,	.remove = msp_remove,	.suspend = msp_suspend,	.resume = msp_resume,#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)	.id_table = msp_id,#endif};/* * Overrides for Emacs so that we follow Linus's tabbing style. * --------------------------------------------------------------------------- * Local variables: * c-basic-offset: 8 * End: */

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?