Commit 721f59ed authored by Tobias Lorenz's avatar Tobias Lorenz Committed by Mauro Carvalho Chehab

V4L/DVB (12142): radio-si470x: Add suport for RDS endpoint interrupt mode

Add support for interrupt mode for RDS endpoint, instead of polling.
Improves RDS reception significantly
Signed-off-by: default avatarEdouard Lafargue <edouard@lafargue.name>
Acked-by: default avatarTobias Lorenz <tobias.lorenz@gmx.net>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 86d71014
...@@ -106,20 +106,24 @@ ...@@ -106,20 +106,24 @@
* Tobias Lorenz <tobias.lorenz@gmx.net> * Tobias Lorenz <tobias.lorenz@gmx.net>
* - add LED status output * - add LED status output
* - get HW/SW version from scratchpad * - get HW/SW version from scratchpad
* 2009-06-16 Edouard Lafargue <edouard@lafargue.name>
* Version 1.0.10
* - add support for interrupt mode for RDS endpoint,
* instead of polling.
* Improves RDS reception significantly
* *
* ToDo: * ToDo:
* - add firmware download/update support * - add firmware download/update support
* - RDS support: interrupt mode, instead of polling
*/ */
/* 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_KERNEL_VERSION KERNEL_VERSION(1, 0, 9) #define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 10)
#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"
#define DRIVER_VERSION "1.0.9" #define DRIVER_VERSION "1.0.10"
/* kernel includes */ /* kernel includes */
...@@ -218,16 +222,6 @@ static unsigned short max_rds_errors = 1; ...@@ -218,16 +222,6 @@ static unsigned short max_rds_errors = 1;
module_param(max_rds_errors, ushort, 0644); module_param(max_rds_errors, ushort, 0644);
MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
/* RDS poll frequency */
static unsigned int rds_poll_time = 40;
/* 40 is used by the original USBRadio.exe */
/* 50 is used by radio-cadet */
/* 75 should be okay */
/* 80 is the usual RDS receive interval */
module_param(rds_poll_time, uint, 0644);
MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*");
/************************************************************************** /**************************************************************************
* Register Definitions * Register Definitions
...@@ -450,6 +444,12 @@ struct si470x_device { ...@@ -450,6 +444,12 @@ struct si470x_device {
struct usb_interface *intf; struct usb_interface *intf;
struct video_device *videodev; struct video_device *videodev;
/* Interrupt endpoint handling */
char *int_in_buffer;
struct usb_endpoint_descriptor *int_in_endpoint;
struct urb *int_in_urb;
int int_in_running;
/* driver management */ /* driver management */
unsigned int users; unsigned int users;
unsigned char disconnected; unsigned char disconnected;
...@@ -459,7 +459,6 @@ struct si470x_device { ...@@ -459,7 +459,6 @@ struct si470x_device {
unsigned short registers[RADIO_REGISTER_NUM]; unsigned short registers[RADIO_REGISTER_NUM];
/* RDS receive buffer */ /* RDS receive buffer */
struct delayed_work work;
wait_queue_head_t read_queue; wait_queue_head_t read_queue;
struct mutex lock; /* buffer locking */ struct mutex lock; /* buffer locking */
unsigned char *buffer; /* size is always multiple of three */ unsigned char *buffer; /* size is always multiple of three */
...@@ -864,43 +863,6 @@ static int si470x_get_all_registers(struct si470x_device *radio) ...@@ -864,43 +863,6 @@ static int si470x_get_all_registers(struct si470x_device *radio)
/**************************************************************************
* General Driver Functions - RDS_REPORT
**************************************************************************/
/*
* si470x_get_rds_registers - read rds registers
*/
static int si470x_get_rds_registers(struct si470x_device *radio)
{
unsigned char buf[RDS_REPORT_SIZE];
int retval;
int size;
unsigned char regnr;
buf[0] = RDS_REPORT;
retval = usb_interrupt_msg(radio->usbdev,
usb_rcvintpipe(radio->usbdev, 1),
(void *) &buf, sizeof(buf), &size, usb_timeout);
if (size != sizeof(buf))
printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
"return size differs: %d != %zu\n", size, sizeof(buf));
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
"usb_interrupt_msg returned %d\n", retval);
if (retval >= 0)
for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
radio->registers[STATUSRSSI + regnr] =
get_unaligned_be16(
&buf[regnr * RADIO_REGISTER_SIZE + 1]);
return (retval < 0) ? -EINVAL : 0;
}
/************************************************************************** /**************************************************************************
* General Driver Functions - LED_REPORT * General Driver Functions - LED_REPORT
**************************************************************************/ **************************************************************************/
...@@ -959,29 +921,55 @@ static int si470x_get_scratch_page_versions(struct si470x_device *radio) ...@@ -959,29 +921,55 @@ static int si470x_get_scratch_page_versions(struct si470x_device *radio)
**************************************************************************/ **************************************************************************/
/* /*
* si470x_rds - rds processing function * si470x_int_in_callback - rds callback and processing function
*
* TODO: do we need to use mutex locks in some sections?
*/ */
static void si470x_rds(struct si470x_device *radio) static void si470x_int_in_callback(struct urb *urb)
{ {
struct si470x_device *radio = urb->context;
unsigned char buf[RDS_REPORT_SIZE];
int retval;
unsigned char regnr;
unsigned char blocknum; unsigned char blocknum;
unsigned short bler; /* rds block errors */ unsigned short bler; /* rds block errors */
unsigned short rds; unsigned short rds;
unsigned char tmpbuf[3]; unsigned char tmpbuf[3];
/* get rds blocks */ if (urb->status) {
if (si470x_get_rds_registers(radio) < 0) if (urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN) {
return; return;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) { } else {
/* No RDS group ready */ printk(KERN_WARNING DRIVER_NAME
": non-zero urb status (%d)\n", urb->status);
goto resubmit; /* Maybe we can recover. */
}
}
/* safety checks */
if (radio->disconnected)
return; return;
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
goto resubmit;
if (urb->actual_length > 0) {
/* Update RDS registers with URB data */
buf[0] = RDS_REPORT;
for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
radio->registers[STATUSRSSI + regnr] =
get_unaligned_be16(&radio->int_in_buffer[
regnr * RADIO_REGISTER_SIZE + 1]);
/* get rds blocks */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
/* No RDS group ready, better luck next time */
goto resubmit;
} }
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) { if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
/* RDS decoder not synchronized */ /* RDS decoder not synchronized */
return; goto resubmit;
} }
/* copy all four RDS blocks to internal buffer */
mutex_lock(&radio->lock);
for (blocknum = 0; blocknum < 4; blocknum++) { for (blocknum = 0; blocknum < 4; blocknum++) {
switch (blocknum) { switch (blocknum) {
default: default:
...@@ -1031,30 +1019,20 @@ static void si470x_rds(struct si470x_device *radio) ...@@ -1031,30 +1019,20 @@ static void si470x_rds(struct si470x_device *radio)
radio->rd_index = 0; radio->rd_index = 0;
} }
} }
mutex_unlock(&radio->lock);
/* wake up read queue */
if (radio->wr_index != radio->rd_index) if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue); 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); resubmit:
schedule_delayed_work(&radio->work, msecs_to_jiffies(rds_poll_time)); /* Resubmit if we're still running. */
if (radio->int_in_running && radio->usbdev) {
retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
if (retval) {
printk(KERN_WARNING DRIVER_NAME
": resubmitting urb failed (%d)", retval);
radio->int_in_running = 0;
}
}
} }
...@@ -1076,8 +1054,6 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf, ...@@ -1076,8 +1054,6 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf,
/* switch on rds reception */ /* switch on rds reception */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) { if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
si470x_rds_on(radio); si470x_rds_on(radio);
schedule_delayed_work(&radio->work,
msecs_to_jiffies(rds_poll_time));
} }
/* block if no new data available */ /* block if no new data available */
...@@ -1136,8 +1112,6 @@ static unsigned int si470x_fops_poll(struct file *file, ...@@ -1136,8 +1112,6 @@ static unsigned int si470x_fops_poll(struct file *file,
/* switch on rds reception */ /* switch on rds reception */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) { if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
si470x_rds_on(radio); si470x_rds_on(radio);
schedule_delayed_work(&radio->work,
msecs_to_jiffies(rds_poll_time));
} }
poll_wait(file, &radio->read_queue, pts); poll_wait(file, &radio->read_queue, pts);
...@@ -1167,13 +1141,39 @@ static int si470x_fops_open(struct file *file) ...@@ -1167,13 +1141,39 @@ static int si470x_fops_open(struct file *file)
goto done; goto done;
} }
printk(KERN_INFO DRIVER_NAME
": Opened radio (users now: %i)\n", radio->users);
if (radio->users == 1) { if (radio->users == 1) {
/* start radio */ /* start radio */
retval = si470x_start(radio); retval = si470x_start(radio);
if (retval < 0) if (retval < 0) {
usb_autopm_put_interface(radio->intf);
goto done;
}
/* Initialize interrupt URB. */
usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
usb_rcvintpipe(radio->usbdev,
radio->int_in_endpoint->bEndpointAddress),
radio->int_in_buffer,
le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
si470x_int_in_callback,
radio,
radio->int_in_endpoint->bInterval);
radio->int_in_running = 1;
mb();
retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
if (retval) {
printk(KERN_INFO DRIVER_NAME
": submitting int urb failed (%d)\n", retval);
radio->int_in_running = 0;
usb_autopm_put_interface(radio->intf); usb_autopm_put_interface(radio->intf);
} }
}
done: done:
unlock_kernel(); unlock_kernel();
return retval; return retval;
...@@ -1196,17 +1196,25 @@ static int si470x_fops_release(struct file *file) ...@@ -1196,17 +1196,25 @@ static int si470x_fops_release(struct file *file)
mutex_lock(&radio->disconnect_lock); mutex_lock(&radio->disconnect_lock);
radio->users--; radio->users--;
printk(KERN_INFO DRIVER_NAME
": Closed radio (remaining users:%i)\n", radio->users);
if (radio->users == 0) { if (radio->users == 0) {
/* Shutdown Interrupt handler */
if (radio->int_in_running) {
radio->int_in_running = 0;
if (radio->int_in_urb)
usb_kill_urb(radio->int_in_urb);
}
if (radio->disconnected) { if (radio->disconnected) {
video_unregister_device(radio->videodev); video_unregister_device(radio->videodev);
kfree(radio->int_in_buffer);
kfree(radio->buffer); kfree(radio->buffer);
kfree(radio); kfree(radio);
goto unlock; goto unlock;
} }
/* stop rds reception */
cancel_delayed_work_sync(&radio->work);
/* cancel read processes */ /* cancel read processes */
wake_up_interruptible(&radio->read_queue); wake_up_interruptible(&radio->read_queue);
...@@ -1657,7 +1665,9 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, ...@@ -1657,7 +1665,9 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
const struct usb_device_id *id) const struct usb_device_id *id)
{ {
struct si470x_device *radio; struct si470x_device *radio;
int retval = 0; struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i, int_end_size, retval = 0;
/* private data allocation and initialization */ /* private data allocation and initialization */
radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL); radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
...@@ -1672,11 +1682,45 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, ...@@ -1672,11 +1682,45 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
mutex_init(&radio->disconnect_lock); mutex_init(&radio->disconnect_lock);
mutex_init(&radio->lock); mutex_init(&radio->lock);
iface_desc = intf->cur_altsetting;
/* Set up interrupt endpoint information. */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
USB_DIR_IN) && ((endpoint->bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))
radio->int_in_endpoint = endpoint;
}
if (!radio->int_in_endpoint) {
printk(KERN_INFO DRIVER_NAME
": could not find interrupt in endpoint\n");
retval = -EIO;
goto err_radio;
}
int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
if (!radio->int_in_buffer) {
printk(KERN_INFO DRIVER_NAME
"could not allocate int_in_buffer");
retval = -ENOMEM;
goto err_radio;
}
radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!radio->int_in_urb) {
printk(KERN_INFO DRIVER_NAME "could not allocate int_in_urb");
retval = -ENOMEM;
goto err_intbuffer;
}
/* video device allocation and initialization */ /* video device allocation and initialization */
radio->videodev = video_device_alloc(); radio->videodev = video_device_alloc();
if (!radio->videodev) { if (!radio->videodev) {
retval = -ENOMEM; retval = -ENOMEM;
goto err_radio; goto err_intbuffer;
} }
memcpy(radio->videodev, &si470x_viddev_template, memcpy(radio->videodev, &si470x_viddev_template,
sizeof(si470x_viddev_template)); sizeof(si470x_viddev_template));
...@@ -1734,9 +1778,6 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, ...@@ -1734,9 +1778,6 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
radio->rd_index = 0; radio->rd_index = 0;
init_waitqueue_head(&radio->read_queue); init_waitqueue_head(&radio->read_queue);
/* prepare rds work function */
INIT_DELAYED_WORK(&radio->work, si470x_work);
/* register video device */ /* register video device */
retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr); retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
if (retval) { if (retval) {
...@@ -1751,6 +1792,8 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, ...@@ -1751,6 +1792,8 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
kfree(radio->buffer); kfree(radio->buffer);
err_video: err_video:
video_device_release(radio->videodev); video_device_release(radio->videodev);
err_intbuffer:
kfree(radio->int_in_buffer);
err_radio: err_radio:
kfree(radio); kfree(radio);
err_initial: err_initial:
...@@ -1764,12 +1807,8 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, ...@@ -1764,12 +1807,8 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
static int si470x_usb_driver_suspend(struct usb_interface *intf, static int si470x_usb_driver_suspend(struct usb_interface *intf,
pm_message_t message) pm_message_t message)
{ {
struct si470x_device *radio = usb_get_intfdata(intf);
printk(KERN_INFO DRIVER_NAME ": suspending now...\n"); printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
cancel_delayed_work_sync(&radio->work);
return 0; return 0;
} }
...@@ -1779,16 +1818,8 @@ static int si470x_usb_driver_suspend(struct usb_interface *intf, ...@@ -1779,16 +1818,8 @@ static int si470x_usb_driver_suspend(struct usb_interface *intf,
*/ */
static int si470x_usb_driver_resume(struct usb_interface *intf) static int si470x_usb_driver_resume(struct usb_interface *intf)
{ {
struct si470x_device *radio = usb_get_intfdata(intf);
printk(KERN_INFO DRIVER_NAME ": resuming now...\n"); printk(KERN_INFO DRIVER_NAME ": resuming now...\n");
mutex_lock(&radio->lock);
if (radio->users && radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)
schedule_delayed_work(&radio->work,
msecs_to_jiffies(rds_poll_time));
mutex_unlock(&radio->lock);
return 0; return 0;
} }
...@@ -1802,12 +1833,15 @@ static void si470x_usb_driver_disconnect(struct usb_interface *intf) ...@@ -1802,12 +1833,15 @@ static void si470x_usb_driver_disconnect(struct usb_interface *intf)
mutex_lock(&radio->disconnect_lock); mutex_lock(&radio->disconnect_lock);
radio->disconnected = 1; radio->disconnected = 1;
cancel_delayed_work_sync(&radio->work);
usb_set_intfdata(intf, NULL); usb_set_intfdata(intf, NULL);
if (radio->users == 0) { if (radio->users == 0) {
/* set led to disconnect state */ /* set led to disconnect state */
si470x_set_led_state(radio, BLINK_ORANGE_LED); si470x_set_led_state(radio, BLINK_ORANGE_LED);
/* Free data structures. */
usb_free_urb(radio->int_in_urb);
kfree(radio->int_in_buffer);
video_unregister_device(radio->videodev); video_unregister_device(radio->videodev);
kfree(radio->buffer); kfree(radio->buffer);
kfree(radio); kfree(radio);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment