[PATCH v3 19/24] media: imx: Add IC subdev drivers
From: Hans Verkuil <hidden>
Date: 2017-01-20 14:29:52
Also in:
linux-devicetree, linux-media, lkml
On 01/07/2017 03:11 AM, Steve Longerbeam wrote:
quoted hunk ↗ jump to hunk
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 <redacted> --- 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.hdiff --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.odiff --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@mentor.com>"); +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