// 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);
|
}
|