aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/xilinx/xilinx-remapper.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/xilinx/xilinx-remapper.c')
-rw-r--r--drivers/media/platform/xilinx/xilinx-remapper.c546
1 files changed, 546 insertions, 0 deletions
diff --git a/drivers/media/platform/xilinx/xilinx-remapper.c b/drivers/media/platform/xilinx/xilinx-remapper.c
new file mode 100644
index 000000000000..d2e84ec1f2d6
--- /dev/null
+++ b/drivers/media/platform/xilinx/xilinx-remapper.c
@@ -0,0 +1,546 @@
+/*
+ * Xilinx Video Remapper
+ *
+ * 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 program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.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 XREMAP_MIN_WIDTH 1
+#define XREMAP_DEF_WIDTH 1920
+#define XREMAP_MAX_WIDTH 65535
+#define XREMAP_MIN_HEIGHT 1
+#define XREMAP_DEF_HEIGHT 1080
+#define XREMAP_MAX_HEIGHT 65535
+
+#define XREMAP_PAD_SINK 0
+#define XREMAP_PAD_SOURCE 1
+
+/**
+ * struct xremap_mapping_output - Output format description
+ * @code: media bus pixel core after remapping
+ * @num_components: number of pixel components after remapping
+ * @component_maps: configuration array corresponding to this output
+ */
+struct xremap_mapping_output {
+ u32 code;
+ unsigned int num_components;
+ unsigned int component_maps[4];
+};
+
+/**
+ * struct xremap_mapping - Input-output remapping description
+ * @code: media bus pixel code before remapping
+ * @width: video bus width in bits
+ * @num_components: number of pixel components before remapping
+ * @outputs: array of possible output formats
+ */
+struct xremap_mapping {
+ u32 code;
+ unsigned int width;
+ unsigned int num_components;
+ const struct xremap_mapping_output *outputs;
+};
+
+/**
+ * struct xremap_device - Xilinx Test Pattern Generator device structure
+ * @xvip: Xilinx Video IP device
+ * @pads: media pads
+ * @formats: V4L2 media bus formats at the sink and source pads
+ * @config: device configuration parsed from its DT node
+ * @config.width: video bus width in bits
+ * @config.num_s_components: number of pixel components at the input
+ * @config.num_m_components: number of pixel components at the output
+ * @config.component_maps: component remapping configuration
+ * @default_mapping: Default mapping compatible with the configuration
+ * @default_output: Default output format for the default mapping
+ */
+struct xremap_device {
+ struct xvip_device xvip;
+ struct media_pad pads[2];
+ struct v4l2_mbus_framefmt formats[2];
+
+ struct {
+ unsigned int width;
+ unsigned int num_s_components;
+ unsigned int num_m_components;
+ unsigned int component_maps[4];
+ } config;
+
+ const struct xremap_mapping *default_mapping;
+ const struct xremap_mapping_output *default_output;
+};
+
+static inline struct xremap_device *to_remap(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct xremap_device, xvip.subdev);
+}
+
+/* -----------------------------------------------------------------------------
+ * Mappings
+ */
+
+static const struct xremap_mapping xremap_mappings[] = {
+ {
+ .code = MEDIA_BUS_FMT_RBG888_1X24,
+ .width = 8,
+ .num_components = 3,
+ .outputs = (const struct xremap_mapping_output[]) {
+ { MEDIA_BUS_FMT_RGB888_1X32_PADHI, 4, { 1, 0, 2, 4 } },
+ { },
+ },
+ },
+};
+
+static const struct xremap_mapping_output *
+xremap_match_mapping(struct xremap_device *xremap,
+ const struct xremap_mapping *mapping)
+{
+ const struct xremap_mapping_output *output;
+
+ if (mapping->width != xremap->config.width ||
+ mapping->num_components != xremap->config.num_s_components)
+ return NULL;
+
+ for (output = mapping->outputs; output->code; ++output) {
+ unsigned int i;
+
+ if (output->num_components != xremap->config.num_m_components)
+ continue;
+
+ for (i = 0; i < output->num_components; ++i) {
+ if (output->component_maps[i] !=
+ xremap->config.component_maps[i])
+ break;
+ }
+
+ if (i == output->num_components)
+ return output;
+ }
+
+ return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdevice Pad Operations
+ */
+
+static int xremap_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct xremap_device *xremap = to_remap(subdev);
+ struct v4l2_mbus_framefmt *format;
+
+ if (code->pad == XREMAP_PAD_SINK) {
+ const struct xremap_mapping *mapping = NULL;
+ unsigned int index = code->index + 1;
+ unsigned int i;
+
+ /* Iterate through the mappings and skip the ones that don't
+ * match the remapper configuration until we reach the requested
+ * index.
+ */
+ for (i = 0; i < ARRAY_SIZE(xremap_mappings) && index; ++i) {
+ mapping = &xremap_mappings[i];
+
+ if (xremap_match_mapping(xremap, mapping))
+ index--;
+ }
+
+ /* If the index was larger than the number of supported mappings
+ * return -EINVAL.
+ */
+ if (index > 0)
+ return -EINVAL;
+
+ code->code = mapping->code;
+ } else {
+ if (code->index)
+ return -EINVAL;
+
+ format = v4l2_subdev_get_try_format(subdev, cfg, code->pad);
+ code->code = format->code;
+ }
+
+ return 0;
+}
+
+static int xremap_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;
+
+ if (fse->pad == XREMAP_PAD_SINK) {
+ /* The remapper doesn't restrict the size on the sink pad. */
+ fse->min_width = XREMAP_MIN_WIDTH;
+ fse->max_width = XREMAP_MAX_WIDTH;
+ fse->min_height = XREMAP_MIN_HEIGHT;
+ fse->max_height = XREMAP_MAX_HEIGHT;
+ } else {
+ /* The size on the source pad are fixed and always identical to
+ * the size on the sink pad.
+ */
+ fse->min_width = format->width;
+ fse->max_width = format->width;
+ fse->min_height = format->height;
+ fse->max_height = format->height;
+ }
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+xremap_get_pad_format(struct xremap_device *xremap,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, u32 which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(&xremap->xvip.subdev, cfg,
+ pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &xremap->formats[pad];
+ default:
+ return NULL;
+ }
+}
+
+static int xremap_get_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct xremap_device *xremap = to_remap(subdev);
+
+ fmt->format = *xremap_get_pad_format(xremap, cfg, fmt->pad, fmt->which);
+
+ return 0;
+}
+
+static int xremap_set_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct xremap_device *xremap = to_remap(subdev);
+ const struct xremap_mapping_output *output;
+ const struct xremap_mapping *mapping;
+ struct v4l2_mbus_framefmt *format;
+ unsigned int i;
+
+ format = xremap_get_pad_format(xremap, cfg, fmt->pad, fmt->which);
+
+ if (fmt->pad == XREMAP_PAD_SOURCE) {
+ fmt->format = *format;
+ return 0;
+ }
+
+ /* Find the mapping. If the requested format has no mapping, use the
+ * default.
+ */
+ for (i = 0; i < ARRAY_SIZE(xremap_mappings); ++i) {
+ mapping = &xremap_mappings[i];
+ if (mapping->code != fmt->format.code)
+ continue;
+
+ output = xremap_match_mapping(xremap, mapping);
+ if (output)
+ break;
+ }
+
+ if (!output) {
+ mapping = xremap->default_mapping;
+ output = xremap->default_output;
+ }
+
+ format->code = mapping->code;
+ format->width = clamp_t(unsigned int, fmt->format.width,
+ XREMAP_MIN_WIDTH, XREMAP_MAX_WIDTH);
+ format->height = clamp_t(unsigned int, fmt->format.height,
+ XREMAP_MIN_HEIGHT, XREMAP_MAX_HEIGHT);
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_SRGB;
+
+ fmt->format = *format;
+
+ /* Propagate the format to the source pad. */
+ format = xremap_get_pad_format(xremap, cfg, XREMAP_PAD_SOURCE,
+ fmt->which);
+ *format = fmt->format;
+ format->code = output->code;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdevice Operations
+ */
+
+/*
+ * xremap_init_formats - Initialize formats on all pads
+ * @subdev: remapper V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static void xremap_init_formats(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh)
+{
+ struct xremap_device *xremap = to_remap(subdev);
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+
+ format.pad = XREMAP_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = xremap->default_mapping->code;
+ format.format.width = XREMAP_DEF_WIDTH;
+ format.format.height = XREMAP_DEF_HEIGHT;
+
+ xremap_set_format(subdev, fh ? fh->pad : NULL, &format);
+}
+
+static int xremap_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ xremap_init_formats(subdev, fh);
+
+ return 0;
+}
+
+static int xremap_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+static struct v4l2_subdev_core_ops xremap_core_ops = {
+};
+
+static struct v4l2_subdev_video_ops xremap_video_ops = {
+};
+
+static struct v4l2_subdev_pad_ops xremap_pad_ops = {
+ .enum_mbus_code = xremap_enum_mbus_code,
+ .enum_frame_size = xremap_enum_frame_size,
+ .get_fmt = xremap_get_format,
+ .set_fmt = xremap_set_format,
+};
+
+static struct v4l2_subdev_ops xremap_ops = {
+ .core = &xremap_core_ops,
+ .video = &xremap_video_ops,
+ .pad = &xremap_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops xremap_internal_ops = {
+ .open = xremap_open,
+ .close = xremap_close,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+
+static const struct media_entity_operations xremap_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/* -----------------------------------------------------------------------------
+ * Platform Device Driver
+ */
+
+static int xremap_parse_of(struct xremap_device *xremap)
+{
+ struct device_node *node = xremap->xvip.dev->of_node;
+ unsigned int i;
+ int ret;
+
+ /* Parse the DT properties. */
+ ret = of_property_read_u32(node, "xlnx,video-width",
+ &xremap->config.width);
+ if (ret < 0) {
+ dev_dbg(xremap->xvip.dev, "unable to parse %s property\n",
+ "xlnx,video-width");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(node, "#xlnx,s-components",
+ &xremap->config.num_s_components);
+ if (ret < 0) {
+ dev_dbg(xremap->xvip.dev, "unable to parse %s property\n",
+ "#xlnx,s-components");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(node, "#xlnx,m-components",
+ &xremap->config.num_m_components);
+ if (ret < 0) {
+ dev_dbg(xremap->xvip.dev, "unable to parse %s property\n",
+ "#xlnx,m-components");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(node, "xlnx,component-maps",
+ xremap->config.component_maps,
+ xremap->config.num_m_components);
+ if (ret < 0) {
+ dev_dbg(xremap->xvip.dev, "unable to parse %s property\n",
+ "xlnx,component-maps");
+ return -EINVAL;
+ }
+
+ /* Validate the parsed values. */
+ if (xremap->config.num_s_components > 4 ||
+ xremap->config.num_m_components > 4) {
+ dev_dbg(xremap->xvip.dev,
+ "invalid number of components (s %u m %u)\n",
+ xremap->config.num_s_components,
+ xremap->config.num_m_components);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < xremap->config.num_m_components; ++i) {
+ if (xremap->config.component_maps[i] > 4) {
+ dev_dbg(xremap->xvip.dev, "invalid map %u @%u\n",
+ xremap->config.component_maps[i], i);
+ return -EINVAL;
+ }
+ }
+
+ /* Find the first mapping that matches the remapper configuration and
+ * store it as the default mapping.
+ */
+ for (i = 0; i < ARRAY_SIZE(xremap_mappings); ++i) {
+ const struct xremap_mapping_output *output;
+ const struct xremap_mapping *mapping;
+
+ mapping = &xremap_mappings[i];
+ output = xremap_match_mapping(xremap, mapping);
+
+ if (output) {
+ xremap->default_mapping = mapping;
+ xremap->default_output = output;
+ return 0;
+ }
+ }
+
+ dev_err(xremap->xvip.dev,
+ "No format compatible with device configuration\n");
+
+ return -EINVAL;
+}
+
+static int xremap_probe(struct platform_device *pdev)
+{
+ struct xremap_device *xremap;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ xremap = devm_kzalloc(&pdev->dev, sizeof(*xremap), GFP_KERNEL);
+ if (!xremap)
+ return -ENOMEM;
+
+ xremap->xvip.dev = &pdev->dev;
+
+ ret = xremap_parse_of(xremap);
+ if (ret < 0)
+ return ret;
+
+ xremap->xvip.clk = devm_clk_get(xremap->xvip.dev, NULL);
+ if (IS_ERR(xremap->xvip.clk))
+ return PTR_ERR(xremap->xvip.clk);
+
+ clk_prepare_enable(xremap->xvip.clk);
+
+ /* Initialize V4L2 subdevice and media entity */
+ subdev = &xremap->xvip.subdev;
+ v4l2_subdev_init(subdev, &xremap_ops);
+ subdev->dev = &pdev->dev;
+ subdev->internal_ops = &xremap_internal_ops;
+ strlcpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name));
+ v4l2_set_subdevdata(subdev, xremap);
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ xremap_init_formats(subdev, NULL);
+
+ xremap->pads[XREMAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ xremap->pads[XREMAP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ subdev->entity.ops = &xremap_media_ops;
+ ret = media_entity_pads_init(&subdev->entity, 2, xremap->pads);
+ if (ret < 0)
+ goto error;
+
+ platform_set_drvdata(pdev, xremap);
+
+ ret = v4l2_async_register_subdev(subdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register subdev\n");
+ goto error;
+ }
+
+ dev_info(&pdev->dev, "device registered\n");
+
+ return 0;
+
+error:
+ media_entity_cleanup(&subdev->entity);
+ clk_disable_unprepare(xremap->xvip.clk);
+ return ret;
+}
+
+static int xremap_remove(struct platform_device *pdev)
+{
+ struct xremap_device *xremap = platform_get_drvdata(pdev);
+ struct v4l2_subdev *subdev = &xremap->xvip.subdev;
+
+ v4l2_async_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ clk_disable_unprepare(xremap->xvip.clk);
+
+ return 0;
+}
+
+static const struct of_device_id xremap_of_id_table[] = {
+ { .compatible = "xlnx,v-remapper" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, xremap_of_id_table);
+
+static struct platform_driver xremap_driver = {
+ .driver = {
+ .name = "xilinx-remapper",
+ .of_match_table = xremap_of_id_table,
+ },
+ .probe = xremap_probe,
+ .remove = xremap_remove,
+};
+
+module_platform_driver(xremap_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Xilinx Video Remapper Driver");
+MODULE_LICENSE("GPL v2");