Загрузка данных


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->bypass && 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;
	const struct mxc_isi_plat_data *pdata = pipe->isi->pdata;
	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);

	/* Check which buffer has just completed. */
	buf_id = 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);
	mxc_isi_channel_set_max_size(pipe, &video->pix, pdata->buf_max_size);
	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);
}