| /* |
| * 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_subdev; |
| |
| struct internal_link { |
| const struct internal_subdev *remote; |
| int local_pad; |
| int remote_pad; |
| }; |
| |
| /* max pads per internal-sd */ |
| #define MAX_INTERNAL_PADS 8 |
| /* max links per internal-sd pad */ |
| #define MAX_INTERNAL_LINKS 8 |
| |
| struct internal_pad { |
| struct internal_link link[MAX_INTERNAL_LINKS]; |
| }; |
| |
| static const struct internal_subdev { |
| const struct internal_subdev_id *id; |
| struct internal_pad pad[MAX_INTERNAL_PADS]; |
| } int_subdev[num_isd] = { |
| [isd_csi0] = { |
| .id = &isd_id[isd_csi0], |
| .pad[CSI_SRC_PAD_DIRECT] = { |
| .link = { |
| { |
| .local_pad = CSI_SRC_PAD_DIRECT, |
| .remote = &int_subdev[isd_ic_prp], |
| .remote_pad = PRP_SINK_PAD, |
| }, { |
| .local_pad = CSI_SRC_PAD_DIRECT, |
| .remote = &int_subdev[isd_vdic], |
| .remote_pad = VDIC_SINK_PAD_DIRECT, |
| }, |
| }, |
| }, |
| }, |
| |
| [isd_csi1] = { |
| .id = &isd_id[isd_csi1], |
| .pad[CSI_SRC_PAD_DIRECT] = { |
| .link = { |
| { |
| .local_pad = CSI_SRC_PAD_DIRECT, |
| .remote = &int_subdev[isd_ic_prp], |
| .remote_pad = PRP_SINK_PAD, |
| }, { |
| .local_pad = CSI_SRC_PAD_DIRECT, |
| .remote = &int_subdev[isd_vdic], |
| .remote_pad = VDIC_SINK_PAD_DIRECT, |
| }, |
| }, |
| }, |
| }, |
| |
| [isd_vdic] = { |
| .id = &isd_id[isd_vdic], |
| .pad[VDIC_SRC_PAD_DIRECT] = { |
| .link = { |
| { |
| .local_pad = VDIC_SRC_PAD_DIRECT, |
| .remote = &int_subdev[isd_ic_prp], |
| .remote_pad = PRP_SINK_PAD, |
| }, |
| }, |
| }, |
| }, |
| |
| [isd_ic_prp] = { |
| .id = &isd_id[isd_ic_prp], |
| .pad[PRP_SRC_PAD_PRPENC] = { |
| .link = { |
| { |
| .local_pad = PRP_SRC_PAD_PRPENC, |
| .remote = &int_subdev[isd_ic_prpenc], |
| .remote_pad = 0, |
| }, |
| }, |
| }, |
| .pad[PRP_SRC_PAD_PRPVF] = { |
| .link = { |
| { |
| .local_pad = PRP_SRC_PAD_PRPVF, |
| .remote = &int_subdev[isd_ic_prpvf], |
| .remote_pad = 0, |
| }, |
| }, |
| }, |
| }, |
| |
| [isd_ic_prpenc] = { |
| .id = &isd_id[isd_ic_prpenc], |
| }, |
| |
| [isd_ic_prpvf] = { |
| .id = &isd_id[isd_ic_prpvf], |
| }, |
| }; |
| |
| /* form a device name given an internal subdev and ipu id */ |
| static inline void isd_to_devname(char *devname, int sz, |
| const struct internal_subdev *isd, |
| int ipu_id) |
| { |
| int pdev_id = ipu_id * num_isd + isd->id->index; |
| |
| snprintf(devname, sz, "%s.%d", isd->id->name, pdev_id); |
| } |
| |
| static const struct internal_subdev *find_intsd_by_grp_id(u32 grp_id) |
| { |
| enum isd_enum i; |
| |
| for (i = 0; i < num_isd; i++) { |
| const struct internal_subdev *isd = &int_subdev[i]; |
| |
| if (isd->id->grp_id == grp_id) |
| return isd; |
| } |
| |
| return NULL; |
| } |
| |
| static struct v4l2_subdev *find_sink(struct imx_media_dev *imxmd, |
| struct v4l2_subdev *src, |
| const struct internal_link *link) |
| { |
| char sink_devname[32]; |
| int ipu_id; |
| |
| /* |
| * retrieve IPU id from subdev name, note: can't get this from |
| * struct imx_media_internal_sd_platformdata because if src is |
| * a CSI, it has different struct ipu_client_platformdata which |
| * does not contain IPU id. |
| */ |
| if (sscanf(src->name, "ipu%d", &ipu_id) != 1) |
| return NULL; |
| |
| isd_to_devname(sink_devname, sizeof(sink_devname), |
| link->remote, ipu_id - 1); |
| |
| return imx_media_find_subdev_by_devname(imxmd, sink_devname); |
| } |
| |
| static int create_ipu_internal_link(struct imx_media_dev *imxmd, |
| struct v4l2_subdev *src, |
| const struct internal_link *link) |
| { |
| struct v4l2_subdev *sink; |
| int ret; |
| |
| sink = find_sink(imxmd, src, link); |
| if (!sink) |
| return -ENODEV; |
| |
| v4l2_info(&imxmd->v4l2_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, |
| "create_pad_link failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| int imx_media_create_internal_links(struct imx_media_dev *imxmd, |
| struct v4l2_subdev *sd) |
| { |
| const struct internal_subdev *intsd; |
| const struct internal_pad *intpad; |
| const struct internal_link *link; |
| struct media_pad *pad; |
| int i, j, ret; |
| |
| intsd = find_intsd_by_grp_id(sd->grp_id); |
| if (!intsd) |
| return -ENODEV; |
| |
| /* 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++) { |
| link = &intpad->link[j]; |
| |
| if (!link->remote) |
| break; |
| |
| ret = create_ipu_internal_link(imxmd, sd, link); |
| if (ret) |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* register an internal subdev as a platform device */ |
| static int 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 = {}; |
| 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 PTR_ERR(pdev); |
| |
| return imx_media_add_async_subdev(imxmd, NULL, pdev); |
| } |
| |
| /* adds the internal subdevs in one ipu */ |
| static int add_ipu_internal_subdevs(struct imx_media_dev *imxmd, int ipu_id) |
| { |
| enum isd_enum i; |
| |
| for (i = 0; i < num_isd; i++) { |
| const struct internal_subdev *isd = &int_subdev[i]; |
| int ret; |
| |
| /* |
| * the CSIs are represented in the device-tree, so those |
| * devices are already added to the async subdev list by |
| * of_parse_subdev(). |
| */ |
| switch (isd->id->grp_id) { |
| case IMX_MEDIA_GRP_ID_CSI0: |
| case IMX_MEDIA_GRP_ID_CSI1: |
| ret = 0; |
| break; |
| default: |
| ret = add_internal_subdev(imxmd, isd, ipu_id); |
| break; |
| } |
| |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int imx_media_add_internal_subdevs(struct imx_media_dev *imxmd) |
| { |
| int ret; |
| |
| ret = add_ipu_internal_subdevs(imxmd, 0); |
| if (ret) |
| goto remove; |
| |
| ret = add_ipu_internal_subdevs(imxmd, 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_async_subdev *imxasd; |
| |
| list_for_each_entry(imxasd, &imxmd->asd_list, list) { |
| if (!imxasd->pdev) |
| continue; |
| |
| platform_device_unregister(imxasd->pdev); |
| } |
| } |