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


static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd,
				      struct v4l2_subdev_state *state,
				      struct v4l2_subdev_selection *sel)
{
	struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
	const struct v4l2_mbus_framefmt *format;
	const struct v4l2_rect *rect;

	switch (sel->target) {
	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
		if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
			/* No compose rectangle on source pad. */
			return -EINVAL;

		/* The sink compose is bound by the sink format. */
		format = mxc_isi_pipe_get_pad_format(pipe, state,
						     MXC_ISI_PIPE_PAD_SINK);
		sel->r.left = 0;
		sel->r.top = 0;
		sel->r.width = format->width;
		sel->r.height = format->height;
		break;

	case V4L2_SEL_TGT_CROP_BOUNDS:
		if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
			/* No crop rectangle on sink pad. */
			return -EINVAL;

		/* The source crop is bound by the sink compose. */
		rect = mxc_isi_pipe_get_pad_compose(pipe, state,
						    MXC_ISI_PIPE_PAD_SINK);
		sel->r = *rect;
		break;

	case V4L2_SEL_TGT_CROP:
		if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
			/* No crop rectangle on sink pad. */
			return -EINVAL;

		rect = mxc_isi_pipe_get_pad_crop(pipe, state, sel->pad);
		sel->r = *rect;
		break;

	case V4L2_SEL_TGT_COMPOSE:
		if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
			/* No compose rectangle on source pad. */
			return -EINVAL;

		rect = mxc_isi_pipe_get_pad_compose(pipe, state, sel->pad);
		sel->r = *rect;
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd,
				      struct v4l2_subdev_state *state,
				      struct v4l2_subdev_selection *sel)
{
	struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
	struct v4l2_mbus_framefmt *format;
	struct v4l2_rect *rect;

	switch (sel->target) {
	case V4L2_SEL_TGT_CROP:
		if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
			/* The pipeline support cropping on the source only. */
			return -EINVAL;

		/* The source crop is bound by the sink compose. */
		rect = mxc_isi_pipe_get_pad_compose(pipe, state,
						    MXC_ISI_PIPE_PAD_SINK);
		sel->r.left = clamp_t(s32, sel->r.left, 0, rect->width - 1);
		sel->r.top = clamp_t(s32, sel->r.top, 0, rect->height - 1);
		sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
				     rect->width - sel->r.left);
		sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
				      rect->height - sel->r.top);

		rect = mxc_isi_pipe_get_pad_crop(pipe, state,
						 MXC_ISI_PIPE_PAD_SOURCE);
		*rect = sel->r;

		/* Propagate the crop rectangle to the source pad. */
		format = mxc_isi_pipe_get_pad_format(pipe, state,
						     MXC_ISI_PIPE_PAD_SOURCE);
		format->width = sel->r.width;
		format->height = sel->r.height;
		break;

	case V4L2_SEL_TGT_COMPOSE:
		if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
			/* Composing is supported on the sink only. */
			return -EINVAL;

		/* The sink crop is bound by the sink format downscaling only). */
		format = mxc_isi_pipe_get_pad_format(pipe, state,
						     MXC_ISI_PIPE_PAD_SINK);

		sel->r.left = 0;
		sel->r.top = 0;
		sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
				     format->width);
		sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
				      format->height);

		rect = mxc_isi_pipe_get_pad_compose(pipe, state,
						    MXC_ISI_PIPE_PAD_SINK);
		*rect = sel->r;

		/* Propagate the compose rectangle to the source pad. */
		rect = mxc_isi_pipe_get_pad_crop(pipe, state,
						 MXC_ISI_PIPE_PAD_SOURCE);
		rect->left = 0;
		rect->top = 0;
		rect->width = sel->r.width;
		rect->height = sel->r.height;

		format = mxc_isi_pipe_get_pad_format(pipe, state,
						     MXC_ISI_PIPE_PAD_SOURCE);
		format->width = sel->r.width;
		format->height = sel->r.height;
		break;

	default:
		return -EINVAL;
	}

	dev_info(pipe->isi->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
		sel->target, sel->r.left, sel->r.top, sel->r.width,
		sel->r.height);

	return 0;
}

static const struct v4l2_subdev_pad_ops mxc_isi_pipe_subdev_pad_ops = {
	.enum_mbus_code = mxc_isi_pipe_enum_mbus_code,
	.get_fmt = v4l2_subdev_get_fmt,
	.set_fmt = mxc_isi_pipe_set_fmt,
	.get_selection = mxc_isi_pipe_get_selection,
	.set_selection = mxc_isi_pipe_set_selection,
};

static const struct v4l2_subdev_ops mxc_isi_pipe_subdev_ops = {
	.pad = &mxc_isi_pipe_subdev_pad_ops,
};

static const struct v4l2_subdev_internal_ops mxc_isi_pipe_internal_ops = {
	.init_state = mxc_isi_pipe_init_state,
};

/* -----------------------------------------------------------------------------
 * IRQ handling
 */

static irqreturn_t mxc_isi_pipe_irq_handler(int irq, void *priv)
{
	struct mxc_isi_pipe *pipe = priv;
	const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
	u32 status;

	status = mxc_isi_channel_irq_status(pipe, true);

	if (status & CHNL_STS_FRM_STRD) {
		if (!WARN_ON(!pipe->irq_handler))
			pipe->irq_handler(pipe, status);
	}

	if (status & (CHNL_STS_AXI_WR_ERR_Y |
		      CHNL_STS_AXI_WR_ERR_U |
		      CHNL_STS_AXI_WR_ERR_V))
		dev_info(pipe->isi->dev, "%s: IRQ AXI Error stat=0x%X\n",
			__func__, status);

	if (status & (ier_reg->panic_y_buf_en.mask |
		      ier_reg->panic_u_buf_en.mask |
		      ier_reg->panic_v_buf_en.mask))
		dev_info(pipe->isi->dev, "%s: IRQ Panic OFLW Error stat=0x%X\n",
			__func__, status);

	if (status & (ier_reg->oflw_y_buf_en.mask |
		      ier_reg->oflw_u_buf_en.mask |
		      ier_reg->oflw_v_buf_en.mask))
		dev_info(pipe->isi->dev, "%s: IRQ OFLW Error stat=0x%X\n",
			__func__, status);

	if (status & (ier_reg->excs_oflw_y_buf_en.mask |
		      ier_reg->excs_oflw_u_buf_en.mask |
		      ier_reg->excs_oflw_v_buf_en.mask))
		dev_info(pipe->isi->dev, "%s: IRQ EXCS OFLW Error stat=0x%X\n",
			__func__, status);

	return IRQ_HANDLED;
}

/* -----------------------------------------------------------------------------
 * Init & cleanup
 */

static const struct media_entity_operations mxc_isi_pipe_entity_ops = {
	.link_validate	= v4l2_subdev_link_validate,
};

int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id)
{
	struct mxc_isi_pipe *pipe = &isi->pipes[id];
	struct v4l2_subdev *sd;
	int irq;
	int ret;

	pipe->id = id;
	pipe->isi = isi;
	pipe->regs = isi->regs + id * isi->pdata->reg_offset;

	mutex_init(&pipe->lock);

	pipe->available_res = MXC_ISI_CHANNEL_RES_LINE_BUF
			    | MXC_ISI_CHANNEL_RES_OUTPUT_BUF;
	pipe->acquired_res = 0;
	pipe->chained_res = 0;
	pipe->chained = false;

	sd = &pipe->sd;
	v4l2_subdev_init(sd, &mxc_isi_pipe_subdev_ops);
	sd->internal_ops = &mxc_isi_pipe_internal_ops;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", pipe->id);
	sd->dev = isi->dev;

	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
	sd->entity.ops = &mxc_isi_pipe_entity_ops;

	pipe->pads[MXC_ISI_PIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
	pipe->pads[MXC_ISI_PIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;

	ret = media_entity_pads_init(&sd->entity, MXC_ISI_PIPE_PADS_NUM,
				     pipe->pads);
	if (ret)
		goto error;

	ret = v4l2_subdev_init_finalize(sd);
	if (ret < 0)
		goto error;

	/* ISI channel is bypassed by default */
	pipe->bypass = true;

	/* Register IRQ handler. */
	mxc_isi_channel_irq_clear(pipe);

	irq = platform_get_irq(to_platform_device(isi->dev), id);
	if (irq < 0) {
		ret = irq;
		goto error;
	}

	ret = devm_request_irq(isi->dev, irq, mxc_isi_pipe_irq_handler,
			       0, dev_name(isi->dev), pipe);
	if (ret < 0) {
		dev_err(isi->dev, "failed to request IRQ (%d)\n", ret);
		goto error;
	}

	return 0;

error:
	media_entity_cleanup(&sd->entity);
	mutex_destroy(&pipe->lock);

	return ret;
}

void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe)
{
	struct v4l2_subdev *sd = &pipe->sd;

	media_entity_cleanup(&sd->entity);
	mutex_destroy(&pipe->lock);
}

int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
			 mxc_isi_pipe_irq_t irq_handler)
{
	struct v4l2_mbus_framefmt *sink_fmt;
	struct v4l2_subdev *sd = &pipe->sd;
	struct v4l2_subdev_state *state;
	int ret;

	state = v4l2_subdev_lock_and_get_active_state(sd);
	sink_fmt = v4l2_subdev_state_get_format(state, MXC_ISI_PIPE_PAD_SINK);
	v4l2_subdev_unlock_state(state);

	ret = mxc_isi_channel_acquire(pipe, irq_handler, pipe->bypass);
	if (ret)
		return ret;

	/* Chain the channel if needed for wide resolutions. */
	if (sink_fmt->width > MXC_ISI_MAX_WIDTH_UNCHAINED && !pipe->bypass) {
		ret = mxc_isi_channel_chain(pipe, false);
		if (ret)
			mxc_isi_channel_release(pipe);
	}

	return ret;
}

void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe)
{
	mxc_isi_channel_release(pipe);
	mxc_isi_channel_unchain(pipe);
}