aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/xilinx/xilinx-scaler.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/xilinx/xilinx-scaler.c')
-rw-r--r--drivers/media/platform/xilinx/xilinx-scaler.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/drivers/media/platform/xilinx/xilinx-scaler.c b/drivers/media/platform/xilinx/xilinx-scaler.c
new file mode 100644
index 000000000000..bb0d52627a50
--- /dev/null
+++ b/drivers/media/platform/xilinx/xilinx-scaler.c
@@ -0,0 +1,708 @@
+/*
+ * Xilinx Scaler
+ *
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * Contacts: Hyun Kwon <hyun.kwon@xilinx.com>
+ * Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/fixp-arith.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-subdev.h>
+
+#include "xilinx-vip.h"
+
+#define XSCALER_MIN_WIDTH 32
+#define XSCALER_MAX_WIDTH 4096
+#define XSCALER_MIN_HEIGHT 32
+#define XSCALER_MAX_HEIGHT 4096
+
+#define XSCALER_HSF 0x0100
+#define XSCALER_VSF 0x0104
+#define XSCALER_SF_SHIFT 20
+#define XSCALER_SF_MASK 0xffffff
+#define XSCALER_SOURCE_SIZE 0x0108
+#define XSCALER_SIZE_HORZ_SHIFT 0
+#define XSCALER_SIZE_VERT_SHIFT 16
+#define XSCALER_SIZE_MASK 0xfff
+#define XSCALER_HAPERTURE 0x010c
+#define XSCALER_VAPERTURE 0x0110
+#define XSCALER_APERTURE_START_SHIFT 0
+#define XSCALER_APERTURE_END_SHIFT 16
+#define XSCALER_OUTPUT_SIZE 0x0114
+#define XSCALER_COEF_DATA_IN 0x0134
+#define XSCALER_COEF_DATA_IN_SHIFT 16
+
+/* Fixed point operations */
+#define FRAC_N 8
+
+static inline s16 fixp_new(s16 a)
+{
+ return a << FRAC_N;
+}
+
+static inline s16 fixp_mult(s16 a, s16 b)
+{
+ return ((s32)(a * b)) >> FRAC_N;
+}
+
+/**
+ * struct xscaler_device - Xilinx Scaler device structure
+ * @xvip: Xilinx Video IP device
+ * @pads: media pads
+ * @formats: V4L2 media bus formats at the sink and source pads
+ * @default_formats: default V4L2 media bus formats
+ * @vip_format: Xilinx Video IP format
+ * @crop: Active crop rectangle for the sink pad
+ * @num_hori_taps: number of vertical taps
+ * @num_vert_taps: number of vertical taps
+ * @max_num_phases: maximum number of phases
+ * @separate_yc_coef: separate coefficients for Luma(y) and Chroma(c)
+ * @separate_hv_coef: separate coefficients for Horizontal(h) and Vertical(v)
+ */
+struct xscaler_device {
+ struct xvip_device xvip;
+
+ struct media_pad pads[2];
+
+ struct v4l2_mbus_framefmt formats[2];
+ struct v4l2_mbus_framefmt default_formats[2];
+ const struct xvip_video_format *vip_format;
+ struct v4l2_rect crop;
+
+ u32 num_hori_taps;
+ u32 num_vert_taps;
+ u32 max_num_phases;
+ bool separate_yc_coef;
+ bool separate_hv_coef;
+};
+
+static inline struct xscaler_device *to_scaler(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct xscaler_device, xvip.subdev);
+}
+
+/*
+ * V4L2 Subdevice Video Operations
+ */
+
+/**
+ * lanczos - Lanczos 2D FIR kernel convolution
+ * @x: phase
+ * @a: Lanczos kernel size
+ *
+ * Return: the coefficient value in fixed point format.
+ */
+static s16 lanczos(s16 x, s16 a)
+{
+ s16 pi;
+ s16 numerator;
+ s16 denominator;
+ s16 temp;
+
+ if (x < -a || x > a)
+ return 0;
+ else if (x == 0)
+ return fixp_new(1);
+
+ /* a * sin(pi * x) * sin(pi * x / a) / (pi * pi * x * x) */
+
+ pi = (fixp_new(157) << FRAC_N) / fixp_new(50);
+
+ if (x < 0)
+ x = -x;
+
+ /* sin(pi * x) */
+ temp = fixp_mult(fixp_new(180), x);
+ temp = fixp_sin16(temp >> FRAC_N);
+
+ /* a * sin(pi * x) */
+ numerator = fixp_mult(temp, a);
+
+ /* sin(pi * x / a) */
+ temp = (fixp_mult(fixp_new(180), x) << FRAC_N) / a;
+ temp = fixp_sin16(temp >> FRAC_N);
+
+ /* a * sin(pi * x) * sin(pi * x / a) */
+ numerator = fixp_mult(temp, numerator);
+
+ /* pi * pi * x * x */
+ denominator = fixp_mult(pi, pi);
+ temp = fixp_mult(x, x);
+ denominator = fixp_mult(temp, denominator);
+
+ return (numerator << FRAC_N) / denominator;
+}
+
+/**
+ * xscaler_set_coefs - generate and program the coefficient table
+ * @xscaler: scaler device
+ * @taps: maximum coefficient tap index
+ *
+ * Generate the coefficient table using Lanczos resampling, and program
+ * generated coefficients to the scaler. The generated coefficients are
+ * supposed to work regardless of resolutions.
+ *
+ * Return: 0 if the coefficient table is programmed, and -ENOMEM if memory
+ * allocation for the table fails.
+ */
+static int xscaler_set_coefs(struct xscaler_device *xscaler, s16 taps)
+{
+ s16 *coef;
+ s16 dy;
+ u32 coef_val;
+ u16 phases = xscaler->max_num_phases;
+ u16 i;
+ u16 j;
+
+ coef = kcalloc(phases, sizeof(*coef), GFP_KERNEL);
+ if (!coef)
+ return -ENOMEM;
+
+ for (i = 0; i < phases; i++) {
+ s16 sum = 0;
+
+ dy = ((fixp_new(i) << FRAC_N) / fixp_new(phases));
+
+ /* Generate Lanczos coefficients */
+ for (j = 0; j < taps; j++) {
+ coef[j] = lanczos(fixp_new(j - (taps >> 1)) + dy,
+ fixp_new(taps >> 1));
+ sum += coef[j];
+ }
+
+ /* Program coefficients */
+ for (j = 0; j < taps; j += 2) {
+ /* Normalize and multiply coefficients */
+ coef_val = (((coef[j] << FRAC_N) << (FRAC_N - 2)) /
+ sum) & 0xffff;
+ if (j + 1 < taps)
+ coef_val |= ((((coef[j + 1] << FRAC_N) <<
+ (FRAC_N - 2)) / sum) & 0xffff) <<
+ 16;
+
+ xvip_write(&xscaler->xvip, XSCALER_COEF_DATA_IN,
+ coef_val);
+ }
+ }
+
+ kfree(coef);
+
+ return 0;
+}
+
+static void xscaler_set_aperture(struct xscaler_device *xscaler)
+{
+ u16 start;
+ u16 end;
+ u32 scale_factor;
+
+ xvip_disable_reg_update(&xscaler->xvip);
+
+ /* set horizontal aperture */
+ start = xscaler->crop.left;
+ end = start + xscaler->crop.width - 1;
+ xvip_write(&xscaler->xvip, XSCALER_HAPERTURE,
+ (end << XSCALER_APERTURE_END_SHIFT) |
+ (start << XSCALER_APERTURE_START_SHIFT));
+
+ /* set vertical aperture */
+ start = xscaler->crop.top;
+ end = start + xscaler->crop.height - 1;
+ xvip_write(&xscaler->xvip, XSCALER_VAPERTURE,
+ (end << XSCALER_APERTURE_END_SHIFT) |
+ (start << XSCALER_APERTURE_START_SHIFT));
+
+ /* set scaling factors */
+ scale_factor = ((xscaler->crop.width << XSCALER_SF_SHIFT) /
+ xscaler->formats[XVIP_PAD_SOURCE].width) &
+ XSCALER_SF_MASK;
+ xvip_write(&xscaler->xvip, XSCALER_HSF, scale_factor);
+
+ scale_factor = ((xscaler->crop.height << XSCALER_SF_SHIFT) /
+ xscaler->formats[XVIP_PAD_SOURCE].height) &
+ XSCALER_SF_MASK;
+ xvip_write(&xscaler->xvip, XSCALER_VSF, scale_factor);
+
+ xvip_enable_reg_update(&xscaler->xvip);
+}
+
+static int xscaler_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+ u32 width;
+ u32 height;
+
+ if (!enable) {
+ xvip_stop(&xscaler->xvip);
+ return 0;
+ }
+
+ /* set input width / height */
+ width = xscaler->formats[XVIP_PAD_SINK].width;
+ height = xscaler->formats[XVIP_PAD_SINK].height;
+ xvip_write(&xscaler->xvip, XSCALER_SOURCE_SIZE,
+ (height << XSCALER_SIZE_VERT_SHIFT) |
+ (width << XSCALER_SIZE_HORZ_SHIFT));
+
+ /* set output width / height */
+ width = xscaler->formats[XVIP_PAD_SOURCE].width;
+ height = xscaler->formats[XVIP_PAD_SOURCE].height;
+ xvip_write(&xscaler->xvip, XSCALER_OUTPUT_SIZE,
+ (height << XSCALER_SIZE_VERT_SHIFT) |
+ (width << XSCALER_SIZE_HORZ_SHIFT));
+
+ /* set aperture */
+ xscaler_set_aperture(xscaler);
+
+ xvip_start(&xscaler->xvip);
+
+ return 0;
+}
+
+/*
+ * V4L2 Subdevice Pad Operations
+ */
+
+static int xscaler_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct v4l2_mbus_framefmt *format;
+
+ format = v4l2_subdev_get_try_format(subdev, cfg, fse->pad);
+
+ if (fse->index || fse->code != format->code)
+ return -EINVAL;
+
+ fse->min_width = XSCALER_MIN_WIDTH;
+ fse->max_width = XSCALER_MAX_WIDTH;
+ fse->min_height = XSCALER_MIN_HEIGHT;
+ fse->max_height = XSCALER_MAX_HEIGHT;
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+__xscaler_get_pad_format(struct xscaler_device *xscaler,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, u32 which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(&xscaler->xvip.subdev, cfg,
+ pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &xscaler->formats[pad];
+ default:
+ return NULL;
+ }
+}
+
+static struct v4l2_rect *__xscaler_get_crop(struct xscaler_device *xscaler,
+ struct v4l2_subdev_pad_config *cfg,
+ u32 which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_crop(&xscaler->xvip.subdev, cfg,
+ XVIP_PAD_SINK);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &xscaler->crop;
+ default:
+ return NULL;
+ }
+}
+
+static int xscaler_get_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+
+ fmt->format = *__xscaler_get_pad_format(xscaler, cfg, fmt->pad,
+ fmt->which);
+
+ return 0;
+}
+
+static void xscaler_try_crop(const struct v4l2_mbus_framefmt *sink,
+ struct v4l2_rect *crop)
+{
+
+ crop->left = min_t(u32, crop->left, sink->width - XSCALER_MIN_WIDTH);
+ crop->top = min_t(u32, crop->top, sink->height - XSCALER_MIN_HEIGHT);
+ crop->width = clamp_t(u32, crop->width, XSCALER_MIN_WIDTH,
+ sink->width - crop->left);
+ crop->height = clamp_t(u32, crop->height, XSCALER_MIN_HEIGHT,
+ sink->height - crop->top);
+}
+
+static int xscaler_set_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+
+ format = __xscaler_get_pad_format(xscaler, cfg, fmt->pad, fmt->which);
+
+ format->width = clamp_t(unsigned int, fmt->format.width,
+ XSCALER_MIN_WIDTH, XSCALER_MAX_WIDTH);
+ format->height = clamp_t(unsigned int, fmt->format.height,
+ XSCALER_MIN_HEIGHT, XSCALER_MAX_HEIGHT);
+
+ fmt->format = *format;
+
+ if (fmt->pad == XVIP_PAD_SINK) {
+ /* Set the crop rectangle to the full frame */
+ crop = __xscaler_get_crop(xscaler, cfg, fmt->which);
+ crop->left = 0;
+ crop->top = 0;
+ crop->width = fmt->format.width;
+ crop->height = fmt->format.height;
+ }
+
+ return 0;
+}
+
+static int xscaler_get_selection(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+ struct v4l2_mbus_framefmt *format;
+
+ if (sel->pad != XVIP_PAD_SINK)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ format = __xscaler_get_pad_format(xscaler, cfg, XVIP_PAD_SINK,
+ sel->which);
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = format->width;
+ sel->r.height = format->height;
+ return 0;
+ case V4L2_SEL_TGT_CROP:
+ sel->r = *__xscaler_get_crop(xscaler, cfg, sel->which);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int xscaler_set_selection(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+ struct v4l2_mbus_framefmt *format;
+
+ if ((sel->target != V4L2_SEL_TGT_CROP) || (sel->pad != XVIP_PAD_SINK))
+ return -EINVAL;
+
+ format = __xscaler_get_pad_format(xscaler, cfg, XVIP_PAD_SINK,
+ sel->which);
+ xscaler_try_crop(format, &sel->r);
+ *__xscaler_get_crop(xscaler, cfg, sel->which) = sel->r;
+
+ return 0;
+}
+
+/*
+ * V4L2 Subdevice Operations
+ */
+
+static int xscaler_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ struct xscaler_device *xscaler = to_scaler(subdev);
+ struct v4l2_mbus_framefmt *format;
+
+ /* Initialize with default formats */
+ format = v4l2_subdev_get_try_format(subdev, fh->pad, XVIP_PAD_SINK);
+ *format = xscaler->default_formats[XVIP_PAD_SINK];
+
+ format = v4l2_subdev_get_try_format(subdev, fh->pad, XVIP_PAD_SOURCE);
+ *format = xscaler->default_formats[XVIP_PAD_SOURCE];
+
+ return 0;
+}
+
+static int xscaler_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+static struct v4l2_subdev_video_ops xscaler_video_ops = {
+ .s_stream = xscaler_s_stream,
+};
+
+static struct v4l2_subdev_pad_ops xscaler_pad_ops = {
+ .enum_mbus_code = xvip_enum_mbus_code,
+ .enum_frame_size = xscaler_enum_frame_size,
+ .get_fmt = xscaler_get_format,
+ .set_fmt = xscaler_set_format,
+ .get_selection = xscaler_get_selection,
+ .set_selection = xscaler_set_selection,
+};
+
+static struct v4l2_subdev_ops xscaler_ops = {
+ .video = &xscaler_video_ops,
+ .pad = &xscaler_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops xscaler_internal_ops = {
+ .open = xscaler_open,
+ .close = xscaler_close,
+};
+
+/*
+ * Media Operations
+ */
+
+static const struct media_entity_operations xscaler_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * Power Management
+ */
+
+static int __maybe_unused xscaler_pm_suspend(struct device *dev)
+{
+ struct xscaler_device *xscaler = dev_get_drvdata(dev);
+
+ xvip_suspend(&xscaler->xvip);
+
+ return 0;
+}
+
+static int __maybe_unused xscaler_pm_resume(struct device *dev)
+{
+ struct xscaler_device *xscaler = dev_get_drvdata(dev);
+
+ xvip_resume(&xscaler->xvip);
+
+ return 0;
+}
+
+/*
+ * Platform Device Driver
+ */
+
+static int xscaler_parse_of(struct xscaler_device *xscaler)
+{
+ struct device *dev = xscaler->xvip.dev;
+ struct device_node *node = xscaler->xvip.dev->of_node;
+ struct device_node *ports;
+ struct device_node *port;
+ int ret;
+
+ ports = of_get_child_by_name(node, "ports");
+ if (ports == NULL)
+ ports = node;
+
+ /* Get the format description for each pad */
+ for_each_child_of_node(ports, port) {
+ if (port->name && (of_node_cmp(port->name, "port") == 0)) {
+ const struct xvip_video_format *vip_format;
+
+ vip_format = xvip_of_get_format(port);
+ if (IS_ERR(vip_format)) {
+ dev_err(dev, "invalid format in DT");
+ return PTR_ERR(vip_format);
+ }
+
+ if (!xscaler->vip_format) {
+ xscaler->vip_format = vip_format;
+ } else if (xscaler->vip_format != vip_format) {
+ dev_err(dev, "in/out format mismatch in DT");
+ return -EINVAL;
+ }
+ }
+ }
+
+ ret = of_property_read_u32(node, "xlnx,num-hori-taps",
+ &xscaler->num_hori_taps);
+ if (ret < 0)
+ return ret;
+
+ ret = of_property_read_u32(node, "xlnx,num-vert-taps",
+ &xscaler->num_vert_taps);
+ if (ret < 0)
+ return ret;
+
+ ret = of_property_read_u32(node, "xlnx,max-num-phases",
+ &xscaler->max_num_phases);
+ if (ret < 0)
+ return ret;
+
+ xscaler->separate_yc_coef =
+ of_property_read_bool(node, "xlnx,separate-yc-coef");
+
+ xscaler->separate_hv_coef =
+ of_property_read_bool(node, "xlnx,separate-hv-coef");
+
+ return 0;
+}
+
+static int xscaler_probe(struct platform_device *pdev)
+{
+ struct xscaler_device *xscaler;
+ struct v4l2_subdev *subdev;
+ struct v4l2_mbus_framefmt *default_format;
+ u32 size;
+ int ret;
+
+ xscaler = devm_kzalloc(&pdev->dev, sizeof(*xscaler), GFP_KERNEL);
+ if (!xscaler)
+ return -ENOMEM;
+
+ xscaler->xvip.dev = &pdev->dev;
+
+ ret = xscaler_parse_of(xscaler);
+ if (ret < 0)
+ return ret;
+
+ ret = xvip_init_resources(&xscaler->xvip);
+ if (ret < 0)
+ return ret;
+
+ /* Reset and initialize the core */
+ xvip_reset(&xscaler->xvip);
+
+ /* Initialize V4L2 subdevice and media entity */
+ subdev = &xscaler->xvip.subdev;
+ v4l2_subdev_init(subdev, &xscaler_ops);
+ subdev->dev = &pdev->dev;
+ subdev->internal_ops = &xscaler_internal_ops;
+ strlcpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name));
+ v4l2_set_subdevdata(subdev, xscaler);
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ /* Initialize default and active formats */
+ default_format = &xscaler->default_formats[XVIP_PAD_SINK];
+ default_format->code = xscaler->vip_format->code;
+ default_format->field = V4L2_FIELD_NONE;
+ default_format->colorspace = V4L2_COLORSPACE_SRGB;
+ size = xvip_read(&xscaler->xvip, XSCALER_SOURCE_SIZE);
+ default_format->width = (size >> XSCALER_SIZE_HORZ_SHIFT) &
+ XSCALER_SIZE_MASK;
+ default_format->height = (size >> XSCALER_SIZE_VERT_SHIFT) &
+ XSCALER_SIZE_MASK;
+
+ xscaler->formats[XVIP_PAD_SINK] = *default_format;
+
+ default_format = &xscaler->default_formats[XVIP_PAD_SOURCE];
+ *default_format = xscaler->default_formats[XVIP_PAD_SINK];
+ size = xvip_read(&xscaler->xvip, XSCALER_OUTPUT_SIZE);
+ default_format->width = (size >> XSCALER_SIZE_HORZ_SHIFT) &
+ XSCALER_SIZE_MASK;
+ default_format->height = (size >> XSCALER_SIZE_VERT_SHIFT) &
+ XSCALER_SIZE_MASK;
+
+ xscaler->formats[XVIP_PAD_SOURCE] = *default_format;
+
+ xscaler->pads[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ xscaler->pads[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ subdev->entity.ops = &xscaler_media_ops;
+
+ ret = media_entity_pads_init(&subdev->entity, 2, xscaler->pads);
+ if (ret < 0)
+ goto error;
+
+ platform_set_drvdata(pdev, xscaler);
+
+ xvip_print_version(&xscaler->xvip);
+
+ ret = xscaler_set_coefs(xscaler, (s16)xscaler->num_hori_taps);
+ if (ret < 0)
+ goto error;
+
+ if (xscaler->separate_hv_coef) {
+ ret = xscaler_set_coefs(xscaler, (s16)xscaler->num_vert_taps);
+ if (ret < 0)
+ goto error;
+ }
+
+ if (xscaler->separate_yc_coef) {
+ ret = xscaler_set_coefs(xscaler, (s16)xscaler->num_hori_taps);
+ if (ret < 0)
+ goto error;
+
+ if (xscaler->separate_hv_coef) {
+ ret = xscaler_set_coefs(xscaler,
+ (s16)xscaler->num_vert_taps);
+ if (ret < 0)
+ goto error;
+ }
+ }
+
+ ret = v4l2_async_register_subdev(subdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register subdev\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ media_entity_cleanup(&subdev->entity);
+ xvip_cleanup_resources(&xscaler->xvip);
+ return ret;
+}
+
+static int xscaler_remove(struct platform_device *pdev)
+{
+ struct xscaler_device *xscaler = platform_get_drvdata(pdev);
+ struct v4l2_subdev *subdev = &xscaler->xvip.subdev;
+
+ v4l2_async_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ xvip_cleanup_resources(&xscaler->xvip);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(xscaler_pm_ops, xscaler_pm_suspend, xscaler_pm_resume);
+
+static const struct of_device_id xscaler_of_id_table[] = {
+ { .compatible = "xlnx,v-scaler-8.1" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, xscaler_of_id_table);
+
+static struct platform_driver xscaler_driver = {
+ .driver = {
+ .name = "xilinx-scaler",
+ .of_match_table = xscaler_of_id_table,
+ },
+ .probe = xscaler_probe,
+ .remove = xscaler_remove,
+};
+
+module_platform_driver(xscaler_driver);
+
+MODULE_DESCRIPTION("Xilinx Scaler Driver");
+MODULE_LICENSE("GPL v2");