+ plugin->pipeline[j++] = reverse_pipeline[i];
+
+ plugin->pipeline_len = j;
+
+ return 0;
+}
+
+/**
+ * @brief Check if the entity belongs to the plugin video pipeline
+ * @param plugin - this plugin.
+ * @param entity_name - name of the entity to look for.
+ *
+ * Check if the entity belongs to the pipeline whose sink element
+ * is the video device represented by the file descriptor passed
+ * to plugin_init().
+ *
+ * @return True if the entity belongs to the pipeline,
+ * and false otherwise.
+ */
+static bool has_pipeline_entity(struct exynos4_camera_plugin *plugin,
+ char *entity_name)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ unsigned int i;
+
+ if (pipeline == NULL || entity_name == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+ if (!strncmp(media_entity_get_info(entity)->name, entity_name,
+ strlen(entity_name)))
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Adjust mbus format to FIMC_IS_ISP limitations.
+ * @param mbus_fmt - format to adjust.
+ *
+ * FIMC_IS_ISP shears the video frame off, effectively the format set
+ * on the preceding entities has to be suitably greater to achieve
+ * the frame size requested by the user.
+ *
+ * Also the miminum frame size supported by FIMC_IS_ISP is 128x128.
+ */
+static void adjust_format_to_fimc_is_isp(struct v4l2_mbus_framefmt *mbus_fmt)
+{
+ mbus_fmt->width = max(128, mbus_fmt->width);
+ mbus_fmt->height = max(128, mbus_fmt->height);
+
+ mbus_fmt->width += 16;
+ mbus_fmt->height += 12;
+}
+
+/**
+ * @brief Negotiate format acceptable for all pipeline entities.
+ * @param plugin - this plugin.
+ * @param dev_fmt - requested video device format.
+ *
+ * Negotiate common format acceptable by all the pipeline entities,
+ * that is closest to the requested one.
+ *
+ * @return 0 if the negotiation succeeded,
+ * or negative error code on failure.
+ */
+static int negotiate_pipeline_fmt(struct exynos4_camera_plugin *plugin,
+ struct v4l2_format *dev_fmt)
+{
+ struct v4l2_mbus_framefmt mbus_fmt = { 0 }, common_fmt;
+ int repeat_negotiation, cnt_negotiation = 0, ret, pad_id, i;
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ enum v4l2_subdev_fmt_mismatch fmt_err;
+ struct media_entity *entity;
+
+ if (pipeline == NULL || dev_fmt == NULL)
+ return -EINVAL;
+
+ mbus_fmt.width = dev_fmt->fmt.pix_mp.width;
+ mbus_fmt.height = dev_fmt->fmt.pix_mp.height;
+ mbus_fmt.field = dev_fmt->fmt.pix_mp.field;
+ mbus_fmt.colorspace = dev_fmt->fmt.pix_mp.colorspace;
+
+ if (has_pipeline_entity(plugin, EXYNOS4_FIMC_IS_ISP))
+ adjust_format_to_fimc_is_isp(&mbus_fmt);
+
+ V4L2_EXYNOS4_DBG("Begin pipeline format negotiation...");
+
+ for (;;) {
+ repeat_negotiation = 0;
+ entity = pipeline[0].entity;
+
+ pad_id = pipeline[0].src_pad->index;
+
+ V4L2_EXYNOS4_DBG("Setting format on the source entity pad %s:%d",
+ media_entity_get_info(entity)->name, pad_id);
+
+ ret = v4l2_subdev_set_format(entity, &mbus_fmt,
+ pad_id, V4L2_SUBDEV_FORMAT_TRY);
+ if (ret < 0)
+ return ret;
+
+ V4L2_EXYNOS4_DBG("Format set on the pad %s:%d: mcode: %s, cs: %s, w: %d, h: %d",
+ media_entity_get_info(entity)->name, pad_id,
+ v4l2_subdev_pixelcode_to_string(mbus_fmt.code),
+ v4l2_subdev_colorspace_to_string(mbus_fmt.colorspace),
+ mbus_fmt.width, mbus_fmt.height);
+
+ common_fmt = mbus_fmt;
+
+ /* Stop iterating on last but one entity as it is not a sub-device. */
+ for (i = 1; i < plugin->pipeline_len - 1; i++) {
+ entity = pipeline[i].entity;
+
+ pad_id = pipeline[i].sink_pad->index;
+
+ V4L2_EXYNOS4_DBG("Setting format on the pad %s:%d: mcode: %s, cs: %s, w: %d, h: %d",
+ media_entity_get_info(entity)->name, pad_id,
+ v4l2_subdev_pixelcode_to_string(mbus_fmt.code),
+ v4l2_subdev_colorspace_to_string(mbus_fmt.colorspace),
+ mbus_fmt.width, mbus_fmt.height);
+
+ /* Set format on the entity sink pad. */
+ ret = v4l2_subdev_set_format(entity, &mbus_fmt, pad_id,
+ V4L2_SUBDEV_FORMAT_TRY);
+ if (ret < 0)
+ return ret;
+
+ fmt_err = v4l2_subdev_format_compare(&mbus_fmt, &common_fmt);
+ if (fmt_err) {
+ if (fmt_err == FMT_MISMATCH_WIDTH &&
+ !strncmp(media_entity_get_info(entity)->name,
+ EXYNOS4_FIMC_LITE_PREFIX,
+ strlen(EXYNOS4_FIMC_LITE_PREFIX))) {
+ /*
+ * Align width downwards, according to the FIMC-LITE
+ * width step. Otherwise pipeline format negotiation
+ * wouldn't succeed for widths excessing maximum sensor
+ * frame width, which is probed by GStreamer, no matter
+ * what actual frame size is to be set.
+ */
+ mbus_fmt.width -= 8;
+ }
+ repeat_negotiation = 1;
+ break;
+ }
+
+ /*
+ * Do not check format on FIMC.[n] source pad
+ * and stop negotiation.
+ */
+ if (!strncmp(media_entity_get_info(entity)->name,
+ EXYNOS4_FIMC_PREFIX,
+ strlen(EXYNOS4_FIMC_PREFIX)))
+ break;
+
+ pad_id = pipeline[i].src_pad->index;
+
+ /* Get format on the entity src pad */
+ ret = v4l2_subdev_get_format(entity, &mbus_fmt, pad_id,
+ V4L2_SUBDEV_FORMAT_TRY);
+ if (ret < 0)
+ return -EINVAL;
+
+ V4L2_EXYNOS4_DBG("Format propagated to the pad %s:%d: mcode: %s, cs: %s, w: %d, h: %d",
+ media_entity_get_info(entity)->name, pad_id,
+ v4l2_subdev_pixelcode_to_string(mbus_fmt.code),
+ v4l2_subdev_colorspace_to_string(mbus_fmt.colorspace),
+ mbus_fmt.width, mbus_fmt.height);
+
+ if (!strcmp(media_entity_get_info(entity)->name,
+ EXYNOS4_FIMC_IS_ISP)) {
+ common_fmt.code = mbus_fmt.code;
+ common_fmt.colorspace = mbus_fmt.colorspace;
+ common_fmt.width -= 16;
+ common_fmt.height -= 12;
+ }
+
+ if (v4l2_subdev_format_compare(&mbus_fmt, &common_fmt)) {
+ repeat_negotiation = 1;
+ break;
+ }
+ }
+
+ if (!repeat_negotiation) {
+ break;
+ } else if (++cnt_negotiation > EXYNOS4_MAX_FMT_NEGO_NUM) {
+ V4L2_EXYNOS4_DBG("Pipeline format negotiation failed!");
+ return -EINVAL;
+ }
+ }
+
+ dev_fmt->fmt.pix_mp.width = mbus_fmt.width;
+ dev_fmt->fmt.pix_mp.height = mbus_fmt.height;
+ dev_fmt->fmt.pix_mp.field = mbus_fmt.field;
+ dev_fmt->fmt.pix_mp.colorspace = mbus_fmt.colorspace;
+
+ V4L2_EXYNOS4_DBG("Pipeline format successfuly negotiated");
+
+ return 0;
+}
+
+/**
+ * @brief Apply previously negotiated pipeline format
+ * @param plugin - this plugin.
+ *
+ * Apply the format, previously negotiated with negotiate_pipeline_fmt(),
+ * on all the pipeline v4l2 sub-devices.
+ *
+ * @return 0 if the format was successfuly applied,
+ * or negative error code on failure.
+ */
+static int apply_pipeline_fmt(struct exynos4_camera_plugin *plugin,
+ struct v4l2_format *fmt)
+{
+ struct v4l2_mbus_framefmt mbus_fmt = { 0 };
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ struct media_pad *pad;
+ unsigned int i;
+ int ret;
+
+ if (pipeline == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < plugin->pipeline_len - 1; i++) {
+ entity = pipeline[i].entity;
+ /*
+ * Source entity is linked only through a source pad
+ * and this pad should be used for setting the format.
+ * For other entities set the format on a sink pad.
+ */
+ pad = pipeline[i].sink_pad ? pipeline[i].sink_pad :
+ pipeline[i].src_pad;
+ if (pad == NULL)
+ return -EINVAL;
+
+ ret = v4l2_subdev_get_format(entity, &mbus_fmt, pad->index,
+ V4L2_SUBDEV_FORMAT_TRY);
+
+ if (ret < 0)
+ return ret;
+
+ V4L2_EXYNOS4_DBG("VIDIOC_SUBDEV_G_FMT %s:%d: mcode: %s, cs: %s, w: %d, h: %d",
+ media_entity_get_info(entity)->name, pad->index,
+ v4l2_subdev_pixelcode_to_string(mbus_fmt.code),
+ v4l2_subdev_colorspace_to_string(mbus_fmt.colorspace),
+ mbus_fmt.width, mbus_fmt.height);
+
+ ret = v4l2_subdev_set_format(entity, &mbus_fmt, pad->index,
+ V4L2_SUBDEV_FORMAT_ACTIVE);
+ if (ret < 0)
+ return ret;
+
+ V4L2_EXYNOS4_DBG("VIDIOC_SUBDEV_S_FMT %s:%d: mcode: %s, cs: %s, w: %d, h: %d",
+ media_entity_get_info(entity)->name, pad->index,
+ v4l2_subdev_pixelcode_to_string(mbus_fmt.code),
+ v4l2_subdev_colorspace_to_string(mbus_fmt.colorspace),
+ mbus_fmt.width, mbus_fmt.height);
+ }
+
+ /*
+ * Sink entity represents /dev/videoN node and is not
+ * a sub-device. Nonetheless because it has associated
+ * file descriptor and can expose v4l2-controls the
+ * v4l2-subdev structure is used for caching the
+ * related data.
+ */
+ struct v4l2_subdev *sd =
+ media_entity_get_v4l2_subdev(pipeline[i].entity);
+ if (!sd)
+ return -EINVAL;
+
+ ret = SYS_IOCTL(sd->fd, VIDIOC_S_FMT, fmt);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * @brief Open all v4l2 sub-devices associated with pipeline entities
+ * @param plugin - this plugin.
+ *
+ * Open all v4l2 sub-devices associated with the entities of the pipeline
+ * discovered with discover_pipeline_by_entity().
+ *
+ * @return 0 if all v4l2 sub-devices were opened successfuly,
+ * or negative error code on failure.
+ */
+static int open_pipeline(struct exynos4_camera_plugin *plugin)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ unsigned int i;
+ int ret;
+
+ if (pipeline == NULL)
+ return -EINVAL;
+
+ /*
+ * Stop walking the pipeline on the last but one entity, because
+ * the sink entity was already opened by libv4l2 core.
+ */
+ for (i = 0; i < plugin->pipeline_len - 1; i++) {
+ entity = pipeline[i].entity;
+ V4L2_EXYNOS4_DBG("Opening sub-device: %s",
+ media_entity_get_info(entity)->name);
+ ret = v4l2_subdev_open(entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Check if there was a v4l2_ctrl binding defined for the entity
+ * @param entity - media entity.
+ * @param ctrl_id - v4l2 control identifier.
+ *
+ * Check if there was a v4l2-ctrl-binding entry defined for the entity.
+ *
+ * @return true if the binding exists, false otherwise.
+ */
+static bool has_v4l2_ctrl_binding(struct media_entity *entity,
+ int ctrl_id)
+{
+ struct v4l2_subdev *sd = media_entity_get_v4l2_subdev(entity);
+ int i;
+
+ if (!sd)
+ return false;
+
+ for (i = 0; i < sd->num_v4l2_ctrl_bindings; ++i)
+ if (sd->v4l2_ctrl_bindings[i] == ctrl_id)
+ return true;
+
+ return false;
+}
+
+/**
+ * @brief Get the first pipeline entity with matching v4l2-ctrl-binding.
+ * @param plugin - this plugin.
+ * @param cid - v4l2-control identifier.
+ *
+ * Get the first pipeline entity for which v4l2-control-binding
+ * with given cid was defined.
+ *
+ * @return associated entity if defined, or NULL if no matching
+ * v4l2-ctrl-binding was defined for any entity
+ * in the pipeline.
+ */
+static struct media_entity *get_pipeline_entity_by_cid(
+ struct exynos4_camera_plugin *plugin,
+ int cid)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ unsigned int i;
+
+ if (pipeline == NULL)
+ return NULL;
+
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+ if (has_v4l2_ctrl_binding(entity, cid))
+ return entity;
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Check if the entity is of capture type
+ * @param entity_name - name of the entity to check
+ *
+ * Check if the entity name ends with a "capture", which
+ * gives a hint that it is of capture type.
+ *
+ * @return True if the entity name ends with a "capture"
+ * string, and false otherwise.
+ */
+static bool is_capture_entity(const char *entity_name)
+{
+ const char capture_segment[] = "capture";
+ int cap_segment_pos;
+
+ if (entity_name == NULL)
+ return false;
+
+ cap_segment_pos = strlen(entity_name) - strlen(capture_segment);
+
+ if (strcmp(entity_name + cap_segment_pos, capture_segment) == 0)
+ return true;
+
+ return false;
+}
+
+static __u32 convert_type(__u32 type)
+{
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ return V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ default:
+ return type;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Ioctl handlers
+ */
+
+static int set_fmt_ioctl(struct exynos4_camera_plugin *plugin,
+ unsigned long int cmd,
+ struct v4l2_format *arg,
+ enum v4l2_subdev_format_whence set_mode)
+{
+ struct v4l2_format fmt = { 0 };
+ int ret;
+
+ fmt.type = convert_type(arg->type);
+ if (fmt.type != arg->type) {
+ fmt.fmt.pix_mp.width = arg->fmt.pix.width;
+ fmt.fmt.pix_mp.height = arg->fmt.pix.height;
+ fmt.fmt.pix_mp.pixelformat = arg->fmt.pix.pixelformat;
+ fmt.fmt.pix_mp.field = arg->fmt.pix.field;
+ fmt.fmt.pix_mp.colorspace = arg->fmt.pix.colorspace;
+ fmt.fmt.pix_mp.num_planes = 1;
+ fmt.fmt.pix_mp.flags = arg->fmt.pix.flags;
+ fmt.fmt.pix_mp.plane_fmt[0].bytesperline = arg->fmt.pix.bytesperline;
+ fmt.fmt.pix_mp.plane_fmt[0].sizeimage = arg->fmt.pix.sizeimage;
+ } else {
+ fmt = *arg;
+ }
+
+ ret = negotiate_pipeline_fmt(plugin, &fmt);
+ if (ret < 0)
+ return ret;
+
+ if (set_mode == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ ret = apply_pipeline_fmt(plugin, &fmt);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (fmt.type != arg->type) {
+ arg->fmt.pix.width = fmt.fmt.pix_mp.width;
+ arg->fmt.pix.height = fmt.fmt.pix_mp.height;
+ arg->fmt.pix.pixelformat = fmt.fmt.pix_mp.pixelformat;
+ arg->fmt.pix.field = fmt.fmt.pix_mp.field;
+ arg->fmt.pix.colorspace = fmt.fmt.pix_mp.colorspace;
+ arg->fmt.pix.bytesperline = fmt.fmt.pix_mp.plane_fmt[0].bytesperline;
+ arg->fmt.pix.sizeimage = fmt.fmt.pix_mp.plane_fmt[0].sizeimage;
+ arg->fmt.pix.flags = fmt.fmt.pix_mp.flags;
+ } else {
+ *arg = fmt;
+ }
+
+ return 0;
+}
+
+static int get_fmt_ioctl(int fd,
+ unsigned long int cmd,
+ struct v4l2_format *arg)
+{
+ struct v4l2_format fmt = { 0 };
+ int ret;
+
+ fmt.type = convert_type(arg->type);
+
+ if (fmt.type == arg->type)
+ return SYS_IOCTL(fd, cmd, arg);
+
+ ret = SYS_IOCTL(fd, cmd, &fmt);
+ if (ret < 0)
+ return ret;
+
+ memset(&arg->fmt.pix, 0, sizeof(arg->fmt.pix));
+ arg->fmt.pix.width = fmt.fmt.pix_mp.width;
+ arg->fmt.pix.height = fmt.fmt.pix_mp.height;
+ arg->fmt.pix.pixelformat = fmt.fmt.pix_mp.pixelformat;
+ arg->fmt.pix.field = fmt.fmt.pix_mp.field;
+ arg->fmt.pix.colorspace = fmt.fmt.pix_mp.colorspace;
+ arg->fmt.pix.bytesperline = fmt.fmt.pix_mp.plane_fmt[0].bytesperline;
+ arg->fmt.pix.sizeimage = fmt.fmt.pix_mp.plane_fmt[0].sizeimage;
+ arg->fmt.pix.flags = fmt.fmt.pix_mp.flags;
+
+ return ret;
+}
+
+static int buf_ioctl(int fd,
+ unsigned long int cmd,
+ struct v4l2_buffer *arg)
+{
+ struct v4l2_buffer buf = *arg;
+ struct v4l2_plane plane = { 0 };
+ int ret;
+
+ buf.type = convert_type(arg->type);
+
+ if (buf.type == arg->type)
+ return SYS_IOCTL(fd, cmd, arg);
+
+ memcpy(&plane.m, &arg->m, sizeof(plane.m));
+ plane.length = arg->length;
+ plane.bytesused = arg->bytesused;
+
+ buf.m.planes = &plane;
+ buf.length = 1;
+
+ ret = SYS_IOCTL(fd, cmd, &buf);
+
+ arg->index = buf.index;
+ arg->memory = buf.memory;
+ arg->flags = buf.flags;
+ arg->field = buf.field;
+ arg->timestamp = buf.timestamp;
+ arg->timecode = buf.timecode;
+ arg->sequence = buf.sequence;
+
+ arg->length = plane.length;
+ arg->bytesused = plane.bytesused;
+ memcpy(&arg->m, &plane.m, sizeof(arg->m));
+
+ return ret;
+}
+
+static int querycap_ioctl(int fd, struct v4l2_capability *arg)
+{
+ int ret;
+
+ ret = SYS_IOCTL(fd, VIDIOC_QUERYCAP, arg);
+ if (ret < 0)
+ return ret;
+
+ arg->device_caps |= V4L2_CAP_VIDEO_CAPTURE;
+ arg->capabilities |= V4L2_CAP_VIDEO_CAPTURE;
+
+ return ret;
+}
+
+int ctrl_ioctl(struct exynos4_camera_plugin *plugin,
+ unsigned long int request, struct v4l2_control *arg)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ struct v4l2_control ctrl = *arg;
+ struct v4l2_queryctrl queryctrl = { 0 };
+ bool ctrl_found = false;
+ int i, ret;
+
+ if (pipeline == NULL)
+ return -EINVAL;
+
+ /*
+ * The control has to be reset to the default value
+ * on all of the pipeline entities, prior setting a new
+ * value. This is required in cases when the control config
+ * is changed between subsequent calls to VIDIOC_S_CTRL,
+ * to avoid the situation when a control is set on more
+ * than one sub-device.
+ */
+ if (request == VIDIOC_S_CTRL) {
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ struct v4l2_control ctrl_def = ctrl;
+
+ entity = pipeline[i].entity;
+
+ queryctrl.id = ctrl.id;
+
+ /* query default control value */
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_QUERYCTRL, &queryctrl);
+ if (ret < 0)
+ continue;
+
+ ctrl_found = true;
+
+ if (queryctrl.type & V4L2_CTRL_TYPE_BUTTON)
+ break;
+
+ ctrl_def.value = queryctrl.default_value;
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_S_CTRL, &ctrl_def);
+ if (ret < 0)
+ return -EINVAL;
+ }
+
+ if (!ctrl_found) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ }
+
+ entity = get_pipeline_entity_by_cid(plugin, ctrl.id);
+
+ if (entity) {
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ request, &ctrl);
+ } else {
+ /* Walk the pipeline until the request succeeds */
+ ret = -ENOENT;
+
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ request, &ctrl);
+ if (!ret)
+ break;
+ }
+ }
+
+exit:
+ *arg = ctrl;
+
+ V4L2_EXYNOS4_DBG("%s [id: 0x%8.8x, name: %s, entity: %s] (%d)",
+ VIDIOC_CTRL(request), ctrl.id,
+ ret ? NULL : queryctrl.name,
+ entity ? media_entity_get_info(entity)->name :
+ NULL, ret);
+
+
+ return ret;
+}
+
+static int single_ext_ctrl_ioctl(struct exynos4_camera_plugin *plugin,
+ unsigned long int request,
+ struct v4l2_ext_controls *arg)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity;
+ struct v4l2_ext_controls ctrls = *arg;
+ struct v4l2_ext_control *ctrl = &ctrls.controls[0];
+ struct v4l2_query_ext_ctrl queryctrl;
+ bool ctrl_found = 0;
+ int i, ret = -EINVAL;
+
+ /*
+ * The control has to be reset to the default value
+ * on all of the pipeline entities, prior setting a new
+ * value. This is required in cases when the control config
+ * is changed between subsequent calls to VIDIOC_S_EXT_CTRLS,
+ * to avoid the situation when a control is set on more
+ * than one sub-device.
+ */
+ if (request == VIDIOC_S_EXT_CTRLS) {
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ struct v4l2_ext_controls ctrls_def = ctrls;
+ struct v4l2_ext_control *ctrl_def = &ctrls_def.controls[0];
+
+ entity = pipeline[i].entity;
+
+ queryctrl.id = ctrl->id;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_QUERY_EXT_CTRL, &queryctrl);
+ if (ret < 0)
+ continue;
+
+ ctrl_found = true;
+
+ if (queryctrl.type & V4L2_CTRL_TYPE_BUTTON)
+ break;
+
+ ctrl_def->value64 = queryctrl.default_value;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_S_EXT_CTRLS, &ctrls_def);
+ if (ret < 0)
+ return -EINVAL;
+ }
+
+ if (!ctrl_found) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ }
+
+ entity = get_pipeline_entity_by_cid(plugin, ctrl->id);
+
+ if (entity) {
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ request, &ctrls);
+ } else {
+ /* Walk the pipeline until the request succeeds */
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ request, &ctrls);
+ if (!ret)
+ break;
+ }
+ }
+
+exit:
+ *arg = ctrls;
+
+ V4L2_EXYNOS4_DBG("%s [id: 0x%8.8x, name: %s, entity: %s] (%d)",
+ VIDIOC_CTRL(request), ctrl->id,
+ ret ? NULL : queryctrl.name,
+ entity ? media_entity_get_info(entity)->name :
+ NULL, ret);
+
+ return ret;
+}
+
+int ext_ctrl_ioctl(struct exynos4_camera_plugin *plugin,
+ unsigned long int request,
+ struct v4l2_ext_controls *arg)
+{
+ struct v4l2_ext_controls out_ctrls = *arg, ctrls = *arg;
+ int ret = -EINVAL, i;
+
+ ctrls.count = 1;
+
+ /*
+ * Split cluster to individual ioctl calls for each control
+ * from the array, to make possible redirection of every
+ * single control to different sub-device, according to the
+ * configuration settings.
+ */
+ for (i = 0; i < arg->count; ++i) {
+ ctrls.controls = &arg->controls[i];
+
+ ret = single_ext_ctrl_ioctl(plugin, request, &ctrls);
+ out_ctrls.controls[i] = ctrls.controls[i];
+ if (ret < 0) {
+ if (ctrls.error_idx == 1)
+ out_ctrls.error_idx = ctrls.count;
+ else
+ out_ctrls.error_idx = i;
+ break;
+ }
+ }
+
+ *arg = out_ctrls;
+ return ret;
+}
+
+int sort_ctrls(const void * a, const void * b)
+{
+ const struct media_entity_to_cid *ctrl_a = a, *ctrl_b = b;
+
+ return ctrl_a->queryctrl.id - ctrl_b->queryctrl.id;
+}
+
+int queryctrl_ioctl(struct exynos4_camera_plugin *plugin,
+ struct v4l2_queryctrl *arg)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity, *target_entity;
+ struct v4l2_queryctrl queryctrl = *arg;
+ struct media_entity_to_cid *ctrls_found;
+ int i, ret = -EINVAL, num_ctrls = 0;
+
+ /*
+ * If id is or'ed with V4L2_CTRL_FLAG_NEXT_CTRL then the control to
+ * be found is the one with the next lowest id among all entities
+ * in the pipeline.
+ */
+ if (queryctrl.id & V4L2_CTRL_FLAG_NEXT_CTRL) {
+ ctrls_found = malloc(sizeof(*ctrls_found));
+
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+
+ queryctrl = *arg;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_QUERYCTRL, &queryctrl);
+ if (!ret) {
+ ctrls_found = realloc(ctrls_found,
+ sizeof(*ctrls_found) * (num_ctrls + 1));
+ ctrls_found[num_ctrls].queryctrl = queryctrl;
+ ctrls_found[num_ctrls].entity = entity;
+ ++num_ctrls;
+ }
+ }
+
+ if (num_ctrls == 0) {
+ ret = -EINVAL;
+ entity = NULL;
+ goto done;
+ }
+
+ qsort(ctrls_found, num_ctrls, sizeof(*ctrls_found), sort_ctrls);
+
+ queryctrl = ctrls_found[0].queryctrl;
+ target_entity = ctrls_found[0].entity;
+
+ free(ctrls_found);
+ }
+
+ entity = get_pipeline_entity_by_cid(plugin, queryctrl.id);
+ if (entity)
+ target_entity = entity;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(target_entity)->fd,
+ VIDIOC_QUERYCTRL, &queryctrl);
+
+done:
+ V4L2_EXYNOS4_DBG(
+ "VIDIOC_QUERYCTRL [id: 0x%8.8x, name: %s, entity: %s] (%d)",
+ ret ? arg->id : queryctrl.id, ret ? NULL : queryctrl.name,
+ target_entity ? media_entity_get_info(target_entity)->name :
+ NULL, ret);
+
+ *arg = queryctrl;
+
+ return ret;
+}
+
+int query_ext_ctrl_ioctl(struct exynos4_camera_plugin *plugin,
+ struct v4l2_query_ext_ctrl *arg)
+{
+ struct pipeline_entity *pipeline = plugin->pipeline;
+ struct media_entity *entity, *target_entity;
+ struct v4l2_query_ext_ctrl query_ext_ctrl = *arg;
+ struct media_entity_to_cid *ctrls_found;
+ int i, ret = -EINVAL, num_ctrls = 0;
+
+ /*
+ * If id is or'ed with V4L2_CTRL_FLAG_NEXT_CTRL then the control to
+ * be found is the one with the next lowest id among all entities
+ * in the pipeline.
+ */
+ if (query_ext_ctrl.id & V4L2_CTRL_FLAG_NEXT_CTRL) {
+ ctrls_found = malloc(sizeof(*ctrls_found));
+
+ for (i = 0; i < plugin->pipeline_len; i++) {
+ entity = pipeline[i].entity;
+
+ query_ext_ctrl = *arg;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(entity)->fd,
+ VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl.id);
+ if (!ret) {
+ ctrls_found = realloc(ctrls_found,
+ sizeof(*ctrls_found) * (num_ctrls + 1));
+ ctrls_found[num_ctrls].query_ext_ctrl =
+ query_ext_ctrl;
+ ctrls_found[num_ctrls].entity = entity;
+ ++num_ctrls;
+ }
+ }
+
+ if (num_ctrls == 0) {
+ ret = -EINVAL;
+ entity = NULL;
+ goto done;
+ }
+
+ qsort(ctrls_found, num_ctrls, sizeof(*ctrls_found), sort_ctrls);
+
+ query_ext_ctrl = ctrls_found[0].query_ext_ctrl;
+ target_entity = ctrls_found[0].entity;
+
+ free(ctrls_found);
+ }
+
+ entity = get_pipeline_entity_by_cid(plugin, query_ext_ctrl.id);
+ if (entity)
+ target_entity = entity;
+
+ ret = SYS_IOCTL(media_entity_get_v4l2_subdev(target_entity)->fd,
+ VIDIOC_QUERYCTRL, &query_ext_ctrl);
+
+done:
+ V4L2_EXYNOS4_DBG(
+ "VIDIOC_QUERY_EXT_CTRL [id: 0x%8.8x, name: %s, entity: %s] (%d)",
+ ret ? arg->id : query_ext_ctrl.id,
+ ret ? NULL : query_ext_ctrl.name,
+ target_entity ? media_entity_get_info(target_entity)->name :
+ NULL, ret);
+
+ *arg = query_ext_ctrl;
+
+ return ret;
+}
+
+int querymenu_ioctl(struct exynos4_camera_plugin *plugin,
+ struct v4l2_querymenu *arg)