| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * V4L2 Capture IC Preprocess Subdev for Freescale i.MX5/6 SOC |
| * |
| * This subdevice handles capture of video frames from the CSI or VDIC, |
| * which are routed directly to the Image Converter preprocess tasks, |
| * for resizing, colorspace conversion, and rotation. |
| * |
| * Copyright (c) 2012-2017 Mentor Graphics Inc. |
| */ |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.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-subdev.h> |
| #include <media/imx.h> |
| #include "imx-media.h" |
| #include "imx-ic.h" |
| |
| /* |
| * Min/Max supported width and heights. |
| */ |
| #define MIN_W 32 |
| #define MIN_H 32 |
| #define MAX_W 4096 |
| #define MAX_H 4096 |
| #define W_ALIGN 4 /* multiple of 16 pixels */ |
| #define H_ALIGN 1 /* multiple of 2 lines */ |
| #define S_ALIGN 1 /* multiple of 2 */ |
| |
| struct prp_priv { |
| struct imx_ic_priv *ic_priv; |
| struct media_pad pad[PRP_NUM_PADS]; |
| |
| /* lock to protect all members below */ |
| struct mutex lock; |
| |
| struct v4l2_subdev *src_sd; |
| struct v4l2_subdev *sink_sd_prpenc; |
| struct v4l2_subdev *sink_sd_prpvf; |
| |
| /* the CSI id at link validate */ |
| int csi_id; |
| |
| struct v4l2_mbus_framefmt format_mbus; |
| struct v4l2_fract frame_interval; |
| |
| int stream_count; |
| }; |
| |
| static inline struct prp_priv *sd_to_priv(struct v4l2_subdev *sd) |
| { |
| struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
| |
| return ic_priv->task_priv; |
| } |
| |
| static int prp_start(struct prp_priv *priv) |
| { |
| struct imx_ic_priv *ic_priv = priv->ic_priv; |
| bool src_is_vdic; |
| |
| /* set IC to receive from CSI or VDI depending on source */ |
| src_is_vdic = !!(priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC); |
| |
| ipu_set_ic_src_mux(ic_priv->ipu, priv->csi_id, src_is_vdic); |
| |
| return 0; |
| } |
| |
| static void prp_stop(struct prp_priv *priv) |
| { |
| } |
| |
| static struct v4l2_mbus_framefmt * |
| __prp_get_fmt(struct prp_priv *priv, struct v4l2_subdev_state *sd_state, |
| unsigned int pad, enum v4l2_subdev_format_whence which) |
| { |
| struct imx_ic_priv *ic_priv = priv->ic_priv; |
| |
| if (which == V4L2_SUBDEV_FORMAT_TRY) |
| return v4l2_subdev_get_try_format(&ic_priv->sd, sd_state, pad); |
| else |
| return &priv->format_mbus; |
| } |
| |
| /* |
| * V4L2 subdev operations. |
| */ |
| |
| static int prp_enum_mbus_code(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_mbus_code_enum *code) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| struct v4l2_mbus_framefmt *infmt; |
| int ret = 0; |
| |
| mutex_lock(&priv->lock); |
| |
| switch (code->pad) { |
| case PRP_SINK_PAD: |
| ret = imx_media_enum_ipu_formats(&code->code, code->index, |
| PIXFMT_SEL_YUV_RGB); |
| break; |
| case PRP_SRC_PAD_PRPENC: |
| case PRP_SRC_PAD_PRPVF: |
| if (code->index != 0) { |
| ret = -EINVAL; |
| goto out; |
| } |
| infmt = __prp_get_fmt(priv, sd_state, PRP_SINK_PAD, |
| code->which); |
| code->code = infmt->code; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_get_fmt(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_format *sdformat) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| struct v4l2_mbus_framefmt *fmt; |
| int ret = 0; |
| |
| if (sdformat->pad >= PRP_NUM_PADS) |
| return -EINVAL; |
| |
| mutex_lock(&priv->lock); |
| |
| fmt = __prp_get_fmt(priv, sd_state, sdformat->pad, sdformat->which); |
| if (!fmt) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| sdformat->format = *fmt; |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_set_fmt(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_format *sdformat) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| struct v4l2_mbus_framefmt *fmt, *infmt; |
| const struct imx_media_pixfmt *cc; |
| int ret = 0; |
| u32 code; |
| |
| if (sdformat->pad >= PRP_NUM_PADS) |
| return -EINVAL; |
| |
| mutex_lock(&priv->lock); |
| |
| if (priv->stream_count > 0) { |
| ret = -EBUSY; |
| goto out; |
| } |
| |
| infmt = __prp_get_fmt(priv, sd_state, PRP_SINK_PAD, sdformat->which); |
| |
| switch (sdformat->pad) { |
| case PRP_SINK_PAD: |
| v4l_bound_align_image(&sdformat->format.width, MIN_W, MAX_W, |
| W_ALIGN, &sdformat->format.height, |
| MIN_H, MAX_H, H_ALIGN, S_ALIGN); |
| |
| cc = imx_media_find_ipu_format(sdformat->format.code, |
| PIXFMT_SEL_YUV_RGB); |
| if (!cc) { |
| imx_media_enum_ipu_formats(&code, 0, |
| PIXFMT_SEL_YUV_RGB); |
| cc = imx_media_find_ipu_format(code, |
| PIXFMT_SEL_YUV_RGB); |
| sdformat->format.code = cc->codes[0]; |
| } |
| |
| if (sdformat->format.field == V4L2_FIELD_ANY) |
| sdformat->format.field = V4L2_FIELD_NONE; |
| break; |
| case PRP_SRC_PAD_PRPENC: |
| case PRP_SRC_PAD_PRPVF: |
| /* Output pads mirror input pad */ |
| sdformat->format = *infmt; |
| break; |
| } |
| |
| imx_media_try_colorimetry(&sdformat->format, true); |
| |
| fmt = __prp_get_fmt(priv, sd_state, sdformat->pad, sdformat->which); |
| *fmt = sdformat->format; |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_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 prp_priv *priv = ic_priv->task_priv; |
| struct v4l2_subdev *remote_sd; |
| int ret = 0; |
| |
| dev_dbg(ic_priv->ipu_dev, "%s: link setup %s -> %s", |
| ic_priv->sd.name, remote->entity->name, local->entity->name); |
| |
| remote_sd = media_entity_to_v4l2_subdev(remote->entity); |
| |
| mutex_lock(&priv->lock); |
| |
| if (local->flags & MEDIA_PAD_FL_SINK) { |
| if (flags & MEDIA_LNK_FL_ENABLED) { |
| if (priv->src_sd) { |
| ret = -EBUSY; |
| goto out; |
| } |
| if (priv->sink_sd_prpenc && |
| (remote_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| priv->src_sd = remote_sd; |
| } else { |
| priv->src_sd = NULL; |
| } |
| |
| goto out; |
| } |
| |
| /* this is a source pad */ |
| if (flags & MEDIA_LNK_FL_ENABLED) { |
| switch (local->index) { |
| case PRP_SRC_PAD_PRPENC: |
| if (priv->sink_sd_prpenc) { |
| ret = -EBUSY; |
| goto out; |
| } |
| if (priv->src_sd && (priv->src_sd->grp_id & |
| IMX_MEDIA_GRP_ID_IPU_VDIC)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| priv->sink_sd_prpenc = remote_sd; |
| break; |
| case PRP_SRC_PAD_PRPVF: |
| if (priv->sink_sd_prpvf) { |
| ret = -EBUSY; |
| goto out; |
| } |
| priv->sink_sd_prpvf = remote_sd; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| } else { |
| switch (local->index) { |
| case PRP_SRC_PAD_PRPENC: |
| priv->sink_sd_prpenc = NULL; |
| break; |
| case PRP_SRC_PAD_PRPVF: |
| priv->sink_sd_prpvf = NULL; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| } |
| |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_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 prp_priv *priv = ic_priv->task_priv; |
| struct v4l2_subdev *csi; |
| int ret; |
| |
| ret = v4l2_subdev_link_validate_default(sd, link, |
| source_fmt, sink_fmt); |
| if (ret) |
| return ret; |
| |
| csi = imx_media_pipeline_subdev(&ic_priv->sd.entity, |
| IMX_MEDIA_GRP_ID_IPU_CSI, true); |
| if (IS_ERR(csi)) |
| csi = NULL; |
| |
| mutex_lock(&priv->lock); |
| |
| if (priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC) { |
| /* |
| * the ->PRPENC link cannot be enabled if the source |
| * is the VDIC |
| */ |
| if (priv->sink_sd_prpenc) { |
| ret = -EINVAL; |
| goto out; |
| } |
| } else { |
| /* the source is a CSI */ |
| if (!csi) { |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| if (csi) { |
| switch (csi->grp_id) { |
| case IMX_MEDIA_GRP_ID_IPU_CSI0: |
| priv->csi_id = 0; |
| break; |
| case IMX_MEDIA_GRP_ID_IPU_CSI1: |
| priv->csi_id = 1; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| } else { |
| priv->csi_id = 0; |
| } |
| |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
| struct prp_priv *priv = ic_priv->task_priv; |
| int ret = 0; |
| |
| mutex_lock(&priv->lock); |
| |
| if (!priv->src_sd || (!priv->sink_sd_prpenc && !priv->sink_sd_prpvf)) { |
| ret = -EPIPE; |
| goto out; |
| } |
| |
| /* |
| * enable/disable streaming only if stream_count is |
| * going from 0 to 1 / 1 to 0. |
| */ |
| if (priv->stream_count != !enable) |
| goto update_count; |
| |
| dev_dbg(ic_priv->ipu_dev, "%s: stream %s\n", sd->name, |
| enable ? "ON" : "OFF"); |
| |
| if (enable) |
| ret = prp_start(priv); |
| else |
| prp_stop(priv); |
| if (ret) |
| goto out; |
| |
| /* start/stop upstream */ |
| ret = v4l2_subdev_call(priv->src_sd, video, s_stream, enable); |
| ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; |
| if (ret) { |
| if (enable) |
| prp_stop(priv); |
| goto out; |
| } |
| |
| update_count: |
| priv->stream_count += enable ? 1 : -1; |
| if (priv->stream_count < 0) |
| priv->stream_count = 0; |
| out: |
| mutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| static int prp_g_frame_interval(struct v4l2_subdev *sd, |
| struct v4l2_subdev_frame_interval *fi) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| |
| if (fi->pad >= PRP_NUM_PADS) |
| return -EINVAL; |
| |
| mutex_lock(&priv->lock); |
| fi->interval = priv->frame_interval; |
| mutex_unlock(&priv->lock); |
| |
| return 0; |
| } |
| |
| static int prp_s_frame_interval(struct v4l2_subdev *sd, |
| struct v4l2_subdev_frame_interval *fi) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| |
| if (fi->pad >= PRP_NUM_PADS) |
| return -EINVAL; |
| |
| mutex_lock(&priv->lock); |
| |
| /* No limits on valid frame intervals */ |
| if (fi->interval.numerator == 0 || fi->interval.denominator == 0) |
| fi->interval = priv->frame_interval; |
| else |
| priv->frame_interval = fi->interval; |
| |
| mutex_unlock(&priv->lock); |
| |
| return 0; |
| } |
| |
| static int prp_registered(struct v4l2_subdev *sd) |
| { |
| struct prp_priv *priv = sd_to_priv(sd); |
| u32 code; |
| |
| /* init default frame interval */ |
| priv->frame_interval.numerator = 1; |
| priv->frame_interval.denominator = 30; |
| |
| /* set a default mbus format */ |
| imx_media_enum_ipu_formats(&code, 0, PIXFMT_SEL_YUV); |
| |
| return imx_media_init_mbus_fmt(&priv->format_mbus, |
| IMX_MEDIA_DEF_PIX_WIDTH, |
| IMX_MEDIA_DEF_PIX_HEIGHT, code, |
| V4L2_FIELD_NONE, NULL); |
| } |
| |
| static const struct v4l2_subdev_pad_ops prp_pad_ops = { |
| .init_cfg = imx_media_init_cfg, |
| .enum_mbus_code = prp_enum_mbus_code, |
| .get_fmt = prp_get_fmt, |
| .set_fmt = prp_set_fmt, |
| .link_validate = prp_link_validate, |
| }; |
| |
| static const struct v4l2_subdev_video_ops prp_video_ops = { |
| .g_frame_interval = prp_g_frame_interval, |
| .s_frame_interval = prp_s_frame_interval, |
| .s_stream = prp_s_stream, |
| }; |
| |
| static const struct media_entity_operations prp_entity_ops = { |
| .link_setup = prp_link_setup, |
| .link_validate = v4l2_subdev_link_validate, |
| }; |
| |
| static const struct v4l2_subdev_ops prp_subdev_ops = { |
| .video = &prp_video_ops, |
| .pad = &prp_pad_ops, |
| }; |
| |
| static const struct v4l2_subdev_internal_ops prp_internal_ops = { |
| .registered = prp_registered, |
| }; |
| |
| static int prp_init(struct imx_ic_priv *ic_priv) |
| { |
| struct prp_priv *priv; |
| int i; |
| |
| priv = devm_kzalloc(ic_priv->ipu_dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| mutex_init(&priv->lock); |
| ic_priv->task_priv = priv; |
| priv->ic_priv = ic_priv; |
| |
| for (i = 0; i < PRP_NUM_PADS; i++) |
| priv->pad[i].flags = (i == PRP_SINK_PAD) ? |
| MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; |
| |
| return media_entity_pads_init(&ic_priv->sd.entity, PRP_NUM_PADS, |
| priv->pad); |
| } |
| |
| static void prp_remove(struct imx_ic_priv *ic_priv) |
| { |
| struct prp_priv *priv = ic_priv->task_priv; |
| |
| mutex_destroy(&priv->lock); |
| } |
| |
| struct imx_ic_ops imx_ic_prp_ops = { |
| .subdev_ops = &prp_subdev_ops, |
| .internal_ops = &prp_internal_ops, |
| .entity_ops = &prp_entity_ops, |
| .init = prp_init, |
| .remove = prp_remove, |
| }; |