| /* |
| * 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 <linux/module.h> |
| #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 <steve_longerbeam@mentor.com>"); |
| MODULE_LICENSE("GPL"); |