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 + -
显示快捷键?