@@ -2,6 +2,7 @@
* tvp5150 - Texas Instruments TVP5150A/AM1 video decoder driver
*
* Copyright (c) 2005,2006 Mauro Carvalho Chehab (mchehab@infradead.org)
+ * Copyright (c) 2011 Enrico Butera (ebutera@users.berlios.de)
* This code is placed under the terms of the GNU General Public License v2
*/
@@ -13,9 +14,30 @@
#include <media/tvp5150.h>
#include <media/v4l2-chip-ident.h>
#include <media/v4l2-ctrls.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
#include "tvp5150_reg.h"
+#define TVP5150_PIXEL_ARRAY_HEIGHT 628
+#define TVP5150_PIXEL_ARRAY_WIDTH 720
+
+#define TVP5150_WINDOW_WIDTH_MIN 1
+#define TVP5150_WINDOW_WIDTH_DEF 720
+#define TVP5150_WINDOW_WIDTH_MAX 720
+
+#define TVP5150_WINDOW_HEIGHT_MIN 1
+#define TVP5150_WINDOW_HEIGHT_DEF 628
+#define TVP5150_WINDOW_HEIGHT_MAX 628
+
+#define TVP5150_COLUMN_START_MIN 0
+#define TVP5150_COLUMN_START_DEF 0
+#define TVP5150_COLUMN_START_MAX 720
+
+#define TVP5150_ROW_START_MIN 0
+#define TVP5150_ROW_START_DEF 0
+#define TVP5150_ROW_START_MAX 628
+
MODULE_DESCRIPTION("Texas Instruments TVP5150A video decoder driver");
MODULE_AUTHOR("Mauro Carvalho Chehab");
MODULE_LICENSE("GPL");
@@ -28,6 +50,9 @@ MODULE_PARM_DESC(debug, "Debug level (0-2)");
struct tvp5150 {
struct v4l2_subdev sd;
struct v4l2_ctrl_handler hdl;
+ struct media_pad pad;
+ struct v4l2_mbus_framefmt format;
+ struct v4l2_rect crop;
v4l2_std_id norm; /* Current set standard */
u32 input;
@@ -779,6 +804,203 @@ static int tvp5150_s_ctrl(struct v4l2_ctrl *ctrl)
}
/****************************************************************************
+ V4L2 subdev pad ops
+ ****************************************************************************/
+
+static struct v4l2_mbus_framefmt *
+__tvp5150_get_pad_format(struct tvp5150 *tvp5150, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(fh, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &tvp5150->format;
+ default:
+ return NULL;
+ }
+}
+
+static struct v4l2_rect *
+__tvp5150_get_pad_crop(struct tvp5150 *tvp5150, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_crop(fh, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &tvp5150->crop;
+ default:
+ return NULL;
+ }
+}
+
+static int tvp5150_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index > 0)
+ return -EINVAL;
+
+ code->code = V4L2_MBUS_FMT_UYVY8_1X16;
+ return 0;
+}
+
+static int tvp5150_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index >= 8 || fse->code != V4L2_MBUS_FMT_UYVY8_1X16)
+ return -EINVAL;
+
+ fse->min_width = TVP5150_WINDOW_WIDTH_DEF / fse->index;
+ fse->max_width = fse->min_width;
+ fse->min_height = TVP5150_WINDOW_HEIGHT_DEF / fse->index;
+ fse->max_height = fse->min_height;
+
+ return 0;
+}
+
+static int tvp5150_get_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *format)
+{
+ struct tvp5150 *tvp5150 = to_tvp5150(subdev);
+
+ format->format = *__tvp5150_get_pad_format(tvp5150, fh, format->pad,
+ format->which);
+ return 0;
+}
+
+static int tvp5150_set_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *format)
+{
+ struct tvp5150 *tvp5150 = to_tvp5150(subdev);
+ struct v4l2_mbus_framefmt *__format;
+ struct v4l2_rect *__crop;
+ unsigned int width;
+ unsigned int height;
+ unsigned int hratio;
+ unsigned int vratio;
+
+ __crop = __tvp5150_get_pad_crop(tvp5150, fh, format->pad,
+ format->which);
+
+ /* Clamp the width and height to avoid dividing by zero. */
+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2),
+ max(__crop->width / 8, TVP5150_WINDOW_WIDTH_MIN),
+ __crop->width);
+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2),
+ max(__crop->height / 8, TVP5150_WINDOW_HEIGHT_MIN),
+ __crop->height);
+
+ hratio = DIV_ROUND_CLOSEST(__crop->width, width);
+ vratio = DIV_ROUND_CLOSEST(__crop->height, height);
+
+ __format = __tvp5150_get_pad_format(tvp5150, fh, format->pad,
+ format->which);
+ __format->width = __crop->width / hratio;
+ __format->height = __crop->height / vratio;
+
+ format->format = *__format;
+
+ return 0;
+}
+
+static int tvp5150_get_crop(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct tvp5150 *tvp5150 = to_tvp5150(subdev);
+
+ crop->rect = *__tvp5150_get_pad_crop(tvp5150, fh, crop->pad,
+ crop->which);
+ return 0;
+}
+
+static int tvp5150_set_crop(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct tvp5150 *tvp5150 = to_tvp5150(subdev);
+ struct v4l2_mbus_framefmt *__format;
+ struct v4l2_rect *__crop;
+ struct v4l2_rect rect;
+
+ /* Clamp the crop rectangle boundaries and align them to a multiple of 2
+ * pixels.
+ */
+ rect.left = clamp(ALIGN(crop->rect.left, 2),
+ TVP5150_COLUMN_START_MIN,
+ TVP5150_COLUMN_START_MAX);
+ rect.top = clamp(ALIGN(crop->rect.top, 2),
+ TVP5150_ROW_START_MIN,
+ TVP5150_ROW_START_MAX);
+ rect.width = clamp(ALIGN(crop->rect.width, 2),
+ TVP5150_WINDOW_WIDTH_MIN,
+ TVP5150_WINDOW_WIDTH_MAX);
+ rect.height = clamp(ALIGN(crop->rect.height, 2),
+ TVP5150_WINDOW_HEIGHT_MIN,
+ TVP5150_WINDOW_HEIGHT_MAX);
+
+ rect.width = min(rect.width, TVP5150_PIXEL_ARRAY_WIDTH - rect.left);
+ rect.height = min(rect.height, TVP5150_PIXEL_ARRAY_HEIGHT - rect.top);
+
+ __crop = __tvp5150_get_pad_crop(tvp5150, fh, crop->pad, crop->which);
+
+ if (rect.width != __crop->width || rect.height != __crop->height) {
+ /* Reset the output image size if the crop rectangle size has
+ * been modified.
+ */
+ __format = __tvp5150_get_pad_format(tvp5150, fh, crop->pad,
+ crop->which);
+ __format->width = rect.width;
+ __format->height = rect.height;
+ }
+
+ *__crop = rect;
+ crop->rect = rect;
+
+ return 0;
+}
+
+/****************************************************************************
+ V4L2 subdev internal ops
+ ****************************************************************************/
+
+static int tvp5150_registered(struct v4l2_subdev *subdev)
+{
+ return 0;
+}
+
+static int tvp5150_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+
+ crop = v4l2_subdev_get_try_crop(fh, 0);
+ crop->left = TVP5150_COLUMN_START_DEF;
+ crop->top = TVP5150_ROW_START_DEF;
+ crop->width = TVP5150_WINDOW_WIDTH_DEF;
+ crop->height = TVP5150_WINDOW_HEIGHT_DEF;
+
+ format = v4l2_subdev_get_try_format(fh, 0);
+ format->code = V4L2_MBUS_FMT_UYVY8_1X16;
+ format->width = TVP5150_WINDOW_WIDTH_DEF;
+ format->height = TVP5150_WINDOW_HEIGHT_DEF;
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ return 0;
+}
+
+static int tvp5150_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+/****************************************************************************
I2C Command
****************************************************************************/
@@ -932,6 +1154,15 @@ static const struct v4l2_subdev_video_ops tvp5150_video_ops = {
.s_routing = tvp5150_s_routing,
};
+static const struct v4l2_subdev_pad_ops tvp5150_subdev_pad_ops = {
+ .enum_mbus_code = tvp5150_enum_mbus_code,
+ .enum_frame_size = tvp5150_enum_frame_size,
+ .get_fmt = tvp5150_get_format,
+ .set_fmt = tvp5150_set_format,
+ .get_crop = tvp5150_get_crop,
+ .set_crop = tvp5150_set_crop,
+};
+
static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = {
.g_sliced_vbi_cap = tvp5150_g_sliced_vbi_cap,
.g_sliced_fmt = tvp5150_g_sliced_fmt,
@@ -939,11 +1170,18 @@ static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = {
.s_raw_fmt = tvp5150_s_raw_fmt,
};
+static const struct v4l2_subdev_internal_ops tvp5150_subdev_internal_ops = {
+ .registered = tvp5150_registered,
+ .open = tvp5150_open,
+ .close = tvp5150_close,
+};
+
static const struct v4l2_subdev_ops tvp5150_ops = {
.core = &tvp5150_core_ops,
.tuner = &tvp5150_tuner_ops,
.video = &tvp5150_video_ops,
.vbi = &tvp5150_vbi_ops,
+ .pad = &tvp5150_subdev_pad_ops,
};
@@ -957,6 +1195,7 @@ static int tvp5150_probe(struct i2c_client *c,
struct tvp5150 *core;
struct v4l2_subdev *sd;
u8 msb_id, lsb_id, msb_rom, lsb_rom;
+ int ret;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(c->adapter,
@@ -968,6 +1207,23 @@ static int tvp5150_probe(struct i2c_client *c,
return -ENOMEM;
}
sd = &core->sd;
+
+ core->crop.left = TVP5150_COLUMN_START_DEF;
+ core->crop.top = TVP5150_ROW_START_DEF;
+ core->crop.width = TVP5150_WINDOW_WIDTH_DEF;
+ core->crop.height = TVP5150_WINDOW_HEIGHT_DEF;
+
+ core->format.code = V4L2_MBUS_FMT_UYVY8_1X16;
+ core->format.width = TVP5150_WINDOW_WIDTH_DEF;
+ core->format.height = TVP5150_WINDOW_HEIGHT_DEF;
+ core->format.field = V4L2_FIELD_NONE;
+ core->format.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ sd->internal_ops = &tvp5150_subdev_internal_ops;
+
+ core->pad.flags = MEDIA_PAD_FL_SOURCE;
+
v4l2_i2c_subdev_init(sd, c, &tvp5150_ops);
v4l_info(c, "chip found @ 0x%02x (%s)\n",
c->addr << 1, c->adapter->name);
@@ -1015,9 +1271,17 @@ static int tvp5150_probe(struct i2c_client *c,
}
v4l2_ctrl_handler_setup(&core->hdl);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ core->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_init(&sd->entity, 1, &core->pad, 0);
+ if (ret < 0)
+ kfree(core);
+
if (debug > 1)
tvp5150_log_status(sd);
- return 0;
+ return ret;
}
static int tvp5150_remove(struct i2c_client *c)
@@ -1031,6 +1295,7 @@ static int tvp5150_remove(struct i2c_client *c)
v4l2_device_unregister_subdev(sd);
v4l2_ctrl_handler_free(&decoder->hdl);
+ media_entity_cleanup(&sd->entity);
kfree(to_tvp5150(sd));
return 0;
}