V4L/DVB (8896): pvrusb2: Implement crop support

Implement pvrusb2 driver plumbing to support cropping.  Submitted by a
pvrusb2 user.

Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
index 657f861..0453244 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h
@@ -319,6 +319,7 @@
 	struct pvr2_ctl_info std_info_cur;
 	struct v4l2_standard *std_defs;
 	const char **std_enum_names;
+	struct v4l2_cropcap cropcap;
 
 	// Generated string names, one per actual V4L2 standard
 	const char *std_mask_ptrs[32];
@@ -367,6 +368,10 @@
 	VCREATE_DATA(bass);
 	VCREATE_DATA(treble);
 	VCREATE_DATA(mute);
+	VCREATE_DATA(cropl);
+	VCREATE_DATA(cropt);
+	VCREATE_DATA(cropw);
+	VCREATE_DATA(croph);
 	VCREATE_DATA(input);
 	VCREATE_DATA(audiomode);
 	VCREATE_DATA(res_hor);
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c
index a7d636f..68f4315 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.c
@@ -402,6 +402,52 @@
 	return 0;
 }
 
+static int ctrl_cropl_min_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap;
+	if (cap->bounds.width > 0) {
+		/* This statement is present purely to shut up
+		   checkpatch.pl */
+		*left = cap->bounds.left - cap->defrect.left;
+	} else {
+		/* This statement is present purely to shut up
+		   checkpatch.pl */
+		*left = -119;
+	}
+	return 0;
+}
+
+static int ctrl_cropl_max_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap;
+	if (cap->bounds.width > 0) {
+		*left = cap->bounds.left + cap->bounds.width
+			- cap->defrect.left;
+		*left += 3;
+		*left -= cptr->hdw->cropw_val;
+	} else {
+		/* This statement is present purely to shut up
+		   checkpatch.pl */
+		*left = 340;
+	}
+	return 0;
+}
+
+static int ctrl_cropt_min_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap;
+	if (cap->bounds.height > 0) {
+		/* This statement is present purely to shut up
+		   checkpatch.pl */
+		*top = cap->bounds.top - cap->defrect.top;
+	} else {
+		/* This statement is present purely to shut up
+		   checkpatch.pl */
+		*top = -19;
+	}
+	return 0;
+}
+
 static int ctrl_vres_max_get(struct pvr2_ctrl *cptr,int *vp)
 {
 	/* Actual maximum depends on the video standard in effect. */
@@ -413,6 +459,19 @@
 	return 0;
 }
 
+static int ctrl_cropt_max_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap;
+	if (cap->bounds.height > 0) {
+		*top = cap->bounds.top + cap->bounds.height - cap->defrect.top;
+		*top -= cptr->hdw->croph_val;
+	} else {
+		ctrl_vres_max_get(cptr, top);
+		*top -= 32;
+	}
+	return 0;
+}
+
 static int ctrl_vres_min_get(struct pvr2_ctrl *cptr,int *vp)
 {
 	/* Actual minimum depends on device digitizer type. */
@@ -779,6 +838,10 @@
 VCREATE_FUNCS(bass)
 VCREATE_FUNCS(treble)
 VCREATE_FUNCS(mute)
+VCREATE_FUNCS(cropl)
+VCREATE_FUNCS(cropt)
+VCREATE_FUNCS(cropw)
+VCREATE_FUNCS(croph)
 VCREATE_FUNCS(audiomode)
 VCREATE_FUNCS(res_hor)
 VCREATE_FUNCS(res_ver)
@@ -849,6 +912,39 @@
 		.default_value = 0,
 		DEFREF(mute),
 		DEFBOOL,
+	}, {
+		.desc = "Capture left margin",
+		.name = "crop_left",
+		.internal_id = PVR2_CID_CROPL,
+		.default_value = 0,
+		DEFREF(cropl),
+		DEFINT(-129, 340),
+		.get_min_value = ctrl_cropl_min_get,
+		.get_max_value = ctrl_cropl_max_get,
+	}, {
+		.desc = "Capture top margin",
+		.name = "crop_top",
+		.internal_id = PVR2_CID_CROPT,
+		.default_value = 0,
+		DEFREF(cropt),
+		DEFINT(-35, 544),
+		.get_min_value = ctrl_cropt_min_get,
+		.get_max_value = ctrl_cropt_max_get,
+	}, {
+		.desc = "Capture width",
+		.name = "crop_width",
+		.internal_id = PVR2_CID_CROPW,
+		.default_value = 720,
+		DEFREF(cropw),
+		DEFINT(388, 849), /* determined empirically, any res_hor>=64 */
+	}, {
+		.desc = "Capture height",
+		.name = "crop_height",
+		.internal_id = PVR2_CID_CROPH,
+		.default_value = 480,
+		DEFREF(croph),
+		DEFINT(32, 576),
+		.get_max_value = ctrl_vres_max_get,
 	},{
 		.desc = "Video Source",
 		.name = "input",
@@ -2092,6 +2188,7 @@
 			valid_std_mask;
 	}
 
+	memset(&hdw->cropcap, 0, sizeof hdw->cropcap);
 	hdw->eeprom_addr = -1;
 	hdw->unit_number = -1;
 	hdw->v4l_minor_number_video = -1;
@@ -2528,6 +2625,28 @@
 		/* Can't commit anything until pathway is ok. */
 		return 0;
 	}
+	/* The broadcast decoder can only scale down, so if
+	 * res_*_dirty && crop window < output format ==> enlarge crop.
+	 *
+	 * The mpeg encoder receives fields of res_hor_val dots and
+	 * res_ver_val halflines.  Limits: hor<=720, ver<=576.
+	 */
+	if (hdw->res_hor_dirty && hdw->cropw_val < hdw->res_hor_val) {
+		hdw->cropw_val = hdw->res_hor_val;
+		hdw->cropw_dirty = !0;
+	} else if (hdw->cropw_dirty) {
+		hdw->res_hor_dirty = !0;           /* must rescale */
+		hdw->res_hor_val = min(720, hdw->cropw_val);
+	}
+	if (hdw->res_ver_dirty && hdw->croph_val < hdw->res_ver_val) {
+		hdw->croph_val = hdw->res_ver_val;
+		hdw->croph_dirty = !0;
+	} else if (hdw->croph_dirty) {
+		int nvres = hdw->std_mask_cur & V4L2_STD_525_60 ? 480 : 576;
+		hdw->res_ver_dirty = !0;
+		hdw->res_ver_val = min(nvres, hdw->croph_val);
+	}
+
 	/* If any of the below has changed, then we can't do the update
 	   while the pipeline is running.  Pipeline must be paused first
 	   and decoder -> encoder connection be made quiescent before we
diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/drivers/media/video/pvrusb2/pvrusb2-hdw.h
index c04956d..b4dcda8 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-hdw.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.h
@@ -36,6 +36,10 @@
 #define PVR2_CID_FREQUENCY 6
 #define PVR2_CID_HRES 7
 #define PVR2_CID_VRES 8
+#define PVR2_CID_CROPL 9
+#define PVR2_CID_CROPT 10
+#define PVR2_CID_CROPW 11
+#define PVR2_CID_CROPH 12
 
 /* Legal values for the INPUT state variable */
 #define PVR2_CVAL_INPUT_TV 0
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c
index ccdb429..94a4771 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c
@@ -37,8 +37,9 @@
 #define OP_VOLUME 3
 #define OP_FREQ 4
 #define OP_AUDIORATE 5
-#define OP_SIZE 6
-#define OP_LOG 7
+#define OP_CROP 6
+#define OP_SIZE 7
+#define OP_LOG 8
 
 static const struct pvr2_i2c_op * const ops[] = {
 	[OP_STANDARD] = &pvr2_i2c_op_v4l2_standard,
@@ -46,6 +47,7 @@
 	[OP_BCSH] = &pvr2_i2c_op_v4l2_bcsh,
 	[OP_VOLUME] = &pvr2_i2c_op_v4l2_volume,
 	[OP_FREQ] = &pvr2_i2c_op_v4l2_frequency,
+	[OP_CROP] = &pvr2_i2c_op_v4l2_crop,
 	[OP_SIZE] = &pvr2_i2c_op_v4l2_size,
 	[OP_LOG] = &pvr2_i2c_op_v4l2_log,
 };
@@ -59,6 +61,7 @@
 			(1 << OP_BCSH) |
 			(1 << OP_VOLUME) |
 			(1 << OP_FREQ) |
+			(1 << OP_CROP) |
 			(1 << OP_SIZE) |
 			(1 << OP_LOG));
 	cp->status_poll = pvr2_v4l2_cmd_status_poll;
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c
index 55f04a0..4408579 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c
+++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c
@@ -233,6 +233,55 @@
 };
 
 
+static void set_crop(struct pvr2_hdw *hdw)
+{
+	struct v4l2_cropcap cap;
+	struct v4l2_crop crop;
+	int stat;
+
+	memset(&cap, 0, sizeof cap);
+	cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	stat = pvr2_i2c_core_cmd(hdw, VIDIOC_CROPCAP, &cap);
+	hdw->cropcap = cap;
+
+	memset(&crop, 0, sizeof crop);
+	crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	crop.c = cap.defrect;
+	crop.c.left += hdw->cropl_val;
+	crop.c.top += hdw->cropt_val;
+	crop.c.height = hdw->croph_val;
+	crop.c.width = hdw->cropw_val;
+
+	pvr2_trace(PVR2_TRACE_CHIPS,
+		   "i2c v4l2 set_crop stat=%d cap=%d:%d:%d:%d"
+		   " crop=%d:%d:%d:%d", stat, cap.bounds.width,
+		   cap.bounds.height, cap.bounds.left, cap.bounds.top,
+		   crop.c.width, crop.c.height, crop.c.left, crop.c.top);
+
+	if (stat >= 0) {
+		/* This comment is present purely to keep
+		   checkpatch.pl quiet */
+		pvr2_i2c_core_cmd(hdw, VIDIOC_S_CROP, &crop);
+	}
+}
+
+static int check_crop(struct pvr2_hdw *hdw)
+{
+	/* The "0 +" stupidity is present only to get checkpatch.pl to
+	   shut up.  I _want_ those parantheses present so that the
+	   two lines automatically line up in my editor.  I despise
+	   checkpatch.pl. */
+	return 0 + (hdw->cropl_dirty || hdw->cropt_dirty ||
+		    hdw->cropw_dirty || hdw->croph_dirty);
+}
+
+const struct pvr2_i2c_op pvr2_i2c_op_v4l2_crop = {
+	.check = check_crop,
+	.update = set_crop,
+	.name = "v4l2_crop",
+};
+
+
 static void do_log(struct pvr2_hdw *hdw)
 {
 	pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 do_log()");
diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h
index 7fa3868..eb744a2 100644
--- a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h
+++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h
@@ -29,6 +29,7 @@
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_bcsh;
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_volume;
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_frequency;
+extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_crop;
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_size;
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_audiomode;
 extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_log;