On 01/07/2017 03:11 AM, Steve Longerbeam wrote: > This is a set of three media entity subdevice drivers for the i.MX > Image Converter. The i.MX IC module contains three independent > "tasks": > > - Pre-processing Encode task: video frames are routed directly from > the CSI and can be scaled, color-space converted, and rotated. > Scaled output is limited to 1024x1024 resolution. Output frames > are routed to the camera interface entities (camif). > > - Pre-processing Viewfinder task: this task can perform the same > conversions as the pre-process encode task, but in addition can > be used for hardware motion compensated deinterlacing. Frames can > come either directly from the CSI or from the SMFC entities (memory > buffers via IDMAC channels). Scaled output is limited to 1024x1024 > resolution. Output frames can be routed to various sinks including > the post-processing task entities. > > - Post-processing task: same conversions as pre-process encode. However > this entity sends frames to the i.MX IPU image converter which supports > image tiling, which allows scaled output up to 4096x4096 resolution. > Output frames can be routed to the camera interfaces. > > Signed-off-by: Steve Longerbeam <steve_longerbeam@xxxxxxxxxx> > --- > drivers/staging/media/imx/Makefile | 2 + > drivers/staging/media/imx/imx-ic-common.c | 109 +++ > drivers/staging/media/imx/imx-ic-pp.c | 636 ++++++++++++++++ > drivers/staging/media/imx/imx-ic-prpenc.c | 1033 +++++++++++++++++++++++++ > drivers/staging/media/imx/imx-ic-prpvf.c | 1179 +++++++++++++++++++++++++++++ > drivers/staging/media/imx/imx-ic.h | 38 + > 6 files changed, 2997 insertions(+) > create mode 100644 drivers/staging/media/imx/imx-ic-common.c > create mode 100644 drivers/staging/media/imx/imx-ic-pp.c > create mode 100644 drivers/staging/media/imx/imx-ic-prpenc.c > create mode 100644 drivers/staging/media/imx/imx-ic-prpvf.c > create mode 100644 drivers/staging/media/imx/imx-ic.h > > diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile > index 3559d7b..d2a962c 100644 > --- a/drivers/staging/media/imx/Makefile > +++ b/drivers/staging/media/imx/Makefile > @@ -1,8 +1,10 @@ > imx-media-objs := imx-media-dev.o imx-media-fim.o imx-media-internal-sd.o \ > imx-media-of.o > +imx-ic-objs := imx-ic-common.o imx-ic-prpenc.o imx-ic-prpvf.o imx-ic-pp.o > > obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media.o > obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-common.o > +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-ic.o > > obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-csi.o > obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-smfc.o > diff --git a/drivers/staging/media/imx/imx-ic-common.c b/drivers/staging/media/imx/imx-ic-common.c > new file mode 100644 > index 0000000..45706ca > --- /dev/null > +++ b/drivers/staging/media/imx/imx-ic-common.c > @@ -0,0 +1,109 @@ > +/* > + * V4L2 Image Converter Subdev for Freescale i.MX5/6 SOC > + * > + * Copyright (c) 2014-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 <linux/platform_device.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-subdev.h> > +#include "imx-media.h" > +#include "imx-ic.h" > + > +static struct imx_ic_ops *ic_ops[IC_NUM_TASKS] = { > + [IC_TASK_ENCODER] = &imx_ic_prpenc_ops, > + [IC_TASK_VIEWFINDER] = &imx_ic_prpvf_ops, > + [IC_TASK_POST_PROCESSOR] = &imx_ic_pp_ops, > +}; > + > +static int imx_ic_probe(struct platform_device *pdev) > +{ > + struct imx_media_internal_sd_platformdata *pdata; > + struct imx_ic_priv *priv; > + int ret; > + > + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, &priv->sd); > + priv->dev = &pdev->dev; > + > + /* get our ipu_id, grp_id and IC task id */ > + pdata = priv->dev->platform_data; > + priv->ipu_id = pdata->ipu_id; > + switch (pdata->grp_id) { > + case IMX_MEDIA_GRP_ID_IC_PRPENC: > + priv->task_id = IC_TASK_ENCODER; > + break; > + case IMX_MEDIA_GRP_ID_IC_PRPVF: > + priv->task_id = IC_TASK_VIEWFINDER; > + break; > + case IMX_MEDIA_GRP_ID_IC_PP0...IMX_MEDIA_GRP_ID_IC_PP3: > + priv->task_id = IC_TASK_POST_PROCESSOR; > + break; > + default: > + return -EINVAL; > + } > + > + v4l2_subdev_init(&priv->sd, ic_ops[priv->task_id]->subdev_ops); > + v4l2_set_subdevdata(&priv->sd, priv); > + priv->sd.internal_ops = ic_ops[priv->task_id]->internal_ops; > + priv->sd.entity.ops = ic_ops[priv->task_id]->entity_ops; > + priv->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; > + priv->sd.dev = &pdev->dev; > + priv->sd.owner = THIS_MODULE; > + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; > + priv->sd.grp_id = pdata->grp_id; > + strncpy(priv->sd.name, pdata->sd_name, sizeof(priv->sd.name)); > + > + ret = ic_ops[priv->task_id]->init(priv); > + if (ret) > + return ret; > + > + ret = v4l2_async_register_subdev(&priv->sd); > + if (ret) > + ic_ops[priv->task_id]->remove(priv); > + > + return ret; > +} > + > +static int imx_ic_remove(struct platform_device *pdev) > +{ > + struct v4l2_subdev *sd = platform_get_drvdata(pdev); > + struct imx_ic_priv *priv = container_of(sd, struct imx_ic_priv, sd); > + > + ic_ops[priv->task_id]->remove(priv); > + > + v4l2_async_unregister_subdev(&priv->sd); > + media_entity_cleanup(&priv->sd.entity); > + v4l2_device_unregister_subdev(sd); > + > + return 0; > +} > + > +static const struct platform_device_id imx_ic_ids[] = { > + { .name = "imx-ipuv3-ic" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(platform, imx_ic_ids); > + > +static struct platform_driver imx_ic_driver = { > + .probe = imx_ic_probe, > + .remove = imx_ic_remove, > + .id_table = imx_ic_ids, > + .driver = { > + .name = "imx-ipuv3-ic", > + }, > +}; > +module_platform_driver(imx_ic_driver); > + > +MODULE_DESCRIPTION("i.MX IC subdev driver"); > +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:imx-ipuv3-ic"); > diff --git a/drivers/staging/media/imx/imx-ic-pp.c b/drivers/staging/media/imx/imx-ic-pp.c > new file mode 100644 > index 0000000..1f75616 > --- /dev/null > +++ b/drivers/staging/media/imx/imx-ic-pp.c > @@ -0,0 +1,636 @@ > +/* > + * V4L2 IC Post-Processor Subdev for Freescale i.MX5/6 SOC > + * > + * Copyright (c) 2014-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/delay.h> > +#include <linux/fs.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/pinctrl/consumer.h> > +#include <linux/platform_device.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/timer.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-ioctl.h> > +#include <media/v4l2-of.h> > +#include <media/v4l2-subdev.h> > +#include <media/videobuf2-dma-contig.h> > +#include <video/imx-ipu-image-convert.h> > +#include <media/imx.h> > +#include "imx-media.h" > +#include "imx-ic.h" > + > +#define PP_NUM_PADS 2 > + > +struct pp_priv { > + struct imx_media_dev *md; > + struct imx_ic_priv *ic_priv; > + int pp_id; > + > + struct ipu_soc *ipu; > + struct ipu_image_convert_ctx *ic_ctx; > + > + struct media_pad pad[PP_NUM_PADS]; > + int input_pad; > + int output_pad; > + > + /* our dma buffer sink ring */ > + struct imx_media_dma_buf_ring *in_ring; > + /* the dma buffer ring we send to sink */ > + struct imx_media_dma_buf_ring *out_ring; > + struct ipu_image_convert_run *out_run; > + > + struct imx_media_dma_buf *inbuf; /* last input buffer */ > + > + bool stream_on; /* streaming is on */ > + bool stop; /* streaming is stopping */ > + spinlock_t irqlock; > + > + struct v4l2_subdev *src_sd; > + struct v4l2_subdev *sink_sd; > + > + struct v4l2_mbus_framefmt format_mbus[PP_NUM_PADS]; > + const struct imx_media_pixfmt *cc[PP_NUM_PADS]; > + > + /* motion select control */ > + struct v4l2_ctrl_handler ctrl_hdlr; > + int rotation; /* degrees */ > + bool hflip; > + bool vflip; > + > + /* derived from rotation, hflip, vflip controls */ > + enum ipu_rotate_mode rot_mode; > +}; > + > +static inline struct pp_priv *sd_to_priv(struct v4l2_subdev *sd) > +{ > + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); > + > + return ic_priv->task_priv; > +} > + > +static void pp_convert_complete(struct ipu_image_convert_run *run, > + void *data) > +{ > + struct pp_priv *priv = data; > + struct imx_media_dma_buf *done; > + unsigned long flags; > + > + spin_lock_irqsave(&priv->irqlock, flags); > + > + done = imx_media_dma_buf_get_active(priv->out_ring); > + /* give the completed buffer to the sink */ > + if (!WARN_ON(!done)) > + imx_media_dma_buf_done(done, run->status ? > + IMX_MEDIA_BUF_STATUS_ERROR : > + IMX_MEDIA_BUF_STATUS_DONE); > + > + /* we're done with the inbuf, queue it back */ > + imx_media_dma_buf_queue(priv->in_ring, priv->inbuf->index); > + > + spin_unlock_irqrestore(&priv->irqlock, flags); > +} > + > +static void pp_queue_conversion(struct pp_priv *priv, > + struct imx_media_dma_buf *inbuf) > +{ > + struct ipu_image_convert_run *run; > + struct imx_media_dma_buf *outbuf; > + > + /* get next queued buffer and make it active */ > + outbuf = imx_media_dma_buf_get_next_queued(priv->out_ring); > + imx_media_dma_buf_set_active(outbuf); > + priv->inbuf = inbuf; > + > + run = &priv->out_run[outbuf->index]; > + run->ctx = priv->ic_ctx; > + run->in_phys = inbuf->phys; > + run->out_phys = outbuf->phys; > + ipu_image_convert_queue(run); > +} > + > +static long pp_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) > +{ > + struct pp_priv *priv = sd_to_priv(sd); > + struct imx_media_dma_buf_ring **ring; > + struct imx_media_dma_buf *buf; > + unsigned long flags; > + > + switch (cmd) { > + case IMX_MEDIA_REQ_DMA_BUF_SINK_RING: > + /* src asks for a buffer ring */ > + if (!priv->in_ring) > + return -EINVAL; > + ring = (struct imx_media_dma_buf_ring **)arg; > + *ring = priv->in_ring; > + break; > + case IMX_MEDIA_NEW_DMA_BUF: > + /* src hands us a new buffer */ > + spin_lock_irqsave(&priv->irqlock, flags); > + if (!priv->stop && > + !imx_media_dma_buf_get_active(priv->out_ring)) { > + buf = imx_media_dma_buf_dequeue(priv->in_ring); > + if (buf) > + pp_queue_conversion(priv, buf); > + } > + spin_unlock_irqrestore(&priv->irqlock, flags); > + break; > + case IMX_MEDIA_REL_DMA_BUF_SINK_RING: > + /* src indicates sink buffer ring can be freed */ > + if (!priv->in_ring) > + return 0; > + v4l2_info(sd, "%s: freeing sink ring\n", __func__); > + imx_media_free_dma_buf_ring(priv->in_ring); > + priv->in_ring = NULL; > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int pp_start(struct pp_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + struct ipu_image image_in, image_out; > + const struct imx_media_pixfmt *incc; > + struct v4l2_mbus_framefmt *infmt; > + int i, in_size, ret; > + > + /* ask the sink for the buffer ring */ > + ret = v4l2_subdev_call(priv->sink_sd, core, ioctl, > + IMX_MEDIA_REQ_DMA_BUF_SINK_RING, > + &priv->out_ring); > + if (ret) > + return ret; > + > + imx_media_mbus_fmt_to_ipu_image(&image_in, > + &priv->format_mbus[priv->input_pad]); > + imx_media_mbus_fmt_to_ipu_image(&image_out, > + &priv->format_mbus[priv->output_pad]); > + > + priv->ipu = priv->md->ipu[ic_priv->ipu_id]; > + priv->ic_ctx = ipu_image_convert_prepare(priv->ipu, > + IC_TASK_POST_PROCESSOR, > + &image_in, &image_out, > + priv->rot_mode, > + pp_convert_complete, priv); > + if (IS_ERR(priv->ic_ctx)) > + return PTR_ERR(priv->ic_ctx); > + > + infmt = &priv->format_mbus[priv->input_pad]; > + incc = priv->cc[priv->input_pad]; > + in_size = (infmt->width * incc->bpp * infmt->height) >> 3; > + > + if (priv->in_ring) { > + v4l2_warn(&ic_priv->sd, "%s: dma-buf ring was not freed\n", > + __func__); > + imx_media_free_dma_buf_ring(priv->in_ring); > + } > + > + priv->in_ring = imx_media_alloc_dma_buf_ring(priv->md, > + &priv->src_sd->entity, > + &ic_priv->sd.entity, > + in_size, > + IMX_MEDIA_MIN_RING_BUFS, > + true); > + if (IS_ERR(priv->in_ring)) { > + v4l2_err(&ic_priv->sd, > + "failed to alloc dma-buf ring\n"); > + ret = PTR_ERR(priv->in_ring); > + priv->in_ring = NULL; > + goto out_unprep; > + } > + > + for (i = 0; i < IMX_MEDIA_MIN_RING_BUFS; i++) > + imx_media_dma_buf_queue(priv->in_ring, i); > + > + priv->out_run = kzalloc(IMX_MEDIA_MAX_RING_BUFS * > + sizeof(*priv->out_run), GFP_KERNEL); > + if (!priv->out_run) { > + v4l2_err(&ic_priv->sd, "failed to alloc src ring runs\n"); > + ret = -ENOMEM; > + goto out_free_ring; > + } > + > + priv->stop = false; > + > + return 0; > + > +out_free_ring: > + imx_media_free_dma_buf_ring(priv->in_ring); > + priv->in_ring = NULL; > +out_unprep: > + ipu_image_convert_unprepare(priv->ic_ctx); > + return ret; > +} > + > +static void pp_stop(struct pp_priv *priv) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&priv->irqlock, flags); > + priv->stop = true; > + spin_unlock_irqrestore(&priv->irqlock, flags); > + > + ipu_image_convert_unprepare(priv->ic_ctx); > + kfree(priv->out_run); > + > + priv->out_ring = NULL; > + > + /* inform sink that its sink buffer ring can now be freed */ > + v4l2_subdev_call(priv->sink_sd, core, ioctl, > + IMX_MEDIA_REL_DMA_BUF_SINK_RING, 0); > +} > + > +static int pp_s_stream(struct v4l2_subdev *sd, int enable) > +{ > + struct pp_priv *priv = sd_to_priv(sd); > + int ret = 0; > + > + if (!priv->src_sd || !priv->sink_sd) > + return -EPIPE; > + > + v4l2_info(sd, "stream %s\n", enable ? "ON" : "OFF"); > + > + if (enable && !priv->stream_on) > + ret = pp_start(priv); > + else if (!enable && priv->stream_on) > + pp_stop(priv); > + > + if (!ret) > + priv->stream_on = enable; > + return ret; > +} > + > +static int pp_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + const struct imx_media_pixfmt *cc; > + u32 fourcc; > + int ret; > + > + if (code->pad >= PP_NUM_PADS) > + return -EINVAL; > + > + ret = ipu_image_convert_enum_format(code->index, &fourcc); > + if (ret) > + return ret; > + > + /* convert returned fourcc to mbus code */ > + cc = imx_media_find_format(fourcc, 0, true, true); > + if (WARN_ON(!cc)) > + return -EINVAL; > + > + code->code = cc->codes[0]; > + return 0; > +} > + > +static int pp_get_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *sdformat) > +{ > + struct pp_priv *priv = sd_to_priv(sd); > + > + if (sdformat->pad >= PP_NUM_PADS) > + return -EINVAL; > + > + sdformat->format = priv->format_mbus[sdformat->pad]; > + > + return 0; > +} > + > +static int pp_set_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *sdformat) > +{ > + struct pp_priv *priv = sd_to_priv(sd); > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + const struct imx_media_pixfmt *cc; > + struct ipu_image test_in, test_out; > + u32 code; > + > + if (sdformat->pad >= PP_NUM_PADS) > + return -EINVAL; > + > + if (priv->stream_on) > + return -EBUSY; > + > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + > + cc = imx_media_find_format(0, sdformat->format.code, true, true); > + if (!cc) { > + imx_media_enum_format(&code, 0, true, true); > + cc = imx_media_find_format(0, code, true, true); > + sdformat->format.code = cc->codes[0]; > + } > + > + if (sdformat->pad == priv->output_pad) { > + imx_media_mbus_fmt_to_ipu_image(&test_out, &sdformat->format); > + imx_media_mbus_fmt_to_ipu_image(&test_in, infmt); > + ipu_image_convert_adjust(&test_in, &test_out, priv->rot_mode); > + imx_media_ipu_image_to_mbus_fmt(&sdformat->format, &test_out); > + } else { > + imx_media_mbus_fmt_to_ipu_image(&test_in, &sdformat->format); > + imx_media_mbus_fmt_to_ipu_image(&test_out, outfmt); > + ipu_image_convert_adjust(&test_in, &test_out, priv->rot_mode); > + imx_media_ipu_image_to_mbus_fmt(&sdformat->format, &test_in); > + } > + > + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { > + cfg->try_fmt = sdformat->format; > + } else { > + if (sdformat->pad == priv->output_pad) { > + *outfmt = sdformat->format; > + imx_media_ipu_image_to_mbus_fmt(infmt, &test_in); > + } else { > + *infmt = sdformat->format; > + imx_media_ipu_image_to_mbus_fmt(outfmt, &test_out); > + } > + priv->cc[sdformat->pad] = cc; > + } > + > + return 0; > +} > + > +static int pp_link_setup(struct media_entity *entity, > + const struct media_pad *local, > + const struct media_pad *remote, u32 flags) > +{ > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); > + struct pp_priv *priv = ic_priv->task_priv; > + struct v4l2_subdev *remote_sd; > + > + dev_dbg(ic_priv->dev, "link setup %s -> %s", remote->entity->name, > + local->entity->name); > + > + remote_sd = media_entity_to_v4l2_subdev(remote->entity); > + > + if (local->flags & MEDIA_PAD_FL_SOURCE) { > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (priv->sink_sd) > + return -EBUSY; > + priv->sink_sd = remote_sd; > + } else { > + priv->sink_sd = NULL; > + } > + } else { > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (priv->src_sd) > + return -EBUSY; > + priv->src_sd = remote_sd; > + } else { > + priv->src_sd = NULL; > + } > + } > + > + return 0; > +} > + > +static int pp_s_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct pp_priv *priv = container_of(ctrl->handler, > + struct pp_priv, ctrl_hdlr); > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + enum ipu_rotate_mode rot_mode; > + bool hflip, vflip; > + int rotation, ret; > + > + rotation = priv->rotation; > + hflip = priv->hflip; > + vflip = priv->vflip; > + > + switch (ctrl->id) { > + case V4L2_CID_HFLIP: > + hflip = (ctrl->val == 1); > + break; > + case V4L2_CID_VFLIP: > + vflip = (ctrl->val == 1); > + break; > + case V4L2_CID_ROTATE: > + rotation = ctrl->val; > + break; > + default: > + v4l2_err(&ic_priv->sd, "Invalid control\n"); > + return -EINVAL; > + } > + > + ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, hflip, vflip); > + if (ret) > + return ret; > + > + if (rot_mode != priv->rot_mode) { > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + struct ipu_image test_in, test_out; > + > + /* can't change rotation mid-streaming */ > + if (priv->stream_on) > + return -EBUSY; > + > + /* > + * make sure this rotation will work with current input/output > + * formats before setting > + */ > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + imx_media_mbus_fmt_to_ipu_image(&test_in, infmt); > + imx_media_mbus_fmt_to_ipu_image(&test_out, outfmt); > + > + ret = ipu_image_convert_verify(&test_in, &test_out, rot_mode); > + if (ret) > + return ret; > + > + priv->rot_mode = rot_mode; > + priv->rotation = rotation; > + priv->hflip = hflip; > + priv->vflip = vflip; > + } > + > + return 0; > +} > + > +static const struct v4l2_ctrl_ops pp_ctrl_ops = { > + .s_ctrl = pp_s_ctrl, > +}; > + > +static const struct v4l2_ctrl_config pp_std_ctrl[] = { > + { > + .id = V4L2_CID_HFLIP, > + .name = "Horizontal Flip", > + .type = V4L2_CTRL_TYPE_BOOLEAN, > + .def = 0, > + .min = 0, > + .max = 1, > + .step = 1, > + }, { > + .id = V4L2_CID_VFLIP, > + .name = "Vertical Flip", > + .type = V4L2_CTRL_TYPE_BOOLEAN, > + .def = 0, > + .min = 0, > + .max = 1, > + .step = 1, > + }, { > + .id = V4L2_CID_ROTATE, > + .name = "Rotation", > + .type = V4L2_CTRL_TYPE_INTEGER, > + .def = 0, > + .min = 0, > + .max = 270, > + .step = 90, > + }, > +}; > + > +#define PP_NUM_CONTROLS ARRAY_SIZE(pp_std_ctrl) > + > +static int pp_init_controls(struct pp_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + struct v4l2_ctrl_handler *hdlr = &priv->ctrl_hdlr; > + const struct v4l2_ctrl_config *c; > + int i, ret; > + > + v4l2_ctrl_handler_init(hdlr, PP_NUM_CONTROLS); > + > + for (i = 0; i < PP_NUM_CONTROLS; i++) { > + c = &pp_std_ctrl[i]; > + v4l2_ctrl_new_std(hdlr, &pp_ctrl_ops, > + c->id, c->min, c->max, c->step, c->def); > + } > + > + ic_priv->sd.ctrl_handler = hdlr; > + > + if (hdlr->error) { > + ret = hdlr->error; > + v4l2_ctrl_handler_free(hdlr); > + return ret; > + } > + > + v4l2_ctrl_handler_setup(hdlr); > + > + return 0; > +} > + > +/* > + * retrieve our pads parsed from the OF graph by the media device > + */ > +static int pp_registered(struct v4l2_subdev *sd) > +{ > + struct pp_priv *priv = sd_to_priv(sd); > + struct imx_media_subdev *imxsd; > + struct imx_media_pad *pad; > + int i, ret; > + > + /* get media device */ > + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); > + > + imxsd = imx_media_find_subdev_by_sd(priv->md, sd); > + if (IS_ERR(imxsd)) > + return PTR_ERR(imxsd); > + > + if (imxsd->num_sink_pads != 1 || imxsd->num_src_pads != 1) > + return -EINVAL; > + > + for (i = 0; i < PP_NUM_PADS; i++) { > + pad = &imxsd->pad[i]; > + priv->pad[i] = pad->pad; > + if (priv->pad[i].flags & MEDIA_PAD_FL_SINK) > + priv->input_pad = i; > + else > + priv->output_pad = i; > + > + /* set a default mbus format */ > + ret = imx_media_init_mbus_fmt(&priv->format_mbus[i], > + 640, 480, 0, V4L2_FIELD_NONE, > + &priv->cc[i]); > + if (ret) > + return ret; > + } > + > + ret = pp_init_controls(priv); > + if (ret) > + return ret; > + > + ret = media_entity_pads_init(&sd->entity, PP_NUM_PADS, priv->pad); > + if (ret) > + goto free_ctrls; > + > + return 0; > +free_ctrls: > + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); > + return ret; > +} > + > +static struct v4l2_subdev_pad_ops pp_pad_ops = { > + .enum_mbus_code = pp_enum_mbus_code, > + .get_fmt = pp_get_fmt, > + .set_fmt = pp_set_fmt, > +}; > + > +static struct v4l2_subdev_video_ops pp_video_ops = { > + .s_stream = pp_s_stream, > +}; > + > +static struct v4l2_subdev_core_ops pp_core_ops = { > + .ioctl = pp_ioctl, > +}; > + > +static struct media_entity_operations pp_entity_ops = { > + .link_setup = pp_link_setup, > + .link_validate = v4l2_subdev_link_validate, > +}; > + > +static struct v4l2_subdev_ops pp_subdev_ops = { > + .video = &pp_video_ops, > + .pad = &pp_pad_ops, > + .core = &pp_core_ops, > +}; > + > +static struct v4l2_subdev_internal_ops pp_internal_ops = { > + .registered = pp_registered, > +}; > + > +static int pp_init(struct imx_ic_priv *ic_priv) > +{ > + struct pp_priv *priv; > + > + priv = devm_kzalloc(ic_priv->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + ic_priv->task_priv = priv; > + priv->ic_priv = ic_priv; > + spin_lock_init(&priv->irqlock); > + > + /* get our PP id */ > + priv->pp_id = (ic_priv->sd.grp_id >> IMX_MEDIA_GRP_ID_IC_PP_BIT) - 1; > + > + return 0; > +} > + > +static void pp_remove(struct imx_ic_priv *ic_priv) > +{ > + struct pp_priv *priv = ic_priv->task_priv; > + > + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); > +} > + > +struct imx_ic_ops imx_ic_pp_ops = { > + .subdev_ops = &pp_subdev_ops, > + .internal_ops = &pp_internal_ops, > + .entity_ops = &pp_entity_ops, > + .init = pp_init, > + .remove = pp_remove, > +}; > diff --git a/drivers/staging/media/imx/imx-ic-prpenc.c b/drivers/staging/media/imx/imx-ic-prpenc.c > new file mode 100644 > index 0000000..3d85a82 > --- /dev/null > +++ b/drivers/staging/media/imx/imx-ic-prpenc.c > @@ -0,0 +1,1033 @@ > +/* > + * V4L2 Capture IC Encoder Subdev for Freescale i.MX5/6 SOC > + * > + * This subdevice handles capture of video frames from the CSI, which > + * are routed directly to the Image Converter preprocess encode task, > + * for resizing, colorspace conversion, and rotation. > + * > + * Copyright (c) 2012-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/delay.h> > +#include <linux/fs.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/pinctrl/consumer.h> > +#include <linux/platform_device.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/spinlock.h> > +#include <linux/timer.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-ioctl.h> > +#include <media/v4l2-of.h> > +#include <media/v4l2-subdev.h> > +#include <media/videobuf2-dma-contig.h> > +#include <media/imx.h> > +#include "imx-media.h" > +#include "imx-ic.h" > + > +#define PRPENC_NUM_PADS 2 > + > +#define MAX_W_IC 1024 > +#define MAX_H_IC 1024 > +#define MAX_W_SINK 4096 > +#define MAX_H_SINK 4096 > + > +struct prpenc_priv { > + struct imx_media_dev *md; > + struct imx_ic_priv *ic_priv; > + > + /* IPU units we require */ > + struct ipu_soc *ipu; > + struct ipu_ic *ic_enc; > + > + struct media_pad pad[PRPENC_NUM_PADS]; > + int input_pad; > + int output_pad; > + > + struct ipuv3_channel *enc_ch; > + struct ipuv3_channel *enc_rot_in_ch; > + struct ipuv3_channel *enc_rot_out_ch; > + > + /* the dma buffer ring to send to sink */ > + struct imx_media_dma_buf_ring *out_ring; > + struct imx_media_dma_buf *next; > + > + int ipu_buf_num; /* ipu double buffer index: 0-1 */ > + > + struct v4l2_subdev *src_sd; > + struct v4l2_subdev *sink_sd; > + > + /* the CSI id at link validate */ > + int csi_id; > + > + /* the attached sensor at stream on */ > + struct imx_media_subdev *sensor; > + > + struct v4l2_mbus_framefmt format_mbus[PRPENC_NUM_PADS]; > + const struct imx_media_pixfmt *cc[PRPENC_NUM_PADS]; > + > + struct imx_media_dma_buf rot_buf[2]; > + > + /* controls */ > + struct v4l2_ctrl_handler ctrl_hdlr; > + int rotation; /* degrees */ > + bool hflip; > + bool vflip; > + > + /* derived from rotation, hflip, vflip controls */ > + enum ipu_rotate_mode rot_mode; > + > + spinlock_t irqlock; > + > + struct timer_list eof_timeout_timer; > + int eof_irq; > + int nfb4eof_irq; > + > + bool stream_on; /* streaming is on */ > + bool last_eof; /* waiting for last EOF at stream off */ > + struct completion last_eof_comp; > +}; > + > +static inline struct prpenc_priv *sd_to_priv(struct v4l2_subdev *sd) > +{ > + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); > + > + return ic_priv->task_priv; > +} > + > +static void prpenc_put_ipu_resources(struct prpenc_priv *priv) > +{ > + if (!IS_ERR_OR_NULL(priv->ic_enc)) > + ipu_ic_put(priv->ic_enc); > + priv->ic_enc = NULL; > + > + if (!IS_ERR_OR_NULL(priv->enc_ch)) > + ipu_idmac_put(priv->enc_ch); > + priv->enc_ch = NULL; > + > + if (!IS_ERR_OR_NULL(priv->enc_rot_in_ch)) > + ipu_idmac_put(priv->enc_rot_in_ch); > + priv->enc_rot_in_ch = NULL; > + > + if (!IS_ERR_OR_NULL(priv->enc_rot_out_ch)) > + ipu_idmac_put(priv->enc_rot_out_ch); > + priv->enc_rot_out_ch = NULL; > +} > + > +static int prpenc_get_ipu_resources(struct prpenc_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + int ret; > + > + priv->ipu = priv->md->ipu[ic_priv->ipu_id]; > + > + priv->ic_enc = ipu_ic_get(priv->ipu, IC_TASK_ENCODER); > + if (IS_ERR(priv->ic_enc)) { > + v4l2_err(&ic_priv->sd, "failed to get IC ENC\n"); > + ret = PTR_ERR(priv->ic_enc); > + goto out; > + } > + > + priv->enc_ch = ipu_idmac_get(priv->ipu, > + IPUV3_CHANNEL_IC_PRP_ENC_MEM); > + if (IS_ERR(priv->enc_ch)) { > + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", > + IPUV3_CHANNEL_IC_PRP_ENC_MEM); > + ret = PTR_ERR(priv->enc_ch); > + goto out; > + } > + > + priv->enc_rot_in_ch = ipu_idmac_get(priv->ipu, > + IPUV3_CHANNEL_MEM_ROT_ENC); > + if (IS_ERR(priv->enc_rot_in_ch)) { > + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", > + IPUV3_CHANNEL_MEM_ROT_ENC); > + ret = PTR_ERR(priv->enc_rot_in_ch); > + goto out; > + } > + > + priv->enc_rot_out_ch = ipu_idmac_get(priv->ipu, > + IPUV3_CHANNEL_ROT_ENC_MEM); > + if (IS_ERR(priv->enc_rot_out_ch)) { > + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", > + IPUV3_CHANNEL_ROT_ENC_MEM); > + ret = PTR_ERR(priv->enc_rot_out_ch); > + goto out; > + } > + > + return 0; > +out: > + prpenc_put_ipu_resources(priv); > + return ret; > +} > + > +static irqreturn_t prpenc_eof_interrupt(int irq, void *dev_id) > +{ > + struct prpenc_priv *priv = dev_id; > + struct imx_media_dma_buf *done, *next; > + struct ipuv3_channel *channel; > + > + spin_lock(&priv->irqlock); > + > + if (priv->last_eof) { > + complete(&priv->last_eof_comp); > + priv->last_eof = false; > + goto unlock; > + } > + > + /* inform CSI of this EOF so it can monitor frame intervals */ > + v4l2_subdev_call(priv->src_sd, core, interrupt_service_routine, > + 0, NULL); > + > + channel = (ipu_rot_mode_is_irt(priv->rot_mode)) ? > + priv->enc_rot_out_ch : priv->enc_ch; > + > + done = imx_media_dma_buf_get_active(priv->out_ring); > + /* give the completed buffer to the sink */ > + if (!WARN_ON(!done)) > + imx_media_dma_buf_done(done, IMX_MEDIA_BUF_STATUS_DONE); > + > + /* priv->next buffer is now the active one */ > + imx_media_dma_buf_set_active(priv->next); > + > + /* bump the EOF timeout timer */ > + mod_timer(&priv->eof_timeout_timer, > + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); > + > + if (ipu_idmac_buffer_is_ready(channel, priv->ipu_buf_num)) > + ipu_idmac_clear_buffer(channel, priv->ipu_buf_num); > + > + /* get next queued buffer */ > + next = imx_media_dma_buf_get_next_queued(priv->out_ring); > + > + ipu_cpmem_set_buffer(channel, priv->ipu_buf_num, next->phys); > + ipu_idmac_select_buffer(channel, priv->ipu_buf_num); > + > + /* toggle IPU double-buffer index */ > + priv->ipu_buf_num ^= 1; > + priv->next = next; > + > +unlock: > + spin_unlock(&priv->irqlock); > + return IRQ_HANDLED; > +} > + > +static irqreturn_t prpenc_nfb4eof_interrupt(int irq, void *dev_id) > +{ > + struct prpenc_priv *priv = dev_id; > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + static const struct v4l2_event ev = { > + .type = V4L2_EVENT_IMX_NFB4EOF, > + }; > + > + v4l2_err(&ic_priv->sd, "NFB4EOF\n"); > + > + v4l2_subdev_notify_event(&ic_priv->sd, &ev); > + > + return IRQ_HANDLED; > +} > + > +/* > + * EOF timeout timer function. > + */ > +static void prpenc_eof_timeout(unsigned long data) > +{ > + struct prpenc_priv *priv = (struct prpenc_priv *)data; > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + static const struct v4l2_event ev = { > + .type = V4L2_EVENT_IMX_EOF_TIMEOUT, > + }; > + > + v4l2_err(&ic_priv->sd, "EOF timeout\n"); > + > + v4l2_subdev_notify_event(&ic_priv->sd, &ev); > +} > + > +static void prpenc_setup_channel(struct prpenc_priv *priv, > + struct ipuv3_channel *channel, > + enum ipu_rotate_mode rot_mode, > + dma_addr_t addr0, dma_addr_t addr1, > + bool rot_swap_width_height) > +{ > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + unsigned int burst_size; > + struct ipu_image image; > + > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + > + if (rot_swap_width_height) > + swap(outfmt->width, outfmt->height); > + > + ipu_cpmem_zero(channel); > + > + imx_media_mbus_fmt_to_ipu_image(&image, outfmt); > + > + image.phys0 = addr0; > + image.phys1 = addr1; > + ipu_cpmem_set_image(channel, &image); > + > + if (channel == priv->enc_rot_in_ch || > + channel == priv->enc_rot_out_ch) { > + burst_size = 8; > + ipu_cpmem_set_block_mode(channel); > + } else { > + burst_size = (outfmt->width & 0xf) ? 8 : 16; > + } > + > + ipu_cpmem_set_burstsize(channel, burst_size); > + > + if (rot_mode) > + ipu_cpmem_set_rotation(channel, rot_mode); > + > + if (outfmt->field == V4L2_FIELD_NONE && > + (V4L2_FIELD_HAS_BOTH(infmt->field) || > + infmt->field == V4L2_FIELD_ALTERNATE) && > + channel == priv->enc_ch) > + ipu_cpmem_interlaced_scan(channel, image.pix.bytesperline); > + > + ipu_ic_task_idma_init(priv->ic_enc, channel, > + outfmt->width, outfmt->height, > + burst_size, rot_mode); > + ipu_cpmem_set_axi_id(channel, 1); > + > + ipu_idmac_set_double_buffer(channel, true); > + > + if (rot_swap_width_height) > + swap(outfmt->width, outfmt->height); > +} > + > +static int prpenc_setup_rotation(struct prpenc_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + const struct imx_media_pixfmt *outcc, *incc; > + struct imx_media_dma_buf *buf0, *buf1; > + int out_size, ret; > + > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + incc = priv->cc[priv->input_pad]; > + outcc = priv->cc[priv->output_pad]; > + > + out_size = (outfmt->width * outcc->bpp * outfmt->height) >> 3; > + > + ret = imx_media_alloc_dma_buf(priv->md, &priv->rot_buf[0], out_size); > + if (ret) { > + v4l2_err(&ic_priv->sd, "failed to alloc rot_buf[0], %d\n", ret); > + return ret; > + } > + ret = imx_media_alloc_dma_buf(priv->md, &priv->rot_buf[1], out_size); > + if (ret) { > + v4l2_err(&ic_priv->sd, "failed to alloc rot_buf[1], %d\n", ret); > + goto free_rot0; > + } > + > + ret = ipu_ic_task_init(priv->ic_enc, > + infmt->width, infmt->height, > + outfmt->height, outfmt->width, > + incc->cs, outcc->cs); > + if (ret) { > + v4l2_err(&ic_priv->sd, "ipu_ic_task_init failed, %d\n", ret); > + goto free_rot1; > + } > + > + /* init the IC ENC-->MEM IDMAC channel */ > + prpenc_setup_channel(priv, priv->enc_ch, > + IPU_ROTATE_NONE, > + priv->rot_buf[0].phys, > + priv->rot_buf[1].phys, > + true); > + > + /* init the MEM-->IC ENC ROT IDMAC channel */ > + prpenc_setup_channel(priv, priv->enc_rot_in_ch, > + priv->rot_mode, > + priv->rot_buf[0].phys, > + priv->rot_buf[1].phys, > + true); > + > + buf0 = imx_media_dma_buf_get_next_queued(priv->out_ring); > + imx_media_dma_buf_set_active(buf0); > + buf1 = imx_media_dma_buf_get_next_queued(priv->out_ring); > + priv->next = buf1; > + > + /* init the destination IC ENC ROT-->MEM IDMAC channel */ > + prpenc_setup_channel(priv, priv->enc_rot_out_ch, > + IPU_ROTATE_NONE, > + buf0->phys, buf1->phys, > + false); > + > + /* now link IC ENC-->MEM to MEM-->IC ENC ROT */ > + ipu_idmac_link(priv->enc_ch, priv->enc_rot_in_ch); > + > + /* enable the IC */ > + ipu_ic_enable(priv->ic_enc); > + > + /* set buffers ready */ > + ipu_idmac_select_buffer(priv->enc_ch, 0); > + ipu_idmac_select_buffer(priv->enc_ch, 1); > + ipu_idmac_select_buffer(priv->enc_rot_out_ch, 0); > + ipu_idmac_select_buffer(priv->enc_rot_out_ch, 1); > + > + /* enable the channels */ > + ipu_idmac_enable_channel(priv->enc_ch); > + ipu_idmac_enable_channel(priv->enc_rot_in_ch); > + ipu_idmac_enable_channel(priv->enc_rot_out_ch); > + > + /* and finally enable the IC PRPENC task */ > + ipu_ic_task_enable(priv->ic_enc); > + > + return 0; > + > +free_rot1: > + imx_media_free_dma_buf(priv->md, &priv->rot_buf[1]); > +free_rot0: > + imx_media_free_dma_buf(priv->md, &priv->rot_buf[0]); > + return ret; > +} > + > +static void prpenc_unsetup_rotation(struct prpenc_priv *priv) > +{ > + ipu_ic_task_disable(priv->ic_enc); > + > + ipu_idmac_disable_channel(priv->enc_ch); > + ipu_idmac_disable_channel(priv->enc_rot_in_ch); > + ipu_idmac_disable_channel(priv->enc_rot_out_ch); > + > + ipu_idmac_unlink(priv->enc_ch, priv->enc_rot_in_ch); > + > + ipu_ic_disable(priv->ic_enc); > + > + imx_media_free_dma_buf(priv->md, &priv->rot_buf[0]); > + imx_media_free_dma_buf(priv->md, &priv->rot_buf[1]); > +} > + > +static int prpenc_setup_norotation(struct prpenc_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + const struct imx_media_pixfmt *outcc, *incc; > + struct imx_media_dma_buf *buf0, *buf1; > + int ret; > + > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + incc = priv->cc[priv->input_pad]; > + outcc = priv->cc[priv->output_pad]; > + > + ret = ipu_ic_task_init(priv->ic_enc, > + infmt->width, infmt->height, > + outfmt->width, outfmt->height, > + incc->cs, outcc->cs); > + if (ret) { > + v4l2_err(&ic_priv->sd, "ipu_ic_task_init failed, %d\n", ret); > + return ret; > + } > + > + buf0 = imx_media_dma_buf_get_next_queued(priv->out_ring); > + imx_media_dma_buf_set_active(buf0); > + buf1 = imx_media_dma_buf_get_next_queued(priv->out_ring); > + priv->next = buf1; > + > + /* init the IC PRP-->MEM IDMAC channel */ > + prpenc_setup_channel(priv, priv->enc_ch, priv->rot_mode, > + buf0->phys, buf1->phys, > + false); > + > + ipu_cpmem_dump(priv->enc_ch); > + ipu_ic_dump(priv->ic_enc); > + ipu_dump(priv->ipu); > + > + ipu_ic_enable(priv->ic_enc); > + > + /* set buffers ready */ > + ipu_idmac_select_buffer(priv->enc_ch, 0); > + ipu_idmac_select_buffer(priv->enc_ch, 1); > + > + /* enable the channels */ > + ipu_idmac_enable_channel(priv->enc_ch); > + > + /* enable the IC ENCODE task */ > + ipu_ic_task_enable(priv->ic_enc); > + > + return 0; > +} > + > +static void prpenc_unsetup_norotation(struct prpenc_priv *priv) > +{ > + ipu_ic_task_disable(priv->ic_enc); > + ipu_idmac_disable_channel(priv->enc_ch); > + ipu_ic_disable(priv->ic_enc); > +} > + > +static int prpenc_start(struct prpenc_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + int ret; > + > + if (!priv->sensor) { > + v4l2_err(&ic_priv->sd, "no sensor attached\n"); > + return -EINVAL; > + } > + > + ret = prpenc_get_ipu_resources(priv); > + if (ret) > + return ret; > + > + /* set IC to receive from CSI */ > + ipu_set_ic_src_mux(priv->ipu, priv->csi_id, false); > + > + /* ask the sink for the buffer ring */ > + ret = v4l2_subdev_call(priv->sink_sd, core, ioctl, > + IMX_MEDIA_REQ_DMA_BUF_SINK_RING, > + &priv->out_ring); > + if (ret) > + goto out_put_ipu; > + > + priv->ipu_buf_num = 0; > + > + /* init EOF completion waitq */ > + init_completion(&priv->last_eof_comp); > + priv->last_eof = false; > + > + if (ipu_rot_mode_is_irt(priv->rot_mode)) > + ret = prpenc_setup_rotation(priv); > + else > + ret = prpenc_setup_norotation(priv); > + if (ret) > + goto out_put_ipu; > + > + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, > + priv->enc_ch, > + IPU_IRQ_NFB4EOF); > + ret = devm_request_irq(ic_priv->dev, priv->nfb4eof_irq, > + prpenc_nfb4eof_interrupt, 0, > + "imx-ic-prpenc-nfb4eof", priv); > + if (ret) { > + v4l2_err(&ic_priv->sd, > + "Error registering NFB4EOF irq: %d\n", ret); > + goto out_unsetup; > + } > + > + if (ipu_rot_mode_is_irt(priv->rot_mode)) > + priv->eof_irq = ipu_idmac_channel_irq( > + priv->ipu, priv->enc_rot_out_ch, IPU_IRQ_EOF); > + else > + priv->eof_irq = ipu_idmac_channel_irq( > + priv->ipu, priv->enc_ch, IPU_IRQ_EOF); > + > + ret = devm_request_irq(ic_priv->dev, priv->eof_irq, > + prpenc_eof_interrupt, 0, > + "imx-ic-prpenc-eof", priv); > + if (ret) { > + v4l2_err(&ic_priv->sd, > + "Error registering eof irq: %d\n", ret); > + goto out_free_nfb4eof_irq; > + } > + > + /* start the EOF timeout timer */ > + mod_timer(&priv->eof_timeout_timer, > + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); > + > + return 0; > + > +out_free_nfb4eof_irq: > + devm_free_irq(ic_priv->dev, priv->nfb4eof_irq, priv); > +out_unsetup: > + if (ipu_rot_mode_is_irt(priv->rot_mode)) > + prpenc_unsetup_rotation(priv); > + else > + prpenc_unsetup_norotation(priv); > +out_put_ipu: > + prpenc_put_ipu_resources(priv); > + return ret; > +} > + > +static void prpenc_stop(struct prpenc_priv *priv) > +{ > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + unsigned long flags; > + int ret; > + > + /* mark next EOF interrupt as the last before stream off */ > + spin_lock_irqsave(&priv->irqlock, flags); > + priv->last_eof = true; > + spin_unlock_irqrestore(&priv->irqlock, flags); > + > + /* > + * and then wait for interrupt handler to mark completion. > + */ > + ret = wait_for_completion_timeout( > + &priv->last_eof_comp, > + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); > + if (ret == 0) > + v4l2_warn(&ic_priv->sd, "wait last EOF timeout\n"); > + > + devm_free_irq(ic_priv->dev, priv->eof_irq, priv); > + devm_free_irq(ic_priv->dev, priv->nfb4eof_irq, priv); > + > + if (ipu_rot_mode_is_irt(priv->rot_mode)) > + prpenc_unsetup_rotation(priv); > + else > + prpenc_unsetup_norotation(priv); > + > + prpenc_put_ipu_resources(priv); > + > + /* cancel the EOF timeout timer */ > + del_timer_sync(&priv->eof_timeout_timer); > + > + priv->out_ring = NULL; > + > + /* inform sink that the buffer ring can now be freed */ > + v4l2_subdev_call(priv->sink_sd, core, ioctl, > + IMX_MEDIA_REL_DMA_BUF_SINK_RING, 0); > +} > + > +static int prpenc_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct prpenc_priv *priv = sd_to_priv(sd); > + bool allow_planar; > + > + if (code->pad >= PRPENC_NUM_PADS) > + return -EINVAL; > + > + allow_planar = (code->pad == priv->output_pad); > + > + return imx_media_enum_format(&code->code, code->index, > + true, allow_planar); > +} > + > +static int prpenc_get_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *sdformat) > +{ > + struct prpenc_priv *priv = sd_to_priv(sd); > + > + if (sdformat->pad >= PRPENC_NUM_PADS) > + return -EINVAL; > + > + sdformat->format = priv->format_mbus[sdformat->pad]; > + > + return 0; > +} > + > +static int prpenc_set_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_pad_config *cfg, > + struct v4l2_subdev_format *sdformat) > +{ > + struct prpenc_priv *priv = sd_to_priv(sd); > + struct v4l2_mbus_framefmt *infmt, *outfmt; > + const struct imx_media_pixfmt *cc; > + bool allow_planar; > + u32 code; > + > + if (sdformat->pad >= PRPENC_NUM_PADS) > + return -EINVAL; > + > + if (priv->stream_on) > + return -EBUSY; > + > + infmt = &priv->format_mbus[priv->input_pad]; > + outfmt = &priv->format_mbus[priv->output_pad]; > + allow_planar = (sdformat->pad == priv->output_pad); > + > + cc = imx_media_find_format(0, sdformat->format.code, > + true, allow_planar); > + if (!cc) { > + imx_media_enum_format(&code, 0, true, false); > + cc = imx_media_find_format(0, code, true, false); > + sdformat->format.code = cc->codes[0]; > + } > + > + if (sdformat->pad == priv->output_pad) { > + sdformat->format.width = min_t(__u32, > + sdformat->format.width, > + MAX_W_IC); > + sdformat->format.height = min_t(__u32, > + sdformat->format.height, > + MAX_H_IC); > + > + if (sdformat->format.field != V4L2_FIELD_NONE) > + sdformat->format.field = infmt->field; > + > + /* IC resizer cannot downsize more than 4:1 */ > + if (ipu_rot_mode_is_irt(priv->rot_mode)) { > + sdformat->format.width = max_t(__u32, > + sdformat->format.width, > + infmt->height / 4); > + sdformat->format.height = max_t(__u32, > + sdformat->format.height, > + infmt->width / 4); > + } else { > + sdformat->format.width = max_t(__u32, > + sdformat->format.width, > + infmt->width / 4); > + sdformat->format.height = max_t(__u32, > + sdformat->format.height, > + infmt->height / 4); > + } > + } else { > + sdformat->format.width = min_t(__u32, > + sdformat->format.width, > + MAX_W_SINK); > + sdformat->format.height = min_t(__u32, > + sdformat->format.height, > + MAX_H_SINK); > + } > + > + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { > + cfg->try_fmt = sdformat->format; > + } else { > + priv->format_mbus[sdformat->pad] = sdformat->format; > + priv->cc[sdformat->pad] = cc; > + } > + > + return 0; > +} > + > +static int prpenc_link_setup(struct media_entity *entity, > + const struct media_pad *local, > + const struct media_pad *remote, u32 flags) > +{ > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); > + struct prpenc_priv *priv = ic_priv->task_priv; > + struct v4l2_subdev *remote_sd; > + > + dev_dbg(ic_priv->dev, "link setup %s -> %s", remote->entity->name, > + local->entity->name); > + > + remote_sd = media_entity_to_v4l2_subdev(remote->entity); > + > + if (local->flags & MEDIA_PAD_FL_SOURCE) { > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (priv->sink_sd) > + return -EBUSY; > + priv->sink_sd = remote_sd; > + } else { > + priv->sink_sd = NULL; > + } > + > + return 0; > + } > + > + /* this is sink pad */ > + if (flags & MEDIA_LNK_FL_ENABLED) { > + if (priv->src_sd) > + return -EBUSY; > + priv->src_sd = remote_sd; > + } else { > + priv->src_sd = NULL; > + return 0; > + } > + > + switch (remote_sd->grp_id) { > + case IMX_MEDIA_GRP_ID_CSI0: > + priv->csi_id = 0; > + break; > + case IMX_MEDIA_GRP_ID_CSI1: > + priv->csi_id = 1; > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int prpenc_link_validate(struct v4l2_subdev *sd, > + struct media_link *link, > + struct v4l2_subdev_format *source_fmt, > + struct v4l2_subdev_format *sink_fmt) > +{ > + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); > + struct prpenc_priv *priv = ic_priv->task_priv; > + struct v4l2_mbus_config sensor_mbus_cfg; > + int ret; > + > + ret = v4l2_subdev_link_validate_default(sd, link, > + source_fmt, sink_fmt); > + if (ret) > + return ret; > + > + priv->sensor = __imx_media_find_sensor(priv->md, &ic_priv->sd.entity); > + if (IS_ERR(priv->sensor)) { > + v4l2_err(&ic_priv->sd, "no sensor attached\n"); > + ret = PTR_ERR(priv->sensor); > + priv->sensor = NULL; > + return ret; > + } > + > + ret = v4l2_subdev_call(priv->sensor->sd, video, g_mbus_config, > + &sensor_mbus_cfg); > + if (ret) > + return ret; > + > + if (sensor_mbus_cfg.type == V4L2_MBUS_CSI2) { > + int vc_num = 0; > + /* see NOTE in imx-csi.c */ > +#if 0 > + vc_num = imx_media_find_mipi_csi2_channel( > + priv->md, &ic_priv->sd.entity); > + if (vc_num < 0) > + return vc_num; > +#endif > + /* only virtual channel 0 can be sent to IC */ > + if (vc_num != 0) > + return -EINVAL; > + } else { > + /* > + * only 8-bit pixels can be sent to IC for parallel > + * busses > + */ > + if (priv->sensor->sensor_ep.bus.parallel.bus_width >= 16) > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int prpenc_s_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct prpenc_priv *priv = container_of(ctrl->handler, > + struct prpenc_priv, ctrl_hdlr); > + struct imx_ic_priv *ic_priv = priv->ic_priv; > + enum ipu_rotate_mode rot_mode; > + bool hflip, vflip; > + int rotation, ret; > + > + rotation = priv->rotation; > + hflip = priv->hflip; > + vflip = priv->vflip; > + > + switch (ctrl->id) { > + case V4L2_CID_HFLIP: > + hflip = (ctrl->val == 1); > + break; > + case V4L2_CID_VFLIP: > + vflip = (ctrl->val == 1); > + break; > + case V4L2_CID_ROTATE: > + rotation = ctrl->val; > + break; > + default: > + v4l2_err(&ic_priv->sd, "Invalid control\n"); > + return -EINVAL; > + } > + > + ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, hflip, vflip); > + if (ret) > + return ret; > + > + if (rot_mode != priv->rot_mode) { > + /* can't change rotation mid-streaming */ > + if (priv->stream_on) > + return -EBUSY; > + > + priv->rot_mode = rot_mode; > + priv->rotation = rotation; > + priv->hflip = hflip; > + priv->vflip = vflip; > + } > + > + return 0; > +} > + > +static const struct v4l2_ctrl_ops prpenc_ctrl_ops = { > + .s_ctrl = prpenc_s_ctrl, > +}; > + > +static const struct v4l2_ctrl_config prpenc_std_ctrl[] = { > + { > + .id = V4L2_CID_HFLIP, > + .name = "Horizontal Flip", > + .type = V4L2_CTRL_TYPE_BOOLEAN, > + .def = 0, > + .min = 0, > + .max = 1, > + .step = 1, > + }, { > + .id = V4L2_CID_VFLIP, > + .name = "Vertical Flip", > + .type = V4L2_CTRL_TYPE_BOOLEAN, > + .def = 0, > + .min = 0, > + .max = 1, > + .step = 1, > + }, { > + .id = V4L2_CID_ROTATE, > + .name = "Rotation", > + .type = V4L2_CTRL_TYPE_INTEGER, > + .def = 0, > + .min = 0, > + .max = 270, > + .step = 90, > + }, > +}; Use v4l2_ctrl_new_std() instead of this array: this avoids duplicating information like the name and type. If this is also done elsewhere, then it should be changed there as well. Regards, Hans -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html