// SPDX-License-Identifier: GPL-2.0 /* * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform * * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which * used to process image from camera sensor to memory or DC * * Copyright (c) 2019 NXP Semiconductor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imx8-isi-core.h" #include "imx8-isi-regs.h" /* Keep the first entry matching MXC_ISI_DEF_PIXEL_FORMAT */ static const struct mxc_isi_format_info mxc_isi_formats[] = { /* YUV formats */ { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_YUYV, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT | MXC_ISI_VIDEO_M2M_CAP, .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_1P8P, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_YUVA32, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_1P8, .mem_planes = 1, .color_planes = 1, .depth = { 32 }, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_NV12, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P, .color_planes = 2, .mem_planes = 1, .depth = { 8, 16 }, .hsub = 2, .vsub = 2, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_NV12M, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P, .mem_planes = 2, .color_planes = 2, .depth = { 8, 16 }, .hsub = 2, .vsub = 2, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_NV16, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P, .color_planes = 2, .mem_planes = 1, .depth = { 8, 16 }, .hsub = 2, .vsub = 1, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_NV16M, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P, .mem_planes = 2, .color_planes = 2, .depth = { 8, 16 }, .hsub = 2, .vsub = 1, .encoding = MXC_ISI_ENC_YUV, }, { .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, .fourcc = V4L2_PIX_FMT_YUV444M, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_3P8P, .mem_planes = 3, .color_planes = 3, .depth = { 8, 8, 8 }, .hsub = 1, .vsub = 1, .encoding = MXC_ISI_ENC_YUV, }, /* RGB formats */ { .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, .fourcc = V4L2_PIX_FMT_RGB565, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT | MXC_ISI_VIDEO_M2M_CAP, .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB565, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RGB, }, { .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, .fourcc = V4L2_PIX_FMT_RGB24, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT | MXC_ISI_VIDEO_M2M_CAP, .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P, .isi_out_format = CHNL_IMG_CTRL_FORMAT_BGR888P, .mem_planes = 1, .color_planes = 1, .depth = { 24 }, .encoding = MXC_ISI_ENC_RGB, }, { .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, .fourcc = V4L2_PIX_FMT_BGR24, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT | MXC_ISI_VIDEO_M2M_CAP, .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB888P, .mem_planes = 1, .color_planes = 1, .depth = { 24 }, .encoding = MXC_ISI_ENC_RGB, }, { .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, .fourcc = V4L2_PIX_FMT_XBGR32, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT | MXC_ISI_VIDEO_M2M_CAP, .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8, .isi_out_format = CHNL_IMG_CTRL_FORMAT_XRGB888, .mem_planes = 1, .color_planes = 1, .depth = { 32 }, .encoding = MXC_ISI_ENC_RGB, }, { .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, .fourcc = V4L2_PIX_FMT_ABGR32, .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_ARGB8888, .mem_planes = 1, .color_planes = 1, .depth = { 32 }, .encoding = MXC_ISI_ENC_RGB, }, /* * RAW formats * * The ISI shifts the 10-bit and 12-bit formats left by 6 and 4 bits * when using CHNL_IMG_CTRL_FORMAT_RAW10 or MXC_ISI_OUT_FMT_RAW12 * respectively, to align the bits to the left and pad with zeros in * the LSBs. The corresponding V4L2 formats are however right-aligned, * we have to use CHNL_IMG_CTRL_FORMAT_RAW16 to avoid the left shift. */ { .mbus_code = MEDIA_BUS_FMT_Y8_1X8, .fourcc = V4L2_PIX_FMT_GREY, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_Y10_1X10, .fourcc = V4L2_PIX_FMT_Y10, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_Y12_1X12, .fourcc = V4L2_PIX_FMT_Y12, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_Y14_1X14, .fourcc = V4L2_PIX_FMT_Y14, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, .fourcc = V4L2_PIX_FMT_SBGGR8, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, .fourcc = V4L2_PIX_FMT_SGBRG8, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, .fourcc = V4L2_PIX_FMT_SGRBG8, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, .fourcc = V4L2_PIX_FMT_SRGGB8, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, .fourcc = V4L2_PIX_FMT_SBGGR10, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, .fourcc = V4L2_PIX_FMT_SGBRG10, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, .fourcc = V4L2_PIX_FMT_SGRBG10, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, .fourcc = V4L2_PIX_FMT_SRGGB10, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, .fourcc = V4L2_PIX_FMT_SBGGR12, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, .fourcc = V4L2_PIX_FMT_SGBRG12, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, .fourcc = V4L2_PIX_FMT_SGRBG12, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, .fourcc = V4L2_PIX_FMT_SRGGB12, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14, .fourcc = V4L2_PIX_FMT_SBGGR14, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14, .fourcc = V4L2_PIX_FMT_SGBRG14, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14, .fourcc = V4L2_PIX_FMT_SGRBG14, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, { .mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14, .fourcc = V4L2_PIX_FMT_SRGGB14, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, .mem_planes = 1, .color_planes = 1, .depth = { 16 }, .encoding = MXC_ISI_ENC_RAW, }, /* JPEG */ { .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, .fourcc = V4L2_PIX_FMT_MJPEG, .type = MXC_ISI_VIDEO_CAP, .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, .mem_planes = 1, .color_planes = 1, .depth = { 8 }, .encoding = MXC_ISI_ENC_RAW, } }; const struct mxc_isi_format_info * mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type) { unsigned int i; for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i]; if (fmt->fourcc == fourcc && fmt->type & type) return fmt; } return NULL; } const struct mxc_isi_format_info * mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type) { unsigned int i; for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i]; if (!(fmt->type & type)) continue; if (!index) return fmt; index--; } return NULL; } const struct mxc_isi_format_info * mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix, enum mxc_isi_video_type type) { const struct mxc_isi_format_info *fmt; unsigned int max_width; unsigned int i; max_width = pipe->id == pipe->isi->pdata->num_channels - 1 ? MXC_ISI_MAX_WIDTH_UNCHAINED : MXC_ISI_MAX_WIDTH_CHAINED; fmt = mxc_isi_format_by_fourcc(pix->pixelformat, type); if (!fmt) fmt = &mxc_isi_formats[0]; pix->width = clamp(pix->width, MXC_ISI_MIN_WIDTH, max_width); pix->height = clamp(pix->height, MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT); pix->pixelformat = fmt->fourcc; pix->field = V4L2_FIELD_NONE; if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) { pix->colorspace = MXC_ISI_DEF_COLOR_SPACE; pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC; pix->quantization = MXC_ISI_DEF_QUANTIZATION; pix->xfer_func = MXC_ISI_DEF_XFER_FUNC; } if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) { bool is_rgb = fmt->encoding == MXC_ISI_ENC_RGB; pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, pix->colorspace, pix->ycbcr_enc); } if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT) pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); pix->num_planes = fmt->mem_planes; for (i = 0; i < fmt->color_planes; ++i) { struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i]; unsigned int bpl; /* The pitch must be identical for all planes. */ if (i == 0) bpl = clamp(plane->bytesperline, pix->width * fmt->depth[0] / 8, 65535U); else bpl = pix->plane_fmt[0].bytesperline; plane->bytesperline = bpl; plane->sizeimage = plane->bytesperline * pix->height; if (i >= 1) plane->sizeimage /= fmt->vsub; } /* * For single-planar pixel formats with multiple color planes, * concatenate the size of all planes and clear all planes but the * first one. */ if (fmt->color_planes != fmt->mem_planes) { for (i = 1; i < fmt->color_planes; ++i) { struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i]; pix->plane_fmt[0].sizeimage += plane->sizeimage; plane->bytesperline = 0; plane->sizeimage = 0; } } return fmt; } /* ----------------------------------------------------------------------------- * videobuf2 queue operations */ static void mxc_isi_video_frame_write_done(struct mxc_isi_pipe *pipe, u32 status) { struct mxc_isi_video *video = &pipe->video; struct device *dev = pipe->isi->dev; struct mxc_isi_buffer *next_buf; struct mxc_isi_buffer *buf; enum mxc_isi_buf_id buf_id; spin_lock(&video->buf_lock); /* * The ISI hardware handles buffers using a ping-pong mechanism with * two sets of destination addresses (with shadow registers to allow * programming addresses for all planes atomically) named BUF1 and * BUF2. Addresses can be loaded and copied to shadow registers at any * at any time. * * The hardware keeps track of which buffer is being written to and * automatically switches to the other buffer at frame end, copying the * corresponding address to another set of shadow registers that track * the address being written to. The active buffer tracking bits are * accessible through the CHNL_STS register. * * BUF1 BUF2 | Event | Action * | | * | | Program initial buffers * | | B0 in BUF1, B1 in BUF2 * | Start ISI | * +----+ | | * | B0 | | | * +----+ | | * +----+ | FRM IRQ 0 | B0 complete, BUF2 now active * | B1 | | | Program B2 in BUF1 * +----+ | | * +----+ | FRM IRQ 1 | B1 complete, BUF1 now active * | B2 | | | Program B3 in BUF2 * +----+ | | * +----+ | FRM IRQ 2 | B2 complete, BUF2 now active * | B3 | | | Program B4 in BUF1 * +----+ | | * +----+ | FRM IRQ 3 | B3 complete, BUF1 now active * | B4 | | | Program B5 in BUF2 * +----+ | | * ... | | * * Races between address programming and buffer switching can be * detected by checking if a frame end interrupt occurred after * programming the addresses. * * As none of the shadow registers are accessible, races can occur * between address programming and buffer switching. It is possible to * detect the race condition by checking if a frame end interrupt * occurred after programming the addresses, but impossible to * determine if the race has been won or lost. * * In addition to this, we need to use discard buffers if no pending * buffers are available. To simplify handling of discard buffer, we * need to allocate three of them, as two can be active concurrently * and we need to still be able to get hold of a next buffer. The logic * could be improved to use two buffers only, but as all discard * buffers share the same memory, an additional buffer is cheap. */ /* Check which buffer has just completed. */ buf_id = pipe->isi->pdata->buf_active_reverse ? (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF2 : MXC_ISI_BUF1) : (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF1 : MXC_ISI_BUF2); buf = list_first_entry_or_null(&video->out_active, struct mxc_isi_buffer, list); /* Safety check, this should really never happen. */ if (!buf) { dev_warn(dev, "trying to access empty active list\n"); goto done; } /* * If the buffer that has completed doesn't match the buffer on the * front of the active list, it means we have lost one frame end * interrupt (or possibly a large odd number of interrupts, although * quite unlikely). * * For instance, if IRQ1 is lost and we handle IRQ2, both B1 and B2 * have been completed, but B3 hasn't been programmed, BUF2 still * addresses B1 and the ISI is now writing in B1 instead of B3. We * can't complete B2 as that would result in out-of-order completion. * * The only option is to ignore this interrupt and try again. When IRQ3 * will be handled, we will complete B1 and be in sync again. */ if (buf->id != buf_id) { dev_dbg(dev, "buffer ID mismatch (expected %u, got %u), skipping\n", buf->id, buf_id); /* * Increment the frame count by two to account for the missed * and the ignored interrupts. */ video->frame_count += 2; goto done; } /* Pick the next buffer and queue it to the hardware. */ next_buf = list_first_entry_or_null(&video->out_pending, struct mxc_isi_buffer, list); if (!next_buf) { next_buf = list_first_entry_or_null(&video->out_discard, struct mxc_isi_buffer, list); /* Safety check, this should never happen. */ if (!next_buf) { dev_warn(dev, "trying to access empty discard list\n"); goto done; } } mxc_isi_channel_set_outbuf(pipe, next_buf->dma_addrs, buf_id); next_buf->id = buf_id; /* * Check if we have raced with the end of frame interrupt. If so, we * can't tell if the ISI has recorded the new address, or is still * using the previous buffer. We must assume the latter as that is the * worst case. * * For instance, if we are handling IRQ1 and now detect the FRM * interrupt, assume B2 has completed and the ISI has switched to BUF2 * using B1 just before we programmed B3. Unlike in the previous race * condition, B3 has been programmed and will be written to the next * time the ISI switches to BUF2. We can however handle this exactly as * the first race condition, as we'll program B3 (still at the head of * the pending list) when handling IRQ3. */ status = mxc_isi_channel_irq_status(pipe, false); if (status & CHNL_STS_FRM_STRD) { dev_dbg(dev, "raced with frame end interrupt\n"); video->frame_count += 2; goto done; } /* * The next buffer has been queued successfully, move it to the active * list, and complete the current buffer. */ list_move_tail(&next_buf->list, &video->out_active); if (!buf->discard) { list_del_init(&buf->list); buf->v4l2_buf.sequence = video->frame_count; buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns(); vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); } else { list_move_tail(&buf->list, &video->out_discard); } video->frame_count++; done: spin_unlock(&video->buf_lock); } static void mxc_isi_video_free_discard_buffers(struct mxc_isi_video *video) { unsigned int i; for (i = 0; i < video->pix.num_planes; i++) { struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i]; if (!buf->addr) continue; dma_free_coherent(video->pipe->isi->dev, buf->size, buf->addr, buf->dma); buf->addr = NULL; } } static int mxc_isi_video_alloc_discard_buffers(struct mxc_isi_video *video) { unsigned int i, j; /* Allocate memory for each plane. */ for (i = 0; i < video->pix.num_planes; i++) { struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i]; buf->size = PAGE_ALIGN(video->pix.plane_fmt[i].sizeimage); buf->addr = dma_alloc_coherent(video->pipe->isi->dev, buf->size, &buf->dma, GFP_DMA | GFP_KERNEL); if (!buf->addr) { mxc_isi_video_free_discard_buffers(video); return -ENOMEM; } dev_dbg(video->pipe->isi->dev, "discard buffer plane %u: %zu bytes @%pad (CPU address %p)\n", i, buf->size, &buf->dma, buf->addr); } /* Fill the DMA addresses in the discard buffers. */ for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) { struct mxc_isi_buffer *buf = &video->buf_discard[i]; buf->discard = true; for (j = 0; j < video->pix.num_planes; ++j) buf->dma_addrs[j] = video->discard_buffer[j].dma; } return 0; } static int mxc_isi_video_validate_format(struct mxc_isi_video *video) { const struct v4l2_mbus_framefmt *format; const struct mxc_isi_format_info *info; struct v4l2_subdev_state *state; struct v4l2_subdev *sd = &video->pipe->sd; int ret = 0; state = v4l2_subdev_lock_and_get_active_state(sd); info = mxc_isi_format_by_fourcc(video->pix.pixelformat, MXC_ISI_VIDEO_CAP); format = v4l2_subdev_state_get_format(state, MXC_ISI_PIPE_PAD_SOURCE); if (format->code != info->mbus_code || format->width != video->pix.width || format->height != video->pix.height) { dev_dbg(video->pipe->isi->dev, "%s: configuration mismatch, 0x%04x/%ux%u != 0x%04x/%ux%u\n", __func__, format->code, format->width, format->height, info->mbus_code, video->pix.width, video->pix.height); ret = -EINVAL; } v4l2_subdev_unlock_state(state); return ret; } static void mxc_isi_video_return_buffers(struct mxc_isi_video *video, enum vb2_buffer_state state) { struct mxc_isi_buffer *buf; spin_lock_irq(&video->buf_lock); while (!list_empty(&video->out_active)) { buf = list_first_entry(&video->out_active, struct mxc_isi_buffer, list); list_del_init(&buf->list); if (buf->discard) continue; vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state); } while (!list_empty(&video->out_pending)) { buf = list_first_entry(&video->out_pending, struct mxc_isi_buffer, list); list_del_init(&buf->list); vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state); } while (!list_empty(&video->out_discard)) { buf = list_first_entry(&video->out_discard, struct mxc_isi_buffer, list); list_del_init(&buf->list); } INIT_LIST_HEAD(&video->out_active); INIT_LIST_HEAD(&video->out_pending); INIT_LIST_HEAD(&video->out_discard); spin_unlock_irq(&video->buf_lock); } static void mxc_isi_video_queue_first_buffers(struct mxc_isi_video *video) { unsigned int discard; unsigned int i; lockdep_assert_held(&video->buf_lock); /* * Queue two ISI channel output buffers. We are not guaranteed to have * any buffer in the pending list when this function is called from the * system resume handler. Use pending buffers as much as possible, and * use discard buffers to fill the remaining slots. */ /* How many discard buffers do we need to queue first ? */ discard = list_empty(&video->out_pending) ? 2 : list_is_singular(&video->out_pending) ? 1 : 0; for (i = 0; i < 2; ++i) { enum mxc_isi_buf_id buf_id = i == 0 ? MXC_ISI_BUF1 : MXC_ISI_BUF2; struct mxc_isi_buffer *buf; struct list_head *list; list = i < discard ? &video->out_discard : &video->out_pending; buf = list_first_entry(list, struct mxc_isi_buffer, list); mxc_isi_channel_set_outbuf(video->pipe, buf->dma_addrs, buf_id); buf->id = buf_id; list_move_tail(&buf->list, &video->out_active); } } static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf) { return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf); } int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format, const struct mxc_isi_format_info *info, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[]) { unsigned int i; if (*num_planes) { if (*num_planes != info->mem_planes) return -EINVAL; for (i = 0; i < info->mem_planes; ++i) { if (sizes[i] < format->plane_fmt[i].sizeimage) return -EINVAL; } return 0; } *num_planes = info->mem_planes; for (i = 0; i < info->mem_planes; ++i) sizes[i] = format->plane_fmt[i].sizeimage; return 0; } void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3], const struct mxc_isi_format_info *info, const struct v4l2_pix_format_mplane *pix) { unsigned int i; for (i = 0; i < info->mem_planes; ++i) dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb2, i); /* * For single-planar pixel formats with multiple color planes, split * the buffer into color planes. */ if (info->color_planes != info->mem_planes) { unsigned int size = pix->plane_fmt[0].bytesperline * pix->height; for (i = 1; i < info->color_planes; ++i) { unsigned int vsub = i > 1 ? info->vsub : 1; dma_addrs[i] = dma_addrs[i - 1] + size / vsub; } } } int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2, const struct mxc_isi_format_info *info, const struct v4l2_pix_format_mplane *pix) { unsigned int i; for (i = 0; i < info->mem_planes; i++) { unsigned long size = pix->plane_fmt[i].sizeimage; if (vb2_plane_size(vb2, i) < size) { dev_err(isi->dev, "User buffer too small (%ld < %ld)\n", vb2_plane_size(vb2, i), size); return -EINVAL; } vb2_set_plane_payload(vb2, i, size); } return 0; } static int mxc_isi_vb2_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]) { struct mxc_isi_video *video = vb2_get_drv_priv(q); return mxc_isi_video_queue_setup(&video->pix, video->fmtinfo, num_buffers, num_planes, sizes); } static int mxc_isi_vb2_buffer_init(struct vb2_buffer *vb2) { struct mxc_isi_buffer *buf = to_isi_buffer(to_vb2_v4l2_buffer(vb2)); struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); mxc_isi_video_buffer_init(vb2, buf->dma_addrs, video->fmtinfo, &video->pix); return 0; } static int mxc_isi_vb2_buffer_prepare(struct vb2_buffer *vb2) { struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); return mxc_isi_video_buffer_prepare(video->pipe->isi, vb2, video->fmtinfo, &video->pix); } static void mxc_isi_vb2_buffer_queue(struct vb2_buffer *vb2) { struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2); struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf); struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); spin_lock_irq(&video->buf_lock); list_add_tail(&buf->list, &video->out_pending); spin_unlock_irq(&video->buf_lock); } static void mxc_isi_video_init_channel(struct mxc_isi_video *video) { struct mxc_isi_pipe *pipe = video->pipe; mxc_isi_channel_get(pipe); mutex_lock(video->ctrls.handler.lock); mxc_isi_channel_set_alpha(pipe, video->ctrls.alpha); mxc_isi_channel_set_flip(pipe, video->ctrls.hflip, video->ctrls.vflip); mutex_unlock(video->ctrls.handler.lock); mxc_isi_channel_set_output_format(pipe, video->fmtinfo, &video->pix); } static int mxc_isi_vb2_start_streaming(struct vb2_queue *q, unsigned int count) { struct mxc_isi_video *video = vb2_get_drv_priv(q); unsigned int i; int ret; /* Initialize the ISI channel. */ mxc_isi_video_init_channel(video); spin_lock_irq(&video->buf_lock); /* Add the discard buffers to the out_discard list. */ for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) { struct mxc_isi_buffer *buf = &video->buf_discard[i]; list_add_tail(&buf->list, &video->out_discard); } /* Queue the first buffers. */ mxc_isi_video_queue_first_buffers(video); /* Clear frame count */ video->frame_count = 0; spin_unlock_irq(&video->buf_lock); ret = mxc_isi_pipe_enable(video->pipe); if (ret) goto error; return 0; error: mxc_isi_channel_put(video->pipe); mxc_isi_video_return_buffers(video, VB2_BUF_STATE_QUEUED); return ret; } static void mxc_isi_vb2_stop_streaming(struct vb2_queue *q) { struct mxc_isi_video *video = vb2_get_drv_priv(q); mxc_isi_pipe_disable(video->pipe); mxc_isi_channel_put(video->pipe); mxc_isi_video_return_buffers(video, VB2_BUF_STATE_ERROR); } static const struct vb2_ops mxc_isi_vb2_qops = { .queue_setup = mxc_isi_vb2_queue_setup, .buf_init = mxc_isi_vb2_buffer_init, .buf_prepare = mxc_isi_vb2_buffer_prepare, .buf_queue = mxc_isi_vb2_buffer_queue, .start_streaming = mxc_isi_vb2_start_streaming, .stop_streaming = mxc_isi_vb2_stop_streaming, }; /* ----------------------------------------------------------------------------- * V4L2 controls */ static inline struct mxc_isi_video *ctrl_to_isi_video(struct v4l2_ctrl *ctrl) { return container_of(ctrl->handler, struct mxc_isi_video, ctrls.handler); } static int mxc_isi_video_s_ctrl(struct v4l2_ctrl *ctrl) { struct mxc_isi_video *video = ctrl_to_isi_video(ctrl); switch (ctrl->id) { case V4L2_CID_ALPHA_COMPONENT: video->ctrls.alpha = ctrl->val; break; case V4L2_CID_VFLIP: video->ctrls.vflip = ctrl->val; break; case V4L2_CID_HFLIP: video->ctrls.hflip = ctrl->val; break; } return 0; } static const struct v4l2_ctrl_ops mxc_isi_video_ctrl_ops = { .s_ctrl = mxc_isi_video_s_ctrl, }; static int mxc_isi_video_ctrls_create(struct mxc_isi_video *video) { struct v4l2_ctrl_handler *handler = &video->ctrls.handler; int ret; v4l2_ctrl_handler_init(handler, 3); v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); if (handler->error) { ret = handler->error; v4l2_ctrl_handler_free(handler); return ret; } video->vdev.ctrl_handler = handler; return 0; } static void mxc_isi_video_ctrls_delete(struct mxc_isi_video *video) { v4l2_ctrl_handler_free(&video->ctrls.handler); } /* ----------------------------------------------------------------------------- * V4L2 ioctls */ static int mxc_isi_video_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); strscpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card)); return 0; } static int mxc_isi_video_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) { const struct mxc_isi_format_info *fmt; unsigned int index = f->index; unsigned int i; if (f->mbus_code) { /* * If a media bus code is specified, only enumerate formats * compatible with it. */ for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { fmt = &mxc_isi_formats[i]; if (fmt->mbus_code != f->mbus_code) continue; if (index == 0) break; index--; } if (i == ARRAY_SIZE(mxc_isi_formats)) return -EINVAL; } else { /* Otherwise, enumerate all formatS. */ if (f->index >= ARRAY_SIZE(mxc_isi_formats)) return -EINVAL; fmt = &mxc_isi_formats[f->index]; } f->pixelformat = fmt->fourcc; f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC | V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC; return 0; } static int mxc_isi_video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct mxc_isi_video *video = video_drvdata(file); f->fmt.pix_mp = video->pix; return 0; } static int mxc_isi_video_try_fmt(struct file *file, void *fh, struct v4l2_format *f) { struct mxc_isi_video *video = video_drvdata(file); mxc_isi_format_try(video->pipe, &f->fmt.pix_mp, MXC_ISI_VIDEO_CAP); return 0; } static int mxc_isi_video_s_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct mxc_isi_video *video = video_drvdata(file); struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; if (vb2_is_busy(&video->vb2_q)) return -EBUSY; video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP); video->pix = *pix; return 0; } static int mxc_isi_video_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { struct mxc_isi_video *video = video_drvdata(file); struct media_device *mdev = &video->pipe->isi->media_dev; struct media_pipeline *pipe; int ret; if (vb2_queue_is_busy(&video->vb2_q, file)) return -EBUSY; /* * Get a pipeline for the video node and start it. This must be done * here and not in the queue .start_streaming() handler, so that * pipeline start errors can be reported from VIDIOC_STREAMON and not * delayed until subsequent VIDIOC_QBUF calls. */ mutex_lock(&mdev->graph_mutex); ret = mxc_isi_pipe_acquire(video->pipe, &mxc_isi_video_frame_write_done); if (ret) { mutex_unlock(&mdev->graph_mutex); return ret; } pipe = media_entity_pipeline(&video->vdev.entity) ? : &video->pipe->pipe; ret = __video_device_pipeline_start(&video->vdev, pipe); if (ret) { mutex_unlock(&mdev->graph_mutex); goto err_release; } mutex_unlock(&mdev->graph_mutex); /* Verify that the video format matches the output of the subdev. */ ret = mxc_isi_video_validate_format(video); if (ret) goto err_stop; /* Allocate buffers for discard operation. */ ret = mxc_isi_video_alloc_discard_buffers(video); if (ret) goto err_stop; ret = vb2_streamon(&video->vb2_q, type); if (ret) goto err_free; video->is_streaming = true; return 0; err_free: mxc_isi_video_free_discard_buffers(video); err_stop: video_device_pipeline_stop(&video->vdev); err_release: mxc_isi_pipe_release(video->pipe); return ret; } static void mxc_isi_video_cleanup_streaming(struct mxc_isi_video *video) { lockdep_assert_held(&video->lock); if (!video->is_streaming) return; mxc_isi_video_free_discard_buffers(video); video_device_pipeline_stop(&video->vdev); mxc_isi_pipe_release(video->pipe); video->is_streaming = false; } static int mxc_isi_video_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { struct mxc_isi_video *video = video_drvdata(file); int ret; ret = vb2_ioctl_streamoff(file, priv, type); if (ret) return ret; mxc_isi_video_cleanup_streaming(video); return 0; } static int mxc_isi_video_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct mxc_isi_video *video = video_drvdata(file); const struct mxc_isi_format_info *info; unsigned int max_width; unsigned int h_align; unsigned int v_align; if (fsize->index) return -EINVAL; info = mxc_isi_format_by_fourcc(fsize->pixel_format, MXC_ISI_VIDEO_CAP); if (!info) return -EINVAL; h_align = max_t(unsigned int, info->hsub, 1); v_align = max_t(unsigned int, info->vsub, 1); max_width = video->pipe->id == video->pipe->isi->pdata->num_channels - 1 ? MXC_ISI_MAX_WIDTH_UNCHAINED : MXC_ISI_MAX_WIDTH_CHAINED; fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; fsize->stepwise.min_width = ALIGN(MXC_ISI_MIN_WIDTH, h_align); fsize->stepwise.min_height = ALIGN(MXC_ISI_MIN_HEIGHT, v_align); fsize->stepwise.max_width = ALIGN_DOWN(max_width, h_align); fsize->stepwise.max_height = ALIGN_DOWN(MXC_ISI_MAX_HEIGHT, v_align); fsize->stepwise.step_width = h_align; fsize->stepwise.step_height = v_align; /* * The width can be further restricted due to line buffer sharing * between pipelines when scaling, but we have no way to know here if * the scaler will be used. */ return 0; } static const struct v4l2_ioctl_ops mxc_isi_video_ioctl_ops = { .vidioc_querycap = mxc_isi_video_querycap, .vidioc_enum_fmt_vid_cap = mxc_isi_video_enum_fmt, .vidioc_try_fmt_vid_cap_mplane = mxc_isi_video_try_fmt, .vidioc_s_fmt_vid_cap_mplane = mxc_isi_video_s_fmt, .vidioc_g_fmt_vid_cap_mplane = mxc_isi_video_g_fmt, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_streamon = mxc_isi_video_streamon, .vidioc_streamoff = mxc_isi_video_streamoff, .vidioc_enum_framesizes = mxc_isi_video_enum_framesizes, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ----------------------------------------------------------------------------- * Video device file operations */ static int mxc_isi_video_open(struct file *file) { struct mxc_isi_video *video = video_drvdata(file); int ret; ret = v4l2_fh_open(file); if (ret) return ret; ret = pm_runtime_resume_and_get(video->pipe->isi->dev); if (ret) { v4l2_fh_release(file); return ret; } return 0; } static int mxc_isi_video_release(struct file *file) { struct mxc_isi_video *video = video_drvdata(file); int ret; ret = vb2_fop_release(file); if (ret) dev_err(video->pipe->isi->dev, "%s fail\n", __func__); mutex_lock(&video->lock); mxc_isi_video_cleanup_streaming(video); mutex_unlock(&video->lock); pm_runtime_put(video->pipe->isi->dev); return ret; } static const struct v4l2_file_operations mxc_isi_video_fops = { .owner = THIS_MODULE, .open = mxc_isi_video_open, .release = mxc_isi_video_release, .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = vb2_fop_mmap, }; /* ----------------------------------------------------------------------------- * Suspend & resume */ void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe) { struct mxc_isi_video *video = &pipe->video; if (!video->is_streaming) return; mxc_isi_pipe_disable(pipe); mxc_isi_channel_put(pipe); spin_lock_irq(&video->buf_lock); /* * Move the active buffers back to the pending or discard list. We must * iterate the active list backward and move the buffers to the head of * the pending list to preserve the buffer queueing order. */ while (!list_empty(&video->out_active)) { struct mxc_isi_buffer *buf = list_last_entry(&video->out_active, struct mxc_isi_buffer, list); if (buf->discard) list_move(&buf->list, &video->out_discard); else list_move(&buf->list, &video->out_pending); } spin_unlock_irq(&video->buf_lock); } int mxc_isi_video_resume(struct mxc_isi_pipe *pipe) { struct mxc_isi_video *video = &pipe->video; if (!video->is_streaming) return 0; mxc_isi_video_init_channel(video); spin_lock_irq(&video->buf_lock); mxc_isi_video_queue_first_buffers(video); spin_unlock_irq(&video->buf_lock); return mxc_isi_pipe_enable(pipe); } /* ----------------------------------------------------------------------------- * Registration */ int mxc_isi_video_register(struct mxc_isi_pipe *pipe, struct v4l2_device *v4l2_dev) { struct mxc_isi_video *video = &pipe->video; struct v4l2_pix_format_mplane *pix = &video->pix; struct video_device *vdev = &video->vdev; struct vb2_queue *q = &video->vb2_q; int ret = -ENOMEM; video->pipe = pipe; mutex_init(&video->lock); spin_lock_init(&video->buf_lock); pix->width = MXC_ISI_DEF_WIDTH; pix->height = MXC_ISI_DEF_HEIGHT; pix->pixelformat = MXC_ISI_DEF_PIXEL_FORMAT; pix->colorspace = MXC_ISI_DEF_COLOR_SPACE; pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC; pix->quantization = MXC_ISI_DEF_QUANTIZATION; pix->xfer_func = MXC_ISI_DEF_XFER_FUNC; video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP); memset(vdev, 0, sizeof(*vdev)); snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", pipe->id); vdev->fops = &mxc_isi_video_fops; vdev->ioctl_ops = &mxc_isi_video_ioctl_ops; vdev->v4l2_dev = v4l2_dev; vdev->minor = -1; vdev->release = video_device_release_empty; vdev->queue = q; vdev->lock = &video->lock; vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_IO_MC; video_set_drvdata(vdev, video); INIT_LIST_HEAD(&video->out_pending); INIT_LIST_HEAD(&video->out_active); INIT_LIST_HEAD(&video->out_discard); memset(q, 0, sizeof(*q)); q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; q->io_modes = VB2_MMAP | VB2_DMABUF; q->drv_priv = video; q->ops = &mxc_isi_vb2_qops; q->mem_ops = &vb2_dma_contig_memops; q->buf_struct_size = sizeof(struct mxc_isi_buffer); q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->min_queued_buffers = 2; q->lock = &video->lock; q->dev = pipe->isi->dev; ret = vb2_queue_init(q); if (ret) goto err_free_ctx; video->pad.flags = MEDIA_PAD_FL_SINK; vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; ret = media_entity_pads_init(&vdev->entity, 1, &video->pad); if (ret) goto err_free_ctx; ret = mxc_isi_video_ctrls_create(video); if (ret) goto err_me_cleanup; ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) goto err_ctrl_free; ret = media_create_pad_link(&pipe->sd.entity, MXC_ISI_PIPE_PAD_SOURCE, &vdev->entity, 0, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (ret) goto err_video_unreg; return 0; err_video_unreg: video_unregister_device(vdev); err_ctrl_free: mxc_isi_video_ctrls_delete(video); err_me_cleanup: media_entity_cleanup(&vdev->entity); err_free_ctx: return ret; } void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe) { struct mxc_isi_video *video = &pipe->video; struct video_device *vdev = &video->vdev; mutex_lock(&video->lock); if (video_is_registered(vdev)) { video_unregister_device(vdev); mxc_isi_video_ctrls_delete(video); media_entity_cleanup(&vdev->entity); } mutex_unlock(&video->lock); }