struct imx7_csi_pixfmt { /* the in-memory FourCC pixel format */
u32 fourcc; /* * the set of equivalent media bus codes for the fourcc. * NOTE! codes pointer is NULL for in-memory-only formats.
*/ const u32 *codes; int bpp; /* total bpp */ bool yuv;
};
/* Video device */ struct video_device *vdev; /* Video device */ struct media_pad vdev_pad; /* Video device pad */
struct v4l2_pix_format vdev_fmt; /* The user format */ conststruct imx7_csi_pixfmt *vdev_cc; struct v4l2_rect vdev_compose; /* The compose rectangle */
/* return any remaining active frames with return_status */ for (i = 0; i < 2; i++) {
buf = csi->active_vb2_buf[i]; if (buf) { struct vb2_buffer *vb = &buf->vbuf.vb2_buf;
/* mark next EOF interrupt as the last before stream off */
spin_lock_irqsave(&csi->irqlock, flags);
csi->last_eof = true;
spin_unlock_irqrestore(&csi->irqlock, flags);
/* * and then wait for interrupt handler to mark completion.
*/
timeout_jiffies = msecs_to_jiffies(IMX7_CSI_VIDEO_EOF_TIMEOUT);
ret = wait_for_completion_timeout(&csi->last_eof_completion,
timeout_jiffies); if (ret == 0)
v4l2_warn(&csi->sd, "wait last EOF timeout\n");
switch (sink_fmt->code) { case MEDIA_BUS_FMT_Y8_1X8: case MEDIA_BUS_FMT_SBGGR8_1X8: case MEDIA_BUS_FMT_SGBRG8_1X8: case MEDIA_BUS_FMT_SGRBG8_1X8: case MEDIA_BUS_FMT_SRGGB8_1X8:
cr18 |= BIT_MIPI_DATA_FORMAT_RAW8; break; case MEDIA_BUS_FMT_Y10_1X10: case MEDIA_BUS_FMT_SBGGR10_1X10: case MEDIA_BUS_FMT_SGBRG10_1X10: case MEDIA_BUS_FMT_SGRBG10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10:
cr3 |= BIT_TWO_8BIT_SENSOR;
cr18 |= BIT_MIPI_DATA_FORMAT_RAW10; break; case MEDIA_BUS_FMT_Y12_1X12: case MEDIA_BUS_FMT_SBGGR12_1X12: case MEDIA_BUS_FMT_SGBRG12_1X12: case MEDIA_BUS_FMT_SGRBG12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12:
cr3 |= BIT_TWO_8BIT_SENSOR;
cr18 |= BIT_MIPI_DATA_FORMAT_RAW12; break; case MEDIA_BUS_FMT_Y14_1X14: case MEDIA_BUS_FMT_SBGGR14_1X14: case MEDIA_BUS_FMT_SGBRG14_1X14: case MEDIA_BUS_FMT_SGRBG14_1X14: case MEDIA_BUS_FMT_SRGGB14_1X14:
cr3 |= BIT_TWO_8BIT_SENSOR;
cr18 |= BIT_MIPI_DATA_FORMAT_RAW14; break;
/* * The CSI bridge has a 16-bit input bus. Depending on the * connected source, data may be transmitted with 8 or 10 bits * per clock sample (in bits [9:2] or [9:0] respectively) or * with 16 bits per clock sample (in bits [15:0]). The data is * then packed into a 32-bit FIFO (as shown in figure 13-11 of * the i.MX8MM reference manual rev. 3). * * The data packing in a 32-bit FIFO input word is controlled by * the CR3 TWO_8BIT_SENSOR field (also known as SENSOR_16BITS in * the i.MX8MM reference manual). When set to 0, data packing * groups four 8-bit input samples (bits [9:2]). When set to 1, * data packing groups two 16-bit input samples (bits [15:0]). * * The register field CR18 MIPI_DOUBLE_CMPNT also needs to be * configured according to the input format for YUV 4:2:2 data. * The field controls the gasket between the CSI-2 receiver and * the CSI bridge. On i.MX7 and i.MX8MM, the field must be set * to 1 when the CSIS outputs 16-bit samples. On i.MX8MQ, the * gasket ignores the MIPI_DOUBLE_CMPNT bit and YUV 4:2:2 always * uses 16-bit samples. Setting MIPI_DOUBLE_CMPNT in that case * has no effect, but doesn't cause any issue.
*/ case MEDIA_BUS_FMT_UYVY8_2X8: case MEDIA_BUS_FMT_YUYV8_2X8:
cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B; break; case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_YUYV8_1X16:
cr3 |= BIT_TWO_8BIT_SENSOR;
cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B |
BIT_MIPI_DOUBLE_CMPNT; break;
}
}
if (status & BIT_ADDR_CH_ERR_INT) {
imx7_csi_hw_disable(csi);
imx7_csi_dma_reflash(csi);
imx7_csi_hw_enable(csi);
}
if ((status & BIT_DMA_TSF_DONE_FB1) &&
(status & BIT_DMA_TSF_DONE_FB2)) { /* * For both FB1 and FB2 interrupter bits set case, * CSI DMA is work in one of FB1 and FB2 buffer, * but software can not know the state. * Skip it to avoid base address updated * when csi work in field0 and field1 will write to * new base address.
*/
} elseif (status & BIT_DMA_TSF_DONE_FB1) {
csi->buf_num = 0;
} elseif (status & BIT_DMA_TSF_DONE_FB2) {
csi->buf_num = 1;
}
if ((status & BIT_DMA_TSF_DONE_FB1) ||
(status & BIT_DMA_TSF_DONE_FB2)) {
imx7_csi_vb2_buf_done(csi);
if (csi->last_eof) {
complete(&csi->last_eof_completion);
csi->last_eof = false;
}
}
spin_unlock(&csi->irqlock);
return IRQ_HANDLED;
}
/* ----------------------------------------------------------------------------- * Format Helpers
*/
/* * List of supported pixel formats for the subdevs. Keep V4L2_PIX_FMT_UYVY and * MEDIA_BUS_FMT_UYVY8_2X8 first to match IMX7_CSI_DEF_PIX_FORMAT and * IMX7_CSI_DEF_MBUS_CODE. * * TODO: Restrict the supported formats list based on the SoC integration. * * The CSI bridge can be configured to sample pixel components from the Rx queue * in single (8bpp) or double (16bpp) component modes. Image format variants * with different sample sizes (ie YUYV_2X8 vs YUYV_1X16) determine the pixel * components sampling size per each clock cycle and their packing mode (see * imx7_csi_configure() for details). * * As the CSI bridge can be interfaced with different IP blocks depending on the * SoC model it is integrated on, the Rx queue sampling size should match the * size of the samples transferred by the transmitting IP block. To avoid * misconfigurations of the capture pipeline, the enumeration of the supported * formats should be restricted to match the pixel source transmitting mode. * * Example: i.MX8MM SoC integrates the CSI bridge with the Samsung CSIS CSI-2 * receiver which operates in dual pixel sampling mode. The CSI bridge should * only expose the 1X16 formats variant which instructs it to operate in dual * pixel sampling mode. When the CSI bridge is instead integrated on an i.MX7, * which supports both serial and parallel input, it should expose both * variants. * * This currently only applies to YUYV formats, but other formats might need to * be handled in the same way.
*/ staticconststruct imx7_csi_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
),
.yuv = true,
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_YUYV,
.codes = IMX_BUS_FMTS(
MEDIA_BUS_FMT_YUYV8_2X8,
MEDIA_BUS_FMT_YUYV8_1X16
),
.yuv = true,
.bpp = 16,
}, /*** raw bayer and grayscale formats start here ***/
{
.fourcc = V4L2_PIX_FMT_SBGGR8,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR8_1X8),
.bpp = 8,
}, {
.fourcc = V4L2_PIX_FMT_SGBRG8,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG8_1X8),
.bpp = 8,
}, {
.fourcc = V4L2_PIX_FMT_SGRBG8,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG8_1X8),
.bpp = 8,
}, {
.fourcc = V4L2_PIX_FMT_SRGGB8,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB8_1X8),
.bpp = 8,
}, {
.fourcc = V4L2_PIX_FMT_SBGGR10,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR10_1X10),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGBRG10,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG10_1X10),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGRBG10,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG10_1X10),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SRGGB10,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB10_1X10),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SBGGR12,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR12_1X12),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGBRG12,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG12_1X12),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGRBG12,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG12_1X12),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SRGGB12,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB12_1X12),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SBGGR14,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR14_1X14),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGBRG14,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG14_1X14),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SGRBG14,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG14_1X14),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_SRGGB14,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB14_1X14),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_GREY,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y8_1X8),
.bpp = 8,
}, {
.fourcc = V4L2_PIX_FMT_Y10,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y10_1X10),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_Y12,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y12_1X12),
.bpp = 16,
}, {
.fourcc = V4L2_PIX_FMT_Y14,
.codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y14_1X14),
.bpp = 16,
},
};
/* * Search in the pixel_formats[] array for an entry with the given fourcc * return it.
*/ staticconststruct imx7_csi_pixfmt *imx7_csi_find_pixel_format(u32 fourcc)
{ unsignedint i;
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { conststruct imx7_csi_pixfmt *fmt = &pixel_formats[i];
if (fmt->fourcc == fourcc) return fmt;
}
return NULL;
}
/* * Search in the pixel_formats[] array for an entry with the given media * bus code and return it.
*/ staticconststruct imx7_csi_pixfmt *imx7_csi_find_mbus_format(u32 code)
{ unsignedint i;
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { conststruct imx7_csi_pixfmt *fmt = &pixel_formats[i]; unsignedint j;
if (!fmt->codes) continue;
for (j = 0; fmt->codes[j]; j++) { if (code == fmt->codes[j]) return fmt;
}
}
return NULL;
}
/* * 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.
*/ staticint imx7_csi_enum_mbus_formats(u32 *code, u32 index)
{ unsignedint i;
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { conststruct imx7_csi_pixfmt *fmt = &pixel_formats[i]; unsignedint j;
if (!fmt->codes) continue;
for (j = 0; fmt->codes[j]; j++) { if (index == 0) {
*code = fmt->codes[j]; return 0;
}
index--;
}
}
return -EINVAL;
}
/* ----------------------------------------------------------------------------- * Video Capture Device - IOCTLs
*/
if (compose) {
compose->width = pixfmt->width;
compose->height = pixfmt->height;
}
/* * Find the pixel format, default to the first supported format if not * found.
*/
cc = imx7_csi_find_pixel_format(pixfmt->pixelformat); if (!cc) {
pixfmt->pixelformat = IMX7_CSI_DEF_PIX_FORMAT;
cc = imx7_csi_find_pixel_format(pixfmt->pixelformat);
}
/* * The width alignment is 8 bytes as indicated by the * CSI_IMAG_PARA.IMAGE_WIDTH documentation. Convert it to pixels. * * TODO: Implement configurable stride support.
*/
walign = 8 * 8 / cc->bpp;
pixfmt->width = clamp(round_up(pixfmt->width, walign), walign,
round_down(65535U, walign));
pixfmt->height = clamp(pixfmt->height, 1U, 65535U);
if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL;
switch (s->target) { case V4L2_SEL_TGT_COMPOSE: case V4L2_SEL_TGT_COMPOSE_DEFAULT: case V4L2_SEL_TGT_COMPOSE_BOUNDS: /* The compose rectangle is fixed to the source format. */
s->r = csi->vdev_compose; break; case V4L2_SEL_TGT_COMPOSE_PADDED: /* * The hardware writes with a configurable but fixed DMA burst * size. If the source format width is not burst size aligned, * the written frame contains padding to the right.
*/
s->r.left = 0;
s->r.top = 0;
s->r.width = csi->vdev_fmt.width;
s->r.height = csi->vdev_fmt.height; break; default: return -EINVAL;
}
if (vb2_plane_size(vb, 0) < pix->sizeimage) {
dev_err(csi->dev, "data will not fit into plane (%lu < %lu)\n",
vb2_plane_size(vb, 0), (long)pix->sizeimage); return -EINVAL;
}
/* * buf_num holds the framebuffer ID of the most recently (*not* the * next anticipated) triggered interrupt. Without loss of generality, * if buf_num is 0, the hardware is capturing to FB2. If FB1 has been * programmed with a dummy buffer (as indicated by active_vb2_buf[0] * being NULL), then we can fast-track the new buffer by programming * its address in FB1 before the hardware completes FB2, instead of * adding it to the buffer queue and incurring a delay of one * additional frame. * * The irqlock prevents races with the interrupt handler that updates * buf_num when it programs the next buffer, but we can still race with * the hardware if we program the buffer in FB1 just after the hardware * completes FB2 and switches to FB1 and before buf_num can be updated * by the interrupt handler for FB2. The fast-tracked buffer would * then be ignored by the hardware while the driver would think it has * successfully been processed. * * To avoid this problem, if we can't avoid the race, we can detect * that we have lost it by checking, after programming the buffer in * FB1, if the interrupt flag indicating completion of FB2 has been * raised. If that is not the case, fast-tracking succeeded, and we can * update active_vb2_buf[0]. Otherwise, we may or may not have lost the * race (as the interrupt flag may have been raised just after * programming FB1 and before we read the interrupt status register), * and we need to assume the worst case of a race loss and queue the * buffer through the slow path.
*/
spin_lock_irqsave(&csi->irqlock, flags);
buf_num = csi->buf_num; if (csi->active_vb2_buf[buf_num]) {
spin_unlock_irqrestore(&csi->irqlock, flags); returnfalse;
}
imx7_csi_update_buf(csi, dma_addr, buf_num);
isr = imx7_csi_reg_read(csi, CSI_CSISR); if (isr & (buf_num ? BIT_DMA_TSF_DONE_FB1 : BIT_DMA_TSF_DONE_FB2)) { /* * The interrupt for the /other/ FB just came (the isr hasn't * run yet though, because we have the lock here); we can't be * sure we've programmed buf_num FB in time, so queue the buffer * to the buffer queue normally. No need to undo writing the FB * register, since we won't return it as active_vb2_buf is NULL, * so it's okay to potentially write it to both FB1 and FB2; * only the one where it was queued normally will be returned.
*/
spin_unlock_irqrestore(&csi->irqlock, flags); returnfalse;
}
/* Retrieve the media bus format on the source subdev. */
ret = v4l2_subdev_call_state_active(&csi->sd, pad, get_fmt, &fmt_src); if (ret) return ret;
/* * Verify that the media bus size matches the size set on the video * node. It is sufficient to check the compose rectangle size without * checking the rounded size from pix_fmt, as the rounded size is * derived directly from the compose rectangle size, and will thus * always match if the compose rectangle matches.
*/ if (csi->vdev_compose.width != fmt_src.format.width ||
csi->vdev_compose.height != fmt_src.format.height) return -EPIPE;
/* * Verify that the media bus code is compatible with the pixel format * set on the video node.
*/
cc = imx7_csi_find_mbus_format(fmt_src.format.code); if (!cc || csi->vdev_cc->yuv != cc->yuv) return -EPIPE;
/* Initialize the default format and compose rectangle. */
imx7_csi_video_init_format(csi);
/* Register the video device. */
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) {
dev_err(csi->dev, "Failed to register video device\n"); return ret;
}
dev_info(csi->dev, "Registered %s as /dev/%s\n", vdev->name,
video_device_node_name(vdev));
/* Create the link from the CSI subdev to the video device. */
ret = media_create_pad_link(&sd->entity, IMX7_CSI_PAD_SRC,
&vdev->entity, 0, MEDIA_LNK_FL_IMMUTABLE |
MEDIA_LNK_FL_ENABLED); if (ret) {
dev_err(csi->dev, "failed to create link to device node\n");
video_unregister_device(vdev); return ret;
}
switch (code->pad) { case IMX7_CSI_PAD_SINK:
ret = imx7_csi_enum_mbus_formats(&code->code, code->index); break;
case IMX7_CSI_PAD_SRC: if (code->index != 0) {
ret = -EINVAL; break;
}
code->code = in_fmt->code; break;
default:
ret = -EINVAL; break;
}
return ret;
}
/* * 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.
*/ staticvoid imx7_csi_try_colorimetry(struct v4l2_mbus_framefmt *tryfmt)
{ conststruct imx7_csi_pixfmt *cc; bool is_rgb = false;
cc = imx7_csi_find_mbus_format(tryfmt->code); if (cc && !cc->yuv)
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 (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);
}
/* * Validate the source link, and record whether the source uses the * parallel input or the CSI-2 receiver.
*/
ret = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); if (ret) return ret;
switch (csi->src_sd->entity.function) { case MEDIA_ENT_F_VID_IF_BRIDGE: /* The input is the CSI-2 receiver. */
csi->is_csi2 = true; break;
case MEDIA_ENT_F_VID_MUX: /* The input is the mux, check its input. */ for (i = 0; i < csi->src_sd->entity.num_pads; i++) { struct media_pad *spad = &csi->src_sd->entity.pads[i];
if (!(spad->flags & MEDIA_PAD_FL_SINK)) continue;
pad = media_pad_remote_pad_first(spad); if (pad) break;
}
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT); if (!ep) {
ret = dev_err_probe(csi->dev, -ENOTCONN, "Failed to get remote endpoint\n"); goto error;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.