text: /*
   * Media driver for Freescale i.MX5/6 SOC
   *
   * Adds the internal subdevices and the media links between them.
   *
   * Copyright (c) 2016 Mentor Graphics Inc.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   */
  #include <linux/platform_device.h>
  #include "imx-media.h"
  
  enum isd_enum {
  	isd_csi0 = 0,
  	isd_csi1,
  	isd_vdic,
  	isd_ic_prp,
  	isd_ic_prpenc,
  	isd_ic_prpvf,
  	num_isd,
  };
  
  static const struct internal_subdev_id {
  	enum isd_enum index;
  	const char *name;
  	u32 grp_id;
  } isd_id[num_isd] = {
  	[isd_csi0] = {
  		.index = isd_csi0,
  		.grp_id = IMX_MEDIA_GRP_ID_CSI0,
  		.name = "imx-ipuv3-csi",
  	},
  	[isd_csi1] = {
  		.index = isd_csi1,
  		.grp_id = IMX_MEDIA_GRP_ID_CSI1,
  		.name = "imx-ipuv3-csi",
  	},
  	[isd_vdic] = {
  		.index = isd_vdic,
  		.grp_id = IMX_MEDIA_GRP_ID_VDIC,
  		.name = "imx-ipuv3-vdic",
  	},
  	[isd_ic_prp] = {
  		.index = isd_ic_prp,
  		.grp_id = IMX_MEDIA_GRP_ID_IC_PRP,
  		.name = "imx-ipuv3-ic",
  	},
  	[isd_ic_prpenc] = {
  		.index = isd_ic_prpenc,
  		.grp_id = IMX_MEDIA_GRP_ID_IC_PRPENC,
  		.name = "imx-ipuv3-ic",
  	},
  	[isd_ic_prpvf] = {
  		.index = isd_ic_prpvf,
  		.grp_id = IMX_MEDIA_GRP_ID_IC_PRPVF,
  		.name = "imx-ipuv3-ic",
  	},
  };
  
  struct internal_link {
  	const struct internal_subdev_id *remote_id;
  	int remote_pad;
  };
  
  struct internal_pad {
  	bool devnode; /* does this pad link to a device node */
  	struct internal_link link[IMX_MEDIA_MAX_LINKS];
  };
  
  static const struct internal_subdev {
  	const struct internal_subdev_id *id;
  	struct internal_pad pad[IMX_MEDIA_MAX_PADS];
  	int num_sink_pads;
  	int num_src_pads;
  } internal_subdev[num_isd] = {
  	[isd_csi0] = {
  		.id = &isd_id[isd_csi0],
  		.num_sink_pads = CSI_NUM_SINK_PADS,
  		.num_src_pads = CSI_NUM_SRC_PADS,
  		.pad[CSI_SRC_PAD_DIRECT] = {
  			.link = {
  				{
  					.remote_id = &isd_id[isd_ic_prp],
  					.remote_pad = PRP_SINK_PAD,
  				}, {
  					.remote_id =  &isd_id[isd_vdic],
  					.remote_pad = VDIC_SINK_PAD_DIRECT,
  				},
  			},
  		},
  		.pad[CSI_SRC_PAD_IDMAC] = {
  			.devnode = true,
  		},
  	},
  
  	[isd_csi1] = {
  		.id = &isd_id[isd_csi1],
  		.num_sink_pads = CSI_NUM_SINK_PADS,
  		.num_src_pads = CSI_NUM_SRC_PADS,
  		.pad[CSI_SRC_PAD_DIRECT] = {
  			.link = {
  				{
  					.remote_id = &isd_id[isd_ic_prp],
  					.remote_pad = PRP_SINK_PAD,
  				}, {
  					.remote_id =  &isd_id[isd_vdic],
  					.remote_pad = VDIC_SINK_PAD_DIRECT,
  				},
  			},
  		},
  		.pad[CSI_SRC_PAD_IDMAC] = {
  			.devnode = true,
  		},
  	},
  
  	[isd_vdic] = {
  		.id = &isd_id[isd_vdic],
  		.num_sink_pads = VDIC_NUM_SINK_PADS,
  		.num_src_pads = VDIC_NUM_SRC_PADS,
  		.pad[VDIC_SINK_PAD_IDMAC] = {
  			.devnode = true,
  		},
  		.pad[VDIC_SRC_PAD_DIRECT] = {
  			.link = {
  				{
  					.remote_id =  &isd_id[isd_ic_prp],
  					.remote_pad = PRP_SINK_PAD,
  				},
  			},
  		},
  	},
  
  	[isd_ic_prp] = {
  		.id = &isd_id[isd_ic_prp],
  		.num_sink_pads = PRP_NUM_SINK_PADS,
  		.num_src_pads = PRP_NUM_SRC_PADS,
  		.pad[PRP_SRC_PAD_PRPENC] = {
  			.link = {
  				{
  					.remote_id = &isd_id[isd_ic_prpenc],
  					.remote_pad = 0,
  				},
  			},
  		},
  		.pad[PRP_SRC_PAD_PRPVF] = {
  			.link = {
  				{
  					.remote_id = &isd_id[isd_ic_prpvf],
  					.remote_pad = 0,
  				},
  			},
  		},
  	},
  
  	[isd_ic_prpenc] = {
  		.id = &isd_id[isd_ic_prpenc],
  		.num_sink_pads = PRPENCVF_NUM_SINK_PADS,
  		.num_src_pads = PRPENCVF_NUM_SRC_PADS,
  		.pad[PRPENCVF_SRC_PAD] = {
  			.devnode = true,
  		},
  	},
  
  	[isd_ic_prpvf] = {
  		.id = &isd_id[isd_ic_prpvf],
  		.num_sink_pads = PRPENCVF_NUM_SINK_PADS,
  		.num_src_pads = PRPENCVF_NUM_SRC_PADS,
  		.pad[PRPENCVF_SRC_PAD] = {
  			.devnode = true,
  		},
  	},
  };
  
  /* form a device name given a group id and ipu id */
  static inline void isd_id_to_devname(char *devname, int sz,
  				     const struct internal_subdev_id *id,
  				     int ipu_id)
  {
  	int pdev_id = ipu_id * num_isd + id->index;
  
  	snprintf(devname, sz, "%s.%d", id->name, pdev_id);
  }
  
  /* adds the links from given internal subdev */
  static int add_internal_links(struct imx_media_dev *imxmd,
  			      const struct internal_subdev *isd,
  			      struct imx_media_subdev *imxsd,
  			      int ipu_id)
  {
  	int i, num_pads, ret;
  
  	num_pads = isd->num_sink_pads + isd->num_src_pads;
  
  	for (i = 0; i < num_pads; i++) {
  		const struct internal_pad *intpad = &isd->pad[i];
  		struct imx_media_pad *pad = &imxsd->pad[i];
  		int j;
  
  		/* init the pad flags for this internal subdev */
  		pad->pad.flags = (i < isd->num_sink_pads) ?
  			MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
  		/* export devnode pad flag to the subdevs */
  		pad->devnode = intpad->devnode;
  
  		for (j = 0; ; j++) {
  			const struct internal_link *link;
  			char remote_devname[32];
  
  			link = &intpad->link[j];
  
  			if (!link->remote_id)
  				break;
  
  			isd_id_to_devname(remote_devname,
  					  sizeof(remote_devname),
  					  link->remote_id, ipu_id);
  
  			ret = imx_media_add_pad_link(imxmd, pad,
  						     NULL, remote_devname,
  						     i, link->remote_pad);
  			if (ret)
  				return ret;
  		}
  	}
  
  	return 0;
  }
  
  /* register an internal subdev as a platform device */
  static struct imx_media_subdev *
  add_internal_subdev(struct imx_media_dev *imxmd,
  		    const struct internal_subdev *isd,
  		    int ipu_id)
  {
  	struct imx_media_internal_sd_platformdata pdata;
  	struct platform_device_info pdevinfo = {0};
  	struct imx_media_subdev *imxsd;
  	struct platform_device *pdev;
  
  	pdata.grp_id = isd->id->grp_id;
  
  	/* the id of IPU this subdev will control */
  	pdata.ipu_id = ipu_id;
  
  	/* create subdev name */
  	imx_media_grp_id_to_sd_name(pdata.sd_name, sizeof(pdata.sd_name),
  				    pdata.grp_id, ipu_id);
  
  	pdevinfo.name = isd->id->name;
  	pdevinfo.id = ipu_id * num_isd + isd->id->index;
  	pdevinfo.parent = imxmd->md.dev;
  	pdevinfo.data = &pdata;
  	pdevinfo.size_data = sizeof(pdata);
  	pdevinfo.dma_mask = DMA_BIT_MASK(32);
  
  	pdev = platform_device_register_full(&pdevinfo);
  	if (IS_ERR(pdev))
  		return ERR_CAST(pdev);
  
  	imxsd = imx_media_add_async_subdev(imxmd, NULL, pdev);
  	if (IS_ERR(imxsd))
  		return imxsd;
  
  	imxsd->num_sink_pads = isd->num_sink_pads;
  	imxsd->num_src_pads = isd->num_src_pads;
  
  	return imxsd;
  }
  
  /* adds the internal subdevs in one ipu */
  static int add_ipu_internal_subdevs(struct imx_media_dev *imxmd,
  				    struct imx_media_subdev *csi0,
  				    struct imx_media_subdev *csi1,
  				    int ipu_id)
  {
  	enum isd_enum i;
  	int ret;
  
  	for (i = 0; i < num_isd; i++) {
  		const struct internal_subdev *isd = &internal_subdev[i];
  		struct imx_media_subdev *imxsd;
  
  		/*
  		 * the CSIs are represented in the device-tree, so those
  		 * devices are added already, and are added to the async
  		 * subdev list by of_parse_subdev(), so we are given those
  		 * subdevs as csi0 and csi1.
  		 */
  		switch (isd->id->grp_id) {
  		case IMX_MEDIA_GRP_ID_CSI0:
  			imxsd = csi0;
  			break;
  		case IMX_MEDIA_GRP_ID_CSI1:
  			imxsd = csi1;
  			break;
  		default:
  			imxsd = add_internal_subdev(imxmd, isd, ipu_id);
  			break;
  		}
  
  		if (IS_ERR(imxsd))
  			return PTR_ERR(imxsd);
  
  		/* add the links from this subdev */
  		if (imxsd) {
  			ret = add_internal_links(imxmd, isd, imxsd, ipu_id);
  			if (ret)
  				return ret;
  		}
  	}
  
  	return 0;
  }
  
  int imx_media_add_internal_subdevs(struct imx_media_dev *imxmd,
  				   struct imx_media_subdev *csi[4])
  {
  	int ret;
  
  	ret = add_ipu_internal_subdevs(imxmd, csi[0], csi[1], 0);
  	if (ret)
  		goto remove;
  
  	ret = add_ipu_internal_subdevs(imxmd, csi[2], csi[3], 1);
  	if (ret)
  		goto remove;
  
  	return 0;
  
  remove:
  	imx_media_remove_internal_subdevs(imxmd);
  	return ret;
  }
  
  void imx_media_remove_internal_subdevs(struct imx_media_dev *imxmd)
  {
  	struct imx_media_subdev *imxsd;
  	int i;
  
  	for (i = 0; i < imxmd->subdev_notifier.num_subdevs; i++) {
  		imxsd = &imxmd->subdev[i];
  		if (!imxsd->pdev)
  			continue;
  		platform_device_unregister(imxsd->pdev);
  	}
  }
  