radio-si470x.c

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

C
1,789
字号
/************************************************************************** * RDS Driver Functions **************************************************************************//* * si470x_rds - rds processing function */static void si470x_rds(struct si470x_device *radio){	unsigned char blocknum;	unsigned short bler; /* rds block errors */	unsigned short rds;	unsigned char tmpbuf[3];	/* get rds blocks */	if (si470x_get_rds_registers(radio) < 0)		return;	if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {		/* No RDS group ready */		return;	}	if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {		/* RDS decoder not synchronized */		return;	}	/* copy all four RDS blocks to internal buffer */	mutex_lock(&radio->lock);	for (blocknum = 0; blocknum < 4; blocknum++) {		switch (blocknum) {		default:			bler = (radio->registers[STATUSRSSI] &					STATUSRSSI_BLERA) >> 9;			rds = radio->registers[RDSA];			break;		case 1:			bler = (radio->registers[READCHAN] &					READCHAN_BLERB) >> 14;			rds = radio->registers[RDSB];			break;		case 2:			bler = (radio->registers[READCHAN] &					READCHAN_BLERC) >> 12;			rds = radio->registers[RDSC];			break;		case 3:			bler = (radio->registers[READCHAN] &					READCHAN_BLERD) >> 10;			rds = radio->registers[RDSD];			break;		};		/* Fill the V4L2 RDS buffer */		put_unaligned_le16(rds, &tmpbuf);		tmpbuf[2] = blocknum;		/* offset name */		tmpbuf[2] |= blocknum << 3;	/* received offset */		if (bler > max_rds_errors)			tmpbuf[2] |= 0x80; /* uncorrectable errors */		else if (bler > 0)			tmpbuf[2] |= 0x40; /* corrected error(s) */		/* copy RDS block to internal buffer */		memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);		radio->wr_index += 3;		/* wrap write pointer */		if (radio->wr_index >= radio->buf_size)			radio->wr_index = 0;		/* check for overflow */		if (radio->wr_index == radio->rd_index) {			/* increment and wrap read pointer */			radio->rd_index += 3;			if (radio->rd_index >= radio->buf_size)				radio->rd_index = 0;		}	}	mutex_unlock(&radio->lock);	/* wake up read queue */	if (radio->wr_index != radio->rd_index)		wake_up_interruptible(&radio->read_queue);}/* * si470x_work - rds work function */static void si470x_work(struct work_struct *work){	struct si470x_device *radio = container_of(work, struct si470x_device,		work.work);	/* safety checks */	if (radio->disconnected)		return;	if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)		return;	si470x_rds(radio);	schedule_delayed_work(&radio->work, msecs_to_jiffies(rds_poll_time));}/************************************************************************** * File Operations Interface **************************************************************************//* * si470x_fops_read - read RDS data */static ssize_t si470x_fops_read(struct file *file, char __user *buf,		size_t count, loff_t *ppos){	struct si470x_device *radio = video_drvdata(file);	int retval = 0;	unsigned int block_count = 0;	/* switch on rds reception */	if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {		si470x_rds_on(radio);		schedule_delayed_work(&radio->work,			msecs_to_jiffies(rds_poll_time));	}	/* block if no new data available */	while (radio->wr_index == radio->rd_index) {		if (file->f_flags & O_NONBLOCK) {			retval = -EWOULDBLOCK;			goto done;		}		if (wait_event_interruptible(radio->read_queue,			radio->wr_index != radio->rd_index) < 0) {			retval = -EINTR;			goto done;		}	}	/* calculate block count from byte count */	count /= 3;	/* copy RDS block out of internal buffer and to user buffer */	mutex_lock(&radio->lock);	while (block_count < count) {		if (radio->rd_index == radio->wr_index)			break;		/* always transfer rds complete blocks */		if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))			/* retval = -EFAULT; */			break;		/* increment and wrap read pointer */		radio->rd_index += 3;		if (radio->rd_index >= radio->buf_size)			radio->rd_index = 0;		/* increment counters */		block_count++;		buf += 3;		retval += 3;	}	mutex_unlock(&radio->lock);done:	return retval;}/* * si470x_fops_poll - poll RDS data */static unsigned int si470x_fops_poll(struct file *file,		struct poll_table_struct *pts){	struct si470x_device *radio = video_drvdata(file);	int retval = 0;	/* switch on rds reception */	if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {		si470x_rds_on(radio);		schedule_delayed_work(&radio->work,			msecs_to_jiffies(rds_poll_time));	}	poll_wait(file, &radio->read_queue, pts);	if (radio->rd_index != radio->wr_index)		retval = POLLIN | POLLRDNORM;	return retval;}/* * si470x_fops_open - file open */static int si470x_fops_open(struct inode *inode, struct file *file){	struct si470x_device *radio = video_drvdata(file);	int retval;	lock_kernel();	radio->users++;	retval = usb_autopm_get_interface(radio->intf);	if (retval < 0) {		radio->users--;		retval = -EIO;		goto done;	}	if (radio->users == 1) {		retval = si470x_start(radio);		if (retval < 0)			usb_autopm_put_interface(radio->intf);	}done:	unlock_kernel();	return retval;}/* * si470x_fops_release - file release */static int si470x_fops_release(struct inode *inode, struct file *file){	struct si470x_device *radio = video_drvdata(file);	int retval = 0;	/* safety check */	if (!radio) {		retval = -ENODEV;		goto done;	}	mutex_lock(&radio->disconnect_lock);	radio->users--;	if (radio->users == 0) {		if (radio->disconnected) {			video_unregister_device(radio->videodev);			kfree(radio->buffer);			kfree(radio);			goto unlock;		}		/* stop rds reception */		cancel_delayed_work_sync(&radio->work);		/* cancel read processes */		wake_up_interruptible(&radio->read_queue);		retval = si470x_stop(radio);		usb_autopm_put_interface(radio->intf);	}unlock:	mutex_unlock(&radio->disconnect_lock);done:	return retval;}/* * si470x_fops - file operations interface */static const struct file_operations si470x_fops = {	.owner		= THIS_MODULE,	.llseek		= no_llseek,	.read		= si470x_fops_read,	.poll		= si470x_fops_poll,	.ioctl		= video_ioctl2,#ifdef CONFIG_COMPAT	.compat_ioctl	= v4l_compat_ioctl32,#endif	.open		= si470x_fops_open,	.release	= si470x_fops_release,};/************************************************************************** * Video4Linux Interface **************************************************************************//* * si470x_v4l2_queryctrl - query control */static struct v4l2_queryctrl si470x_v4l2_queryctrl[] = {	{		.id		= V4L2_CID_AUDIO_VOLUME,		.type		= V4L2_CTRL_TYPE_INTEGER,		.name		= "Volume",		.minimum	= 0,		.maximum	= 15,		.step		= 1,		.default_value	= 15,	},	{		.id		= V4L2_CID_AUDIO_MUTE,		.type		= V4L2_CTRL_TYPE_BOOLEAN,		.name		= "Mute",		.minimum	= 0,		.maximum	= 1,		.step		= 1,		.default_value	= 1,	},};/* * si470x_vidioc_querycap - query device capabilities */static int si470x_vidioc_querycap(struct file *file, void *priv,		struct v4l2_capability *capability){	strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));	strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));	sprintf(capability->bus_info, "USB");	capability->version = DRIVER_KERNEL_VERSION;	capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |		V4L2_CAP_TUNER | V4L2_CAP_RADIO;	return 0;}/* * si470x_vidioc_queryctrl - enumerate control items */static int si470x_vidioc_queryctrl(struct file *file, void *priv,		struct v4l2_queryctrl *qc){	unsigned char i = 0;	int retval = -EINVAL;	/* abort if qc->id is below V4L2_CID_BASE */	if (qc->id < V4L2_CID_BASE)		goto done;	/* search video control */	for (i = 0; i < ARRAY_SIZE(si470x_v4l2_queryctrl); i++) {		if (qc->id == si470x_v4l2_queryctrl[i].id) {			memcpy(qc, &(si470x_v4l2_queryctrl[i]), sizeof(*qc));			retval = 0; /* found */			break;		}	}	/* disable unsupported base controls */	/* to satisfy kradio and such apps */	if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {		qc->flags = V4L2_CTRL_FLAG_DISABLED;		retval = 0;	}done:	if (retval < 0)		printk(KERN_WARNING DRIVER_NAME			": query controls failed with %d\n", retval);	return retval;}/* * si470x_vidioc_g_ctrl - get the value of a control */static int si470x_vidioc_g_ctrl(struct file *file, void *priv,		struct v4l2_control *ctrl){	struct si470x_device *radio = video_drvdata(file);	int retval = 0;	/* safety checks */	if (radio->disconnected) {		retval = -EIO;		goto done;	}	switch (ctrl->id) {	case V4L2_CID_AUDIO_VOLUME:		ctrl->value = radio->registers[SYSCONFIG2] &				SYSCONFIG2_VOLUME;		break;	case V4L2_CID_AUDIO_MUTE:		ctrl->value = ((radio->registers[POWERCFG] &				POWERCFG_DMUTE) == 0) ? 1 : 0;		break;	default:		retval = -EINVAL;	}done:	if (retval < 0)		printk(KERN_WARNING DRIVER_NAME			": get control failed with %d\n", retval);	return retval;}/* * si470x_vidioc_s_ctrl - set the value of a control */static int si470x_vidioc_s_ctrl(struct file *file, void *priv,		struct v4l2_control *ctrl){	struct si470x_device *radio = video_drvdata(file);	int retval = 0;	/* safety checks */	if (radio->disconnected) {		retval = -EIO;		goto done;	}	switch (ctrl->id) {	case V4L2_CID_AUDIO_VOLUME:		radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;		radio->registers[SYSCONFIG2] |= ctrl->value;		retval = si470x_set_register(radio, SYSCONFIG2);		break;	case V4L2_CID_AUDIO_MUTE:		if (ctrl->value == 1)			radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;		else			radio->registers[POWERCFG] |= POWERCFG_DMUTE;		retval = si470x_set_register(radio, POWERCFG);		break;	default:		retval = -EINVAL;	}done:	if (retval < 0)		printk(KERN_WARNING DRIVER_NAME			": set control failed with %d\n", retval);	return retval;}/* * si470x_vidioc_g_audio - get audio attributes */

⌨️ 快捷键说明

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