364 lines
7.7 KiB
C
364 lines
7.7 KiB
C
/*
|
|
* 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;
|
|
struct v4l2_async_subdev *asd;
|
|
|
|
list_for_each_entry(asd, &imxmd->notifier.asd_list, asd_list) {
|
|
imxasd = to_imx_media_asd(asd);
|
|
|
|
if (!imxasd->pdev)
|
|
continue;
|
|
|
|
platform_device_unregister(imxasd->pdev);
|
|
}
|
|
}
|