blob: fb1faa5612cca473fe98d0c5902456e53e4292dc [file] [log] [blame]
/*
* Copyright 2017 NXP
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/bug.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-of.h>
#include <media/media-device.h>
#include "mxc-media-dev.h"
#include "mxc-isi-core.h"
#include "mxc-mipi-csi2.h"
#include "mxc-parallel-csi.h"
/*create default links between registered entities */
static int mxc_md_create_links(struct mxc_md *mxc_md)
{
struct media_entity *source, *sink;
struct mxc_isi_dev *mxc_isi;
struct mxc_sensor_info *sensor;
struct mxc_mipi_csi2_dev *mipi_csi2;
struct mxc_parallel_csi_dev *pcsidev;
int i, j, ret = 0;
u16 source_pad, sink_pad;
u32 flags;
u32 mipi_vc = 0;
/* Create links between each ISI's subdev and video node */
flags = MEDIA_LNK_FL_ENABLED;
for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
mxc_isi = mxc_md->mxc_isi[i];
if (!mxc_isi)
continue;
/* Connect ISI source to video device */
source = &mxc_isi->isi_cap.sd.entity;
sink = &mxc_isi->isi_cap.vdev.entity;
sink_pad = 0;
switch (mxc_isi->interface[OUT_PORT]) {
case ISI_OUTPUT_INTERFACE_DC0:
source_pad = MXC_ISI_SD_PAD_SOURCE_DC0;
break;
case ISI_OUTPUT_INTERFACE_DC1:
source_pad = MXC_ISI_SD_PAD_SOURCE_DC1;
break;
case ISI_OUTPUT_INTERFACE_MEM:
source_pad = MXC_ISI_SD_PAD_SOURCE_MEM;
break;
default:
v4l2_err(&mxc_md->v4l2_dev, "Wrong output interface: %x\n",
mxc_isi->interface[OUT_PORT]);
return -EINVAL;
}
ret = media_create_pad_link(source, source_pad,
sink, sink_pad, flags);
if (ret) {
v4l2_err(&mxc_md->v4l2_dev, "Failed created link [%s] %c> [%s]\n",
source->name, flags ? '=' : '-', sink->name);
break;
}
/* Notify capture subdev entity ,ISI cap link setup */
ret = media_entity_call(source, link_setup, &source->pads[source_pad],
&sink->pads[sink_pad], flags);
if (ret) {
v4l2_err(&mxc_md->v4l2_dev, "failed call link_setup [%s] %c> [%s]\n",
source->name, flags ? '=' : '-', sink->name);
break;
}
v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n",
source->name, flags ? '=' : '-', sink->name);
/* Connect MIPI/HDMI/Mem source to ISI sink */
sink = &mxc_isi->isi_cap.sd.entity;
switch (mxc_isi->interface[IN_PORT]) {
case ISI_INPUT_INTERFACE_MIPI0_CSI2:
if (mxc_md->mipi_csi2[0] == NULL)
continue;
source = &mxc_md->mipi_csi2[0]->sd.entity;
switch (mxc_isi->interface[SUB_IN_PORT]) {
case ISI_INPUT_SUB_INTERFACE_VC1:
source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC1;
break;
case ISI_INPUT_SUB_INTERFACE_VC2:
source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC2;
break;
case ISI_INPUT_SUB_INTERFACE_VC3:
source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC3;
break;
default:
source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC0;
break;
}
break;
case ISI_INPUT_INTERFACE_MIPI1_CSI2:
if (mxc_md->mipi_csi2[1] == NULL)
continue;
source = &mxc_md->mipi_csi2[1]->sd.entity;
switch (mxc_isi->interface[SUB_IN_PORT]) {
case ISI_INPUT_SUB_INTERFACE_VC1:
source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC1;
break;
case ISI_INPUT_SUB_INTERFACE_VC2:
source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC2;
break;
case ISI_INPUT_SUB_INTERFACE_VC3:
source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC3;
break;
default:
source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC0;
break;
}
break;
case ISI_INPUT_INTERFACE_PARALLEL_CSI:
if (mxc_md->pcsidev == NULL)
continue;
source = &mxc_md->pcsidev->sd.entity;
source_pad = MXC_PARALLEL_CSI_PAD_SOURCE;
sink_pad = MXC_ISI_SD_PAD_SINK_PARALLEL_CSI;
break;
case ISI_INPUT_INTERFACE_HDMI:
case ISI_INPUT_INTERFACE_DC0:
case ISI_INPUT_INTERFACE_DC1:
case ISI_INPUT_INTERFACE_MEM:
default:
v4l2_err(&mxc_md->v4l2_dev, "Not support input interface: %x\n",
mxc_isi->interface[IN_PORT]);
return -EINVAL;
}
/* Create link MIPI/HDMI to ISI */
ret = media_create_pad_link(source, source_pad, sink, sink_pad, flags);
if (ret) {
v4l2_err(&mxc_md->v4l2_dev, "created link [%s] %c> [%s] fail\n",
source->name, flags ? '=' : '-', sink->name);
break;
}
/* Notify ISI subdev entity */
ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad],
&source->pads[source_pad], 0);
if (ret)
break;
/* Notify MIPI/HDMI entity */
ret = media_entity_call(source, link_setup, &source->pads[source_pad],
&sink->pads[sink_pad], 0);
if (ret)
break;
v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n",
source->name, flags ? '=' : '-', sink->name);
}
/* Connect MIPI Sensor to MIPI CSI2 */
for (i = 0; i < mxc_md->num_sensors; i++) {
sensor = &mxc_md->sensor[i];
if (sensor == NULL || sensor->sd == NULL)
continue;
if (mxc_md->parallel_csi) {
pcsidev = mxc_md->pcsidev;
if (pcsidev == NULL)
continue;
source = &sensor->sd->entity;
sink = &pcsidev->sd.entity;
source_pad = 0;
sink_pad = MXC_PARALLEL_CSI_PAD_SINK;
ret = media_create_pad_link(source, source_pad, sink, sink_pad,
MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
if (ret)
return ret;
/* Notify MIPI subdev entity */
ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad],
&source->pads[source_pad], 0);
if (ret)
return ret;
/* Notify MIPI sensor subdev entity */
ret = media_entity_call(source, link_setup, &source->pads[source_pad],
&sink->pads[sink_pad], 0);
if (ret)
return ret;
v4l2_info(&mxc_md->v4l2_dev, "created link [%s] => [%s]\n",
sensor->sd->entity.name, pcsidev->sd.entity.name);
} else {
mipi_csi2 = mxc_md->mipi_csi2[sensor->id];
if (mipi_csi2 == NULL)
continue;
source = &sensor->sd->entity;
sink = &mipi_csi2->sd.entity;
source_pad = 0; //sensor source pad: MIPI_CSI2_SENS_VC0_PAD_SOURCE
sink_pad = source_pad; //mipi sink pad: MXC_MIPI_CSI2_VC0_PAD_SINK;
if (mipi_csi2->vchannel == true)
mipi_vc = 4;
else
mipi_vc = 1;
for (j = 0; j < mipi_vc; j++) {
ret = media_create_pad_link(source, source_pad + j, sink, sink_pad + j,
MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
if (ret)
return ret;
/* Notify MIPI subdev entity */
ret = media_entity_call(sink, link_setup, &sink->pads[sink_pad + j],
&source->pads[source_pad + j], 0);
if (ret)
return ret;
/* Notify MIPI sensor subdev entity */
ret = media_entity_call(source, link_setup, &source->pads[source_pad + j],
&sink->pads[sink_pad + j], 0);
if (ret)
return ret;
}
v4l2_info(&mxc_md->v4l2_dev, "created link [%s] => [%s]\n",
sensor->sd->entity.name, mipi_csi2->sd.entity.name);
}
}
dev_info(&mxc_md->pdev->dev, "%s\n", __func__);
return 0;
}
static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_subdev *asd)
{
struct mxc_md *mxc_md = notifier_to_mxc_md(notifier);
struct mxc_sensor_info *sensor = NULL;
int i;
/* Find platform data for this sensor subdev */
for (i = 0; i < ARRAY_SIZE(mxc_md->sensor); i++) {
if (mxc_md->sensor[i].asd.match.of.node == sd->dev->of_node)
sensor = &mxc_md->sensor[i];
}
if (sensor == NULL)
return -EINVAL;
sd->grp_id = GRP_ID_MXC_SENSOR;
sensor->sd = sd;
mxc_md->num_sensors++;
v4l2_info(&mxc_md->v4l2_dev, "Registered sensor subdevice: %s (%d)\n",
sd->name, mxc_md->num_sensors);
return 0;
}
static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
{
struct mxc_md *mxc_md = notifier_to_mxc_md(notifier);
int ret;
mutex_lock(&mxc_md->media_dev.graph_mutex);
ret = mxc_md_create_links(mxc_md);
if (ret < 0)
goto unlock;
ret = v4l2_device_register_subdev_nodes(&mxc_md->v4l2_dev);
unlock:
mutex_unlock(&mxc_md->media_dev.graph_mutex);
if (ret < 0) {
v4l2_err(&mxc_md->v4l2_dev, "%s error exit\n", __func__);
return ret;
}
return media_device_register(&mxc_md->media_dev);
}
/**
* mxc_sensor_notify - v4l2_device notification from a sensor subdev
*/
void mxc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification,
void *arg)
{
return;
}
/* Register mipi sensor sub-devices */
static int register_sensor_entities(struct mxc_md *mxc_md)
{
struct device_node *parent = mxc_md->pdev->dev.of_node;
struct device_node *node, *ep, *rem;
struct v4l2_of_endpoint endpoint;
int index = 0;
mxc_md->num_sensors = 0;
/* Attach sensors linked to MIPI CSI2 */
for_each_available_child_of_node(parent, node) {
struct device_node *port;
if (of_node_cmp(node->name, "csi") &&
of_node_cmp(node->name, "pcsi")) {
continue;
}
if (!of_device_is_available(node))
continue;
/* csi2 node have only port */
port = of_get_next_child(node, NULL);
if (!port)
continue;
/* port can have only endpoint */
ep = of_get_next_child(port, NULL);
if (!ep)
return -EINVAL;
v4l2_of_parse_endpoint(ep, &endpoint);
if (WARN_ON(endpoint.base.port >= MXC_MAX_MIPI_SENSORS)) {
v4l2_err(&mxc_md->v4l2_dev, "Failed to get sensor endpoint\n");
return -EINVAL;
}
mxc_md->sensor[index].id = endpoint.base.port;
/* remote port---sensor node */
rem = of_graph_get_remote_port_parent(ep);
of_node_put(ep);
if (rem == NULL) {
v4l2_info(&mxc_md->v4l2_dev, "Remote device at %s not found\n",
ep->full_name);
continue;
}
mxc_md->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF;
mxc_md->sensor[index].asd.match.of.node = rem;
mxc_md->async_subdevs[index] = &mxc_md->sensor[index].asd;
mxc_md->num_sensors++;
index++;
}
return 0;
}
static int register_isi_entity(struct mxc_md *mxc_md, struct mxc_isi_dev *mxc_isi)
{
struct v4l2_subdev *sd = &mxc_isi->isi_cap.sd;
int ret;
if (WARN_ON(mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_md->mxc_isi[mxc_isi->id]))
return -EBUSY;
sd->grp_id = GRP_ID_MXC_ISI;
ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
if (!ret)
mxc_md->mxc_isi[mxc_isi->id] = mxc_isi;
else
v4l2_err(&mxc_md->v4l2_dev, "Failed to register ISI.%d (%d)\n",
mxc_isi->id, ret);
return ret;
}
static int register_mipi_csi2_entity(struct mxc_md *mxc_md,
struct mxc_mipi_csi2_dev *mipi_csi2)
{
struct v4l2_subdev *sd = &mipi_csi2->sd;
int ret;
if (WARN_ON(mipi_csi2->id >= MXC_MIPI_CSI2_MAX_DEVS))
return -ENOENT;
sd->grp_id = GRP_ID_MXC_MIPI_CSI2;
ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
if (!ret)
mxc_md->mipi_csi2[mipi_csi2->id] = mipi_csi2;
else
v4l2_err(&mxc_md->v4l2_dev,
"Failed to register MIPI-CSIS.%d (%d)\n", mipi_csi2->id, ret);
return ret;
}
static int register_parallel_csi_entity(struct mxc_md *mxc_md,
struct mxc_parallel_csi_dev *pcsidev)
{
struct v4l2_subdev *sd = &pcsidev->sd;
int ret;
sd->grp_id = GRP_ID_MXC_PARALLEL_CSI;
ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
if (!ret)
mxc_md->pcsidev = pcsidev;
else
v4l2_err(&mxc_md->v4l2_dev,
"Failed to register PARALLEL CSI ret=(%d)\n", ret);
return ret;
}
static int mxc_md_register_platform_entity(struct mxc_md *mxc_md,
struct platform_device *pdev,
int plat_entity)
{
struct device *dev = &pdev->dev;
int ret = -EPROBE_DEFER;
void *drvdata;
/* Lock to ensure dev->driver won't change. */
device_lock(dev);
if (!dev->driver || !try_module_get(dev->driver->owner))
goto dev_unlock;
drvdata = dev_get_drvdata(dev);
/* Some subdev didn't probe successfully id drvdata is NULL */
if (drvdata) {
switch (plat_entity) {
case IDX_ISI:
ret = register_isi_entity(mxc_md, drvdata);
break;
case IDX_MIPI_CSI2:
ret = register_mipi_csi2_entity(mxc_md, drvdata);
break;
case IDX_PARALLEL_CSI:
ret = register_parallel_csi_entity(mxc_md, drvdata);
break;
default:
ret = -ENODEV;
}
}
module_put(dev->driver->owner);
dev_unlock:
device_unlock(dev);
if (ret == -EPROBE_DEFER)
dev_info(&mxc_md->pdev->dev, "deferring %s device registration\n",
dev_name(dev));
else if (ret < 0)
dev_err(&mxc_md->pdev->dev, "%s device registration failed (%d)\n",
dev_name(dev), ret);
return ret;
}
/* Register ISI, MIPI CSI2 and HDMI In Media entities */
static int mxc_md_register_platform_entities(struct mxc_md *mxc_md,
struct device_node *parent)
{
struct device_node *node;
int ret = 0;
for_each_available_child_of_node(parent, node) {
struct platform_device *pdev;
int plat_entity = -1;
pdev = of_find_device_by_node(node);
if (!pdev)
continue;
/* If driver of any entity isn't ready try all again later. */
if (!strcmp(node->name, ISI_OF_NODE_NAME))
plat_entity = IDX_ISI;
else if (!strcmp(node->name, MIPI_CSI2_OF_NODE_NAME))
plat_entity = IDX_MIPI_CSI2;
else if (!strcmp(node->name, PARALLEL_CSI_OF_NODE_NAME))
plat_entity = IDX_PARALLEL_CSI;
if (plat_entity >= 0)
ret = mxc_md_register_platform_entity(mxc_md, pdev,
plat_entity);
put_device(&pdev->dev);
if (ret < 0)
break;
}
return ret;
}
static void mxc_md_unregister_entities(struct mxc_md *mxc_md)
{
int i;
for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
struct mxc_isi_dev *dev = mxc_md->mxc_isi[i];
if (dev == NULL)
continue;
v4l2_device_unregister_subdev(&dev->isi_cap.sd);
mxc_md->mxc_isi[i] = NULL;
}
for (i = 0; i < MXC_MIPI_CSI2_MAX_DEVS; i++) {
if (mxc_md->mipi_csi2[i] == NULL)
continue;
v4l2_device_unregister_subdev(&mxc_md->mipi_csi2[i]->sd);
mxc_md->mipi_csi2[i] = NULL;
}
if (mxc_md->pcsidev) {
v4l2_device_unregister_subdev(&mxc_md->pcsidev->sd);
mxc_md->pcsidev = NULL;
}
v4l2_info(&mxc_md->v4l2_dev, "Unregistered all entities\n");
}
static int mxc_md_link_notify(struct media_link *link, unsigned int flags,
unsigned int notification)
{
return 0;
}
static const struct media_device_ops mxc_md_ops = {
.link_notify = mxc_md_link_notify,
};
static int mxc_md_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct v4l2_device *v4l2_dev;
struct mxc_md *mxc_md;
int ret;
mxc_md = devm_kzalloc(dev, sizeof(*mxc_md), GFP_KERNEL);
if (!mxc_md)
return -ENOMEM;
mxc_md->pdev = pdev;
platform_set_drvdata(pdev, mxc_md);
mxc_md->parallel_csi = of_property_read_bool(dev->of_node, "parallel_csi");
/* register media device */
strlcpy(mxc_md->media_dev.model, "FSL Capture Media Deivce",
sizeof(mxc_md->media_dev.model));
mxc_md->media_dev.ops = &mxc_md_ops;
mxc_md->media_dev.dev = dev;
/* register v4l2 device */
v4l2_dev = &mxc_md->v4l2_dev;
v4l2_dev->mdev = &mxc_md->media_dev;
v4l2_dev->notify = mxc_sensor_notify;
strlcpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name));
media_device_init(&mxc_md->media_dev);
ret = v4l2_device_register(dev, &mxc_md->v4l2_dev);
if (ret < 0) {
v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret);
goto err_md;
}
ret = mxc_md_register_platform_entities(mxc_md, dev->of_node);
if (ret < 0)
goto err_v4l2_dev;
ret = register_sensor_entities(mxc_md);
if (ret < 0)
goto err_m_ent;
if (mxc_md->num_sensors > 0) {
mxc_md->subdev_notifier.subdevs = mxc_md->async_subdevs;
mxc_md->subdev_notifier.num_subdevs = mxc_md->num_sensors;
mxc_md->subdev_notifier.bound = subdev_notifier_bound;
mxc_md->subdev_notifier.complete = subdev_notifier_complete;
mxc_md->num_sensors = 0;
ret = v4l2_async_notifier_register(&mxc_md->v4l2_dev,
&mxc_md->subdev_notifier);
if (ret < 0) {
printk("Sensor register failed\n");
goto err_m_ent;
}
}
return 0;
err_m_ent:
mxc_md_unregister_entities(mxc_md);
err_v4l2_dev:
v4l2_device_unregister(&mxc_md->v4l2_dev);
err_md:
media_device_cleanup(&mxc_md->media_dev);
return ret;
}
static int mxc_md_remove(struct platform_device *pdev)
{
struct mxc_md *mxc_md = platform_get_drvdata(pdev);
if (!mxc_md)
return 0;
v4l2_async_notifier_unregister(&mxc_md->subdev_notifier);
v4l2_device_unregister(&mxc_md->v4l2_dev);
mxc_md_unregister_entities(mxc_md);
media_device_unregister(&mxc_md->media_dev);
media_device_cleanup(&mxc_md->media_dev);
return 0;
}
static const struct of_device_id mxc_md_of_match[] = {
{ .compatible = "fsl,mxc-md",},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, mxc_md_of_match);
static struct platform_driver mxc_md_driver = {
.driver = {
.name = MXC_MD_DRIVER_NAME,
.of_match_table = mxc_md_of_match,
},
.probe = mxc_md_probe,
.remove = mxc_md_remove,
};
module_platform_driver(mxc_md_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC Media Device driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" MXC_MD_DRIVER_NAME);