V4L/DVB (7062): radio-si570x: Some fixes and new USB ID addition

- avoid poss. locking when doing copy_to_user which may sleep
- RDS is automatically activated on read now
- code cleaned of unnecessary rds_commands
- USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
  (thanks to Guillaume RAMOUSSE)

Signed-off-by: Tobias Lorenz <tobias.lorenz@gmx.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
This commit is contained in:
Tobias Lorenz 2008-01-25 05:14:57 -03:00 committed by Mauro Carvalho Chehab
parent 8bf5e5ca39
commit 2fb8840663

View file

@ -55,13 +55,17 @@
* - applied all checkpatch.pl v1.12 suggestions * - applied all checkpatch.pl v1.12 suggestions
* except the warning about the too long lines with bit comments * except the warning about the too long lines with bit comments
* - renamed FMRADIO to RADIO to cut line length (checkpatch.pl) * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
* 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.4
* - avoid poss. locking when doing copy_to_user which may sleep
* - RDS is automatically activated on read now
* - code cleaned of unnecessary rds_commands
* - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
* (thanks to Guillaume RAMOUSSE)
* *
* ToDo: * ToDo:
* - check USB Vendor/Product ID for ADS/Tech FM Radio Receiver
* (formerly Instant FM Music) (RDX-155-EF) is 06e1:a155
* - add seeking support * - add seeking support
* - add firmware download/update support * - add firmware download/update support
* - add possibility to switch off RDS
* - RDS support: interrupt mode, instead of polling * - RDS support: interrupt mode, instead of polling
* - add LED status output (check if that's not already done in firmware) * - add LED status output (check if that's not already done in firmware)
*/ */
@ -70,7 +74,7 @@
/* driver definitions */ /* driver definitions */
#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>" #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
#define DRIVER_NAME "radio-si470x" #define DRIVER_NAME "radio-si470x"
#define DRIVER_VERSION KERNEL_VERSION(1, 0, 3) #define DRIVER_VERSION KERNEL_VERSION(1, 0, 4)
#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver" #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers" #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
@ -93,6 +97,8 @@
static struct usb_device_id si470x_usb_driver_id_table[] = { static struct usb_device_id si470x_usb_driver_id_table[] = {
/* Silicon Labs USB FM Radio Reference Design */ /* Silicon Labs USB FM Radio Reference Design */
{ USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
/* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
/* Terminating entry */ /* Terminating entry */
{ } { }
}; };
@ -159,6 +165,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
/* RDS poll frequency */ /* RDS poll frequency */
static int rds_poll_time = 40; static int rds_poll_time = 40;
/* 40 is used by the original USBRadio.exe */ /* 40 is used by the original USBRadio.exe */
/* 50 is used by radio-cadet */
/* 75 should be okay */ /* 75 should be okay */
/* 80 is the usual RDS receive interval */ /* 80 is the usual RDS receive interval */
module_param(rds_poll_time, int, 0); module_param(rds_poll_time, int, 0);
@ -399,16 +406,13 @@ struct si470x_device {
/* RDS receive buffer */ /* RDS receive buffer */
struct work_struct work; struct work_struct work;
wait_queue_head_t read_queue;
struct timer_list timer; struct timer_list timer;
spinlock_t lock; /* buffer locking */ spinlock_t lock; /* buffer locking */
unsigned char *buffer; unsigned char *buffer; /* size is always multiple of three */
unsigned int buf_size; unsigned int buf_size;
unsigned int rd_index; unsigned int rd_index;
unsigned int wr_index; unsigned int wr_index;
unsigned int block_count;
unsigned char last_blocknum;
wait_queue_head_t read_queue;
int data_available_for_read;
}; };
@ -658,8 +662,7 @@ static int si470x_start(struct si470x_device *radio)
return retval; return retval;
/* sysconfig 1 */ /* sysconfig 1 */
radio->registers[SYSCONFIG1] = radio->registers[SYSCONFIG1] = SYSCONFIG1_DE;
SYSCONFIG1_DE | SYSCONFIG1_RDS;
retval = si470x_set_register(radio, SYSCONFIG1); retval = si470x_set_register(radio, SYSCONFIG1);
if (retval < 0) if (retval < 0)
return retval; return retval;
@ -685,6 +688,14 @@ static int si470x_start(struct si470x_device *radio)
*/ */
static int si470x_stop(struct si470x_device *radio) static int si470x_stop(struct si470x_device *radio)
{ {
int retval;
/* sysconfig 1 */
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
retval = si470x_set_register(radio, SYSCONFIG1);
if (retval < 0)
return retval;
/* powercfg */ /* powercfg */
radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
/* POWERCFG_ENABLE has to automatically go low */ /* POWERCFG_ENABLE has to automatically go low */
@ -693,6 +704,17 @@ static int si470x_stop(struct si470x_device *radio)
} }
/*
* si470x_rds_on - switch on rds reception
*/
static int si470x_rds_on(struct si470x_device *radio)
{
/* sysconfig 1 */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
return si470x_set_register(radio, SYSCONFIG1);
}
/************************************************************************** /**************************************************************************
* RDS Driver Functions * RDS Driver Functions
@ -703,15 +725,13 @@ static int si470x_stop(struct si470x_device *radio)
*/ */
static void si470x_rds(struct si470x_device *radio) static void si470x_rds(struct si470x_device *radio)
{ {
unsigned long flags;
unsigned char tmpbuf[3]; unsigned char tmpbuf[3];
unsigned char blocknum; unsigned char blocknum;
unsigned char bler; /* RDS block errors */ unsigned char bler; /* rds block errors */
unsigned short rds; unsigned short rds;
unsigned int i; unsigned int i;
if (radio->users == 0) /* get rds blocks */
return;
if (si470x_get_rds_registers(radio) < 0) if (si470x_get_rds_registers(radio) < 0)
return; return;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) { if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
@ -723,63 +743,67 @@ static void si470x_rds(struct si470x_device *radio)
return; return;
} }
for (blocknum = 0; blocknum < 4; blocknum++) { /* copy four RDS blocks to internal buffer */
switch (blocknum) { if (spin_trylock(&radio->lock)) {
default: /* process each rds block */
bler = (radio->registers[STATUSRSSI] & for (blocknum = 0; blocknum < 4; blocknum++) {
STATUSRSSI_BLERA) >> 9; switch (blocknum) {
rds = radio->registers[RDSA]; default:
break; bler = (radio->registers[STATUSRSSI] &
case 1: STATUSRSSI_BLERA) >> 9;
bler = (radio->registers[READCHAN] & rds = radio->registers[RDSA];
READCHAN_BLERB) >> 14; break;
rds = radio->registers[RDSB]; case 1:
break; bler = (radio->registers[READCHAN] &
case 2: READCHAN_BLERB) >> 14;
bler = (radio->registers[READCHAN] & rds = radio->registers[RDSB];
READCHAN_BLERC) >> 12; break;
rds = radio->registers[RDSC]; case 2:
break; bler = (radio->registers[READCHAN] &
case 3: READCHAN_BLERC) >> 12;
bler = (radio->registers[READCHAN] & rds = radio->registers[RDSC];
READCHAN_BLERD) >> 10; break;
rds = radio->registers[RDSD]; case 3:
break; bler = (radio->registers[READCHAN] &
}; READCHAN_BLERD) >> 10;
rds = radio->registers[RDSD];
break;
};
/* Fill the V4L2 RDS buffer */ /* Fill the V4L2 RDS buffer */
tmpbuf[0] = rds & 0x00ff; /* LSB */ tmpbuf[0] = rds & 0x00ff; /* LSB */
tmpbuf[1] = (rds & 0xff00) >> 8;/* MSB */ tmpbuf[1] = (rds & 0xff00) >> 8;/* MSB */
tmpbuf[2] = blocknum; /* offset name */ tmpbuf[2] = blocknum; /* offset name */
tmpbuf[2] |= blocknum << 3; /* received offset */ tmpbuf[2] |= blocknum << 3; /* received offset */
if (bler > max_rds_errors) if (bler > max_rds_errors)
tmpbuf[2] |= 0x80; /* uncorrectable errors */ tmpbuf[2] |= 0x80; /* uncorrectable errors */
else if (bler > 0) else if (bler > 0)
tmpbuf[2] |= 0x40; /* corrected error(s) */ tmpbuf[2] |= 0x40; /* corrected error(s) */
spin_lock_irqsave(&radio->lock, flags); /* copy RDS block to internal buffer */
for (i = 0; i < 3; i++) {
radio->buffer[radio->wr_index] = tmpbuf[i];
radio->wr_index++;
}
/* copy RDS block to internal buffer */ /* wrap write pointer */
for (i = 0; i < 3; i++) { if (radio->wr_index >= radio->buf_size)
radio->buffer[radio->wr_index] = tmpbuf[i]; radio->wr_index = 0;
radio->wr_index++;
/* 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;
}
} }
spin_unlock(&radio->lock);
if (radio->wr_index >= radio->buf_size)
radio->wr_index = 0;
if (radio->wr_index == radio->rd_index) {
radio->rd_index += 3;
if (radio->rd_index >= radio->buf_size)
radio->rd_index = 0;
} else
radio->block_count++;
spin_unlock_irqrestore(&radio->lock, flags);
} }
radio->data_available_for_read = 1; /* wake up read queue */
wake_up_interruptible(&radio->read_queue); if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue);
} }
@ -795,14 +819,14 @@ static void si470x_timer(unsigned long data)
/* /*
* si470x_timer - rds work function * si470x_work - rds work function
*/ */
static void si470x_work(struct work_struct *work) static void si470x_work(struct work_struct *work)
{ {
struct si470x_device *radio = container_of(work, struct si470x_device, struct si470x_device *radio = container_of(work, struct si470x_device,
work); work);
if (radio->users == 0) if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
return; return;
si470x_rds(radio); si470x_rds(radio);
@ -822,53 +846,52 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos) size_t count, loff_t *ppos)
{ {
struct si470x_device *radio = video_get_drvdata(video_devdata(file)); struct si470x_device *radio = video_get_drvdata(video_devdata(file));
struct rds_command cmd; int retval = 0;
unsigned long flags; unsigned int block_count = 0;
unsigned int i;
unsigned int rd_blocks;
cmd.block_count = count / 3; /* each RDS block needs 3 bytes */ /* switch on rds reception */
cmd.result = 0; if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
cmd.buffer = buf; si470x_rds_on(radio);
cmd.instance = file; schedule_work(&radio->work);
/* copy RDS block out of internal buffer */
while (!radio->data_available_for_read) {
if (wait_event_interruptible(radio->read_queue,
radio->data_available_for_read) < 0)
return -EINTR;
} }
spin_lock_irqsave(&radio->lock, flags); /* block if no new data available */
rd_blocks = cmd.block_count; while (radio->wr_index == radio->rd_index) {
if (rd_blocks > radio->block_count) if (file->f_flags & O_NONBLOCK)
rd_blocks = radio->block_count; return -EWOULDBLOCK;
interruptible_sleep_on(&radio->read_queue);
if (!rd_blocks) {
spin_unlock_irqrestore(&radio->lock, flags);
return cmd.result;
} }
for (i = 0; i < rd_blocks; i++) { /* calculate block count from byte count */
/* copy RDS block to user buffer */ count /= 3;
if (radio->rd_index == radio->wr_index)
break;
if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) /* copy RDS block out of internal buffer and to user buffer */
break; if (spin_trylock(&radio->lock)) {
while (block_count < count) {
if (radio->rd_index == radio->wr_index)
break;
radio->rd_index += 3; /* always transfer rds complete blocks */
if (radio->rd_index >= radio->buf_size) if (copy_to_user(buf,
radio->rd_index = 0; &radio->buffer[radio->rd_index], 3))
radio->block_count--; /* retval = -EFAULT; */
break;
buf += 3; /* increment and wrap read pointer */
cmd.result += 3; radio->rd_index += 3;
if (radio->rd_index >= radio->buf_size)
radio->rd_index = 0;
/* increment counters */
block_count++;
buf += 3;
retval += 3;
}
spin_unlock(&radio->lock);
} }
radio->data_available_for_read = (radio->block_count > 0);
spin_unlock_irqrestore(&radio->lock, flags);
return cmd.result; return retval;
} }
@ -879,14 +902,19 @@ static unsigned int si470x_fops_poll(struct file *file,
struct poll_table_struct *pts) struct poll_table_struct *pts)
{ {
struct si470x_device *radio = video_get_drvdata(video_devdata(file)); struct si470x_device *radio = video_get_drvdata(video_devdata(file));
int retval;
retval = 0; /* switch on rds reception */
if (radio->data_available_for_read) if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
retval = POLLIN | POLLRDNORM; si470x_rds_on(radio);
schedule_work(&radio->work);
}
poll_wait(file, &radio->read_queue, pts); poll_wait(file, &radio->read_queue, pts);
return retval; if (radio->rd_index != radio->wr_index)
return POLLIN | POLLRDNORM;
return 0;
} }
@ -895,17 +923,11 @@ static unsigned int si470x_fops_poll(struct file *file,
*/ */
static int si470x_fops_open(struct inode *inode, struct file *file) static int si470x_fops_open(struct inode *inode, struct file *file)
{ {
int retval;
struct si470x_device *radio = video_get_drvdata(video_devdata(file)); struct si470x_device *radio = video_get_drvdata(video_devdata(file));
radio->users++; radio->users++;
if (radio->users == 1) { if (radio->users == 1)
retval = si470x_start(radio); return si470x_start(radio);
if (retval < 0)
return retval;
schedule_work(&radio->work);
}
return 0; return 0;
} }
@ -916,7 +938,6 @@ static int si470x_fops_open(struct inode *inode, struct file *file)
*/ */
static int si470x_fops_release(struct inode *inode, struct file *file) static int si470x_fops_release(struct inode *inode, struct file *file)
{ {
int retval;
struct si470x_device *radio = video_get_drvdata(video_devdata(file)); struct si470x_device *radio = video_get_drvdata(video_devdata(file));
if (!radio) if (!radio)
@ -924,12 +945,14 @@ static int si470x_fops_release(struct inode *inode, struct file *file)
radio->users--; radio->users--;
if (radio->users == 0) { if (radio->users == 0) {
radio->data_available_for_read = 1; /* ? */ /* stop rds reception */
wake_up_interruptible(&radio->read_queue); /* ? */ del_timer_sync(&radio->timer);
flush_scheduled_work();
retval = si470x_stop(radio); /* cancel read processes */
if (retval < 0) wake_up_interruptible(&radio->read_queue);
return retval;
return si470x_stop(radio);
} }
return 0; return 0;
@ -1314,9 +1337,10 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
< RADIO_SW_VERSION_CURRENT) < RADIO_SW_VERSION_CURRENT)
printk(KERN_WARNING DRIVER_NAME printk(KERN_WARNING DRIVER_NAME
": This driver is known to work with chip version %d, " ": This driver is known to work with chip version %d, "
"but the device has firmware %d. If you have some " "but the device has firmware %d.\n"
"trouble using this driver, please report to V4L ML " DRIVER_NAME
"at video4linux-list@redhat.com\n", "If you have some trouble using this driver, please "
"report to V4L ML at video4linux-list@redhat.com\n",
radio->registers[CHIPID] & CHIPID_FIRMWARE, radio->registers[CHIPID] & CHIPID_FIRMWARE,
RADIO_SW_VERSION_CURRENT); RADIO_SW_VERSION_CURRENT);
@ -1331,12 +1355,9 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
kfree(radio); kfree(radio);
return -ENOMEM; return -ENOMEM;
} }
radio->block_count = 0;
radio->wr_index = 0; radio->wr_index = 0;
radio->rd_index = 0; radio->rd_index = 0;
radio->last_blocknum = 0xff;
init_waitqueue_head(&radio->read_queue); init_waitqueue_head(&radio->read_queue);
radio->data_available_for_read = 0;
/* prepare polling via eventd */ /* prepare polling via eventd */
INIT_WORK(&radio->work, si470x_work); INIT_WORK(&radio->work, si470x_work);
@ -1408,4 +1429,4 @@ module_exit(si470x_module_exit);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC); MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION("1.0.3"); MODULE_VERSION("1.0.4");