root/drivers/staging/media/imx/imx-ic-prp.c
// 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)
{
        if (which == V4L2_SUBDEV_FORMAT_TRY)
                return v4l2_subdev_state_get_format(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_get_frame_interval(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_state *sd_state,
                                  struct v4l2_subdev_frame_interval *fi)
{
        struct prp_priv *priv = sd_to_priv(sd);

        /*
         * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2
         * subdev active state API.
         */
        if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE)
                return -EINVAL;

        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_set_frame_interval(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_state *sd_state,
                                  struct v4l2_subdev_frame_interval *fi)
{
        struct prp_priv *priv = sd_to_priv(sd);

        /*
         * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2
         * subdev active state API.
         */
        if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE)
                return -EINVAL;

        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 = {
        .enum_mbus_code = prp_enum_mbus_code,
        .get_fmt = prp_get_fmt,
        .set_fmt = prp_set_fmt,
        .get_frame_interval = prp_get_frame_interval,
        .set_frame_interval = prp_set_frame_interval,
        .link_validate = prp_link_validate,
};

static const struct v4l2_subdev_video_ops prp_video_ops = {
        .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 = {
        .init_state = imx_media_init_state,
        .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,
};