/* * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC * * Copyright (c) 2016 Mentor Graphics Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include "imx-media.h" /* * List of supported pixel formats for the subdevs. * * In all of these tables, the non-mbus formats (with no * mbus codes) must all fall at the end of the table. */ static const struct imx_media_pixfmt yuv_formats[] = { { .fourcc = V4L2_PIX_FMT_UYVY, .codes = { MEDIA_BUS_FMT_UYVY8_2X8, MEDIA_BUS_FMT_UYVY8_1X16 }, .cs = IPUV3_COLORSPACE_YUV, .bpp = 16, }, { .fourcc = V4L2_PIX_FMT_YUYV, .codes = { MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_YUYV8_1X16 }, .cs = IPUV3_COLORSPACE_YUV, .bpp = 16, }, /*** * non-mbus YUV formats start here. NOTE! when adding non-mbus * formats, NUM_NON_MBUS_YUV_FORMATS must be updated below. ***/ { .fourcc = V4L2_PIX_FMT_YUV420, .cs = IPUV3_COLORSPACE_YUV, .bpp = 12, .planar = true, }, { .fourcc = V4L2_PIX_FMT_YVU420, .cs = IPUV3_COLORSPACE_YUV, .bpp = 12, .planar = true, }, { .fourcc = V4L2_PIX_FMT_YUV422P, .cs = IPUV3_COLORSPACE_YUV, .bpp = 16, .planar = true, }, { .fourcc = V4L2_PIX_FMT_NV12, .cs = IPUV3_COLORSPACE_YUV, .bpp = 12, .planar = true, }, { .fourcc = V4L2_PIX_FMT_NV16, .cs = IPUV3_COLORSPACE_YUV, .bpp = 16, .planar = true, }, }; #define NUM_NON_MBUS_YUV_FORMATS 5 #define NUM_YUV_FORMATS ARRAY_SIZE(yuv_formats) #define NUM_MBUS_YUV_FORMATS (NUM_YUV_FORMATS - NUM_NON_MBUS_YUV_FORMATS) static const struct imx_media_pixfmt rgb_formats[] = { { .fourcc = V4L2_PIX_FMT_RGB565, .codes = {MEDIA_BUS_FMT_RGB565_2X8_LE}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, }, { .fourcc = V4L2_PIX_FMT_RGB24, .codes = { MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB888_2X12_LE }, .cs = IPUV3_COLORSPACE_RGB, .bpp = 24, }, { .fourcc = V4L2_PIX_FMT_RGB32, .codes = {MEDIA_BUS_FMT_ARGB8888_1X32}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 32, .ipufmt = true, }, /*** raw bayer formats start here ***/ { .fourcc = V4L2_PIX_FMT_SBGGR8, .codes = {MEDIA_BUS_FMT_SBGGR8_1X8}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 8, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG8, .codes = {MEDIA_BUS_FMT_SGBRG8_1X8}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 8, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG8, .codes = {MEDIA_BUS_FMT_SGRBG8_1X8}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 8, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB8, .codes = {MEDIA_BUS_FMT_SRGGB8_1X8}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 8, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SBGGR16, .codes = { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SBGGR14_1X14, MEDIA_BUS_FMT_SBGGR16_1X16 }, .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SGBRG16, .codes = { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SGBRG12_1X12, MEDIA_BUS_FMT_SGBRG14_1X14, MEDIA_BUS_FMT_SGBRG16_1X16, }, .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SGRBG16, .codes = { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SGRBG14_1X14, MEDIA_BUS_FMT_SGRBG16_1X16, }, .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, .bayer = true, }, { .fourcc = V4L2_PIX_FMT_SRGGB16, .codes = { MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SRGGB12_1X12, MEDIA_BUS_FMT_SRGGB14_1X14, MEDIA_BUS_FMT_SRGGB16_1X16, }, .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, .bayer = true, }, /*** * non-mbus RGB formats start here. NOTE! when adding non-mbus * formats, NUM_NON_MBUS_RGB_FORMATS must be updated below. ***/ { .fourcc = V4L2_PIX_FMT_BGR24, .cs = IPUV3_COLORSPACE_RGB, .bpp = 24, }, { .fourcc = V4L2_PIX_FMT_BGR32, .cs = IPUV3_COLORSPACE_RGB, .bpp = 32, }, }; #define NUM_NON_MBUS_RGB_FORMATS 2 #define NUM_RGB_FORMATS ARRAY_SIZE(rgb_formats) #define NUM_MBUS_RGB_FORMATS (NUM_RGB_FORMATS - NUM_NON_MBUS_RGB_FORMATS) static const struct imx_media_pixfmt ipu_yuv_formats[] = { { .fourcc = V4L2_PIX_FMT_YUV32, .codes = {MEDIA_BUS_FMT_AYUV8_1X32}, .cs = IPUV3_COLORSPACE_YUV, .bpp = 32, .ipufmt = true, }, }; #define NUM_IPU_YUV_FORMATS ARRAY_SIZE(ipu_yuv_formats) static const struct imx_media_pixfmt ipu_rgb_formats[] = { { .fourcc = V4L2_PIX_FMT_RGB32, .codes = {MEDIA_BUS_FMT_ARGB8888_1X32}, .cs = IPUV3_COLORSPACE_RGB, .bpp = 32, .ipufmt = true, }, }; #define NUM_IPU_RGB_FORMATS ARRAY_SIZE(ipu_rgb_formats) static void init_mbus_colorimetry(struct v4l2_mbus_framefmt *mbus, const struct imx_media_pixfmt *fmt) { mbus->colorspace = (fmt->cs == IPUV3_COLORSPACE_RGB) ? V4L2_COLORSPACE_SRGB : V4L2_COLORSPACE_SMPTE170M; mbus->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(mbus->colorspace); mbus->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(mbus->colorspace); mbus->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(fmt->cs == IPUV3_COLORSPACE_RGB, mbus->colorspace, mbus->ycbcr_enc); } static const struct imx_media_pixfmt *find_format(u32 fourcc, u32 code, enum codespace_sel cs_sel, bool allow_non_mbus, bool allow_bayer) { const struct imx_media_pixfmt *array, *fmt, *ret = NULL; u32 array_size; int i, j; switch (cs_sel) { case CS_SEL_YUV: array_size = NUM_YUV_FORMATS; array = yuv_formats; break; case CS_SEL_RGB: array_size = NUM_RGB_FORMATS; array = rgb_formats; break; case CS_SEL_ANY: array_size = NUM_YUV_FORMATS + NUM_RGB_FORMATS; array = yuv_formats; break; default: return NULL; } for (i = 0; i < array_size; i++) { if (cs_sel == CS_SEL_ANY && i >= NUM_YUV_FORMATS) fmt = &rgb_formats[i - NUM_YUV_FORMATS]; else fmt = &array[i]; if ((!allow_non_mbus && fmt->codes[0] == 0) || (!allow_bayer && fmt->bayer)) continue; if (fourcc && fmt->fourcc == fourcc) { ret = fmt; goto out; } for (j = 0; code && fmt->codes[j]; j++) { if (code == fmt->codes[j]) { ret = fmt; goto out; } } } out: return ret; } static int enum_format(u32 *fourcc, u32 *code, u32 index, enum codespace_sel cs_sel, bool allow_non_mbus, bool allow_bayer) { const struct imx_media_pixfmt *fmt; u32 mbus_yuv_sz = NUM_MBUS_YUV_FORMATS; u32 mbus_rgb_sz = NUM_MBUS_RGB_FORMATS; u32 yuv_sz = NUM_YUV_FORMATS; u32 rgb_sz = NUM_RGB_FORMATS; switch (cs_sel) { case CS_SEL_YUV: if (index >= yuv_sz || (!allow_non_mbus && index >= mbus_yuv_sz)) return -EINVAL; fmt = &yuv_formats[index]; break; case CS_SEL_RGB: if (index >= rgb_sz || (!allow_non_mbus && index >= mbus_rgb_sz)) return -EINVAL; fmt = &rgb_formats[index]; if (!allow_bayer && fmt->bayer) return -EINVAL; break; case CS_SEL_ANY: if (!allow_non_mbus) { if (index >= mbus_yuv_sz) { index -= mbus_yuv_sz; if (index >= mbus_rgb_sz) return -EINVAL; fmt = &rgb_formats[index]; if (!allow_bayer && fmt->bayer) return -EINVAL; } else { fmt = &yuv_formats[index]; } } else { if (index >= yuv_sz + rgb_sz) return -EINVAL; if (index >= yuv_sz) { fmt = &rgb_formats[index - yuv_sz]; if (!allow_bayer && fmt->bayer) return -EINVAL; } else { fmt = &yuv_formats[index]; } } break; default: return -EINVAL; } if (fourcc) *fourcc = fmt->fourcc; if (code) *code = fmt->codes[0]; return 0; } const struct imx_media_pixfmt * imx_media_find_format(u32 fourcc, enum codespace_sel cs_sel, bool allow_bayer) { return find_format(fourcc, 0, cs_sel, true, allow_bayer); } EXPORT_SYMBOL_GPL(imx_media_find_format); int imx_media_enum_format(u32 *fourcc, u32 index, enum codespace_sel cs_sel) { return enum_format(fourcc, NULL, index, cs_sel, true, false); } EXPORT_SYMBOL_GPL(imx_media_enum_format); const struct imx_media_pixfmt * imx_media_find_mbus_format(u32 code, enum codespace_sel cs_sel, bool allow_bayer) { return find_format(0, code, cs_sel, false, allow_bayer); } EXPORT_SYMBOL_GPL(imx_media_find_mbus_format); int imx_media_enum_mbus_format(u32 *code, u32 index, enum codespace_sel cs_sel, bool allow_bayer) { return enum_format(NULL, code, index, cs_sel, false, allow_bayer); } EXPORT_SYMBOL_GPL(imx_media_enum_mbus_format); const struct imx_media_pixfmt * imx_media_find_ipu_format(u32 code, enum codespace_sel cs_sel) { const struct imx_media_pixfmt *array, *fmt, *ret = NULL; u32 array_size; int i, j; switch (cs_sel) { case CS_SEL_YUV: array_size = NUM_IPU_YUV_FORMATS; array = ipu_yuv_formats; break; case CS_SEL_RGB: array_size = NUM_IPU_RGB_FORMATS; array = ipu_rgb_formats; break; case CS_SEL_ANY: array_size = NUM_IPU_YUV_FORMATS + NUM_IPU_RGB_FORMATS; array = ipu_yuv_formats; break; default: return NULL; } for (i = 0; i < array_size; i++) { if (cs_sel == CS_SEL_ANY && i >= NUM_IPU_YUV_FORMATS) fmt = &ipu_rgb_formats[i - NUM_IPU_YUV_FORMATS]; else fmt = &array[i]; for (j = 0; code && fmt->codes[j]; j++) { if (code == fmt->codes[j]) { ret = fmt; goto out; } } } out: return ret; } EXPORT_SYMBOL_GPL(imx_media_find_ipu_format); int imx_media_enum_ipu_format(u32 *code, u32 index, enum codespace_sel cs_sel) { switch (cs_sel) { case CS_SEL_YUV: if (index >= NUM_IPU_YUV_FORMATS) return -EINVAL; *code = ipu_yuv_formats[index].codes[0]; break; case CS_SEL_RGB: if (index >= NUM_IPU_RGB_FORMATS) return -EINVAL; *code = ipu_rgb_formats[index].codes[0]; break; case CS_SEL_ANY: if (index >= NUM_IPU_YUV_FORMATS + NUM_IPU_RGB_FORMATS) return -EINVAL; if (index >= NUM_IPU_YUV_FORMATS) { index -= NUM_IPU_YUV_FORMATS; *code = ipu_rgb_formats[index].codes[0]; } else { *code = ipu_yuv_formats[index].codes[0]; } break; default: return -EINVAL; } return 0; } EXPORT_SYMBOL_GPL(imx_media_enum_ipu_format); int imx_media_init_mbus_fmt(struct v4l2_mbus_framefmt *mbus, u32 width, u32 height, u32 code, u32 field, const struct imx_media_pixfmt **cc) { const struct imx_media_pixfmt *lcc; mbus->width = width; mbus->height = height; mbus->field = field; if (code == 0) imx_media_enum_mbus_format(&code, 0, CS_SEL_YUV, false); lcc = imx_media_find_mbus_format(code, CS_SEL_ANY, false); if (!lcc) { lcc = imx_media_find_ipu_format(code, CS_SEL_ANY); if (!lcc) return -EINVAL; } mbus->code = code; init_mbus_colorimetry(mbus, lcc); if (cc) *cc = lcc; return 0; } EXPORT_SYMBOL_GPL(imx_media_init_mbus_fmt); /* * Check whether the field and colorimetry parameters in tryfmt are * uninitialized, and if so fill them with the values from fmt, * or if tryfmt->colorspace has been initialized, all the default * colorimetry params can be derived from tryfmt->colorspace. * * tryfmt->code must be set on entry. * * If this format is destined to be routed through the Image Converter, * quantization and Y`CbCr encoding must be fixed. The IC expects and * produces fixed quantization and Y`CbCr encoding at its input and output * (full range for RGB, limited range for YUV, and V4L2_YCBCR_ENC_601). */ void imx_media_fill_default_mbus_fields(struct v4l2_mbus_framefmt *tryfmt, struct v4l2_mbus_framefmt *fmt, bool ic_route) { const struct imx_media_pixfmt *cc; bool is_rgb = false; cc = imx_media_find_mbus_format(tryfmt->code, CS_SEL_ANY, true); if (!cc) cc = imx_media_find_ipu_format(tryfmt->code, CS_SEL_ANY); if (cc && cc->cs != IPUV3_COLORSPACE_YUV) is_rgb = true; /* fill field if necessary */ if (tryfmt->field == V4L2_FIELD_ANY) tryfmt->field = fmt->field; /* fill colorimetry if necessary */ if (tryfmt->colorspace == V4L2_COLORSPACE_DEFAULT) { tryfmt->colorspace = fmt->colorspace; tryfmt->xfer_func = fmt->xfer_func; tryfmt->ycbcr_enc = fmt->ycbcr_enc; tryfmt->quantization = fmt->quantization; } else { if (tryfmt->xfer_func == V4L2_XFER_FUNC_DEFAULT) { tryfmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(tryfmt->colorspace); } if (tryfmt->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) { tryfmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(tryfmt->colorspace); } if (tryfmt->quantization == V4L2_QUANTIZATION_DEFAULT) { tryfmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT( is_rgb, tryfmt->colorspace, tryfmt->ycbcr_enc); } } if (ic_route) { tryfmt->quantization = is_rgb ? V4L2_QUANTIZATION_FULL_RANGE : V4L2_QUANTIZATION_LIM_RANGE; tryfmt->ycbcr_enc = V4L2_YCBCR_ENC_601; } } EXPORT_SYMBOL_GPL(imx_media_fill_default_mbus_fields); int imx_media_mbus_fmt_to_pix_fmt(struct v4l2_pix_format *pix, struct v4l2_mbus_framefmt *mbus, const struct imx_media_pixfmt *cc) { u32 stride; if (!cc) { cc = imx_media_find_ipu_format(mbus->code, CS_SEL_ANY); if (!cc) cc = imx_media_find_mbus_format(mbus->code, CS_SEL_ANY, true); if (!cc) return -EINVAL; } /* * TODO: the IPU currently does not support the AYUV32 format, * so until it does convert to a supported YUV format. */ if (cc->ipufmt && cc->cs == IPUV3_COLORSPACE_YUV) { u32 code; imx_media_enum_mbus_format(&code, 0, CS_SEL_YUV, false); cc = imx_media_find_mbus_format(code, CS_SEL_YUV, false); } stride = cc->planar ? mbus->width : (mbus->width * cc->bpp) >> 3; pix->width = mbus->width; pix->height = mbus->height; pix->pixelformat = cc->fourcc; pix->colorspace = mbus->colorspace; pix->xfer_func = mbus->xfer_func; pix->ycbcr_enc = mbus->ycbcr_enc; pix->quantization = mbus->quantization; pix->field = mbus->field; pix->bytesperline = stride; pix->sizeimage = (pix->width * pix->height * cc->bpp) >> 3; return 0; } EXPORT_SYMBOL_GPL(imx_media_mbus_fmt_to_pix_fmt); int imx_media_mbus_fmt_to_ipu_image(struct ipu_image *image, struct v4l2_mbus_framefmt *mbus) { int ret; memset(image, 0, sizeof(*image)); ret = imx_media_mbus_fmt_to_pix_fmt(&image->pix, mbus, NULL); if (ret) return ret; image->rect.width = mbus->width; image->rect.height = mbus->height; return 0; } EXPORT_SYMBOL_GPL(imx_media_mbus_fmt_to_ipu_image); int imx_media_ipu_image_to_mbus_fmt(struct v4l2_mbus_framefmt *mbus, struct ipu_image *image) { const struct imx_media_pixfmt *fmt; fmt = imx_media_find_format(image->pix.pixelformat, CS_SEL_ANY, true); if (!fmt) return -EINVAL; memset(mbus, 0, sizeof(*mbus)); mbus->width = image->pix.width; mbus->height = image->pix.height; mbus->code = fmt->codes[0]; mbus->field = image->pix.field; mbus->colorspace = image->pix.colorspace; mbus->xfer_func = image->pix.xfer_func; mbus->ycbcr_enc = image->pix.ycbcr_enc; mbus->quantization = image->pix.quantization; return 0; } EXPORT_SYMBOL_GPL(imx_media_ipu_image_to_mbus_fmt); void imx_media_free_dma_buf(struct imx_media_dev *imxmd, struct imx_media_dma_buf *buf) { if (buf->virt) dma_free_coherent(imxmd->md.dev, buf->len, buf->virt, buf->phys); buf->virt = NULL; buf->phys = 0; } EXPORT_SYMBOL_GPL(imx_media_free_dma_buf); int imx_media_alloc_dma_buf(struct imx_media_dev *imxmd, struct imx_media_dma_buf *buf, int size) { imx_media_free_dma_buf(imxmd, buf); buf->len = PAGE_ALIGN(size); buf->virt = dma_alloc_coherent(imxmd->md.dev, buf->len, &buf->phys, GFP_DMA | GFP_KERNEL); if (!buf->virt) { dev_err(imxmd->md.dev, "failed to alloc dma buffer\n"); return -ENOMEM; } return 0; } EXPORT_SYMBOL_GPL(imx_media_alloc_dma_buf); /* form a subdev name given a group id and ipu id */ void imx_media_grp_id_to_sd_name(char *sd_name, int sz, u32 grp_id, int ipu_id) { int id; switch (grp_id) { case IMX_MEDIA_GRP_ID_CSI0...IMX_MEDIA_GRP_ID_CSI1: id = (grp_id >> IMX_MEDIA_GRP_ID_CSI_BIT) - 1; snprintf(sd_name, sz, "ipu%d_csi%d", ipu_id + 1, id); break; case IMX_MEDIA_GRP_ID_VDIC: snprintf(sd_name, sz, "ipu%d_vdic", ipu_id + 1); break; case IMX_MEDIA_GRP_ID_IC_PRP: snprintf(sd_name, sz, "ipu%d_ic_prp", ipu_id + 1); break; case IMX_MEDIA_GRP_ID_IC_PRPENC: snprintf(sd_name, sz, "ipu%d_ic_prpenc", ipu_id + 1); break; case IMX_MEDIA_GRP_ID_IC_PRPVF: snprintf(sd_name, sz, "ipu%d_ic_prpvf", ipu_id + 1); break; default: break; } } EXPORT_SYMBOL_GPL(imx_media_grp_id_to_sd_name); struct imx_media_subdev * imx_media_find_subdev_by_sd(struct imx_media_dev *imxmd, struct v4l2_subdev *sd) { struct imx_media_subdev *imxsd; int i; for (i = 0; i < imxmd->num_subdevs; i++) { imxsd = &imxmd->subdev[i]; if (sd == imxsd->sd) return imxsd; } return ERR_PTR(-ENODEV); } EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_sd); struct imx_media_subdev * imx_media_find_subdev_by_id(struct imx_media_dev *imxmd, u32 grp_id) { struct imx_media_subdev *imxsd; int i; for (i = 0; i < imxmd->num_subdevs; i++) { imxsd = &imxmd->subdev[i]; if (imxsd->sd && imxsd->sd->grp_id == grp_id) return imxsd; } return ERR_PTR(-ENODEV); } EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_id); /* * Adds a video device to the master video device list. This is called by * an async subdev that owns a video device when it is registered. */ int imx_media_add_video_device(struct imx_media_dev *imxmd, struct imx_media_video_dev *vdev) { int vdev_idx, ret = 0; mutex_lock(&imxmd->mutex); vdev_idx = imxmd->num_vdevs; if (vdev_idx >= IMX_MEDIA_MAX_VDEVS) { dev_err(imxmd->md.dev, "%s: too many video devices! can't add %s\n", __func__, vdev->vfd->name); ret = -ENOSPC; goto out; } imxmd->vdev[vdev_idx] = vdev; imxmd->num_vdevs++; out: mutex_unlock(&imxmd->mutex); return ret; } EXPORT_SYMBOL_GPL(imx_media_add_video_device); /* * Search upstream or downstream for a subdevice in the current pipeline * with given grp_id, starting from start_entity. Returns the subdev's * source/sink pad that it was reached from. Must be called with * mdev->graph_mutex held. */ static struct media_pad * find_pipeline_pad(struct imx_media_dev *imxmd, struct media_entity *start_entity, u32 grp_id, bool upstream) { struct media_entity *me = start_entity; struct media_pad *pad = NULL; struct v4l2_subdev *sd; int i; for (i = 0; i < me->num_pads; i++) { struct media_pad *spad = &me->pads[i]; if ((upstream && !(spad->flags & MEDIA_PAD_FL_SINK)) || (!upstream && !(spad->flags & MEDIA_PAD_FL_SOURCE))) continue; pad = media_entity_remote_pad(spad); if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) continue; sd = media_entity_to_v4l2_subdev(pad->entity); if (sd->grp_id & grp_id) return pad; return find_pipeline_pad(imxmd, pad->entity, grp_id, upstream); } return NULL; } /* * Search upstream for a subdev in the current pipeline with * given grp_id. Must be called with mdev->graph_mutex held. */ static struct v4l2_subdev * find_upstream_subdev(struct imx_media_dev *imxmd, struct media_entity *start_entity, u32 grp_id) { struct v4l2_subdev *sd; struct media_pad *pad; if (is_media_entity_v4l2_subdev(start_entity)) { sd = media_entity_to_v4l2_subdev(start_entity); if (sd->grp_id & grp_id) return sd; } pad = find_pipeline_pad(imxmd, start_entity, grp_id, true); return pad ? media_entity_to_v4l2_subdev(pad->entity) : NULL; } /* * Find the upstream mipi-csi2 virtual channel reached from the given * start entity in the current pipeline. * Must be called with mdev->graph_mutex held. */ int imx_media_find_mipi_csi2_channel(struct imx_media_dev *imxmd, struct media_entity *start_entity) { struct media_pad *pad; int ret = -EPIPE; pad = find_pipeline_pad(imxmd, start_entity, IMX_MEDIA_GRP_ID_CSI2, true); if (pad) { ret = pad->index - 1; dev_dbg(imxmd->md.dev, "found vc%d from %s\n", ret, start_entity->name); } return ret; } EXPORT_SYMBOL_GPL(imx_media_find_mipi_csi2_channel); /* * Find a subdev reached upstream from the given start entity in * the current pipeline. * Must be called with mdev->graph_mutex held. */ struct imx_media_subdev * imx_media_find_upstream_subdev(struct imx_media_dev *imxmd, struct media_entity *start_entity, u32 grp_id) { struct v4l2_subdev *sd; sd = find_upstream_subdev(imxmd, start_entity, grp_id); if (!sd) return ERR_PTR(-ENODEV); return imx_media_find_subdev_by_sd(imxmd, sd); } EXPORT_SYMBOL_GPL(imx_media_find_upstream_subdev); struct imx_media_subdev * __imx_media_find_sensor(struct imx_media_dev *imxmd, struct media_entity *start_entity) { return imx_media_find_upstream_subdev(imxmd, start_entity, IMX_MEDIA_GRP_ID_SENSOR); } EXPORT_SYMBOL_GPL(__imx_media_find_sensor); struct imx_media_subdev * imx_media_find_sensor(struct imx_media_dev *imxmd, struct media_entity *start_entity) { struct imx_media_subdev *sensor; mutex_lock(&imxmd->md.graph_mutex); sensor = __imx_media_find_sensor(imxmd, start_entity); mutex_unlock(&imxmd->md.graph_mutex); return sensor; } EXPORT_SYMBOL_GPL(imx_media_find_sensor); /* * Turn current pipeline streaming on/off starting from entity. */ int imx_media_pipeline_set_stream(struct imx_media_dev *imxmd, struct media_entity *entity, bool on) { struct v4l2_subdev *sd; int ret = 0; if (!is_media_entity_v4l2_subdev(entity)) return -EINVAL; sd = media_entity_to_v4l2_subdev(entity); mutex_lock(&imxmd->md.graph_mutex); if (on) { ret = __media_pipeline_start(entity, &imxmd->pipe); if (ret) goto out; ret = v4l2_subdev_call(sd, video, s_stream, 1); if (ret) __media_pipeline_stop(entity); } else { v4l2_subdev_call(sd, video, s_stream, 0); if (entity->pipe) __media_pipeline_stop(entity); } out: mutex_unlock(&imxmd->md.graph_mutex); return ret; } EXPORT_SYMBOL_GPL(imx_media_pipeline_set_stream); MODULE_DESCRIPTION("i.MX5/6 v4l2 media controller driver"); MODULE_AUTHOR("Steve Longerbeam "); MODULE_LICENSE("GPL");