blob: cdfbf40dfcbeb51ad779bd9d56227b8ee881909b [file] [log] [blame]
/*
* 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);
}
}