root/drivers/staging/media/imx/imx-media-internal-sd.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Media driver for Freescale i.MX5/6 SOC
 *
 * Adds the IPU internal subdevices and the media links between them.
 *
 * Copyright (c) 2016 Mentor Graphics Inc.
 */
#include <linux/platform_device.h>
#include "imx-media.h"

/* max pads per internal-sd */
#define MAX_INTERNAL_PADS   8
/* max links per internal-sd pad */
#define MAX_INTERNAL_LINKS  8

struct internal_subdev;

struct internal_link {
        int remote;
        int local_pad;
        int remote_pad;
};

struct internal_pad {
        int num_links;
        struct internal_link link[MAX_INTERNAL_LINKS];
};

struct internal_subdev {
        u32 grp_id;
        struct internal_pad pad[MAX_INTERNAL_PADS];

        struct v4l2_subdev * (*sync_register)(struct v4l2_device *v4l2_dev,
                                              struct device *ipu_dev,
                                              struct ipu_soc *ipu,
                                              u32 grp_id);
        int (*sync_unregister)(struct v4l2_subdev *sd);
};

static const struct internal_subdev int_subdev[NUM_IPU_SUBDEVS] = {
        [IPU_CSI0] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_CSI0,
                .pad[CSI_SRC_PAD_DIRECT] = {
                        .num_links = 2,
                        .link = {
                                {
                                        .local_pad = CSI_SRC_PAD_DIRECT,
                                        .remote = IPU_IC_PRP,
                                        .remote_pad = PRP_SINK_PAD,
                                }, {
                                        .local_pad = CSI_SRC_PAD_DIRECT,
                                        .remote = IPU_VDIC,
                                        .remote_pad = VDIC_SINK_PAD_DIRECT,
                                },
                        },
                },
        },

        [IPU_CSI1] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_CSI1,
                .pad[CSI_SRC_PAD_DIRECT] = {
                        .num_links = 2,
                        .link = {
                                {
                                        .local_pad = CSI_SRC_PAD_DIRECT,
                                        .remote = IPU_IC_PRP,
                                        .remote_pad = PRP_SINK_PAD,
                                }, {
                                        .local_pad = CSI_SRC_PAD_DIRECT,
                                        .remote = IPU_VDIC,
                                        .remote_pad = VDIC_SINK_PAD_DIRECT,
                                },
                        },
                },
        },

        [IPU_VDIC] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_VDIC,
                .sync_register = imx_media_vdic_register,
                .sync_unregister = imx_media_vdic_unregister,
                .pad[VDIC_SRC_PAD_DIRECT] = {
                        .num_links = 1,
                        .link = {
                                {
                                        .local_pad = VDIC_SRC_PAD_DIRECT,
                                        .remote = IPU_IC_PRP,
                                        .remote_pad = PRP_SINK_PAD,
                                },
                        },
                },
        },

        [IPU_IC_PRP] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRP,
                .sync_register = imx_media_ic_register,
                .sync_unregister = imx_media_ic_unregister,
                .pad[PRP_SRC_PAD_PRPENC] = {
                        .num_links = 1,
                        .link = {
                                {
                                        .local_pad = PRP_SRC_PAD_PRPENC,
                                        .remote = IPU_IC_PRPENC,
                                        .remote_pad = PRPENCVF_SINK_PAD,
                                },
                        },
                },
                .pad[PRP_SRC_PAD_PRPVF] = {
                        .num_links = 1,
                        .link = {
                                {
                                        .local_pad = PRP_SRC_PAD_PRPVF,
                                        .remote = IPU_IC_PRPVF,
                                        .remote_pad = PRPENCVF_SINK_PAD,
                                },
                        },
                },
        },

        [IPU_IC_PRPENC] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPENC,
                .sync_register = imx_media_ic_register,
                .sync_unregister = imx_media_ic_unregister,
        },

        [IPU_IC_PRPVF] = {
                .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPVF,
                .sync_register = imx_media_ic_register,
                .sync_unregister = imx_media_ic_unregister,
        },
};

static int create_internal_link(struct imx_media_dev *imxmd,
                                struct v4l2_subdev *src,
                                struct v4l2_subdev *sink,
                                const struct internal_link *link)
{
        int ret;

        /* skip if this link already created */
        if (media_entity_find_link(&src->entity.pads[link->local_pad],
                                   &sink->entity.pads[link->remote_pad]))
                return 0;

        dev_dbg(imxmd->md.dev, "%s:%d -> %s:%d\n",
                src->name, link->local_pad,
                sink->name, link->remote_pad);

        ret = media_create_pad_link(&src->entity, link->local_pad,
                                    &sink->entity, link->remote_pad, 0);
        if (ret)
                v4l2_err(&imxmd->v4l2_dev, "%s failed: %d\n", __func__, ret);

        return ret;
}

static int create_ipu_internal_links(struct imx_media_dev *imxmd,
                                     const struct internal_subdev *intsd,
                                     struct v4l2_subdev *sd,
                                     int ipu_id)
{
        const struct internal_pad *intpad;
        const struct internal_link *link;
        struct media_pad *pad;
        int i, j, ret;

        /* create the source->sink links */
        for (i = 0; i < sd->entity.num_pads; i++) {
                intpad = &intsd->pad[i];
                pad = &sd->entity.pads[i];

                if (!(pad->flags & MEDIA_PAD_FL_SOURCE))
                        continue;

                for (j = 0; j < intpad->num_links; j++) {
                        struct v4l2_subdev *sink;

                        link = &intpad->link[j];
                        sink = imxmd->sync_sd[ipu_id][link->remote];

                        ret = create_internal_link(imxmd, sd, sink, link);
                        if (ret)
                                return ret;
                }
        }

        return 0;
}

int imx_media_register_ipu_internal_subdevs(struct imx_media_dev *imxmd,
                                            struct v4l2_subdev *csi)
{
        struct device *ipu_dev = csi->dev->parent;
        const struct internal_subdev *intsd;
        struct v4l2_subdev *sd;
        struct ipu_soc *ipu;
        int i, ipu_id, ret;

        ipu = dev_get_drvdata(ipu_dev);
        if (!ipu) {
                v4l2_err(&imxmd->v4l2_dev, "invalid IPU device!\n");
                return -ENODEV;
        }

        ipu_id = ipu_get_num(ipu);
        if (ipu_id > 1) {
                v4l2_err(&imxmd->v4l2_dev, "invalid IPU id %d!\n", ipu_id);
                return -ENODEV;
        }

        mutex_lock(&imxmd->mutex);

        /* record this IPU */
        if (!imxmd->ipu[ipu_id])
                imxmd->ipu[ipu_id] = ipu;

        /* register the synchronous subdevs */
        for (i = 0; i < NUM_IPU_SUBDEVS; i++) {
                intsd = &int_subdev[i];

                sd = imxmd->sync_sd[ipu_id][i];

                /*
                 * skip if this sync subdev already registered or its
                 * not a sync subdev (one of the CSIs)
                 */
                if (sd || !intsd->sync_register)
                        continue;

                mutex_unlock(&imxmd->mutex);
                sd = intsd->sync_register(&imxmd->v4l2_dev, ipu_dev, ipu,
                                          intsd->grp_id);
                mutex_lock(&imxmd->mutex);
                if (IS_ERR(sd)) {
                        ret = PTR_ERR(sd);
                        goto err_unwind;
                }

                imxmd->sync_sd[ipu_id][i] = sd;
        }

        /*
         * all the sync subdevs are registered, create the media links
         * between them.
         */
        for (i = 0; i < NUM_IPU_SUBDEVS; i++) {
                intsd = &int_subdev[i];

                if (intsd->grp_id == csi->grp_id) {
                        sd = csi;
                } else {
                        sd = imxmd->sync_sd[ipu_id][i];
                        if (!sd)
                                continue;
                }

                ret = create_ipu_internal_links(imxmd, intsd, sd, ipu_id);
                if (ret) {
                        mutex_unlock(&imxmd->mutex);
                        imx_media_unregister_ipu_internal_subdevs(imxmd);
                        return ret;
                }
        }

        mutex_unlock(&imxmd->mutex);
        return 0;

err_unwind:
        while (--i >= 0) {
                intsd = &int_subdev[i];
                sd = imxmd->sync_sd[ipu_id][i];
                if (!sd || !intsd->sync_unregister)
                        continue;
                mutex_unlock(&imxmd->mutex);
                intsd->sync_unregister(sd);
                mutex_lock(&imxmd->mutex);
        }

        mutex_unlock(&imxmd->mutex);
        return ret;
}

void imx_media_unregister_ipu_internal_subdevs(struct imx_media_dev *imxmd)
{
        const struct internal_subdev *intsd;
        struct v4l2_subdev *sd;
        int i, j;

        mutex_lock(&imxmd->mutex);

        for (i = 0; i < 2; i++) {
                for (j = 0; j < NUM_IPU_SUBDEVS; j++) {
                        intsd = &int_subdev[j];
                        sd = imxmd->sync_sd[i][j];

                        if (!sd || !intsd->sync_unregister)
                                continue;

                        mutex_unlock(&imxmd->mutex);
                        intsd->sync_unregister(sd);
                        mutex_lock(&imxmd->mutex);
                }
        }

        mutex_unlock(&imxmd->mutex);
}