summaryrefslogtreecommitdiffstats
path: root/drivers/media/radio/radio-si470x.c
diff options
context:
space:
mode:
authorIngo Molnar <mingo@elte.hu>2008-07-21 17:19:50 +0200
committerIngo Molnar <mingo@elte.hu>2008-07-21 17:19:50 +0200
commiteb6a12c2428d21a9f3e0f1a50e927d5fd80fc3d0 (patch)
tree5ac6f43899648abeab1d43aad3107f664e7f13d5 /drivers/media/radio/radio-si470x.c
parentc4762aba0b1f72659aae9ce37b772ca8bd8f06f4 (diff)
parent14b395e35d1afdd8019d11b92e28041fad591b71 (diff)
downloadblackbird-op-linux-eb6a12c2428d21a9f3e0f1a50e927d5fd80fc3d0.tar.gz
blackbird-op-linux-eb6a12c2428d21a9f3e0f1a50e927d5fd80fc3d0.zip
Merge branch 'linus' into cpus4096-for-linus
Conflicts: net/sunrpc/svc.c Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'drivers/media/radio/radio-si470x.c')
-rw-r--r--drivers/media/radio/radio-si470x.c483
1 files changed, 345 insertions, 138 deletions
diff --git a/drivers/media/radio/radio-si470x.c b/drivers/media/radio/radio-si470x.c
index 77354ca6e8e9..dc93a882b385 100644
--- a/drivers/media/radio/radio-si470x.c
+++ b/drivers/media/radio/radio-si470x.c
@@ -24,6 +24,19 @@
/*
+ * 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>
* Version 1.0.0
@@ -85,10 +98,14 @@
* 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)
@@ -98,10 +115,10 @@
/* 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 */
@@ -175,6 +192,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 +447,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];
@@ -442,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.
* The tuner is able to have a channel spacing of 50, 100 or 200 kHz.
@@ -476,11 +493,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 +516,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 +538,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 +553,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 +576,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 +599,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 +608,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 +629,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 +687,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;
}
@@ -714,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
*/
static int si470x_start(struct si470x_device *radio)
@@ -725,27 +799,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 +837,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 +923,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 +963,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 +998,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 +1035,7 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf,
}
mutex_unlock(&radio->lock);
+done:
return retval;
}
@@ -961,6 +1047,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 +1059,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 +1078,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 +1099,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 +1127,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 +1209,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 +1219,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 +1231,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 +1255,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 +1282,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 +1299,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 +1318,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 +1342,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 +1357,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 +1382,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 +1405,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 +1453,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 +1471,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 +1489,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 +1505,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,19 +1534,55 @@ 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 retval;
+}
- return 0;
+
+/*
+ * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
+ */
+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;
}
@@ -1408,6 +1606,7 @@ 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,
+ .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
.owner = THIS_MODULE,
};
@@ -1424,31 +1623,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 +1678,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 +1693,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 +1753,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);
}
OpenPOWER on IntegriCloud