// SPDX-License-Identifier: GPL-2.0+
|
/*
|
* V4L2 Media Controller Driver for Freescale i.MX5/6 SOC
|
*
|
* Copyright (c) 2016 Mentor Graphics Inc.
|
*/
|
#include <linux/module.h>
|
#include "imx-media.h"
|
|
#define IMX_BUS_FMTS(fmt...) (const u32[]) {fmt, 0}
|
|
/*
|
* List of supported pixel formats for the subdevs.
|
*/
|
static const struct imx_media_pixfmt pixel_formats[] = {
|
/*** YUV formats start here ***/
|
{
|
.fourcc = V4L2_PIX_FMT_UYVY,
|
.codes = IMX_BUS_FMTS(
|
MEDIA_BUS_FMT_UYVY8_2X8,
|
MEDIA_BUS_FMT_UYVY8_1X16
|
),
|
.cs = IPUV3_COLORSPACE_YUV,
|
.bpp = 16,
|
}, {
|
.fourcc = V4L2_PIX_FMT_YUYV,
|
.codes = IMX_BUS_FMTS(
|
MEDIA_BUS_FMT_YUYV8_2X8,
|
MEDIA_BUS_FMT_YUYV8_1X16
|
),
|
.cs = IPUV3_COLORSPACE_YUV,
|
.bpp = 16,
|
}, {
|
.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,
|
}, {
|
.fourcc = V4L2_PIX_FMT_YUV32,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_AYUV8_1X32),
|
.cs = IPUV3_COLORSPACE_YUV,
|
.bpp = 32,
|
.ipufmt = true,
|
},
|
/*** RGB formats start here ***/
|
{
|
.fourcc = V4L2_PIX_FMT_RGB565,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_RGB565_2X8_LE),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 16,
|
.cycles = 2,
|
}, {
|
.fourcc = V4L2_PIX_FMT_RGB24,
|
.codes = IMX_BUS_FMTS(
|
MEDIA_BUS_FMT_RGB888_1X24,
|
MEDIA_BUS_FMT_RGB888_2X12_LE
|
),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 24,
|
}, {
|
.fourcc = V4L2_PIX_FMT_BGR24,
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 24,
|
}, {
|
.fourcc = V4L2_PIX_FMT_XRGB32,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_ARGB8888_1X32),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 32,
|
}, {
|
.fourcc = V4L2_PIX_FMT_XRGB32,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_ARGB8888_1X32),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 32,
|
.ipufmt = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_XBGR32,
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 32,
|
}, {
|
.fourcc = V4L2_PIX_FMT_BGRX32,
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 32,
|
}, {
|
.fourcc = V4L2_PIX_FMT_RGBX32,
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 32,
|
},
|
/*** raw bayer and grayscale formats start here ***/
|
{
|
.fourcc = V4L2_PIX_FMT_SBGGR8,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR8_1X8),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 8,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_SGBRG8,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG8_1X8),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 8,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_SGRBG8,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG8_1X8),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 8,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_SRGGB8,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB8_1X8),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 8,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_SBGGR16,
|
.codes = IMX_BUS_FMTS(
|
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 = IMX_BUS_FMTS(
|
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 = IMX_BUS_FMTS(
|
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 = IMX_BUS_FMTS(
|
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,
|
}, {
|
.fourcc = V4L2_PIX_FMT_GREY,
|
.codes = IMX_BUS_FMTS(
|
MEDIA_BUS_FMT_Y8_1X8,
|
MEDIA_BUS_FMT_Y10_1X10,
|
MEDIA_BUS_FMT_Y12_1X12
|
),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 8,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_Y10,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y10_1X10),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 16,
|
.bayer = true,
|
}, {
|
.fourcc = V4L2_PIX_FMT_Y12,
|
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y12_1X12),
|
.cs = IPUV3_COLORSPACE_RGB,
|
.bpp = 16,
|
.bayer = true,
|
},
|
};
|
|
/*
|
* Search in the pixel_formats[] array for an entry with the given fourcc
|
* that matches the requested selection criteria and return it.
|
*
|
* @fourcc: Search for an entry with the given fourcc pixel format.
|
* @fmt_sel: Allow entries only with the given selection criteria.
|
*/
|
const struct imx_media_pixfmt *
|
imx_media_find_pixel_format(u32 fourcc, enum imx_pixfmt_sel fmt_sel)
|
{
|
bool sel_ipu = fmt_sel & PIXFMT_SEL_IPU;
|
unsigned int i;
|
|
fmt_sel &= ~PIXFMT_SEL_IPU;
|
|
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
|
const struct imx_media_pixfmt *fmt = &pixel_formats[i];
|
enum imx_pixfmt_sel sel;
|
|
if (sel_ipu != fmt->ipufmt)
|
continue;
|
|
sel = fmt->bayer ? PIXFMT_SEL_BAYER :
|
((fmt->cs == IPUV3_COLORSPACE_YUV) ?
|
PIXFMT_SEL_YUV : PIXFMT_SEL_RGB);
|
|
if ((fmt_sel & sel) && fmt->fourcc == fourcc)
|
return fmt;
|
}
|
|
return NULL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_find_pixel_format);
|
|
/*
|
* Search in the pixel_formats[] array for an entry with the given media
|
* bus code that matches the requested selection criteria and return it.
|
*
|
* @code: Search for an entry with the given media-bus code.
|
* @fmt_sel: Allow entries only with the given selection criteria.
|
*/
|
const struct imx_media_pixfmt *
|
imx_media_find_mbus_format(u32 code, enum imx_pixfmt_sel fmt_sel)
|
{
|
bool sel_ipu = fmt_sel & PIXFMT_SEL_IPU;
|
unsigned int i;
|
|
fmt_sel &= ~PIXFMT_SEL_IPU;
|
|
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
|
const struct imx_media_pixfmt *fmt = &pixel_formats[i];
|
enum imx_pixfmt_sel sel;
|
unsigned int j;
|
|
if (sel_ipu != fmt->ipufmt)
|
continue;
|
|
sel = fmt->bayer ? PIXFMT_SEL_BAYER :
|
((fmt->cs == IPUV3_COLORSPACE_YUV) ?
|
PIXFMT_SEL_YUV : PIXFMT_SEL_RGB);
|
|
if (!(fmt_sel & sel) || !fmt->codes)
|
continue;
|
|
for (j = 0; fmt->codes[j]; j++) {
|
if (code == fmt->codes[j])
|
return fmt;
|
}
|
}
|
|
return NULL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_find_mbus_format);
|
|
/*
|
* Enumerate entries in the pixel_formats[] array that match the
|
* requested selection criteria. Return the fourcc that matches the
|
* selection criteria at the requested match index.
|
*
|
* @fourcc: The returned fourcc that matches the search criteria at
|
* the requested match index.
|
* @index: The requested match index.
|
* @fmt_sel: Include in the enumeration entries with the given selection
|
* criteria.
|
*/
|
int imx_media_enum_pixel_formats(u32 *fourcc, u32 index,
|
enum imx_pixfmt_sel fmt_sel)
|
{
|
bool sel_ipu = fmt_sel & PIXFMT_SEL_IPU;
|
unsigned int i;
|
|
fmt_sel &= ~PIXFMT_SEL_IPU;
|
|
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
|
const struct imx_media_pixfmt *fmt = &pixel_formats[i];
|
enum imx_pixfmt_sel sel;
|
|
if (sel_ipu != fmt->ipufmt)
|
continue;
|
|
sel = fmt->bayer ? PIXFMT_SEL_BAYER :
|
((fmt->cs == IPUV3_COLORSPACE_YUV) ?
|
PIXFMT_SEL_YUV : PIXFMT_SEL_RGB);
|
|
if (!(fmt_sel & sel))
|
continue;
|
|
if (index == 0) {
|
*fourcc = fmt->fourcc;
|
return 0;
|
}
|
|
index--;
|
}
|
|
return -EINVAL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_enum_pixel_formats);
|
|
/*
|
* Enumerate entries in the pixel_formats[] array that match the
|
* requested search criteria. Return the media-bus code that matches
|
* the search criteria at the requested match index.
|
*
|
* @code: The returned media-bus code that matches the search criteria at
|
* the requested match index.
|
* @index: The requested match index.
|
* @fmt_sel: Include in the enumeration entries with the given selection
|
* criteria.
|
*/
|
int imx_media_enum_mbus_formats(u32 *code, u32 index,
|
enum imx_pixfmt_sel fmt_sel)
|
{
|
bool sel_ipu = fmt_sel & PIXFMT_SEL_IPU;
|
unsigned int i;
|
|
fmt_sel &= ~PIXFMT_SEL_IPU;
|
|
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
|
const struct imx_media_pixfmt *fmt = &pixel_formats[i];
|
enum imx_pixfmt_sel sel;
|
unsigned int j;
|
|
if (sel_ipu != fmt->ipufmt)
|
continue;
|
|
sel = fmt->bayer ? PIXFMT_SEL_BAYER :
|
((fmt->cs == IPUV3_COLORSPACE_YUV) ?
|
PIXFMT_SEL_YUV : PIXFMT_SEL_RGB);
|
|
if (!(fmt_sel & sel) || !fmt->codes)
|
continue;
|
|
for (j = 0; fmt->codes[j]; j++) {
|
if (index == 0) {
|
*code = fmt->codes[j];
|
return 0;
|
}
|
|
index--;
|
}
|
}
|
|
return -EINVAL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_enum_mbus_formats);
|
|
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_formats(&code, 0, PIXFMT_SEL_YUV);
|
|
lcc = imx_media_find_mbus_format(code, PIXFMT_SEL_ANY);
|
if (!lcc) {
|
lcc = imx_media_find_ipu_format(code, PIXFMT_SEL_YUV_RGB);
|
if (!lcc)
|
return -EINVAL;
|
}
|
|
mbus->code = code;
|
|
mbus->colorspace = V4L2_COLORSPACE_SRGB;
|
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(lcc->cs == IPUV3_COLORSPACE_RGB,
|
mbus->colorspace,
|
mbus->ycbcr_enc);
|
|
if (cc)
|
*cc = lcc;
|
|
return 0;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_init_mbus_fmt);
|
|
/*
|
* Initializes the TRY format to the ACTIVE format on all pads
|
* of a subdev. Can be used as the .init_cfg pad operation.
|
*/
|
int imx_media_init_cfg(struct v4l2_subdev *sd,
|
struct v4l2_subdev_pad_config *cfg)
|
{
|
struct v4l2_mbus_framefmt *mf_try;
|
struct v4l2_subdev_format format;
|
unsigned int pad;
|
int ret;
|
|
for (pad = 0; pad < sd->entity.num_pads; pad++) {
|
memset(&format, 0, sizeof(format));
|
|
format.pad = pad;
|
format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
|
ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &format);
|
if (ret)
|
continue;
|
|
mf_try = v4l2_subdev_get_try_format(sd, cfg, pad);
|
*mf_try = format.format;
|
}
|
|
return 0;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_init_cfg);
|
|
/*
|
* Default the colorspace in tryfmt to SRGB if set to an unsupported
|
* colorspace or not initialized. Then set the remaining colorimetry
|
* parameters based on the colorspace if they are uninitialized.
|
*
|
* tryfmt->code must be set on entry.
|
*
|
* If this format is destined to be routed through the Image Converter,
|
* Y`CbCr encoding must be fixed. The IC supports only BT.601 Y`CbCr
|
* or Rec.709 Y`CbCr encoding.
|
*/
|
void imx_media_try_colorimetry(struct v4l2_mbus_framefmt *tryfmt,
|
bool ic_route)
|
{
|
const struct imx_media_pixfmt *cc;
|
bool is_rgb = false;
|
|
cc = imx_media_find_mbus_format(tryfmt->code, PIXFMT_SEL_ANY);
|
if (!cc)
|
cc = imx_media_find_ipu_format(tryfmt->code,
|
PIXFMT_SEL_YUV_RGB);
|
|
if (cc && cc->cs == IPUV3_COLORSPACE_RGB)
|
is_rgb = true;
|
|
switch (tryfmt->colorspace) {
|
case V4L2_COLORSPACE_SMPTE170M:
|
case V4L2_COLORSPACE_REC709:
|
case V4L2_COLORSPACE_JPEG:
|
case V4L2_COLORSPACE_SRGB:
|
case V4L2_COLORSPACE_BT2020:
|
case V4L2_COLORSPACE_OPRGB:
|
case V4L2_COLORSPACE_DCI_P3:
|
case V4L2_COLORSPACE_RAW:
|
break;
|
default:
|
tryfmt->colorspace = V4L2_COLORSPACE_SRGB;
|
break;
|
}
|
|
if (tryfmt->xfer_func == V4L2_XFER_FUNC_DEFAULT)
|
tryfmt->xfer_func =
|
V4L2_MAP_XFER_FUNC_DEFAULT(tryfmt->colorspace);
|
|
if (ic_route) {
|
if (tryfmt->ycbcr_enc != V4L2_YCBCR_ENC_601 &&
|
tryfmt->ycbcr_enc != V4L2_YCBCR_ENC_709)
|
tryfmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
|
} else {
|
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);
|
}
|
EXPORT_SYMBOL_GPL(imx_media_try_colorimetry);
|
|
int imx_media_mbus_fmt_to_pix_fmt(struct v4l2_pix_format *pix,
|
const struct v4l2_mbus_framefmt *mbus,
|
const struct imx_media_pixfmt *cc)
|
{
|
u32 width;
|
u32 stride;
|
|
if (!cc) {
|
cc = imx_media_find_ipu_format(mbus->code,
|
PIXFMT_SEL_YUV_RGB);
|
if (!cc)
|
cc = imx_media_find_mbus_format(mbus->code,
|
PIXFMT_SEL_ANY);
|
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_formats(&code, 0, PIXFMT_SEL_YUV);
|
cc = imx_media_find_mbus_format(code, PIXFMT_SEL_YUV);
|
}
|
|
/* Round up width for minimum burst size */
|
width = round_up(mbus->width, 8);
|
|
/* Round up stride for IDMAC line start address alignment */
|
if (cc->planar)
|
stride = round_up(width, 16);
|
else
|
stride = round_up((width * cc->bpp) >> 3, 8);
|
|
pix->width = 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 = cc->planar ? ((stride * pix->height * cc->bpp) >> 3) :
|
stride * pix->height;
|
|
return 0;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_mbus_fmt_to_pix_fmt);
|
|
int imx_media_mbus_fmt_to_ipu_image(struct ipu_image *image,
|
const 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,
|
const struct ipu_image *image)
|
{
|
const struct imx_media_pixfmt *fmt;
|
|
fmt = imx_media_find_pixel_format(image->pix.pixelformat,
|
PIXFMT_SEL_ANY);
|
if (!fmt || !fmt->codes || !fmt->codes[0])
|
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 device *dev,
|
struct imx_media_dma_buf *buf)
|
{
|
if (buf->virt)
|
dma_free_coherent(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 device *dev,
|
struct imx_media_dma_buf *buf,
|
int size)
|
{
|
imx_media_free_dma_buf(dev, buf);
|
|
buf->len = PAGE_ALIGN(size);
|
buf->virt = dma_alloc_coherent(dev, buf->len, &buf->phys,
|
GFP_DMA | GFP_KERNEL);
|
if (!buf->virt) {
|
dev_err(dev, "%s: failed\n", __func__);
|
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_IPU_CSI0...IMX_MEDIA_GRP_ID_IPU_CSI1:
|
id = (grp_id >> IMX_MEDIA_GRP_ID_IPU_CSI_BIT) - 1;
|
snprintf(sd_name, sz, "ipu%d_csi%d", ipu_id + 1, id);
|
break;
|
case IMX_MEDIA_GRP_ID_IPU_VDIC:
|
snprintf(sd_name, sz, "ipu%d_vdic", ipu_id + 1);
|
break;
|
case IMX_MEDIA_GRP_ID_IPU_IC_PRP:
|
snprintf(sd_name, sz, "ipu%d_ic_prp", ipu_id + 1);
|
break;
|
case IMX_MEDIA_GRP_ID_IPU_IC_PRPENC:
|
snprintf(sd_name, sz, "ipu%d_ic_prpenc", ipu_id + 1);
|
break;
|
case IMX_MEDIA_GRP_ID_IPU_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 v4l2_subdev *
|
imx_media_find_subdev_by_fwnode(struct imx_media_dev *imxmd,
|
struct fwnode_handle *fwnode)
|
{
|
struct v4l2_subdev *sd;
|
|
list_for_each_entry(sd, &imxmd->v4l2_dev.subdevs, list) {
|
if (sd->fwnode == fwnode)
|
return sd;
|
}
|
|
return NULL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_fwnode);
|
|
struct v4l2_subdev *
|
imx_media_find_subdev_by_devname(struct imx_media_dev *imxmd,
|
const char *devname)
|
{
|
struct v4l2_subdev *sd;
|
|
list_for_each_entry(sd, &imxmd->v4l2_dev.subdevs, list) {
|
if (!strcmp(devname, dev_name(sd->dev)))
|
return sd;
|
}
|
|
return NULL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_devname);
|
|
/*
|
* Adds a video device to the master video device list. This is called
|
* when a video device is registered.
|
*/
|
void imx_media_add_video_device(struct imx_media_dev *imxmd,
|
struct imx_media_video_dev *vdev)
|
{
|
mutex_lock(&imxmd->mutex);
|
|
list_add_tail(&vdev->list, &imxmd->vdev_list);
|
|
mutex_unlock(&imxmd->mutex);
|
}
|
EXPORT_SYMBOL_GPL(imx_media_add_video_device);
|
|
/*
|
* Search upstream/downstream for a subdevice or video device pad in the
|
* current pipeline, starting from start_entity. Returns the device's
|
* source/sink pad that it was reached from. Must be called with
|
* mdev->graph_mutex held.
|
*
|
* If grp_id != 0, finds a subdevice's pad of given grp_id.
|
* Else If buftype != 0, finds a video device's pad of given buffer type.
|
* Else, returns the nearest source/sink pad to start_entity.
|
*/
|
struct media_pad *
|
imx_media_pipeline_pad(struct media_entity *start_entity, u32 grp_id,
|
enum v4l2_buf_type buftype, bool upstream)
|
{
|
struct media_entity *me = start_entity;
|
struct media_pad *pad = NULL;
|
struct video_device *vfd;
|
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)
|
continue;
|
|
if (grp_id) {
|
if (is_media_entity_v4l2_subdev(pad->entity)) {
|
sd = media_entity_to_v4l2_subdev(pad->entity);
|
if (sd->grp_id & grp_id)
|
return pad;
|
}
|
|
return imx_media_pipeline_pad(pad->entity, grp_id,
|
buftype, upstream);
|
} else if (buftype) {
|
if (is_media_entity_v4l2_video_device(pad->entity)) {
|
vfd = media_entity_to_video_device(pad->entity);
|
if (buftype == vfd->queue->type)
|
return pad;
|
}
|
|
return imx_media_pipeline_pad(pad->entity, grp_id,
|
buftype, upstream);
|
} else {
|
return pad;
|
}
|
}
|
|
return NULL;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_pipeline_pad);
|
|
/*
|
* Search upstream/downstream for a subdev or video device in the current
|
* pipeline. Must be called with mdev->graph_mutex held.
|
*/
|
static struct media_entity *
|
find_pipeline_entity(struct media_entity *start, u32 grp_id,
|
enum v4l2_buf_type buftype, bool upstream)
|
{
|
struct media_pad *pad = NULL;
|
struct video_device *vfd;
|
struct v4l2_subdev *sd;
|
|
if (grp_id && is_media_entity_v4l2_subdev(start)) {
|
sd = media_entity_to_v4l2_subdev(start);
|
if (sd->grp_id & grp_id)
|
return &sd->entity;
|
} else if (buftype && is_media_entity_v4l2_video_device(start)) {
|
vfd = media_entity_to_video_device(start);
|
if (buftype == vfd->queue->type)
|
return &vfd->entity;
|
}
|
|
pad = imx_media_pipeline_pad(start, grp_id, buftype, upstream);
|
|
return pad ? 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_pipeline_csi2_channel(struct media_entity *start_entity)
|
{
|
struct media_pad *pad;
|
int ret = -EPIPE;
|
|
pad = imx_media_pipeline_pad(start_entity, IMX_MEDIA_GRP_ID_CSI2,
|
0, true);
|
if (pad)
|
ret = pad->index - 1;
|
|
return ret;
|
}
|
EXPORT_SYMBOL_GPL(imx_media_pipeline_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 v4l2_subdev *
|
imx_media_pipeline_subdev(struct media_entity *start_entity, u32 grp_id,
|
bool upstream)
|
{
|
struct media_entity *me;
|
|
me = find_pipeline_entity(start_entity, grp_id, 0, upstream);
|
if (!me)
|
return ERR_PTR(-ENODEV);
|
|
return media_entity_to_v4l2_subdev(me);
|
}
|
EXPORT_SYMBOL_GPL(imx_media_pipeline_subdev);
|
|
/*
|
* Find a subdev reached upstream from the given start entity in
|
* the current pipeline.
|
* Must be called with mdev->graph_mutex held.
|
*/
|
struct video_device *
|
imx_media_pipeline_video_device(struct media_entity *start_entity,
|
enum v4l2_buf_type buftype, bool upstream)
|
{
|
struct media_entity *me;
|
|
me = find_pipeline_entity(start_entity, 0, buftype, upstream);
|
if (!me)
|
return ERR_PTR(-ENODEV);
|
|
return media_entity_to_video_device(me);
|
}
|
EXPORT_SYMBOL_GPL(imx_media_pipeline_video_device);
|
|
/*
|
* Find a fwnode endpoint that maps to the given subdevice's pad.
|
* If there are multiple endpoints that map to the pad, only the
|
* first endpoint encountered is returned.
|
*
|
* On success the refcount of the returned fwnode endpoint is
|
* incremented.
|
*/
|
struct fwnode_handle *imx_media_get_pad_fwnode(struct media_pad *pad)
|
{
|
struct fwnode_handle *endpoint;
|
struct v4l2_subdev *sd;
|
|
if (!is_media_entity_v4l2_subdev(pad->entity))
|
return ERR_PTR(-ENODEV);
|
|
sd = media_entity_to_v4l2_subdev(pad->entity);
|
|
fwnode_graph_for_each_endpoint(dev_fwnode(sd->dev), endpoint) {
|
int pad_idx = media_entity_get_fwnode_pad(&sd->entity,
|
endpoint,
|
pad->flags);
|
if (pad_idx < 0)
|
continue;
|
|
if (pad_idx == pad->index)
|
return endpoint;
|
}
|
|
return ERR_PTR(-ENODEV);
|
}
|
EXPORT_SYMBOL_GPL(imx_media_get_pad_fwnode);
|
|
/*
|
* 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");
|