[media] au0828: improve firmware loading & locking

- open/close/read and poll need to take the core lock as well.
- when the tuner goes to sleep we should set std_set_in_tuner_core
  to 0 since the tuner loses the firmware at that time.
- initialize the tuner if std_set_in_tuner_core == 0 whenever:
  1) g/s_tuner, s_std or s_frequency is called
  2) read or poll is called
  3) streamon is called

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Reviewed-by: Devin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c
index a90a0b9..75ac9947 100644
--- a/drivers/media/usb/au0828/au0828-video.c
+++ b/drivers/media/usb/au0828/au0828-video.c
@@ -998,11 +998,17 @@
 	v4l2_fh_init(&fh->fh, vdev);
 	filp->private_data = fh;
 
-	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 0) {
+	if (mutex_lock_interruptible(&dev->lock)) {
+		kfree(fh);
+		return -ERESTARTSYS;
+	}
+	if (dev->users == 0) {
 		/* set au0828 interface0 to AS5 here again */
 		ret = usb_set_interface(dev->usbdev, 0, 5);
 		if (ret < 0) {
+			mutex_unlock(&dev->lock);
 			printk(KERN_INFO "Au0828 can't set alternate to 5!\n");
+			kfree(fh);
 			return -EBUSY;
 		}
 
@@ -1017,6 +1023,7 @@
 	}
 
 	dev->users++;
+	mutex_unlock(&dev->lock);
 
 	videobuf_queue_vmalloc_init(&fh->vb_vidq, &au0828_video_qops,
 				    NULL, &dev->slock,
@@ -1044,6 +1051,7 @@
 
 	v4l2_fh_del(&fh->fh);
 	v4l2_fh_exit(&fh->fh);
+	mutex_lock(&dev->lock);
 	if (res_check(fh, AU0828_RESOURCE_VIDEO)) {
 		/* Cancel timeout thread in case they didn't call streamoff */
 		dev->vid_timeout_running = 0;
@@ -1069,6 +1077,7 @@
 
 		/* Save some power by putting tuner to sleep */
 		v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0);
+		dev->std_set_in_tuner_core = 0;
 
 		/* When close the device, set the usb intf0 into alt0 to free
 		   USB bandwidth */
@@ -1076,6 +1085,7 @@
 		if (ret < 0)
 			printk(KERN_INFO "Au0828 can't set alternate to 0!\n");
 	}
+	mutex_unlock(&dev->lock);
 
 	videobuf_mmap_free(&fh->vb_vidq);
 	videobuf_mmap_free(&fh->vb_vbiq);
@@ -1085,6 +1095,26 @@
 	return 0;
 }
 
+/* Must be called with dev->lock held */
+static void au0828_init_tuner(struct au0828_dev *dev)
+{
+	struct v4l2_frequency f = {
+		.frequency = dev->ctrl_freq,
+		.type = V4L2_TUNER_ANALOG_TV,
+	};
+
+	if (dev->std_set_in_tuner_core)
+		return;
+	dev->std_set_in_tuner_core = 1;
+	i2c_gate_ctrl(dev, 1);
+	/* If we've never sent the standard in tuner core, do so now.
+	   We don't do this at device probe because we don't want to
+	   incur the cost of a firmware load */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->std);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+	i2c_gate_ctrl(dev, 0);
+}
+
 static ssize_t au0828_v4l2_read(struct file *filp, char __user *buf,
 				size_t count, loff_t *pos)
 {
@@ -1096,6 +1126,11 @@
 	if (rc < 0)
 		return rc;
 
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	au0828_init_tuner(dev);
+	mutex_unlock(&dev->lock);
+
 	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
 		if (res_locked(dev, AU0828_RESOURCE_VIDEO))
 			return -EBUSY;
@@ -1136,6 +1171,11 @@
 	if (!(req_events & (POLLIN | POLLRDNORM)))
 		return res;
 
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	au0828_init_tuner(dev);
+	mutex_unlock(&dev->lock);
+
 	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
 		if (!res_get(fh, AU0828_RESOURCE_VIDEO))
 			return POLLERR;
@@ -1319,6 +1359,10 @@
 	struct au0828_fh *fh = priv;
 	struct au0828_dev *dev = fh->dev;
 
+	dev->std = norm;
+
+	au0828_init_tuner(dev);
+
 	i2c_gate_ctrl(dev, 1);
 
 	/* FIXME: when we support something other than NTSC, we are going to
@@ -1326,10 +1370,8 @@
 	   buffer, which is currently hardcoded at 720x480 */
 
 	v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, norm);
-	dev->std_set_in_tuner_core = 1;
 
 	i2c_gate_ctrl(dev, 0);
-	dev->std = norm;
 
 	return 0;
 }
@@ -1506,7 +1548,11 @@
 		return -EINVAL;
 
 	strcpy(t->name, "Auvitek tuner");
+
+	au0828_init_tuner(dev);
+	i2c_gate_ctrl(dev, 1);
 	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
+	i2c_gate_ctrl(dev, 0);
 	return 0;
 }
 
@@ -1519,10 +1565,9 @@
 	if (t->index != 0)
 		return -EINVAL;
 
+	au0828_init_tuner(dev);
 	i2c_gate_ctrl(dev, 1);
-
 	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
-
 	i2c_gate_ctrl(dev, 0);
 
 	dprintk(1, "VIDIOC_S_TUNER: signal = %x, afc = %x\n", t->signal,
@@ -1554,17 +1599,9 @@
 	if (freq->tuner != 0)
 		return -EINVAL;
 
+	au0828_init_tuner(dev);
 	i2c_gate_ctrl(dev, 1);
 
-	if (dev->std_set_in_tuner_core == 0) {
-		/* If we've never sent the standard in tuner core, do so now.
-		   We don't do this at device probe because we don't want to
-		   incur the cost of a firmware load */
-		v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std,
-				     dev->vdev->tvnorms);
-		dev->std_set_in_tuner_core = 1;
-	}
-
 	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, freq);
 	/* Get the actual set (and possibly clamped) frequency */
 	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, &new_freq);
@@ -1663,6 +1700,7 @@
 	if (unlikely(!res_get(fh, get_ressource(fh))))
 		return -EBUSY;
 
+	au0828_init_tuner(dev);
 	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
 		au0828_analog_stream_enable(dev);
 		v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1);