summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2021-11-30 14:58:52 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2021-11-30 14:58:52 +0800
commit9d223b25631248abffac9224668cf0e22ddbd99e (patch)
tree9eba5d7091897a5ad6e0d3051b35315654d52bf6
parent0678ad28981547710dbc01f7494dc0ec7b68f957 (diff)
parent61728379722528568beccc1fd2c7d968e3fbb30e (diff)
Merge remote-tracking branch 'origin/capture/media-dev' into capture/next
* origin/capture/media-dev: (13 commits) LF-4202 media: isi: fix camera create pipeline failed when isi cap_device set disabled LF-4269-05: media: imx8: media-dev: correct copyright information LF-4269-02: media: imx: imx8-common: correct copyright information MA-18933-01: media: imx8-common: change integer data type to unsigned LF-3334-11: media: isi: cap: add 4K and 8K macro ...
-rw-r--r--Documentation/devicetree/bindings/media/imx8-media-dev.txt38
-rw-r--r--drivers/staging/media/imx/Kconfig9
-rw-r--r--drivers/staging/media/imx/Makefile1
-rw-r--r--drivers/staging/media/imx/imx8-common.h108
-rw-r--r--drivers/staging/media/imx/imx8-media-dev.c1176
5 files changed, 1332 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/media/imx8-media-dev.txt b/Documentation/devicetree/bindings/media/imx8-media-dev.txt
new file mode 100644
index 000000000000..dd7de1cbde44
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/imx8-media-dev.txt
@@ -0,0 +1,38 @@
+Virtual Media device
+-------------------------------
+
+Virtual Media device is used to manage all modules in image capture subsystem
+of imx8qxp/qm platform. ISI(Image Sensor Interface), MIPI CSI, Parallel CSI
+device node should be under it.
+
+Required properties:
+ - compatible : must be "fsl,mxc-md";
+ - reg : Must contain an entry for each entry in reg-names;
+ - #address-cells: should be <1>;
+ - #size-cells : should be <1>;
+ - ranges : use to handle address space
+
+Optional properties:
+ - parallel_csi: indicate that camera sensor use parallel interface
+
+
+
+For example:
+
+ cameradev: camera {
+ compatible = "fsl,mxc-md", "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ isi@58100000 {
+ compatible = "fsl,imx8-isi";
+ reg = <0x58100000 0x10000>;
+ ...
+ };
+ csi@58227000 {
+ compatible = "fsl,mxc-mipi-csi2";
+ ...
+ };
+ ...
+ };
diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig
index 1cf1cc575265..b011dd490e05 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -97,6 +97,15 @@ config GMSL_MAX9286
default y
help
Enable support for video4linux camera sensor driver for GMSL MAX9286
+
+config IMX8_MEDIA_DEVICE
+ tristate "IMX8 Media Device Driver"
+ select V4L2_FWNODE
+ default y
+ help
+ This media device is a virtual device which used to manage
+ all modules in image subsystem of imx8qxp/qm platform.
+
endmenu
source "drivers/staging/media/imx/hdmirx/Kconfig"
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index 715e98c03c09..f33c405d38cf 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-capture.o
obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-mem2mem.o
obj-$(CONFIG_IMX8_ISI_HW) += imx8-isi-hw.o
obj-$(CONFIG_GMSL_MAX9286) += gmsl-max9286.o
+obj-$(CONFIG_IMX8_MEDIA_DEVICE) += imx8-media-dev.o
obj-y += hdmirx/
diff --git a/drivers/staging/media/imx/imx8-common.h b/drivers/staging/media/imx/imx8-common.h
new file mode 100644
index 000000000000..3160fc87b22f
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-common.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * V4L2 Capture ISI subdev for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor to memory or DC
+ *
+ * Copyright 2019-2021 NXP
+ *
+ */
+
+#ifndef __MXC_COMMON_H__
+#define __MXC_COMMON_H__
+
+#define ISI_OF_NODE_NAME "isi"
+#define MIPI_CSI2_OF_NODE_NAME "csi"
+#define PARALLEL_OF_NODE_NAME "pcsi"
+#define HDMI_RX_OF_NODE_NAME "hdmi_rx"
+
+#define MXC_ISI_MAX_DEVS 8
+#define MXC_MIPI_CSI2_MAX_DEVS 2
+#define MXC_MAX_SENSORS 3
+
+/* ISI PADS */
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC0 0
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC1 1
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC2 2
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC3 3
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC0 4
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC1 5
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC2 6
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC3 7
+
+#define MXC_ISI_SD_PAD_SINK_DC0 8
+#define MXC_ISI_SD_PAD_SINK_DC1 9
+#define MXC_ISI_SD_PAD_SINK_HDMI 10
+#define MXC_ISI_SD_PAD_SINK_MEM 11
+#define MXC_ISI_SD_PAD_SOURCE_MEM 12
+#define MXC_ISI_SD_PAD_SOURCE_DC0 13
+#define MXC_ISI_SD_PAD_SOURCE_DC1 14
+#define MXC_ISI_SD_PAD_SINK_PARALLEL_CSI 15
+#define MXC_ISI_SD_PADS_NUM 16
+
+/* MIPI CSI PADS */
+#define MXC_MIPI_CSI2_VC0_PAD_SINK 0
+#define MXC_MIPI_CSI2_VC1_PAD_SINK 1
+#define MXC_MIPI_CSI2_VC2_PAD_SINK 2
+#define MXC_MIPI_CSI2_VC3_PAD_SINK 3
+
+#define MXC_MIPI_CSI2_VC0_PAD_SOURCE 4
+#define MXC_MIPI_CSI2_VC1_PAD_SOURCE 5
+#define MXC_MIPI_CSI2_VC2_PAD_SOURCE 6
+#define MXC_MIPI_CSI2_VC3_PAD_SOURCE 7
+#define MXC_MIPI_CSI2_VCX_PADS_NUM 8
+
+/* Parallel CSI PADS */
+#define MXC_PARALLEL_CSI_PAD_SOURCE 0
+#define MXC_PARALLEL_CSI_PAD_SINK 1
+#define MXC_PARALLEL_CSI_PADS_NUM 2
+
+/* HDMI PADS */
+#define MXC_HDMI_RX_PAD_SINK 1
+#define MXC_HDMI_RX_PAD_SOURCE 2
+#define MXC_HDMI_RX_PADS_NUM 3
+
+#define ISI_2K 2048U
+#define ISI_4K 4096U
+#define ISI_8K 8192U
+#define ISI_MIN 32U
+
+enum {
+ IN_PORT,
+ SUB_IN_PORT,
+ OUT_PORT,
+ MAX_PORTS,
+};
+
+enum isi_input_interface {
+ ISI_INPUT_INTERFACE_DC0 = 0,
+ ISI_INPUT_INTERFACE_DC1,
+ ISI_INPUT_INTERFACE_MIPI0_CSI2,
+ ISI_INPUT_INTERFACE_MIPI1_CSI2,
+ ISI_INPUT_INTERFACE_HDMI,
+ ISI_INPUT_INTERFACE_MEM,
+ ISI_INPUT_INTERFACE_PARALLEL_CSI,
+ ISI_INPUT_INTERFACE_MAX,
+};
+
+enum isi_input_sub_interface {
+ ISI_INPUT_SUB_INTERFACE_VC0 = 0,
+ ISI_INPUT_SUB_INTERFACE_VC1,
+ ISI_INPUT_SUB_INTERFACE_VC2,
+ ISI_INPUT_SUB_INTERFACE_VC3,
+};
+
+enum isi_output_interface {
+ ISI_OUTPUT_INTERFACE_DC0 = 0,
+ ISI_OUTPUT_INTERFACE_DC1,
+ ISI_OUTPUT_INTERFACE_MEM,
+ ISI_OUTPUT_INTERFACE_MAX,
+};
+
+enum mxc_isi_buf_id {
+ MXC_ISI_BUF1 = 0x0,
+ MXC_ISI_BUF2,
+};
+
+#endif /* MXC_ISI_CORE_H_ */
diff --git a/drivers/staging/media/imx/imx8-media-dev.c b/drivers/staging/media/imx/imx8-media-dev.c
new file mode 100644
index 000000000000..0d0355844eab
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-media-dev.c
@@ -0,0 +1,1176 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Media Controller Driver for NXP IMX8QXP/QM SOC
+ *
+ * Copyright 2019-2021 NXP
+ *
+ */
+
+#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/module.h>
+#include <linux/list.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 <media/v4l2-device.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/media-device.h>
+
+#include "imx8-common.h"
+
+#define MXC_MD_DRIVER_NAME "mxc-md"
+#define ISI_OF_NODE_NAME "isi"
+#define MIPI_CSI2_OF_NODE_NAME "csi"
+
+#define MXC_MAX_SENSORS 3
+#define MXC_MIPI_CSI2_MAX_DEVS 2
+
+#define MXC_NAME_LENS 32
+
+/*
+ * The subdevices' group IDs.
+ */
+#define GRP_ID_MXC_SENSOR BIT(8)
+#define GRP_ID_MXC_ISI BIT(9)
+#define GRP_ID_MXC_MIPI_CSI2 BIT(11)
+#define GRP_ID_MXC_HDMI_RX BIT(12)
+#define GRP_ID_MXC_MJPEG_DEC BIT(13)
+#define GRP_ID_MXC_MJPEG_ENC BIT(14)
+#define GRP_ID_MXC_PARALLEL_CSI BIT(15)
+
+enum mxc_subdev_index {
+ IDX_SENSOR,
+ IDX_ISI,
+ IDX_MIPI_CSI2,
+ IDX_HDMI_RX,
+ IDX_MJPEG_ENC,
+ IDX_MJPEG_DEC,
+ IDX_PARALLEL_CSI,
+ IDX_MAX,
+};
+
+struct mxc_isi_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+ u32 interface[MAX_PORTS];
+
+ char vdev_name[MXC_NAME_LENS];
+ char sd_name[MXC_NAME_LENS];
+ int id;
+};
+
+struct mxc_mipi_csi2_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+
+ char sd_name[MXC_NAME_LENS];
+ int id;
+ bool vchannel;
+};
+
+struct mxc_parallel_csi_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+
+ char sd_name[MXC_NAME_LENS];
+ int id;
+};
+
+struct mxc_hdmi_rx_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+
+ char sd_name[MXC_NAME_LENS];
+ int id;
+};
+
+struct mxc_sensor_info {
+ int id;
+ struct v4l2_subdev *sd;
+ struct fwnode_handle *fwnode;
+ bool mipi_mode;
+};
+
+struct mxc_md {
+ struct mxc_isi_info mxc_isi[MXC_ISI_MAX_DEVS];
+ struct mxc_mipi_csi2_info mipi_csi2[MXC_MIPI_CSI2_MAX_DEVS];
+ struct mxc_parallel_csi_info pcsidev;
+ struct mxc_hdmi_rx_info hdmi_rx;
+ struct mxc_sensor_info sensor[MXC_MAX_SENSORS];
+
+ int link_status;
+ int num_sensors;
+ int valid_num_sensors;
+ unsigned int nr_isi;
+ bool parallel_csi;
+
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct platform_device *pdev;
+
+ struct v4l2_async_notifier subdev_notifier;
+};
+
+static inline struct mxc_md *notifier_to_mxc_md(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct mxc_md, subdev_notifier);
+};
+
+static void mxc_md_unregister_entities(struct mxc_md *mxc_md)
+{
+ struct mxc_parallel_csi_info *pcsidev = &mxc_md->pcsidev;
+ struct mxc_hdmi_rx_info *hdmi_rx = &mxc_md->hdmi_rx;
+ int i;
+
+ for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
+ struct mxc_isi_info *isi = &mxc_md->mxc_isi[i];
+
+ if (!isi->sd)
+ continue;
+ v4l2_device_unregister_subdev(isi->sd);
+ memset(isi, 0, sizeof(*isi));
+ }
+
+ for (i = 0; i < MXC_MIPI_CSI2_MAX_DEVS; i++) {
+ struct mxc_mipi_csi2_info *mipi_csi2 = &mxc_md->mipi_csi2[i];
+ if (!mipi_csi2->sd)
+ continue;
+ v4l2_device_unregister_subdev(mipi_csi2->sd);
+ memset(mipi_csi2, 0, sizeof(*mipi_csi2));
+ }
+
+ if (pcsidev->sd)
+ v4l2_device_unregister_subdev(pcsidev->sd);
+
+ if (hdmi_rx->sd)
+ v4l2_device_unregister_subdev(hdmi_rx->sd);
+
+ v4l2_info(&mxc_md->v4l2_dev, "Unregistered all entities\n");
+}
+
+static struct media_entity *find_entity_by_name(struct mxc_md *mxc_md,
+ const char *name)
+{
+ struct media_entity *ent = NULL;
+
+ if (!mxc_md || !name)
+ return NULL;
+
+ media_device_for_each_entity(ent, &mxc_md->media_dev) {
+ if (!strcmp(ent->name, name)) {
+ dev_dbg(&mxc_md->pdev->dev,
+ "%s entity is found\n", ent->name);
+ return ent;
+ }
+ }
+
+ return NULL;
+}
+
+static int mxc_md_do_clean(struct mxc_md *mxc_md, struct media_pad *pad)
+{
+ struct device *dev = &mxc_md->pdev->dev;
+ struct media_pad *remote_pad;
+ struct v4l2_subdev *subdev;
+
+ if (!pad->entity->num_links)
+ return 0;
+
+ remote_pad = media_entity_remote_pad(pad);
+ if (remote_pad == NULL) {
+ dev_err(dev, "%s get remote pad fail\n", __func__);
+ return -ENODEV;
+ }
+
+ subdev = media_entity_to_v4l2_subdev(remote_pad->entity);
+ if (subdev == NULL) {
+ dev_err(dev, "%s media entity to v4l2 subdev fail\n", __func__);
+ return -ENODEV;
+ }
+
+ v4l2_device_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ pr_info("clean ISI channel: %s\n", subdev->name);
+
+ return 0;
+}
+
+static int mxc_md_clean_channel(struct mxc_md *mxc_md, int index)
+{
+ struct mxc_sensor_info *sensor = &mxc_md->sensor[index];
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ struct media_pad *local_pad;
+ struct media_entity *local_en;
+ u32 i, mipi_vc = 0;
+ int ret;
+
+ if (mxc_md->mipi_csi2[index].sd) {
+ mipi_csi2 = &mxc_md->mipi_csi2[index];
+
+ if (mipi_csi2->vchannel == true)
+ mipi_vc = 4;
+ else
+ mipi_vc = 1;
+
+ local_en = &mipi_csi2->sd->entity;
+ if (local_en == NULL)
+ return -ENODEV;
+
+ for (i = 0; i < mipi_vc; i++) {
+ local_pad = &local_en->pads[MXC_MIPI_CSI2_VC0_PAD_SOURCE + i];
+ ret = mxc_md_do_clean(mxc_md, local_pad);
+ if (ret < 0)
+ return -ENODEV;
+ }
+ } else if (mxc_md->parallel_csi && !sensor->mipi_mode) {
+ pcsidev = &mxc_md->pcsidev;
+ if (pcsidev->sd == NULL)
+ return -ENODEV;
+
+ local_en = &pcsidev->sd->entity;
+ if (local_en == NULL)
+ return -ENODEV;
+
+ local_pad = &local_en->pads[MXC_PARALLEL_CSI_PAD_SOURCE];
+ ret = mxc_md_do_clean(mxc_md, local_pad);
+ if (ret < 0)
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int mxc_md_clean_unlink_channels(struct mxc_md *mxc_md)
+{
+ struct mxc_sensor_info *sensor;
+ int num_subdevs = mxc_md->num_sensors;
+ int i, ret;
+
+ for (i = 0; i < num_subdevs; i++) {
+ sensor = &mxc_md->sensor[i];
+ if (sensor->sd != NULL)
+ continue;
+
+ ret = mxc_md_clean_channel(mxc_md, i);
+ if (ret < 0) {
+ pr_err("%s: clean channel fail(%d)\n", __func__, i);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void mxc_md_unregister_all(struct mxc_md *mxc_md)
+{
+ struct mxc_isi_info *mxc_isi;
+ int i;
+
+ for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
+ mxc_isi = &mxc_md->mxc_isi[i];
+ if (!mxc_isi->sd)
+ continue;
+
+ v4l2_device_unregister_subdev(mxc_isi->sd);
+ media_entity_cleanup(&mxc_isi->sd->entity);
+
+ pr_info("unregister ISI channel: %s\n", mxc_isi->sd->name);
+ }
+}
+
+static int mxc_md_create_links(struct mxc_md *mxc_md)
+{
+ struct media_entity *source, *sink;
+ struct mxc_isi_info *mxc_isi;
+ struct mxc_sensor_info *sensor;
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ struct mxc_hdmi_rx_info *hdmi_rx;
+ int num_sensors = mxc_md->num_sensors;
+ 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->sd)
+ continue;
+
+ /* Connect ISI source to video device */
+ source = find_entity_by_name(mxc_md, mxc_isi->sd_name);
+ sink = find_entity_by_name(mxc_md, mxc_isi->vdev_name);
+ 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 = find_entity_by_name(mxc_md, mxc_isi->sd_name);
+
+ switch (mxc_isi->interface[IN_PORT]) {
+ case ISI_INPUT_INTERFACE_MIPI0_CSI2:
+ mipi_csi2 = &mxc_md->mipi_csi2[0];
+ if (!mipi_csi2->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+
+ 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:
+ mipi_csi2 = &mxc_md->mipi_csi2[1];
+ if (!mipi_csi2->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+
+ 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:
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, pcsidev->sd_name);
+ source_pad = MXC_PARALLEL_CSI_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_PARALLEL_CSI;
+ break;
+
+ case ISI_INPUT_INTERFACE_HDMI:
+ hdmi_rx = &mxc_md->hdmi_rx;
+ if (!hdmi_rx->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, hdmi_rx->sd_name);
+ source_pad = MXC_HDMI_RX_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_HDMI;
+ break;
+
+ 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 < num_sensors; i++) {
+ sensor = &mxc_md->sensor[i];
+ if (!sensor || !sensor->sd)
+ continue;
+
+ if (mxc_md->parallel_csi && !sensor->mipi_mode) {
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev->sd)
+ continue;
+ source = &sensor->sd->entity;
+ sink = find_entity_by_name(mxc_md, pcsidev->sd_name);
+
+ 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",
+ source->name, sink->name);
+ } else if (mxc_md->mipi_csi2[sensor->id].sd) {
+ mipi_csi2 = &mxc_md->mipi_csi2[sensor->id];
+
+ source = &sensor->sd->entity;
+ sink = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+ source_pad = 0;
+ sink_pad = source_pad;
+
+ mipi_vc = (mipi_csi2->vchannel) ? 4 : 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",
+ source->name, sink->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;
+
+ dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__);
+
+ /* Find platform data for this sensor subdev */
+ for (i = 0; i < ARRAY_SIZE(mxc_md->sensor); i++) {
+ if (mxc_md->sensor[i].fwnode ==
+ of_fwnode_handle(sd->dev->of_node)) {
+ sensor = &mxc_md->sensor[i];
+ }
+ }
+
+ if (!sensor)
+ return -EINVAL;
+
+ sd->grp_id = GRP_ID_MXC_SENSOR;
+ sensor->sd = sd;
+ mxc_md->valid_num_sensors++;
+
+ v4l2_info(&mxc_md->v4l2_dev, "Registered sensor subdevice: %s (%d)\n",
+ sd->name, mxc_md->valid_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;
+
+ dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__);
+ mutex_lock(&mxc_md->media_dev.graph_mutex);
+
+ ret = mxc_md_create_links(mxc_md);
+ if (ret < 0)
+ goto unlock;
+
+ mxc_md->link_status = 1;
+
+ 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;
+ }
+
+ if (mxc_md->media_dev.devnode)
+ return ret;
+
+ return media_device_register(&mxc_md->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations sd_async_notifier_ops = {
+ .bound = subdev_notifier_bound,
+ .complete = subdev_notifier_complete,
+};
+
+void mxc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification,
+ void *arg)
+{
+}
+
+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 struct mxc_isi_info *mxc_md_parse_isi_entity(struct mxc_md *mxc_md,
+ struct device_node *node)
+{
+ struct device *dev;
+ struct mxc_isi_info *mxc_isi;
+ struct device_node *child;
+ int ret, id = -1;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ dev = &mxc_md->pdev->dev;
+
+ id = of_alias_get_id(node, ISI_OF_NODE_NAME);
+ if (id < 0 || id >= MXC_ISI_MAX_DEVS)
+ return NULL;
+
+ mxc_isi = &mxc_md->mxc_isi[id];
+
+ child = of_get_child_by_name(node, "cap_device");
+ if (!child) {
+ dev_err(dev, "Can not get child node for %s.%d\n",
+ ISI_OF_NODE_NAME, id);
+ return NULL;
+ }
+ of_node_put(child);
+
+ mxc_isi->id = id;
+ mxc_isi->node = child;
+ sprintf(mxc_isi->sd_name, "mxc_isi.%d", mxc_isi->id);
+ sprintf(mxc_isi->vdev_name, "mxc_isi.%d.capture", mxc_isi->id);
+
+ ret = of_property_read_u32_array(node, "interface",
+ mxc_isi->interface, 3);
+ if (ret < 0) {
+ dev_err(dev, "%s node has not interface property\n", child->name);
+ return NULL;
+ }
+
+ return mxc_isi;
+}
+
+static struct mxc_mipi_csi2_info *
+mxc_md_parse_csi_entity(struct mxc_md *mxc_md,
+ struct device_node *node)
+{
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ int id = -1;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ id = of_alias_get_id(node, MIPI_CSI2_OF_NODE_NAME);
+ if (id < 0 || id >= MXC_MIPI_CSI2_MAX_DEVS)
+ return NULL;
+
+ mipi_csi2 = &mxc_md->mipi_csi2[id];
+ if (!mipi_csi2)
+ return NULL;
+
+ mipi_csi2->vchannel = of_property_read_bool(node, "virtual-channel");
+ mipi_csi2->id = id;
+ mipi_csi2->node = node;
+ sprintf(mipi_csi2->sd_name, "mxc-mipi-csi2.%d", mipi_csi2->id);
+
+ return mipi_csi2;
+}
+
+static struct mxc_parallel_csi_info*
+mxc_md_parse_pcsi_entity(struct mxc_md *mxc_md, struct device_node *node)
+{
+ struct mxc_parallel_csi_info *pcsidev;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev)
+ return NULL;
+
+ pcsidev->node = node;
+ sprintf(pcsidev->sd_name, "mxc-parallel-csi");
+
+ return pcsidev;
+}
+
+struct mxc_hdmi_rx_info*
+mxc_md_parse_hdmi_rx_entity(struct mxc_md *mxc_md, struct device_node *node)
+{
+ struct mxc_hdmi_rx_info *hdmi_rx;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ hdmi_rx = &mxc_md->hdmi_rx;
+ if (!hdmi_rx)
+ return NULL;
+
+ hdmi_rx->node = node;
+ sprintf(hdmi_rx->sd_name, "mxc-hdmi-rx");
+
+ return hdmi_rx;
+}
+
+static struct v4l2_subdev *get_subdev_by_node(struct device_node *node)
+{
+ struct platform_device *pdev;
+ struct v4l2_subdev *sd = NULL;
+ struct device *dev;
+ void *drvdata;
+
+ pdev = of_find_device_by_node(node);
+ if (!pdev)
+ return NULL;
+
+ dev = &pdev->dev;
+ device_lock(&pdev->dev);
+ if (!dev->driver || !try_module_get(dev->driver->owner))
+ goto dev_unlock;
+
+ drvdata = dev_get_drvdata(dev);
+ if (!drvdata)
+ goto module_put;
+
+ sd = (struct v4l2_subdev *)drvdata;
+
+module_put:
+ module_put(dev->driver->owner);
+dev_unlock:
+ device_unlock(dev);
+ return sd;
+}
+
+static int register_isi_entity(struct mxc_md *mxc_md,
+ struct mxc_isi_info *mxc_isi)
+{
+ struct v4l2_subdev *sd;
+ int ret = 0;
+
+ sd = get_subdev_by_node(mxc_isi->node);
+ if (!sd) {
+ ret = of_device_is_available(mxc_isi->node);
+ if (!ret) {
+ dev_info(&mxc_md->pdev->dev, "%s device is disabled\n",
+ mxc_isi->node->name);
+ } else {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s registration\n",
+ mxc_isi->node->name);
+ ret = -EPROBE_DEFER;
+ }
+ return ret;
+ }
+
+ if (mxc_isi->id >= MXC_ISI_MAX_DEVS)
+ return -EBUSY;
+
+ sd->grp_id = GRP_ID_MXC_ISI;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ mxc_isi->sd = sd;
+ 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_info *mipi_csi2)
+{
+ struct v4l2_subdev *sd;
+ int ret;
+
+ sd = get_subdev_by_node(mipi_csi2->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ mipi_csi2->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ if (mipi_csi2->id >= MXC_MIPI_CSI2_MAX_DEVS)
+ return -EBUSY;
+
+ sd->grp_id = GRP_ID_MXC_MIPI_CSI2;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ mipi_csi2->sd = sd;
+ else
+ v4l2_err(&mxc_md->v4l2_dev, "Failed to register MIPI-CSI.%d (%d)\n",
+ mipi_csi2->id, ret);
+ return ret;
+}
+
+static int register_parallel_csi_entity(struct mxc_md *mxc_md,
+ struct mxc_parallel_csi_info *pcsidev)
+{
+ struct v4l2_subdev *sd;
+ int ret;
+
+ sd = get_subdev_by_node(pcsidev->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ pcsidev->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ sd->grp_id = GRP_ID_MXC_PARALLEL_CSI;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ pcsidev->sd = sd;
+ else
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Failed to register Parallel (%d)\n", ret);
+ return ret;
+}
+
+static int register_hdmi_rx_entity(struct mxc_md *mxc_md,
+ struct mxc_hdmi_rx_info *hdmi_rx)
+{
+ struct v4l2_subdev *sd;
+
+ sd = get_subdev_by_node(hdmi_rx->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ hdmi_rx->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ sd->grp_id = GRP_ID_MXC_HDMI_RX;
+
+ hdmi_rx->sd = sd;
+
+ return true;
+}
+
+static int mxc_md_register_platform_entity(struct mxc_md *mxc_md,
+ struct device_node *node,
+ int plat_entity)
+{
+ struct device *dev = &mxc_md->pdev->dev;
+ struct mxc_isi_info *isi;
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ struct mxc_hdmi_rx_info *hdmi_rx;
+ int ret = -EINVAL;
+
+ switch (plat_entity) {
+ case IDX_ISI:
+ isi = mxc_md_parse_isi_entity(mxc_md, node);
+ if (!isi)
+ return -ENODEV;
+ ret = register_isi_entity(mxc_md, isi);
+ break;
+ case IDX_MIPI_CSI2:
+ mipi_csi2 = mxc_md_parse_csi_entity(mxc_md, node);
+ if (!mipi_csi2)
+ return -ENODEV;
+ ret = register_mipi_csi2_entity(mxc_md, mipi_csi2);
+ break;
+ case IDX_PARALLEL_CSI:
+ pcsidev = mxc_md_parse_pcsi_entity(mxc_md, node);
+ if (!pcsidev)
+ return -ENODEV;
+ ret = register_parallel_csi_entity(mxc_md, pcsidev);
+ break;
+ case IDX_HDMI_RX:
+ hdmi_rx = mxc_md_parse_hdmi_rx_entity(mxc_md, node);
+ if (!hdmi_rx)
+ return -ENODEV;
+ ret = register_hdmi_rx_entity(mxc_md, hdmi_rx);
+ break;
+ default:
+ dev_err(dev, "Invalid platform entity (%d)", plat_entity);
+ return ret;
+ }
+
+ return ret;
+}
+
+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) {
+ int plat_entity = -1;
+
+ if (!of_device_is_available(node))
+ 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_OF_NODE_NAME))
+ plat_entity = IDX_PARALLEL_CSI;
+ else if (!strcmp(node->name, HDMI_RX_OF_NODE_NAME))
+ plat_entity = IDX_HDMI_RX;
+
+ if (plat_entity >= IDX_SENSOR && plat_entity < IDX_MAX) {
+ ret = mxc_md_register_platform_entity(mxc_md, node,
+ plat_entity);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+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_fwnode_endpoint endpoint;
+ struct i2c_client *client;
+ struct v4l2_async_subdev *asd;
+ int index = 0;
+ int ret;
+
+ mxc_md->num_sensors = 0;
+
+ /* Attach sensors linked to MIPI CSI2 / paralle csi / HDMI Rx */
+ for_each_available_child_of_node(parent, node) {
+ struct device_node *port;
+
+ if (!of_node_cmp(node->name, HDMI_RX_OF_NODE_NAME)) {
+ mxc_md->sensor[index].fwnode = of_fwnode_handle(node);
+ v4l2_async_notifier_add_fwnode_subdev(
+ &mxc_md->subdev_notifier,
+ mxc_md->sensor[index].fwnode,
+ struct v4l2_async_subdev);
+ mxc_md->num_sensors++;
+ index++;
+ continue;
+ }
+
+ if (of_node_cmp(node->name, MIPI_CSI2_OF_NODE_NAME) &&
+ of_node_cmp(node->name, PARALLEL_OF_NODE_NAME))
+ 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;
+
+ memset(&endpoint, 0, sizeof(endpoint));
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint);
+ if (WARN_ON(endpoint.base.port >= MXC_MAX_SENSORS || ret)) {
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Failed to get sensor endpoint\n");
+ return -EINVAL;
+ }
+
+ mxc_md->sensor[index].id = endpoint.base.port;
+
+ if (!of_node_cmp(node->name, MIPI_CSI2_OF_NODE_NAME))
+ mxc_md->sensor[index].mipi_mode = true;
+
+ /* remote port---sensor node */
+ rem = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!rem) {
+ v4l2_info(&mxc_md->v4l2_dev,
+ "Remote device at %s not found\n",
+ ep->full_name);
+ continue;
+ }
+
+ /*
+ * Need to wait sensor driver probed for the first time
+ */
+ client = of_find_i2c_device_by_node(rem);
+ if (!client) {
+ v4l2_info(&mxc_md->v4l2_dev,
+ "Can't find i2c client device for %s\n",
+ of_node_full_name(rem));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_md->sensor[index].fwnode = of_fwnode_handle(rem);
+ asd = v4l2_async_notifier_add_fwnode_subdev(
+ &mxc_md->subdev_notifier,
+ mxc_md->sensor[index].fwnode,
+ struct v4l2_async_subdev);
+ if (IS_ERR(asd)) {
+ v4l2_info(&mxc_md->v4l2_dev, "Can't find async subdev\n");
+ return PTR_ERR(asd);
+ }
+
+ mxc_md->num_sensors++;
+
+ index++;
+ }
+
+ return 0;
+}
+
+static int mxc_md_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *nd = dev->of_node;
+ 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(nd, "parallel_csi");
+
+ /* register media device */
+ strlcpy(mxc_md->media_dev.model, "FSL Capture Media Device",
+ 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 clean_md;
+ }
+
+ v4l2_async_notifier_init(&mxc_md->subdev_notifier);
+ ret = mxc_md_register_platform_entities(mxc_md, dev->of_node);
+ if (ret < 0)
+ goto clean_v4l2;
+
+ ret = register_sensor_entities(mxc_md);
+ if (ret < 0)
+ goto clean_ents;
+
+ if (mxc_md->num_sensors > 0) {
+ mxc_md->subdev_notifier.ops = &sd_async_notifier_ops;
+ mxc_md->valid_num_sensors = 0;
+ mxc_md->link_status = 0;
+
+ ret = v4l2_async_notifier_register(&mxc_md->v4l2_dev,
+ &mxc_md->subdev_notifier);
+ if (ret < 0) {
+ dev_warn(&mxc_md->pdev->dev, "Sensor register failed\n");
+ return ret;
+ }
+
+ if (!mxc_md->link_status) {
+ if (mxc_md->valid_num_sensors > 0) {
+ ret = subdev_notifier_complete(&mxc_md->subdev_notifier);
+ if (ret < 0)
+ goto clean_ents;
+
+ mxc_md_clean_unlink_channels(mxc_md);
+ } else {
+ /* no sensors connected */
+ mxc_md_unregister_all(mxc_md);
+ v4l2_async_notifier_unregister(&mxc_md->subdev_notifier);
+ }
+ }
+ }
+
+ return 0;
+
+clean_ents:
+ mxc_md_unregister_entities(mxc_md);
+clean_v4l2:
+ v4l2_device_unregister(&mxc_md->v4l2_dev);
+clean_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_cleanup(&mxc_md->subdev_notifier);
+ 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);