]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/media/radio/radio-si470x.c
Merge branch 'for-linus' of git://oss.sgi.com:8090/xfs/xfs-pull
[linux-2.6-omap-h63xx.git] / drivers / media / radio / radio-si470x.c
index 77354ca6e8e928005cfcf631ed97d698bfd9b1ab..a4984ff87c9c8328bd7e59b92022adf994d74430 100644 (file)
  */
 
 
+/*
+ * User Notes:
+ * - USB Audio is provided by the alsa snd_usb_audio module.
+ *   For listing you have to redirect the sound, for example using:
+ *   arecord -D hw:1,0 -r96000 -c2 -f S16_LE | artsdsp aplay -B -
+ * - regarding module parameters in /sys/module/radio_si470x/parameters:
+ *   the contents of read-only files (0444) are not updated, even if
+ *   space, band and de are changed using private video controls
+ * - increase tune_timeout, if you often get -EIO errors
+ * - hw_freq_seek returns -EAGAIN, when timed out or band limit is reached
+ */
+
+
 /*
  * History:
  * 2008-01-12  Tobias Lorenz <tobias.lorenz@gmx.net>
  *             Oliver Neukum <oliver@neukum.org>
  *             Version 1.0.7
  *             - usb autosuspend support
- *             - unplugging fixed
+ *             - unplugging fixed
+ * 2008-05-07  Tobias Lorenz <tobias.lorenz@gmx.net>
+ *             Version 1.0.8
+ *             - hardware frequency seek support
+ *             - afc indication
+ *             - more safety checks, let si470x_get_freq return errno
  *
  * ToDo:
- * - add seeking support
  * - add firmware download/update support
  * - RDS support: interrupt mode, instead of polling
  * - add LED status output (check if that's not already done in firmware)
 /* driver definitions */
 #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
 #define DRIVER_NAME "radio-si470x"
-#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 7)
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 8)
 #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.7"
+#define DRIVER_VERSION "1.0.8"
 
 
 /* kernel includes */
 #include <linux/videodev2.h>
 #include <linux/mutex.h>
 #include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
 #include <media/rds.h>
 #include <asm/unaligned.h>
 
@@ -175,6 +193,11 @@ static unsigned int tune_timeout = 3000;
 module_param(tune_timeout, uint, 0);
 MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
 
+/* Seek timeout */
+static unsigned int seek_timeout = 5000;
+module_param(seek_timeout, uint, 0);
+MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
+
 /* RDS buffer blocks */
 static unsigned int rds_buf = 100;
 module_param(rds_buf, uint, 0);
@@ -425,7 +448,8 @@ struct si470x_device {
 
        /* driver management */
        unsigned int users;
-       unsigned char disconnected;
+       unsigned char disconnected;
+       struct mutex disconnect_lock;
 
        /* Silabs internal registers (0..15) */
        unsigned short registers[RADIO_REGISTER_NUM];
@@ -441,12 +465,6 @@ struct si470x_device {
 };
 
 
-/*
- * Lock to prevent kfree of data before all users have releases the device.
- */
-static DEFINE_MUTEX(open_close_lock);
-
-
 /*
  * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW,
  * 62.5 kHz otherwise.
@@ -476,11 +494,11 @@ static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
                USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
                report[0], 2,
                buf, size, usb_timeout);
+
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
                        ": si470x_get_report: usb_control_msg returned %d\n",
                        retval);
-
        return retval;
 }
 
@@ -499,11 +517,11 @@ static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
                USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
                report[0], 2,
                buf, size, usb_timeout);
+
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
                        ": si470x_set_report: usb_control_msg returned %d\n",
                        retval);
-
        return retval;
 }
 
@@ -521,8 +539,7 @@ static int si470x_get_register(struct si470x_device *radio, int regnr)
        retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
 
        if (retval >= 0)
-               radio->registers[regnr] = be16_to_cpu(get_unaligned(
-                       (unsigned short *) &buf[1]));
+               radio->registers[regnr] = get_unaligned_be16(&buf[1]);
 
        return (retval < 0) ? -EINVAL : 0;
 }
@@ -537,8 +554,7 @@ static int si470x_set_register(struct si470x_device *radio, int regnr)
        int retval;
 
        buf[0] = REGISTER_REPORT(regnr);
-       put_unaligned(cpu_to_be16(radio->registers[regnr]),
-               (unsigned short *) &buf[1]);
+       put_unaligned_be16(radio->registers[regnr], &buf[1]);
 
        retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
 
@@ -561,9 +577,8 @@ static int si470x_get_all_registers(struct si470x_device *radio)
 
        if (retval >= 0)
                for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
-                       radio->registers[regnr] = be16_to_cpu(get_unaligned(
-                               (unsigned short *)
-                               &buf[regnr * RADIO_REGISTER_SIZE + 1]));
+                       radio->registers[regnr] = get_unaligned_be16(
+                               &buf[regnr * RADIO_REGISTER_SIZE + 1]);
 
        return (retval < 0) ? -EINVAL : 0;
 }
@@ -585,7 +600,7 @@ static int si470x_get_rds_registers(struct si470x_device *radio)
                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: "
+               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: "
@@ -594,8 +609,8 @@ static int si470x_get_rds_registers(struct si470x_device *radio)
        if (retval >= 0)
                for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
                        radio->registers[STATUSRSSI + regnr] =
-                               be16_to_cpu(get_unaligned((unsigned short *)
-                               &buf[regnr * RADIO_REGISTER_SIZE + 1]));
+                               get_unaligned_be16(
+                               &buf[regnr * RADIO_REGISTER_SIZE + 1]);
 
        return (retval < 0) ? -EINVAL : 0;
 }
@@ -615,33 +630,39 @@ static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
        radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
        retval = si470x_set_register(radio, CHANNEL);
        if (retval < 0)
-               return retval;
+               goto done;
 
-       /* wait till seek operation has completed */
+       /* wait till tune operation has completed */
        timeout = jiffies + msecs_to_jiffies(tune_timeout);
        do {
                retval = si470x_get_register(radio, STATUSRSSI);
                if (retval < 0)
-                       return retval;
+                       goto stop;
                timed_out = time_after(jiffies, timeout);
        } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
                (!timed_out));
+       if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+               printk(KERN_WARNING DRIVER_NAME ": tune does not complete\n");
        if (timed_out)
                printk(KERN_WARNING DRIVER_NAME
-                       ": seek does not finish after %u ms\n", tune_timeout);
+                       ": tune timed out after %u ms\n", tune_timeout);
 
+stop:
        /* stop tuning */
        radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
-       return si470x_set_register(radio, CHANNEL);
+       retval = si470x_set_register(radio, CHANNEL);
+
+done:
+       return retval;
 }
 
 
 /*
  * si470x_get_freq - get the frequency
  */
-static unsigned int si470x_get_freq(struct si470x_device *radio)
+static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
 {
-       unsigned int spacing, band_bottom, freq;
+       unsigned int spacing, band_bottom;
        unsigned short chan;
        int retval;
 
@@ -667,14 +688,12 @@ static unsigned int si470x_get_freq(struct si470x_device *radio)
 
        /* read channel */
        retval = si470x_get_register(radio, READCHAN);
-       if (retval < 0)
-               return retval;
        chan = radio->registers[READCHAN] & READCHAN_READCHAN;
 
        /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
-       freq = chan * spacing + band_bottom;
+       *freq = chan * spacing + band_bottom;
 
-       return freq;
+       return retval;
 }
 
 
@@ -713,6 +732,62 @@ static int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
 }
 
 
+/*
+ * si470x_set_seek - set seek
+ */
+static int si470x_set_seek(struct si470x_device *radio,
+               unsigned int wrap_around, unsigned int seek_upward)
+{
+       int retval = 0;
+       unsigned long timeout;
+       bool timed_out = 0;
+
+       /* start seeking */
+       radio->registers[POWERCFG] |= POWERCFG_SEEK;
+       if (wrap_around == 1)
+               radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
+       else
+               radio->registers[POWERCFG] |= POWERCFG_SKMODE;
+       if (seek_upward == 1)
+               radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
+       else
+               radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
+       retval = si470x_set_register(radio, POWERCFG);
+       if (retval < 0)
+               goto done;
+
+       /* wait till seek operation has completed */
+       timeout = jiffies + msecs_to_jiffies(seek_timeout);
+       do {
+               retval = si470x_get_register(radio, STATUSRSSI);
+               if (retval < 0)
+                       goto stop;
+               timed_out = time_after(jiffies, timeout);
+       } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
+               (!timed_out));
+       if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+               printk(KERN_WARNING DRIVER_NAME ": seek does not complete\n");
+       if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": seek failed / band limit reached\n");
+       if (timed_out)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": seek timed out after %u ms\n", seek_timeout);
+
+stop:
+       /* stop seeking */
+       radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
+       retval = si470x_set_register(radio, POWERCFG);
+
+done:
+       /* try again, if timed out */
+       if ((retval == 0) && timed_out)
+               retval = -EAGAIN;
+
+       return retval;
+}
+
+
 /*
  * si470x_start - switch on radio
  */
@@ -725,27 +800,30 @@ static int si470x_start(struct si470x_device *radio)
                POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
        retval = si470x_set_register(radio, POWERCFG);
        if (retval < 0)
-               return retval;
+               goto done;
 
        /* sysconfig 1 */
        radio->registers[SYSCONFIG1] = SYSCONFIG1_DE;
        retval = si470x_set_register(radio, SYSCONFIG1);
        if (retval < 0)
-               return retval;
+               goto done;
 
        /* sysconfig 2 */
        radio->registers[SYSCONFIG2] =
-               (0x3f  << 8) |  /* SEEKTH */
-               (band  << 6) |  /* BAND */
-               (space << 4) |  /* SPACE */
-               15;             /* VOLUME (max) */
+               (0x3f  << 8) |                          /* SEEKTH */
+               ((band  << 6) & SYSCONFIG2_BAND)  |     /* BAND */
+               ((space << 4) & SYSCONFIG2_SPACE) |     /* SPACE */
+               15;                                     /* VOLUME (max) */
        retval = si470x_set_register(radio, SYSCONFIG2);
        if (retval < 0)
-               return retval;
+               goto done;
 
        /* reset last channel */
-       return si470x_set_chan(radio,
+       retval = si470x_set_chan(radio,
                radio->registers[CHANNEL] & CHANNEL_CHAN);
+
+done:
+       return retval;
 }
 
 
@@ -760,13 +838,16 @@ static int si470x_stop(struct si470x_device *radio)
        radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
        retval = si470x_set_register(radio, SYSCONFIG1);
        if (retval < 0)
-               return retval;
+               goto done;
 
        /* powercfg */
        radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
        /* POWERCFG_ENABLE has to automatically go low */
        radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
-       return si470x_set_register(radio, POWERCFG);
+       retval = si470x_set_register(radio, POWERCFG);
+
+done:
+       return retval;
 }
 
 
@@ -843,7 +924,7 @@ static void si470x_rds(struct si470x_device *radio)
                };
 
                /* Fill the V4L2 RDS buffer */
-               put_unaligned(cpu_to_le16(rds), (unsigned short *) &tmpbuf);
+               put_unaligned_le16(rds, &tmpbuf);
                tmpbuf[2] = blocknum;           /* offset name */
                tmpbuf[2] |= blocknum << 3;     /* received offset */
                if (bler > max_rds_errors)
@@ -883,8 +964,9 @@ static void si470x_work(struct work_struct *work)
        struct si470x_device *radio = container_of(work, struct si470x_device,
                work.work);
 
-       if (radio->disconnected)
-              return;
+       /* safety checks */
+       if (radio->disconnected)
+               return;
        if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
                return;
 
@@ -917,11 +999,15 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf,
 
        /* block if no new data available */
        while (radio->wr_index == radio->rd_index) {
-               if (file->f_flags & O_NONBLOCK)
-                       return -EWOULDBLOCK;
+               if (file->f_flags & O_NONBLOCK) {
+                       retval = -EWOULDBLOCK;
+                       goto done;
+               }
                if (wait_event_interruptible(radio->read_queue,
-                       radio->wr_index != radio->rd_index) < 0)
-                       return -EINTR;
+                       radio->wr_index != radio->rd_index) < 0) {
+                       retval = -EINTR;
+                       goto done;
+               }
        }
 
        /* calculate block count from byte count */
@@ -950,6 +1036,7 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf,
        }
        mutex_unlock(&radio->lock);
 
+done:
        return retval;
 }
 
@@ -961,6 +1048,7 @@ static unsigned int si470x_fops_poll(struct file *file,
                struct poll_table_struct *pts)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
+       int retval = 0;
 
        /* switch on rds reception */
        if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
@@ -972,9 +1060,9 @@ static unsigned int si470x_fops_poll(struct file *file,
        poll_wait(file, &radio->read_queue, pts);
 
        if (radio->rd_index != radio->wr_index)
-               return POLLIN | POLLRDNORM;
+               retval = POLLIN | POLLRDNORM;
 
-       return 0;
+       return retval;
 }
 
 
@@ -991,17 +1079,18 @@ static int si470x_fops_open(struct inode *inode, struct file *file)
        retval = usb_autopm_get_interface(radio->intf);
        if (retval < 0) {
                radio->users--;
-               return -EIO;
+               retval = -EIO;
+               goto done;
        }
 
        if (radio->users == 1) {
                retval = si470x_start(radio);
                if (retval < 0)
                        usb_autopm_put_interface(radio->intf);
-               return retval;
        }
 
-       return 0;
+done:
+       return retval;
 }
 
 
@@ -1011,20 +1100,23 @@ static int si470x_fops_open(struct inode *inode, struct file *file)
 static int si470x_fops_release(struct inode *inode, struct file *file)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
-       int retval = 0;
+       int retval = 0;
 
-       if (!radio)
-               return -ENODEV;
+       /* safety check */
+       if (!radio) {
+               retval = -ENODEV;
+               goto done;
+       }
 
-       mutex_lock(&open_close_lock);
+       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 done;
-              }
+               if (radio->disconnected) {
+                       video_unregister_device(radio->videodev);
+                       kfree(radio->buffer);
+                       kfree(radio);
+                       goto unlock;
+               }
 
                /* stop rds reception */
                cancel_delayed_work_sync(&radio->work);
@@ -1036,9 +1128,11 @@ static int si470x_fops_release(struct inode *inode, struct file *file)
                usb_autopm_put_interface(radio->intf);
        }
 
+unlock:
+       mutex_unlock(&radio->disconnect_lock);
+
 done:
-       mutex_unlock(&open_close_lock);
-       return retval;
+       return retval;
 }
 
 
@@ -1116,7 +1210,8 @@ static int si470x_vidioc_querycap(struct file *file, void *priv,
        strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
        sprintf(capability->bus_info, "USB");
        capability->version = DRIVER_KERNEL_VERSION;
-       capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+       capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
+               V4L2_CAP_TUNER | V4L2_CAP_RADIO;
 
        return 0;
 }
@@ -1125,7 +1220,7 @@ static int si470x_vidioc_querycap(struct file *file, void *priv,
 /*
  * si470x_vidioc_g_input - get input
  */
-static int si470x_vidioc_g_input(struct file *filp, void *priv,
+static int si470x_vidioc_g_input(struct file *file, void *priv,
                unsigned int *i)
 {
        *i = 0;
@@ -1137,12 +1232,18 @@ static int si470x_vidioc_g_input(struct file *filp, void *priv,
 /*
  * si470x_vidioc_s_input - set input
  */
-static int si470x_vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+static int si470x_vidioc_s_input(struct file *file, void *priv, unsigned int i)
 {
+       int retval = 0;
+
+       /* safety checks */
        if (i != 0)
-               return -EINVAL;
+               retval = -EINVAL;
 
-       return 0;
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": set input failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1155,17 +1256,22 @@ static int si470x_vidioc_queryctrl(struct file *file, void *priv,
        unsigned char i;
        int retval = -EINVAL;
 
+       /* safety checks */
+       if (!qc->id)
+               goto done;
+
        for (i = 0; i < ARRAY_SIZE(si470x_v4l2_queryctrl); i++) {
-               if (qc->id && qc->id == si470x_v4l2_queryctrl[i].id) {
+               if (qc->id == si470x_v4l2_queryctrl[i].id) {
                        memcpy(qc, &(si470x_v4l2_queryctrl[i]), sizeof(*qc));
                        retval = 0;
                        break;
                }
        }
+
+done:
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
-                       ": query control failed with %d\n", retval);
-
+                       ": query controls failed with %d\n", retval);
        return retval;
 }
 
@@ -1177,9 +1283,13 @@ static int si470x_vidioc_g_ctrl(struct file *file, void *priv,
                struct v4l2_control *ctrl)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
 
        switch (ctrl->id) {
        case V4L2_CID_AUDIO_VOLUME:
@@ -1190,9 +1300,15 @@ static int si470x_vidioc_g_ctrl(struct file *file, void *priv,
                ctrl->value = ((radio->registers[POWERCFG] &
                                POWERCFG_DMUTE) == 0) ? 1 : 0;
                break;
+       default:
+               retval = -EINVAL;
        }
 
-       return 0;
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": get control failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1203,10 +1319,13 @@ static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
                struct v4l2_control *ctrl)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
-       int retval;
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
 
        switch (ctrl->id) {
        case V4L2_CID_AUDIO_VOLUME:
@@ -1224,10 +1343,11 @@ static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
        default:
                retval = -EINVAL;
        }
+
+done:
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
                        ": set control failed with %d\n", retval);
-
        return retval;
 }
 
@@ -1238,13 +1358,22 @@ static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
 static int si470x_vidioc_g_audio(struct file *file, void *priv,
                struct v4l2_audio *audio)
 {
-       if (audio->index > 1)
-               return -EINVAL;
+       int retval = 0;
+
+       /* safety checks */
+       if (audio->index != 0) {
+               retval = -EINVAL;
+               goto done;
+       }
 
        strcpy(audio->name, "Radio");
        audio->capability = V4L2_AUDCAP_STEREO;
 
-       return 0;
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": get audio failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1254,10 +1383,19 @@ static int si470x_vidioc_g_audio(struct file *file, void *priv,
 static int si470x_vidioc_s_audio(struct file *file, void *priv,
                struct v4l2_audio *audio)
 {
-       if (audio->index != 0)
-               return -EINVAL;
+       int retval = 0;
 
-       return 0;
+       /* safety checks */
+       if (audio->index != 0) {
+               retval = -EINVAL;
+               goto done;
+       }
+
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": set audio failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1268,20 +1406,23 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv,
                struct v4l2_tuner *tuner)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
-       int retval;
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
-       if (tuner->index > 0)
-               return -EINVAL;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
+       if ((tuner->index != 0) && (tuner->type != V4L2_TUNER_RADIO)) {
+               retval = -EINVAL;
+               goto done;
+       }
 
-       /* read status rssi */
        retval = si470x_get_register(radio, STATUSRSSI);
        if (retval < 0)
-               return retval;
+               goto done;
 
        strcpy(tuner->name, "FM");
-       tuner->type = V4L2_TUNER_RADIO;
        switch (band) {
        /* 0: 87.5 - 108 MHz (USA, Europe, default) */
        default:
@@ -1313,9 +1454,14 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv,
                                * 0x0101;
 
        /* automatic frequency control: -1: freq to low, 1 freq to high */
-       tuner->afc = 0;
+       /* AFCRL does only indicate that freq. differs, not if too low/high */
+       tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
 
-       return 0;
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": get tuner failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1326,12 +1472,17 @@ static int si470x_vidioc_s_tuner(struct file *file, void *priv,
                struct v4l2_tuner *tuner)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
-       int retval;
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
-       if (tuner->index > 0)
-               return -EINVAL;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
+       if ((tuner->index != 0) && (tuner->type != V4L2_TUNER_RADIO)) {
+               retval = -EINVAL;
+               goto done;
+       }
 
        if (tuner->audmode == V4L2_TUNER_MODE_MONO)
                radio->registers[POWERCFG] |= POWERCFG_MONO;  /* force mono */
@@ -1339,10 +1490,11 @@ static int si470x_vidioc_s_tuner(struct file *file, void *priv,
                radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
 
        retval = si470x_set_register(radio, POWERCFG);
+
+done:
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
                        ": set tuner failed with %d\n", retval);
-
        return retval;
 }
 
@@ -1354,14 +1506,25 @@ static int si470x_vidioc_g_frequency(struct file *file, void *priv,
                struct v4l2_frequency *freq)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
+       if ((freq->tuner != 0) && (freq->type != V4L2_TUNER_RADIO)) {
+               retval = -EINVAL;
+               goto done;
+       }
 
-       freq->type = V4L2_TUNER_RADIO;
-       freq->frequency = si470x_get_freq(radio);
+       retval = si470x_get_freq(radio, &freq->frequency);
 
-       return 0;
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": get frequency failed with %d\n", retval);
+       return retval;
 }
 
 
@@ -1372,30 +1535,58 @@ static int si470x_vidioc_s_frequency(struct file *file, void *priv,
                struct v4l2_frequency *freq)
 {
        struct si470x_device *radio = video_get_drvdata(video_devdata(file));
-       int retval;
+       int retval = 0;
 
-       if (radio->disconnected)
-              return -EIO;
-       if (freq->type != V4L2_TUNER_RADIO)
-               return -EINVAL;
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
+       if ((freq->tuner != 0) && (freq->type != V4L2_TUNER_RADIO)) {
+               retval = -EINVAL;
+               goto done;
+       }
 
        retval = si470x_set_freq(radio, freq->frequency);
+
+done:
        if (retval < 0)
                printk(KERN_WARNING DRIVER_NAME
                        ": set frequency failed with %d\n", retval);
-
-       return 0;
+       return retval;
 }
 
 
 /*
- * si470x_viddev_tamples - video device interface
+ * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
  */
-static struct video_device si470x_viddev_template = {
-       .fops                   = &si470x_fops,
-       .name                   = DRIVER_NAME,
-       .type                   = VID_TYPE_TUNER,
-       .release                = video_device_release,
+static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+               struct v4l2_hw_freq_seek *seek)
+{
+       struct si470x_device *radio = video_get_drvdata(video_devdata(file));
+       int retval = 0;
+
+       /* safety checks */
+       if (radio->disconnected) {
+               retval = -EIO;
+               goto done;
+       }
+       if ((seek->tuner != 0) && (seek->type != V4L2_TUNER_RADIO)) {
+               retval = -EINVAL;
+               goto done;
+       }
+
+       retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward);
+
+done:
+       if (retval < 0)
+               printk(KERN_WARNING DRIVER_NAME
+                       ": set hardware frequency seek failed with %d\n",
+                       retval);
+       return retval;
+}
+
+static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
        .vidioc_querycap        = si470x_vidioc_querycap,
        .vidioc_g_input         = si470x_vidioc_g_input,
        .vidioc_s_input         = si470x_vidioc_s_input,
@@ -1408,7 +1599,17 @@ static struct video_device si470x_viddev_template = {
        .vidioc_s_tuner         = si470x_vidioc_s_tuner,
        .vidioc_g_frequency     = si470x_vidioc_g_frequency,
        .vidioc_s_frequency     = si470x_vidioc_s_frequency,
-       .owner                  = THIS_MODULE,
+       .vidioc_s_hw_freq_seek  = si470x_vidioc_s_hw_freq_seek,
+};
+
+/*
+ * si470x_viddev_tamples - video device interface
+ */
+static struct video_device si470x_viddev_template = {
+       .fops                   = &si470x_fops,
+       .ioctl_ops              = &si470x_ioctl_ops,
+       .name                   = DRIVER_NAME,
+       .release                = video_device_release,
 };
 
 
@@ -1424,31 +1625,36 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
                const struct usb_device_id *id)
 {
        struct si470x_device *radio;
-       int retval = -ENOMEM;
+       int retval = 0;
 
-       /* private data allocation */
+       /* private data allocation and initialization */
        radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
-       if (!radio)
+       if (!radio) {
+               retval = -ENOMEM;
                goto err_initial;
+       }
+       radio->users = 0;
+       radio->disconnected = 0;
+       radio->usbdev = interface_to_usbdev(intf);
+       radio->intf = intf;
+       mutex_init(&radio->disconnect_lock);
+       mutex_init(&radio->lock);
 
-       /* video device allocation */
+       /* video device allocation and initialization */
        radio->videodev = video_device_alloc();
-       if (!radio->videodev)
+       if (!radio->videodev) {
+               retval = -ENOMEM;
                goto err_radio;
-
-       /* initial configuration */
+       }
        memcpy(radio->videodev, &si470x_viddev_template,
                        sizeof(si470x_viddev_template));
-       radio->users = 0;
-       radio->usbdev = interface_to_usbdev(intf);
-       radio->intf = intf;
-       mutex_init(&radio->lock);
        video_set_drvdata(radio->videodev, radio);
 
        /* show some infos about the specific device */
-       retval = -EIO;
-       if (si470x_get_all_registers(radio) < 0)
+       if (si470x_get_all_registers(radio) < 0) {
+               retval = -EIO;
                goto err_all;
+       }
        printk(KERN_INFO DRIVER_NAME ": DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
                        radio->registers[DEVICEID], radio->registers[CHIPID]);
 
@@ -1474,8 +1680,10 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
        /* rds buffer allocation */
        radio->buf_size = rds_buf * 3;
        radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
-       if (!radio->buffer)
+       if (!radio->buffer) {
+               retval = -EIO;
                goto err_all;
+       }
 
        /* rds buffer configuration */
        radio->wr_index = 0;
@@ -1487,6 +1695,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
 
        /* register video device */
        if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) {
+               retval = -EIO;
                printk(KERN_WARNING DRIVER_NAME
                                ": Could not register video device\n");
                goto err_all;
@@ -1546,16 +1755,16 @@ static void si470x_usb_driver_disconnect(struct usb_interface *intf)
 {
        struct si470x_device *radio = usb_get_intfdata(intf);
 
-       mutex_lock(&open_close_lock);
-       radio->disconnected = 1;
+       mutex_lock(&radio->disconnect_lock);
+       radio->disconnected = 1;
        cancel_delayed_work_sync(&radio->work);
        usb_set_intfdata(intf, NULL);
-       if (radio->users == 0) {
-              video_unregister_device(radio->videodev);
-              kfree(radio->buffer);
-              kfree(radio);
-       }
-       mutex_unlock(&open_close_lock);
+       if (radio->users == 0) {
+               video_unregister_device(radio->videodev);
+               kfree(radio->buffer);
+               kfree(radio);
+       }
+       mutex_unlock(&radio->disconnect_lock);
 }