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