The patch number 12145 was added via Mauro Carvalho Chehab <[email protected]>
to http://linuxtv.org/hg/v4l-dvb master development tree.

Kernel patches in this development tree may be modified to be backward
compatible with older kernels. Compatibility modifications will be
removed before inclusion into the mainstream Kernel

If anyone has any objections, please let us know by sending a message to:
        Linux Media Mailing List <[email protected]>

------

From: Mauro Carvalho Chehab  <[email protected]>
merge: http://linuxtv.org/hg/~tlorenz/v4l-dvb


Signed-off-by: Mauro Carvalho Chehab <[email protected]>


---

 linux/drivers/media/radio/radio-si470x.c |  387 +++++++++++------------
 1 file changed, 194 insertions(+), 193 deletions(-)

diff -r f1234ea6abb4 -r 519e04ce8fda linux/drivers/media/radio/radio-si470x.c
--- a/linux/drivers/media/radio/radio-si470x.c  Tue Jun 30 08:57:26 2009 -0300
+++ b/linux/drivers/media/radio/radio-si470x.c  Tue Jun 30 12:25:57 2009 -0300
@@ -106,20 +106,24 @@
  *             Tobias Lorenz <[email protected]>
  *             - add LED status output
  *             - get HW/SW version from scratchpad
+ * 2009-06-16   Edouard Lafargue <[email protected]>
+ *             Version 1.0.10
+ *             - add support for interrupt mode for RDS endpoint,
+ *                instead of polling.
+ *                Improves RDS reception significantly
  *
  * ToDo:
  * - add firmware download/update support
- * - RDS support: interrupt mode, instead of polling
  */
 
 
 /* driver definitions */
 #define DRIVER_AUTHOR "Tobias Lorenz <[email protected]>"
 #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_DESC "USB radio driver for Si470x FM Radio Receivers"
-#define DRIVER_VERSION "1.0.9"
+#define DRIVER_VERSION "1.0.10"
 
 
 /* kernel includes */
@@ -218,16 +222,6 @@ module_param(max_rds_errors, ushort, 064
 module_param(max_rds_errors, ushort, 0644);
 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
@@ -450,6 +444,12 @@ struct si470x_device {
        struct usb_interface *intf;
        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 */
        unsigned int users;
        unsigned char disconnected;
@@ -459,7 +459,6 @@ struct si470x_device {
        unsigned short registers[RADIO_REGISTER_NUM];
 
        /* RDS receive buffer */
-       struct delayed_work work;
        wait_queue_head_t read_queue;
        struct mutex lock;              /* buffer locking */
        unsigned char *buffer;          /* size is always multiple of three */
@@ -865,43 +864,6 @@ static int si470x_get_all_registers(stru
 
 
 /**************************************************************************
- * 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
  **************************************************************************/
 
@@ -959,102 +921,118 @@ static int si470x_get_scratch_page_versi
  **************************************************************************/
 
 /*
- * si470x_rds - rds processing function
- */
-static void si470x_rds(struct si470x_device *radio)
-{
+ * si470x_int_in_callback - rds callback and processing function
+ *
+ * TODO: do we need to use mutex locks in some sections?
+ */
+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 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;
+       if (urb->status) {
+               if (urb->status == -ENOENT ||
+                               urb->status == -ECONNRESET ||
+                               urb->status == -ESHUTDOWN) {
+                       return;
+               } else {
+                       printk(KERN_WARNING DRIVER_NAME
+                        ": non-zero urb status (%d)\n", urb->status);
+                       goto resubmit; /* Maybe we can recover. */
                }
        }
-       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));
+               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) {
+                       /* RDS decoder not synchronized */
+                       goto resubmit;
+               }
+               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;
+                       }
+               }
+               if (radio->wr_index != radio->rd_index)
+                       wake_up_interruptible(&radio->read_queue);
+       }
+
+resubmit:
+       /* 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 f
        /* 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 */
@@ -1136,8 +1112,6 @@ static unsigned int si470x_fops_poll(str
        /* 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);
@@ -1170,8 +1144,31 @@ static int si470x_fops_open(struct file 
        if (radio->users == 1) {
                /* 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);
+               }
        }
 
 done:
@@ -1197,15 +1194,20 @@ static int si470x_fops_release(struct fi
        mutex_lock(&radio->disconnect_lock);
        radio->users--;
        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) {
                        video_unregister_device(radio->videodev);
+                       kfree(radio->int_in_buffer);
                        kfree(radio->buffer);
                        kfree(radio);
                        goto done;
                }
-
-               /* stop rds reception */
-               cancel_delayed_work_sync(&radio->work);
 
                /* cancel read processes */
                wake_up_interruptible(&radio->read_queue);
@@ -1241,31 +1243,6 @@ static const struct v4l2_file_operations
  **************************************************************************/
 
 /*
- * 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,
@@ -1290,7 +1267,6 @@ static int si470x_vidioc_queryctrl(struc
 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 */
@@ -1298,12 +1274,11 @@ static int si470x_vidioc_queryctrl(struc
                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;
-               }
+       switch (qc->id) {
+       case V4L2_CID_AUDIO_VOLUME:
+               return v4l2_ctrl_query_fill(qc, 0, 15, 1, 15);
+       case V4L2_CID_AUDIO_MUTE:
+               return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
        }
 
        /* disable unsupported base controls */
@@ -1658,7 +1633,9 @@ static int si470x_usb_driver_probe(struc
                const struct usb_device_id *id)
 {
        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 */
        radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
@@ -1673,11 +1650,45 @@ static int si470x_usb_driver_probe(struc
        mutex_init(&radio->disconnect_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 */
        radio->videodev = video_device_alloc();
        if (!radio->videodev) {
                retval = -ENOMEM;
-               goto err_radio;
+               goto err_intbuffer;
        }
        memcpy(radio->videodev, &si470x_viddev_template,
                        sizeof(si470x_viddev_template));
@@ -1735,9 +1746,6 @@ static int si470x_usb_driver_probe(struc
        radio->rd_index = 0;
        init_waitqueue_head(&radio->read_queue);
 
-       /* prepare rds work function */
-       INIT_DELAYED_WORK(&radio->work, si470x_work);
-
        /* register video device */
        retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, 
radio_nr);
        if (retval) {
@@ -1752,6 +1760,8 @@ err_all:
        kfree(radio->buffer);
 err_video:
        video_device_release(radio->videodev);
+err_intbuffer:
+       kfree(radio->int_in_buffer);
 err_radio:
        kfree(radio);
 err_initial:
@@ -1765,12 +1775,8 @@ static int si470x_usb_driver_suspend(str
 static int si470x_usb_driver_suspend(struct usb_interface *intf,
                pm_message_t message)
 {
-       struct si470x_device *radio = usb_get_intfdata(intf);
-
        printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
 
-       cancel_delayed_work_sync(&radio->work);
-
        return 0;
 }
 
@@ -1780,15 +1786,7 @@ static int si470x_usb_driver_suspend(str
  */
 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");
-
-       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;
 }
@@ -1803,12 +1801,15 @@ static void si470x_usb_driver_disconnect
 
        mutex_lock(&radio->disconnect_lock);
        radio->disconnected = 1;
-       cancel_delayed_work_sync(&radio->work);
        usb_set_intfdata(intf, NULL);
        if (radio->users == 0) {
                /* set led to disconnect state */
                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);
                kfree(radio->buffer);
                kfree(radio);


---

Patch is available at: 
http://linuxtv.org/hg/v4l-dvb/rev/519e04ce8fda46afcfc1d264ca4b4bdc6511a143

_______________________________________________
linuxtv-commits mailing list
[email protected]
http://www.linuxtv.org/cgi-bin/mailman/listinfo/linuxtv-commits

Reply via email to