/* * Support for Medifield PNW Camera Imaging ISP subsystem. * * Copyright (c) 2010 Intel Corporation. All Rights Reserved. * * 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. * * 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 #include #include #include #include #include #include #include #include #include "atomisp_cmd.h" #include "atomisp_common.h" #include "atomisp_compat.h" #include "atomisp_internal.h" const struct atomisp_in_fmt_conv atomisp_in_fmt_conv[] = { { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, ATOMISP_INPUT_FORMAT_RAW_8, CSS_BAYER_ORDER_BGGR, CSS_FORMAT_RAW_8 }, { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, ATOMISP_INPUT_FORMAT_RAW_8, CSS_BAYER_ORDER_GBRG, CSS_FORMAT_RAW_8 }, { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, ATOMISP_INPUT_FORMAT_RAW_8, CSS_BAYER_ORDER_GRBG, CSS_FORMAT_RAW_8 }, { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, ATOMISP_INPUT_FORMAT_RAW_8, CSS_BAYER_ORDER_RGGB, CSS_FORMAT_RAW_8 }, { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, ATOMISP_INPUT_FORMAT_RAW_10, CSS_BAYER_ORDER_BGGR, CSS_FORMAT_RAW_10 }, { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, ATOMISP_INPUT_FORMAT_RAW_10, CSS_BAYER_ORDER_GBRG, CSS_FORMAT_RAW_10 }, { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, ATOMISP_INPUT_FORMAT_RAW_10, CSS_BAYER_ORDER_GRBG, CSS_FORMAT_RAW_10 }, { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, ATOMISP_INPUT_FORMAT_RAW_10, CSS_BAYER_ORDER_RGGB, CSS_FORMAT_RAW_10 }, { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, ATOMISP_INPUT_FORMAT_RAW_12, CSS_BAYER_ORDER_BGGR, CSS_FORMAT_RAW_12 }, { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, ATOMISP_INPUT_FORMAT_RAW_12, CSS_BAYER_ORDER_GBRG, CSS_FORMAT_RAW_12 }, { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, ATOMISP_INPUT_FORMAT_RAW_12, CSS_BAYER_ORDER_GRBG, CSS_FORMAT_RAW_12 }, { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, ATOMISP_INPUT_FORMAT_RAW_12, CSS_BAYER_ORDER_RGGB, CSS_FORMAT_RAW_12 }, { MEDIA_BUS_FMT_UYVY8_1X16, 8, 8, ATOMISP_INPUT_FORMAT_YUV422_8, 0, IA_CSS_STREAM_FORMAT_YUV422_8 }, { MEDIA_BUS_FMT_YUYV8_1X16, 8, 8, ATOMISP_INPUT_FORMAT_YUV422_8, 0, IA_CSS_STREAM_FORMAT_YUV422_8 }, { MEDIA_BUS_FMT_JPEG_1X8, 8, 8, CSS_FRAME_FORMAT_BINARY_8, 0, IA_CSS_STREAM_FORMAT_BINARY_8 }, { V4L2_MBUS_FMT_CUSTOM_NV12, 12, 12, CSS_FRAME_FORMAT_NV12, 0, CSS_FRAME_FORMAT_NV12 }, { V4L2_MBUS_FMT_CUSTOM_NV21, 12, 12, CSS_FRAME_FORMAT_NV21, 0, CSS_FRAME_FORMAT_NV21 }, { V4L2_MBUS_FMT_CUSTOM_YUV420, 12, 12, ATOMISP_INPUT_FORMAT_YUV420_8_LEGACY, 0, IA_CSS_STREAM_FORMAT_YUV420_8_LEGACY }, #if 0 { V4L2_MBUS_FMT_CUSTOM_M10MO_RAW, 8, 8, CSS_FRAME_FORMAT_BINARY_8, 0, IA_CSS_STREAM_FORMAT_BINARY_8 }, #endif /* no valid V4L2 MBUS code for metadata format, so leave it 0. */ { 0, 0, 0, ATOMISP_INPUT_FORMAT_EMBEDDED, 0, IA_CSS_STREAM_FORMAT_EMBEDDED }, {} }; static const struct { u32 code; u32 compressed; } compressed_codes[] = { { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8 }, { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8 }, { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8 }, { MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8 }, }; u32 atomisp_subdev_uncompressed_code(u32 code) { unsigned int i; for (i = 0; i < ARRAY_SIZE(compressed_codes); i++) if (code == compressed_codes[i].compressed) return compressed_codes[i].code; return code; } bool atomisp_subdev_is_compressed(u32 code) { int i; for (i = 0; i < ARRAY_SIZE(atomisp_in_fmt_conv) - 1; i++) if (code == atomisp_in_fmt_conv[i].code) return atomisp_in_fmt_conv[i].bpp != atomisp_in_fmt_conv[i].depth; return false; } const struct atomisp_in_fmt_conv *atomisp_find_in_fmt_conv(u32 code) { int i; for (i = 0; i < ARRAY_SIZE(atomisp_in_fmt_conv) - 1; i++) if (code == atomisp_in_fmt_conv[i].code) return atomisp_in_fmt_conv + i; return NULL; } const struct atomisp_in_fmt_conv *atomisp_find_in_fmt_conv_by_atomisp_in_fmt( enum atomisp_css_stream_format atomisp_in_fmt) { int i; for (i = 0; i < ARRAY_SIZE(atomisp_in_fmt_conv) - 1; i++) if (atomisp_in_fmt_conv[i].atomisp_in_fmt == atomisp_in_fmt) return atomisp_in_fmt_conv + i; return NULL; } bool atomisp_subdev_format_conversion(struct atomisp_sub_device *asd, unsigned int source_pad) { struct v4l2_mbus_framefmt *sink, *src; sink = atomisp_subdev_get_ffmt(&asd->subdev, NULL, V4L2_SUBDEV_FORMAT_ACTIVE, ATOMISP_SUBDEV_PAD_SINK); src = atomisp_subdev_get_ffmt(&asd->subdev, NULL, V4L2_SUBDEV_FORMAT_ACTIVE, source_pad); return atomisp_is_mbuscode_raw(sink->code) && !atomisp_is_mbuscode_raw(src->code); } uint16_t atomisp_subdev_source_pad(struct video_device * vdev) { struct media_link *link; uint16_t ret = 0; list_for_each_entry(link, &vdev->entity.links, list) { if (link->source) { ret = link->source->index; break; } } return ret; } /* * V4L2 subdev operations */ /* * isp_subdev_ioctl - CCDC module private ioctl's * @sd: ISP V4L2 subdevice * @cmd: ioctl command * @arg: ioctl argument * * Return 0 on success or a negative error code otherwise. */ static long isp_subdev_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { return 0; } /* * isp_subdev_set_power - Power on/off the CCDC module * @sd: ISP V4L2 subdevice * @on: power on/off * * Return 0 on success or a negative error code otherwise. */ static int isp_subdev_set_power(struct v4l2_subdev *sd, int on) { return 0; } static int isp_subdev_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); struct atomisp_device *isp = isp_sd->isp; if (sub->type != V4L2_EVENT_FRAME_SYNC && sub->type != V4L2_EVENT_FRAME_END && sub->type != V4L2_EVENT_ATOMISP_3A_STATS_READY && sub->type != V4L2_EVENT_ATOMISP_METADATA_READY && sub->type != V4L2_EVENT_ATOMISP_PAUSE_BUFFER && sub->type != V4L2_EVENT_ATOMISP_CSS_RESET && sub->type != V4L2_EVENT_ATOMISP_RAW_BUFFERS_ALLOC_DONE && sub->type != V4L2_EVENT_ATOMISP_ACC_COMPLETE) return -EINVAL; if (sub->type == V4L2_EVENT_FRAME_SYNC && !atomisp_css_valid_sof(isp)) return -EINVAL; return v4l2_event_subscribe(fh, sub, 16, NULL); } static int isp_subdev_unsubscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { return v4l2_event_unsubscribe(fh, sub); } /* * isp_subdev_enum_mbus_code - Handle pixel format enumeration * @sd: pointer to v4l2 subdev structure * @fh : V4L2 subdev file handle * @code: pointer to v4l2_subdev_pad_mbus_code_enum structure * return -EINVAL or zero on success */ static int isp_subdev_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { if (code->index >= ARRAY_SIZE(atomisp_in_fmt_conv) - 1) return -EINVAL; code->code = atomisp_in_fmt_conv[code->index].code; return 0; } static int isp_subdev_validate_rect(struct v4l2_subdev *sd, uint32_t pad, uint32_t target) { switch (pad) { case ATOMISP_SUBDEV_PAD_SINK: switch (target) { case V4L2_SEL_TGT_CROP: return 0; } break; default: switch (target) { case V4L2_SEL_TGT_COMPOSE: return 0; } break; } return -EINVAL; } struct v4l2_rect *atomisp_subdev_get_rect(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, uint32_t pad, uint32_t target) { struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); if (which == V4L2_SUBDEV_FORMAT_TRY) { switch (target) { case V4L2_SEL_TGT_CROP: return v4l2_subdev_get_try_crop(sd, cfg, pad); case V4L2_SEL_TGT_COMPOSE: return v4l2_subdev_get_try_compose(sd, cfg, pad); } } switch (target) { case V4L2_SEL_TGT_CROP: return &isp_sd->fmt[pad].crop; case V4L2_SEL_TGT_COMPOSE: return &isp_sd->fmt[pad].compose; } return NULL; } struct v4l2_mbus_framefmt *atomisp_subdev_get_ffmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, uint32_t pad) { struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); if (which == V4L2_SUBDEV_FORMAT_TRY) return v4l2_subdev_get_try_format(sd, cfg, pad); return &isp_sd->fmt[pad].fmt; } static void isp_get_fmt_rect(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, struct v4l2_mbus_framefmt **ffmt, struct v4l2_rect *crop[ATOMISP_SUBDEV_PADS_NUM], struct v4l2_rect *comp[ATOMISP_SUBDEV_PADS_NUM]) { unsigned int i; for (i = 0; i < ATOMISP_SUBDEV_PADS_NUM; i++) { ffmt[i] = atomisp_subdev_get_ffmt(sd, cfg, which, i); crop[i] = atomisp_subdev_get_rect(sd, cfg, which, i, V4L2_SEL_TGT_CROP); comp[i] = atomisp_subdev_get_rect(sd, cfg, which, i, V4L2_SEL_TGT_COMPOSE); } } static void isp_subdev_propagate(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, uint32_t pad, uint32_t target, uint32_t flags) { struct v4l2_mbus_framefmt *ffmt[ATOMISP_SUBDEV_PADS_NUM]; struct v4l2_rect *crop[ATOMISP_SUBDEV_PADS_NUM], *comp[ATOMISP_SUBDEV_PADS_NUM]; if (flags & V4L2_SEL_FLAG_KEEP_CONFIG) return; isp_get_fmt_rect(sd, cfg, which, ffmt, crop, comp); switch (pad) { case ATOMISP_SUBDEV_PAD_SINK: { struct v4l2_rect r = {0}; /* Only crop target supported on sink pad. */ r.width = ffmt[pad]->width; r.height = ffmt[pad]->height; atomisp_subdev_set_selection(sd, cfg, which, pad, target, flags, &r); break; } } } static int isp_subdev_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { struct v4l2_rect *rec; int rval = isp_subdev_validate_rect(sd, sel->pad, sel->target); if (rval) return rval; rec = atomisp_subdev_get_rect(sd, cfg, sel->which, sel->pad, sel->target); if (!rec) return -EINVAL; sel->r = *rec; return 0; } static char *atomisp_pad_str[] = { "ATOMISP_SUBDEV_PAD_SINK", "ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE", "ATOMISP_SUBDEV_PAD_SOURCE_VF", "ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW", "ATOMISP_SUBDEV_PAD_SOURCE_VIDEO"}; int atomisp_subdev_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, uint32_t pad, uint32_t target, uint32_t flags, struct v4l2_rect *r) { struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); struct atomisp_device *isp = isp_sd->isp; struct v4l2_mbus_framefmt *ffmt[ATOMISP_SUBDEV_PADS_NUM]; uint16_t vdev_pad = atomisp_subdev_source_pad(sd->devnode); struct v4l2_rect *crop[ATOMISP_SUBDEV_PADS_NUM], *comp[ATOMISP_SUBDEV_PADS_NUM]; enum atomisp_input_stream_id stream_id; unsigned int i; unsigned int padding_w = pad_w; unsigned int padding_h = pad_h; stream_id = atomisp_source_pad_to_stream_id(isp_sd, vdev_pad); isp_get_fmt_rect(sd, cfg, which, ffmt, crop, comp); dev_dbg(isp->dev, "sel: pad %s tgt %s l %d t %d w %d h %d which %s f 0x%8.8x\n", atomisp_pad_str[pad], target == V4L2_SEL_TGT_CROP ? "V4L2_SEL_TGT_CROP" : "V4L2_SEL_TGT_COMPOSE", r->left, r->top, r->width, r->height, which == V4L2_SUBDEV_FORMAT_TRY ? "V4L2_SUBDEV_FORMAT_TRY" : "V4L2_SUBDEV_FORMAT_ACTIVE", flags); r->width = rounddown(r->width, ATOM_ISP_STEP_WIDTH); r->height = rounddown(r->height, ATOM_ISP_STEP_HEIGHT); switch (pad) { case ATOMISP_SUBDEV_PAD_SINK: { /* Only crop target supported on sink pad. */ unsigned int dvs_w, dvs_h; crop[pad]->width = ffmt[pad]->width; crop[pad]->height = ffmt[pad]->height; /* Workaround for BYT 1080p perfectshot since the maxinum resolution of * front camera ov2722 is 1932x1092 and cannot use pad_w > 12*/ if (!strncmp(isp->inputs[isp_sd->input_curr].camera->name, "ov2722", 6) && crop[pad]->height == 1092) { padding_w = 12; padding_h = 12; } if (isp->inputs[isp_sd->input_curr].type == SOC_CAMERA) { padding_w = 0; padding_h = 0; } if (atomisp_subdev_format_conversion(isp_sd, isp_sd->capture_pad) && crop[pad]->width && crop[pad]->height) crop[pad]->width -= padding_w, crop[pad]->height -= padding_h; /* if subdev type is SOC camera,we do not need to set DVS */ if (isp->inputs[isp_sd->input_curr].type == SOC_CAMERA) isp_sd->params.video_dis_en = 0; if (isp_sd->params.video_dis_en && isp_sd->run_mode->val == ATOMISP_RUN_MODE_VIDEO && !isp_sd->continuous_mode->val) { /* This resolution contains 20 % of DVS slack * (of the desired captured image before * scaling, or 1 / 6 of what we get from the * sensor) in both width and height. Remove * it. */ crop[pad]->width = roundup(crop[pad]->width * 5 / 6, ATOM_ISP_STEP_WIDTH); crop[pad]->height = roundup(crop[pad]->height * 5 / 6, ATOM_ISP_STEP_HEIGHT); } crop[pad]->width = min(crop[pad]->width, r->width); crop[pad]->height = min(crop[pad]->height, r->height); if (!(flags & V4L2_SEL_FLAG_KEEP_CONFIG)) { for (i = ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE; i < ATOMISP_SUBDEV_PADS_NUM; i++) { struct v4l2_rect tmp = *crop[pad]; atomisp_subdev_set_selection( sd, cfg, which, i, V4L2_SEL_TGT_COMPOSE, flags, &tmp); } } if (which == V4L2_SUBDEV_FORMAT_TRY) break; if (isp_sd->params.video_dis_en && isp_sd->run_mode->val == ATOMISP_RUN_MODE_VIDEO && !isp_sd->continuous_mode->val) { dvs_w = rounddown(crop[pad]->width / 5, ATOM_ISP_STEP_WIDTH); dvs_h = rounddown(crop[pad]->height / 5, ATOM_ISP_STEP_HEIGHT); } else if (!isp_sd->params.video_dis_en && isp_sd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { /* * For CSS2.0, digital zoom needs to set dvs envelope to 12 * when dvs is disabled. */ dvs_w = dvs_h = 12; } else dvs_w = dvs_h = 0; atomisp_css_video_set_dis_envelope(isp_sd, dvs_w, dvs_h); atomisp_css_input_set_effective_resolution(isp_sd, stream_id, crop[pad]->width, crop[pad]->height); break; } case ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE: case ATOMISP_SUBDEV_PAD_SOURCE_VIDEO: { /* Only compose target is supported on source pads. */ if (isp_sd->vfpp->val == ATOMISP_VFPP_DISABLE_LOWLAT) { /* Scaling is disabled in this mode */ r->width = crop[ATOMISP_SUBDEV_PAD_SINK]->width; r->height = crop[ATOMISP_SUBDEV_PAD_SINK]->height; } if (crop[ATOMISP_SUBDEV_PAD_SINK]->width == r->width && crop[ATOMISP_SUBDEV_PAD_SINK]->height == r->height) isp_sd->params.yuv_ds_en = false; else isp_sd->params.yuv_ds_en = true; comp[pad]->width = r->width; comp[pad]->height = r->height; if (r->width == 0 || r->height == 0 || crop[ATOMISP_SUBDEV_PAD_SINK]->width == 0 || crop[ATOMISP_SUBDEV_PAD_SINK]->height == 0) break; /* * do cropping on sensor input if ratio of required resolution * is different with sensor output resolution ratio: * * ratio = width / height * * if ratio_output < ratio_sensor: * effect_width = sensor_height * out_width / out_height; * effect_height = sensor_height; * else * effect_width = sensor_width; * effect_height = sensor_width * out_height / out_width; * */ if (r->width * crop[ATOMISP_SUBDEV_PAD_SINK]->height < crop[ATOMISP_SUBDEV_PAD_SINK]->width * r->height) atomisp_css_input_set_effective_resolution(isp_sd, stream_id, rounddown(crop[ATOMISP_SUBDEV_PAD_SINK]-> height * r->width / r->height, ATOM_ISP_STEP_WIDTH), crop[ATOMISP_SUBDEV_PAD_SINK]->height); else atomisp_css_input_set_effective_resolution(isp_sd, stream_id, crop[ATOMISP_SUBDEV_PAD_SINK]->width, rounddown(crop[ATOMISP_SUBDEV_PAD_SINK]-> width * r->height / r->width, ATOM_ISP_STEP_WIDTH)); break; } case ATOMISP_SUBDEV_PAD_SOURCE_VF: case ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW: comp[pad]->width = r->width; comp[pad]->height = r->height; break; default: return -EINVAL; } /* Set format dimensions on non-sink pads as well. */ if (pad != ATOMISP_SUBDEV_PAD_SINK) { ffmt[pad]->width = comp[pad]->width; ffmt[pad]->height = comp[pad]->height; } if (!atomisp_subdev_get_rect(sd, cfg, which, pad, target)) return -EINVAL; *r = *atomisp_subdev_get_rect(sd, cfg, which, pad, target); dev_dbg(isp->dev, "sel actual: l %d t %d w %d h %d\n", r->left, r->top, r->width, r->height); return 0; } static int isp_subdev_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { int rval = isp_subdev_validate_rect(sd, sel->pad, sel->target); if (rval) return rval; return atomisp_subdev_set_selection(sd, cfg, sel->which, sel->pad, sel->target, sel->flags, &sel->r); } static int atomisp_get_sensor_bin_factor(struct atomisp_sub_device *asd) { struct v4l2_control ctrl = {0}; struct atomisp_device *isp = asd->isp; int hbin, vbin; int ret; if (isp->inputs[asd->input_curr].type == FILE_INPUT || isp->inputs[asd->input_curr].type == TEST_PATTERN) return 0; ctrl.id = V4L2_CID_BIN_FACTOR_HORZ; ret = v4l2_g_ctrl(isp->inputs[asd->input_curr].camera->ctrl_handler, &ctrl); hbin = ctrl.value; ctrl.id = V4L2_CID_BIN_FACTOR_VERT; ret |= v4l2_g_ctrl(isp->inputs[asd->input_curr].camera->ctrl_handler, &ctrl); vbin = ctrl.value; /* * ISP needs to know binning factor from sensor. * In case horizontal and vertical sensor's binning factors * are different or sensor does not support binning factor CID, * ISP will apply default 0 value. */ if (ret || hbin != vbin) hbin = 0; return hbin; } void atomisp_subdev_set_ffmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, uint32_t which, uint32_t pad, struct v4l2_mbus_framefmt *ffmt) { struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); struct atomisp_device *isp = isp_sd->isp; struct v4l2_mbus_framefmt *__ffmt = atomisp_subdev_get_ffmt(sd, cfg, which, pad); uint16_t vdev_pad = atomisp_subdev_source_pad(sd->devnode); enum atomisp_input_stream_id stream_id; dev_dbg(isp->dev, "ffmt: pad %s w %d h %d code 0x%8.8x which %s\n", atomisp_pad_str[pad], ffmt->width, ffmt->height, ffmt->code, which == V4L2_SUBDEV_FORMAT_TRY ? "V4L2_SUBDEV_FORMAT_TRY" : "V4L2_SUBDEV_FORMAT_ACTIVE"); stream_id = atomisp_source_pad_to_stream_id(isp_sd, vdev_pad); switch (pad) { case ATOMISP_SUBDEV_PAD_SINK: { const struct atomisp_in_fmt_conv *fc = atomisp_find_in_fmt_conv(ffmt->code); if (!fc) { fc = atomisp_in_fmt_conv; ffmt->code = fc->code; dev_dbg(isp->dev, "using 0x%8.8x instead\n", ffmt->code); } *__ffmt = *ffmt; isp_subdev_propagate(sd, cfg, which, pad, V4L2_SEL_TGT_CROP, 0); if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { atomisp_css_input_set_resolution(isp_sd, stream_id, ffmt); atomisp_css_input_set_binning_factor(isp_sd, stream_id, atomisp_get_sensor_bin_factor(isp_sd)); atomisp_css_input_set_bayer_order(isp_sd, stream_id, fc->bayer_order); atomisp_css_input_set_format(isp_sd, stream_id, fc->css_stream_fmt); atomisp_css_set_default_isys_config(isp_sd, stream_id, ffmt); } break; } case ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE: case ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW: case ATOMISP_SUBDEV_PAD_SOURCE_VF: case ATOMISP_SUBDEV_PAD_SOURCE_VIDEO: __ffmt->code = ffmt->code; break; } } /* * isp_subdev_get_format - Retrieve the video format on a pad * @sd : ISP V4L2 subdevice * @fh : V4L2 subdev file handle * @pad: Pad number * @fmt: Format * * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond * to the format type. */ static int isp_subdev_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { fmt->format = *atomisp_subdev_get_ffmt(sd, cfg, fmt->which, fmt->pad); return 0; } /* * isp_subdev_set_format - Set the video format on a pad * @sd : ISP subdev V4L2 subdevice * @fh : V4L2 subdev file handle * @pad: Pad number * @fmt: Format * * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond * to the format type. */ static int isp_subdev_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { atomisp_subdev_set_ffmt(sd, cfg, fmt->which, fmt->pad, &fmt->format); return 0; } /* V4L2 subdev core operations */ static const struct v4l2_subdev_core_ops isp_subdev_v4l2_core_ops = { .ioctl = isp_subdev_ioctl, .s_power = isp_subdev_set_power, .subscribe_event = isp_subdev_subscribe_event, .unsubscribe_event = isp_subdev_unsubscribe_event, }; /* V4L2 subdev pad operations */ static const struct v4l2_subdev_pad_ops isp_subdev_v4l2_pad_ops = { .enum_mbus_code = isp_subdev_enum_mbus_code, .get_fmt = isp_subdev_get_format, .set_fmt = isp_subdev_set_format, .get_selection = isp_subdev_get_selection, .set_selection = isp_subdev_set_selection, .link_validate = v4l2_subdev_link_validate_default, }; /* V4L2 subdev operations */ static const struct v4l2_subdev_ops isp_subdev_v4l2_ops = { .core = &isp_subdev_v4l2_core_ops, .pad = &isp_subdev_v4l2_pad_ops, }; static void isp_subdev_init_params(struct atomisp_sub_device *asd) { unsigned int i; /* parameters initialization */ INIT_LIST_HEAD(&asd->s3a_stats); INIT_LIST_HEAD(&asd->s3a_stats_in_css); INIT_LIST_HEAD(&asd->s3a_stats_ready); INIT_LIST_HEAD(&asd->dis_stats); INIT_LIST_HEAD(&asd->dis_stats_in_css); spin_lock_init(&asd->dis_stats_lock); for (i = 0; i < ATOMISP_METADATA_TYPE_NUM; i++) { INIT_LIST_HEAD(&asd->metadata[i]); INIT_LIST_HEAD(&asd->metadata_in_css[i]); INIT_LIST_HEAD(&asd->metadata_ready[i]); } } /* * isp_subdev_link_setup - Setup isp subdev connections * @entity: ispsubdev media entity * @local: Pad at the local end of the link * @remote: Pad at the remote end of the link * @flags: Link flags * * return -EINVAL or zero on success */ static int isp_subdev_link_setup(struct media_entity *entity, const struct media_pad *local, const struct media_pad *remote, u32 flags) { struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); struct atomisp_sub_device *isp_sd = v4l2_get_subdevdata(sd); struct atomisp_device *isp = isp_sd->isp; unsigned int i; switch (local->index | is_media_entity_v4l2_subdev(remote->entity)) { case ATOMISP_SUBDEV_PAD_SINK | MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN: /* Read from the sensor CSI2-ports. */ if (!(flags & MEDIA_LNK_FL_ENABLED)) { isp_sd->input = ATOMISP_SUBDEV_INPUT_NONE; break; } if (isp_sd->input != ATOMISP_SUBDEV_INPUT_NONE) return -EBUSY; for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { if (remote->entity != &isp->csi2_port[i].subdev.entity) continue; isp_sd->input = ATOMISP_SUBDEV_INPUT_CSI2_PORT1 + i; return 0; } return -EINVAL; case ATOMISP_SUBDEV_PAD_SINK | MEDIA_ENT_F_OLD_BASE: /* read from memory */ if (flags & MEDIA_LNK_FL_ENABLED) { if (isp_sd->input >= ATOMISP_SUBDEV_INPUT_CSI2_PORT1 && isp_sd->input < (ATOMISP_SUBDEV_INPUT_CSI2_PORT1 + ATOMISP_CAMERA_NR_PORTS)) return -EBUSY; isp_sd->input = ATOMISP_SUBDEV_INPUT_MEMORY; } else { if (isp_sd->input == ATOMISP_SUBDEV_INPUT_MEMORY) isp_sd->input = ATOMISP_SUBDEV_INPUT_NONE; } break; case ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW | MEDIA_ENT_F_OLD_BASE: /* always write to memory */ break; case ATOMISP_SUBDEV_PAD_SOURCE_VF | MEDIA_ENT_F_OLD_BASE: /* always write to memory */ break; case ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE | MEDIA_ENT_F_OLD_BASE: /* always write to memory */ break; case ATOMISP_SUBDEV_PAD_SOURCE_VIDEO | MEDIA_ENT_F_OLD_BASE: /* always write to memory */ break; default: return -EINVAL; } return 0; } /* media operations */ static const struct media_entity_operations isp_subdev_media_ops = { .link_setup = isp_subdev_link_setup, .link_validate = v4l2_subdev_link_validate, /* .set_power = v4l2_subdev_set_power, */ }; static int __atomisp_update_run_mode(struct atomisp_sub_device *asd) { struct atomisp_device *isp = asd->isp; struct v4l2_ctrl *ctrl = asd->run_mode; struct v4l2_ctrl *c; struct v4l2_streamparm p = {0}; int modes[] = { CI_MODE_NONE, CI_MODE_VIDEO, CI_MODE_STILL_CAPTURE, CI_MODE_CONTINUOUS, CI_MODE_PREVIEW }; s32 mode; if (ctrl->val != ATOMISP_RUN_MODE_VIDEO && asd->continuous_mode->val) mode = ATOMISP_RUN_MODE_PREVIEW; else mode = ctrl->val; c = v4l2_ctrl_find( isp->inputs[asd->input_curr].camera->ctrl_handler, V4L2_CID_RUN_MODE); if (c) return v4l2_ctrl_s_ctrl(c, mode); /* Fall back to obsolete s_parm */ p.parm.capture.capturemode = modes[mode]; return v4l2_subdev_call( isp->inputs[asd->input_curr].camera, video, s_parm, &p); } int atomisp_update_run_mode(struct atomisp_sub_device *asd) { int rval; mutex_lock(asd->ctrl_handler.lock); rval = __atomisp_update_run_mode(asd); mutex_unlock(asd->ctrl_handler.lock); return rval; } static int s_ctrl(struct v4l2_ctrl *ctrl) { struct atomisp_sub_device *asd = container_of( ctrl->handler, struct atomisp_sub_device, ctrl_handler); switch (ctrl->id) { case V4L2_CID_RUN_MODE: return __atomisp_update_run_mode(asd); case V4L2_CID_DEPTH_MODE: if (asd->streaming != ATOMISP_DEVICE_STREAMING_DISABLED) { dev_err(asd->isp->dev, "ISP is streaming, it is not supported to change the depth mode\n"); return -EINVAL; } break; } return 0; } static const struct v4l2_ctrl_ops ctrl_ops = { .s_ctrl = &s_ctrl, }; static const struct v4l2_ctrl_config ctrl_fmt_auto = { .ops = &ctrl_ops, .id = V4L2_CID_FMT_AUTO, .name = "Automatic format guessing", .type = V4L2_CTRL_TYPE_BOOLEAN, .min = 0, .max = 1, .step = 1, .def = 1, }; static const char * const ctrl_run_mode_menu[] = { NULL, "Video", "Still capture", "Continuous capture", "Preview", }; static const struct v4l2_ctrl_config ctrl_run_mode = { .ops = &ctrl_ops, .id = V4L2_CID_RUN_MODE, .name = "Atomisp run mode", .type = V4L2_CTRL_TYPE_MENU, .min = 1, .def = 1, .max = 4, .qmenu = ctrl_run_mode_menu, }; static const char * const ctrl_vfpp_mode_menu[] = { "Enable", /* vfpp always enabled */ "Disable to scaler mode", /* CSS into video mode and disable */ "Disable to low latency mode", /* CSS into still mode and disable */ }; static const struct v4l2_ctrl_config ctrl_vfpp = { .id = V4L2_CID_VFPP, .name = "Atomisp vf postprocess", .type = V4L2_CTRL_TYPE_MENU, .min = 0, .def = 0, .max = 2, .qmenu = ctrl_vfpp_mode_menu, }; /* * Control for ISP continuous mode * * When enabled, capture processing is possible without * stopping the preview pipeline. When disabled, ISP needs * to be restarted between preview and capture. */ static const struct v4l2_ctrl_config ctrl_continuous_mode = { .ops = &ctrl_ops, .id = V4L2_CID_ATOMISP_CONTINUOUS_MODE, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Continuous mode", .min = 0, .max = 1, .step = 1, .def = 0, }; /* * Control for continuous mode raw buffer size * * The size of the RAW ringbuffer sets limit on how much * back in time application can go when requesting capture * frames to be rendered, and how many frames can be rendered * in a burst at full sensor rate. * * Note: this setting has a big impact on memory consumption of * the CSS subsystem. */ static const struct v4l2_ctrl_config ctrl_continuous_raw_buffer_size = { .ops = &ctrl_ops, .id = V4L2_CID_ATOMISP_CONTINUOUS_RAW_BUFFER_SIZE, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Continuous raw ringbuffer size", .min = 1, .max = 100, /* depends on CSS version, runtime checked */ .step = 1, .def = 3, }; /* * Control for enabling continuous viewfinder * * When enabled, and ISP is in continuous mode (see ctrl_continuous_mode ), * preview pipeline continues concurrently with capture * processing. When disabled, and continuous mode is used, * preview is paused while captures are processed, but * full pipeline restart is not needed. * * By setting this to disabled, capture processing is * essentially given priority over preview, and the effective * capture output rate may be higher than with continuous * viewfinder enabled. */ static const struct v4l2_ctrl_config ctrl_continuous_viewfinder = { .id = V4L2_CID_ATOMISP_CONTINUOUS_VIEWFINDER, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Continuous viewfinder", .min = 0, .max = 1, .step = 1, .def = 0, }; /* * Control for enabling Lock&Unlock Raw Buffer mechanism * * When enabled, Raw Buffer can be locked and unlocked. * Application can hold the exp_id of Raw Buffer * and unlock it when no longer needed. * Note: Make sure set this configuration before creating stream. */ static const struct v4l2_ctrl_config ctrl_enable_raw_buffer_lock = { .id = V4L2_CID_ENABLE_RAW_BUFFER_LOCK, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Lock Unlock Raw Buffer", .min = 0, .max = 1, .step = 1, .def = 0, }; /* * Control to disable digital zoom of the whole stream * * When it is true, pipe configuation enable_dz will be set to false. * This can help get a better performance by disabling pp binary. * * Note: Make sure set this configuration before creating stream. */ static const struct v4l2_ctrl_config ctrl_disable_dz = { .id = V4L2_CID_DISABLE_DZ, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Disable digital zoom", .min = 0, .max = 1, .step = 1, .def = 0, }; /* * Control for ISP depth mode * * When enabled, that means ISP will deal with dual streams and sensors will be * in slave/master mode. * slave sensor will have no output until master sensor is streamed on. */ static const struct v4l2_ctrl_config ctrl_depth_mode = { .ops = &ctrl_ops, .id = V4L2_CID_DEPTH_MODE, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Depth mode", .min = 0, .max = 1, .step = 1, .def = 0, }; #ifdef ISP2401 /* * Control for selectting ISP version * * When enabled, that means ISP version will be used ISP2.7. when disable, the * isp will default to use ISP2.2. * Note: Make sure set this configuration before creating stream. */ static const struct v4l2_ctrl_config ctrl_select_isp_version = { .ops = &ctrl_ops, .id = V4L2_CID_ATOMISP_SELECT_ISP_VERSION, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Select Isp version", .min = 0, .max = 1, .step = 1, .def = 0, }; #ifdef CONFIG_ION /* * Control for ISP ion device fd * * userspace will open ion device and pass the fd to kernel. * this fd will be used to map shared fd to buffer. */ static const struct v4l2_ctrl_config ctrl_ion_dev_fd = { .ops = &ctrl_ops, .id = V4L2_CID_ATOMISP_ION_DEVICE_FD, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Ion Device Fd", .min = -1, .max = 1024, .step = 1, .def = ION_FD_UNSET }; #endif #endif static void atomisp_init_subdev_pipe(struct atomisp_sub_device *asd, struct atomisp_video_pipe *pipe, enum v4l2_buf_type buf_type) { pipe->type = buf_type; pipe->asd = asd; pipe->isp = asd->isp; spin_lock_init(&pipe->irq_lock); INIT_LIST_HEAD(&pipe->activeq); INIT_LIST_HEAD(&pipe->activeq_out); INIT_LIST_HEAD(&pipe->buffers_waiting_for_param); INIT_LIST_HEAD(&pipe->per_frame_params); memset(pipe->frame_request_config_id, 0, VIDEO_MAX_FRAME * sizeof(unsigned int)); memset(pipe->frame_params, 0, VIDEO_MAX_FRAME * sizeof(struct atomisp_css_params_with_list *)); } static void atomisp_init_acc_pipe(struct atomisp_sub_device *asd, struct atomisp_acc_pipe *pipe) { pipe->asd = asd; pipe->isp = asd->isp; INIT_LIST_HEAD(&asd->acc.fw); INIT_LIST_HEAD(&asd->acc.memory_maps); ida_init(&asd->acc.ida); } /* * isp_subdev_init_entities - Initialize V4L2 subdev and media entity * @asd: ISP CCDC module * * Return 0 on success and a negative error code on failure. */ static int isp_subdev_init_entities(struct atomisp_sub_device *asd) { struct v4l2_subdev *sd = &asd->subdev; struct media_pad *pads = asd->pads; struct media_entity *me = &sd->entity; int ret; asd->input = ATOMISP_SUBDEV_INPUT_NONE; v4l2_subdev_init(sd, &isp_subdev_v4l2_ops); sprintf(sd->name, "ATOMISP_SUBDEV_%d", asd->index); v4l2_set_subdevdata(sd, asd); sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; pads[ATOMISP_SUBDEV_PAD_SINK].flags = MEDIA_PAD_FL_SINK; pads[ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW].flags = MEDIA_PAD_FL_SOURCE; pads[ATOMISP_SUBDEV_PAD_SOURCE_VF].flags = MEDIA_PAD_FL_SOURCE; pads[ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE].flags = MEDIA_PAD_FL_SOURCE; pads[ATOMISP_SUBDEV_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE; asd->fmt[ATOMISP_SUBDEV_PAD_SINK].fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; asd->fmt[ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW].fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; asd->fmt[ATOMISP_SUBDEV_PAD_SOURCE_VF].fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; asd->fmt[ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE].fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; asd->fmt[ATOMISP_SUBDEV_PAD_SOURCE_VIDEO].fmt.code = MEDIA_BUS_FMT_SBGGR10_1X10; me->ops = &isp_subdev_media_ops; me->function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; ret = media_entity_pads_init(me, ATOMISP_SUBDEV_PADS_NUM, pads); if (ret < 0) return ret; atomisp_init_subdev_pipe(asd, &asd->video_in, V4L2_BUF_TYPE_VIDEO_OUTPUT); atomisp_init_subdev_pipe(asd, &asd->video_out_preview, V4L2_BUF_TYPE_VIDEO_CAPTURE); atomisp_init_subdev_pipe(asd, &asd->video_out_vf, V4L2_BUF_TYPE_VIDEO_CAPTURE); atomisp_init_subdev_pipe(asd, &asd->video_out_capture, V4L2_BUF_TYPE_VIDEO_CAPTURE); atomisp_init_subdev_pipe(asd, &asd->video_out_video_capture, V4L2_BUF_TYPE_VIDEO_CAPTURE); atomisp_init_acc_pipe(asd, &asd->video_acc); ret = atomisp_video_init(&asd->video_in, "MEMORY"); if (ret < 0) return ret; ret = atomisp_video_init(&asd->video_out_capture, "CAPTURE"); if (ret < 0) return ret; ret = atomisp_video_init(&asd->video_out_vf, "VIEWFINDER"); if (ret < 0) return ret; ret = atomisp_video_init(&asd->video_out_preview, "PREVIEW"); if (ret < 0) return ret; ret = atomisp_video_init(&asd->video_out_video_capture, "VIDEO"); if (ret < 0) return ret; atomisp_acc_init(&asd->video_acc, "ACC"); ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, 1); if (ret) return ret; asd->fmt_auto = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_fmt_auto, NULL); asd->run_mode = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_run_mode, NULL); asd->vfpp = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_vfpp, NULL); asd->continuous_mode = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_continuous_mode, NULL); asd->continuous_viewfinder = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_continuous_viewfinder, NULL); asd->continuous_raw_buffer_size = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_continuous_raw_buffer_size, NULL); asd->enable_raw_buffer_lock = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_enable_raw_buffer_lock, NULL); asd->depth_mode = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_depth_mode, NULL); asd->disable_dz = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_disable_dz, NULL); #ifdef ISP2401 asd->select_isp_version = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_select_isp_version, NULL); #ifdef CONFIG_ION asd->ion_dev_fd = v4l2_ctrl_new_custom(&asd->ctrl_handler, &ctrl_ion_dev_fd, NULL); #endif #endif /* Make controls visible on subdev as well. */ asd->subdev.ctrl_handler = &asd->ctrl_handler; spin_lock_init(&asd->raw_buffer_bitmap_lock); return asd->ctrl_handler.error; } int atomisp_create_pads_links(struct atomisp_device *isp) { struct atomisp_sub_device *asd; int i, j, ret = 0; isp->num_of_streams = 2; for (i = 0; i < ATOMISP_CAMERA_NR_PORTS; i++) { for (j = 0; j < isp->num_of_streams; j++) { ret = media_create_pad_link(&isp->csi2_port[i].subdev. entity, CSI2_PAD_SOURCE, &isp->asd[j].subdev.entity, ATOMISP_SUBDEV_PAD_SINK, 0); if (ret < 0) return ret; } } for (i = 0; i < isp->input_cnt - 2; i++) { ret = media_create_pad_link(&isp->inputs[i].camera->entity, 0, &isp->csi2_port[isp->inputs[i]. port].subdev.entity, CSI2_PAD_SINK, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); if (ret < 0) return ret; } for (i = 0; i < isp->num_of_streams; i++) { asd = &isp->asd[i]; ret = media_create_pad_link(&asd->subdev.entity, ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW, &asd->video_out_preview.vdev.entity, 0, 0); if (ret < 0) return ret; ret = media_create_pad_link(&asd->subdev.entity, ATOMISP_SUBDEV_PAD_SOURCE_VF, &asd->video_out_vf.vdev.entity, 0, 0); if (ret < 0) return ret; ret = media_create_pad_link(&asd->subdev.entity, ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE, &asd->video_out_capture.vdev.entity, 0, 0); if (ret < 0) return ret; ret = media_create_pad_link(&asd->subdev.entity, ATOMISP_SUBDEV_PAD_SOURCE_VIDEO, &asd->video_out_video_capture.vdev. entity, 0, 0); if (ret < 0) return ret; /* * file input only supported on subdev0 * so do not create pad link for subdevs other then subdev0 */ if (asd->index) return 0; ret = media_create_pad_link(&asd->video_in.vdev.entity, 0, &asd->subdev.entity, ATOMISP_SUBDEV_PAD_SINK, 0); if (ret < 0) return ret; } return 0; } static void atomisp_subdev_cleanup_entities(struct atomisp_sub_device *asd) { v4l2_ctrl_handler_free(&asd->ctrl_handler); media_entity_cleanup(&asd->subdev.entity); } void atomisp_subdev_cleanup_pending_events(struct atomisp_sub_device *asd) { struct v4l2_fh *fh, *fh_tmp; struct v4l2_event event; unsigned int i, pending_event; list_for_each_entry_safe(fh, fh_tmp, &asd->subdev.devnode->fh_list, list) { pending_event = v4l2_event_pending(fh); for (i = 0; i < pending_event; i++) v4l2_event_dequeue(fh, &event, 1); } } void atomisp_subdev_unregister_entities(struct atomisp_sub_device *asd) { atomisp_subdev_cleanup_entities(asd); v4l2_device_unregister_subdev(&asd->subdev); atomisp_video_unregister(&asd->video_in); atomisp_video_unregister(&asd->video_out_preview); atomisp_video_unregister(&asd->video_out_vf); atomisp_video_unregister(&asd->video_out_capture); atomisp_video_unregister(&asd->video_out_video_capture); atomisp_acc_unregister(&asd->video_acc); } int atomisp_subdev_register_entities(struct atomisp_sub_device *asd, struct v4l2_device *vdev) { int ret; /* Register the subdev and video node. */ ret = v4l2_device_register_subdev(vdev, &asd->subdev); if (ret < 0) goto error; ret = atomisp_video_register(&asd->video_out_capture, vdev); if (ret < 0) goto error; ret = atomisp_video_register(&asd->video_out_vf, vdev); if (ret < 0) goto error; ret = atomisp_video_register(&asd->video_out_preview, vdev); if (ret < 0) goto error; ret = atomisp_video_register(&asd->video_out_video_capture, vdev); if (ret < 0) goto error; ret = atomisp_acc_register(&asd->video_acc, vdev); if (ret < 0) goto error; /* * file input only supported on subdev0 * so do not create video node for subdevs other then subdev0 */ if (asd->index) return 0; ret = atomisp_video_register(&asd->video_in, vdev); if (ret < 0) goto error; return 0; error: atomisp_subdev_unregister_entities(asd); return ret; } /* * atomisp_subdev_init - ISP Subdevice initialization. * @dev: Device pointer specific to the ATOM ISP. * * TODO: Get the initialisation values from platform data. * * Return 0 on success or a negative error code otherwise. */ int atomisp_subdev_init(struct atomisp_device *isp) { struct atomisp_sub_device *asd; int i, ret = 0; /* * CSS2.0 running ISP2400 support * multiple streams */ isp->num_of_streams = 2; isp->asd = devm_kzalloc(isp->dev, sizeof(struct atomisp_sub_device) * isp->num_of_streams, GFP_KERNEL); if (!isp->asd) return -ENOMEM; for (i = 0; i < isp->num_of_streams; i++) { asd = &isp->asd[i]; spin_lock_init(&asd->lock); asd->isp = isp; isp_subdev_init_params(asd); asd->index = i; ret = isp_subdev_init_entities(asd); if (ret < 0) { atomisp_subdev_cleanup_entities(asd); break; } } return ret; }