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