Contributors: 11
Author Tokens Token Proportion Commits Commit Proportion
Helen Mae Koike Fornazier 1349 47.35% 6 11.32%
Paul Elder 440 15.44% 9 16.98%
Laurent Pinchart 434 15.23% 18 33.96%
Tomi Valkeinen 230 8.07% 5 9.43%
Heiko Stübner 191 6.70% 3 5.66%
Dafna Hirschfeld 119 4.18% 4 7.55%
Jacob Chen 58 2.04% 2 3.77%
Sakari Ailus 17 0.60% 3 5.66%
Ezequiel García 8 0.28% 1 1.89%
Uwe Kleine-König 2 0.07% 1 1.89%
Rob Herring 1 0.04% 1 1.89%
Total 2849 53


// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Rockchip ISP1 Driver - Base driver
 *
 * Copyright (C) 2019 Collabora, Ltd.
 *
 * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd.
 * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
 */

#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>

#include "rkisp1-common.h"
#include "rkisp1-csi.h"

/*
 * ISP Details
 * -----------
 *
 * ISP Comprises with:
 *	MIPI serial camera interface
 *	Image Signal Processing
 *	Many Image Enhancement Blocks
 *	Crop
 *	Resizer
 *	RBG display ready image
 *	Image Rotation
 *
 * ISP Block Diagram
 * -----------------
 *                                                             rkisp1-resizer.c          rkisp1-capture.c
 *                                                          |====================|  |=======================|
 *                                rkisp1-isp.c                              Main Picture Path
 *                        |==========================|      |===============================================|
 *                        +-----------+  +--+--+--+--+      +--------+  +--------+              +-----------+
 *                        |           |  |  |  |  |  |      |        |  |        |              |           |
 * +--------+    |\       |           |  |  |  |  |  |   -->|  Crop  |->|  RSZ   |------------->|           |
 * |  MIPI  |--->|  \     |           |  |  |  |  |  |   |  |        |  |        |              |           |
 * +--------+    |   |    |           |  |IE|IE|IE|IE|   |  +--------+  +--------+              |  Memory   |
 *               |MUX|--->|    ISP    |->|0 |1 |2 |3 |---+                                      | Interface |
 * +--------+    |   |    |           |  |  |  |  |  |   |  +--------+  +--------+  +--------+  |           |
 * |Parallel|--->|  /     |           |  |  |  |  |  |   |  |        |  |        |  |        |  |           |
 * +--------+    |/       |           |  |  |  |  |  |   -->|  Crop  |->|  RSZ   |->|  RGB   |->|           |
 *                        |           |  |  |  |  |  |      |        |  |        |  | Rotate |  |           |
 *                        +-----------+  +--+--+--+--+      +--------+  +--------+  +--------+  +-----------+
 *                                               ^
 * +--------+                                    |          |===============================================|
 * |  DMA   |------------------------------------+                          Self Picture Path
 * +--------+
 *
 *         rkisp1-stats.c        rkisp1-params.c
 *       |===============|      |===============|
 *       +---------------+      +---------------+
 *       |               |      |               |
 *       |      ISP      |      |      ISP      |
 *       |               |      |               |
 *       +---------------+      +---------------+
 *
 *
 * Media Topology
 * --------------
 *
 *          +----------+       +----------+
 *          | Sensor 1 |       | Sensor X |
 *          ------------  ...  ------------
 *          |    0     |       |    0     |
 *          +----------+       +----------+
 *               |                  |
 *                \----\       /----/
 *                     |       |
 *                     v       v
 *                  +-------------+
 *                  |      0      |
 *                  ---------------
 *                  |  CSI-2 RX   |
 *                  ---------------         +-----------+
 *                  |      1      |         |  params   |
 *                  +-------------+         | (output)  |
 *                         |               +-----------+
 *                         v                     |
 *                      +------+------+          |
 *                      |  0   |  1   |<---------+
 *                      |------+------|
 *                      |     ISP     |
 *                      |------+------|
 *        +-------------|  2   |  3   |----------+
 *        |             +------+------+          |
 *        |                |                     |
 *        v                v                     v
 *  +- ---------+    +-----------+         +-----------+
 *  |     0     |    |     0     |         |   stats   |
 *  -------------    -------------         | (capture) |
 *  |  Resizer  |    |  Resizer  |         +-----------+
 *  ------------|    ------------|
 *  |     1     |    |     1     |
 *  +-----------+    +-----------+
 *        |                |
 *        v                v
 *  +-----------+    +-----------+
 *  | selfpath  |    | mainpath  |
 *  | (capture) |    | (capture) |
 *  +-----------+    +-----------+
 */

struct rkisp1_isr_data {
	const char *name;
	irqreturn_t (*isr)(int irq, void *ctx);
	u32 line_mask;
};

/* ----------------------------------------------------------------------------
 * Sensor DT bindings
 */

static int rkisp1_subdev_notifier_bound(struct v4l2_async_notifier *notifier,
					struct v4l2_subdev *sd,
					struct v4l2_async_connection *asc)
{
	struct rkisp1_device *rkisp1 =
		container_of(notifier, struct rkisp1_device, notifier);
	struct rkisp1_sensor_async *s_asd =
		container_of(asc, struct rkisp1_sensor_async, asd);
	int source_pad;
	int ret;

	s_asd->sd = sd;

	source_pad = media_entity_get_fwnode_pad(&sd->entity, s_asd->source_ep,
						 MEDIA_PAD_FL_SOURCE);
	if (source_pad < 0) {
		dev_err(rkisp1->dev, "failed to find source pad for %s\n",
			sd->name);
		return source_pad;
	}

	if (s_asd->port == 0)
		return rkisp1_csi_link_sensor(rkisp1, sd, s_asd, source_pad);

	ret = media_create_pad_link(&sd->entity, source_pad,
				    &rkisp1->isp.sd.entity,
				    RKISP1_ISP_PAD_SINK_VIDEO,
				    !s_asd->index ? MEDIA_LNK_FL_ENABLED : 0);
	if (ret) {
		dev_err(rkisp1->dev, "failed to link source pad of %s\n",
			sd->name);
		return ret;
	}

	return 0;
}

static int rkisp1_subdev_notifier_complete(struct v4l2_async_notifier *notifier)
{
	struct rkisp1_device *rkisp1 =
		container_of(notifier, struct rkisp1_device, notifier);

	return v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev);
}

static void rkisp1_subdev_notifier_destroy(struct v4l2_async_connection *asc)
{
	struct rkisp1_sensor_async *rk_asd =
		container_of(asc, struct rkisp1_sensor_async, asd);

	fwnode_handle_put(rk_asd->source_ep);
}

static const struct v4l2_async_notifier_operations rkisp1_subdev_notifier_ops = {
	.bound = rkisp1_subdev_notifier_bound,
	.complete = rkisp1_subdev_notifier_complete,
	.destroy = rkisp1_subdev_notifier_destroy,
};

static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1)
{
	struct v4l2_async_notifier *ntf = &rkisp1->notifier;
	struct fwnode_handle *fwnode = dev_fwnode(rkisp1->dev);
	struct fwnode_handle *ep;
	unsigned int index = 0;
	int ret = 0;

	v4l2_async_nf_init(ntf, &rkisp1->v4l2_dev);

	ntf->ops = &rkisp1_subdev_notifier_ops;

	fwnode_graph_for_each_endpoint(fwnode, ep) {
		struct fwnode_handle *port;
		struct v4l2_fwnode_endpoint vep = { };
		struct rkisp1_sensor_async *rk_asd;
		struct fwnode_handle *source;
		u32 reg = 0;

		/* Select the bus type based on the port. */
		port = fwnode_get_parent(ep);
		fwnode_property_read_u32(port, "reg", &reg);
		fwnode_handle_put(port);

		switch (reg) {
		case 0:
			/* MIPI CSI-2 port */
			if (!rkisp1_has_feature(rkisp1, MIPI_CSI2)) {
				dev_err(rkisp1->dev,
					"internal CSI must be available for port 0\n");
				ret = -EINVAL;
				break;
			}

			vep.bus_type = V4L2_MBUS_CSI2_DPHY;
			break;

		case 1:
			/*
			 * Parallel port. The bus-type property in DT is
			 * mandatory for port 1, it will be used to determine if
			 * it's PARALLEL or BT656.
			 */
			vep.bus_type = V4L2_MBUS_UNKNOWN;
			break;
		}

		/* Parse the endpoint and validate the bus type. */
		ret = v4l2_fwnode_endpoint_parse(ep, &vep);
		if (ret) {
			dev_err(rkisp1->dev, "failed to parse endpoint %pfw\n",
				ep);
			break;
		}

		if (vep.base.port == 1) {
			if (vep.bus_type != V4L2_MBUS_PARALLEL &&
			    vep.bus_type != V4L2_MBUS_BT656) {
				dev_err(rkisp1->dev,
					"port 1 must be parallel or BT656\n");
				ret = -EINVAL;
				break;
			}
		}

		/* Add the async subdev to the notifier. */
		source = fwnode_graph_get_remote_endpoint(ep);
		if (!source) {
			dev_err(rkisp1->dev,
				"endpoint %pfw has no remote endpoint\n",
				ep);
			ret = -ENODEV;
			break;
		}

		rk_asd = v4l2_async_nf_add_fwnode(ntf, source,
						  struct rkisp1_sensor_async);
		if (IS_ERR(rk_asd)) {
			fwnode_handle_put(source);
			ret = PTR_ERR(rk_asd);
			break;
		}

		rk_asd->index = index++;
		rk_asd->source_ep = source;
		rk_asd->mbus_type = vep.bus_type;
		rk_asd->port = vep.base.port;

		if (vep.bus_type == V4L2_MBUS_CSI2_DPHY) {
			rk_asd->mbus_flags = vep.bus.mipi_csi2.flags;
			rk_asd->lanes = vep.bus.mipi_csi2.num_data_lanes;
		} else {
			rk_asd->mbus_flags = vep.bus.parallel.flags;
		}

		dev_dbg(rkisp1->dev, "registered ep id %d, bus type %u, %u lanes\n",
			vep.base.id, rk_asd->mbus_type, rk_asd->lanes);
	}

	if (ret) {
		fwnode_handle_put(ep);
		v4l2_async_nf_cleanup(ntf);
		return ret;
	}

	if (!index)
		dev_dbg(rkisp1->dev, "no remote subdevice found\n");

	ret = v4l2_async_nf_register(ntf);
	if (ret) {
		v4l2_async_nf_cleanup(ntf);
		return ret;
	}

	return 0;
}

/* ----------------------------------------------------------------------------
 * Power
 */

static int __maybe_unused rkisp1_runtime_suspend(struct device *dev)
{
	struct rkisp1_device *rkisp1 = dev_get_drvdata(dev);

	rkisp1->irqs_enabled = false;
	/* Make sure the IRQ handler will see the above */
	mb();

	/*
	 * Wait until any running IRQ handler has returned. The IRQ handler
	 * may get called even after this (as it's a shared interrupt line)
	 * but the 'irqs_enabled' flag will make the handler return immediately.
	 */
	for (unsigned int il = 0; il < ARRAY_SIZE(rkisp1->irqs); ++il) {
		if (rkisp1->irqs[il] == -1)
			continue;

		/* Skip if the irq line is the same as previous */
		if (il == 0 || rkisp1->irqs[il - 1] != rkisp1->irqs[il])
			synchronize_irq(rkisp1->irqs[il]);
	}

	clk_bulk_disable_unprepare(rkisp1->clk_size, rkisp1->clks);
	return pinctrl_pm_select_sleep_state(dev);
}

static int __maybe_unused rkisp1_runtime_resume(struct device *dev)
{
	struct rkisp1_device *rkisp1 = dev_get_drvdata(dev);
	int ret;

	ret = pinctrl_pm_select_default_state(dev);
	if (ret)
		return ret;
	ret = clk_bulk_prepare_enable(rkisp1->clk_size, rkisp1->clks);
	if (ret)
		return ret;

	rkisp1->irqs_enabled = true;
	/* Make sure the IRQ handler will see the above */
	mb();

	return 0;
}

static const struct dev_pm_ops rkisp1_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
				pm_runtime_force_resume)
	SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL)
};

/* ----------------------------------------------------------------------------
 * Core
 */

static int rkisp1_create_links(struct rkisp1_device *rkisp1)
{
	unsigned int dev_count = rkisp1_path_count(rkisp1);
	unsigned int i;
	int ret;

	if (rkisp1_has_feature(rkisp1, MIPI_CSI2)) {
		/* Link the CSI receiver to the ISP. */
		ret = media_create_pad_link(&rkisp1->csi.sd.entity,
					    RKISP1_CSI_PAD_SRC,
					    &rkisp1->isp.sd.entity,
					    RKISP1_ISP_PAD_SINK_VIDEO,
					    MEDIA_LNK_FL_ENABLED);
		if (ret)
			return ret;
	}

	/* create ISP->RSZ->CAP links */
	for (i = 0; i < dev_count; i++) {
		struct media_entity *resizer =
			&rkisp1->resizer_devs[i].sd.entity;
		struct media_entity *capture =
			&rkisp1->capture_devs[i].vnode.vdev.entity;

		ret = media_create_pad_link(&rkisp1->isp.sd.entity,
					    RKISP1_ISP_PAD_SOURCE_VIDEO,
					    resizer, RKISP1_RSZ_PAD_SINK,
					    MEDIA_LNK_FL_ENABLED);
		if (ret)
			return ret;

		ret = media_create_pad_link(resizer, RKISP1_RSZ_PAD_SRC,
					    capture, 0,
					    MEDIA_LNK_FL_ENABLED |
					    MEDIA_LNK_FL_IMMUTABLE);
		if (ret)
			return ret;
	}

	/* params links */
	ret = media_create_pad_link(&rkisp1->params.vnode.vdev.entity, 0,
				    &rkisp1->isp.sd.entity,
				    RKISP1_ISP_PAD_SINK_PARAMS,
				    MEDIA_LNK_FL_ENABLED |
				    MEDIA_LNK_FL_IMMUTABLE);
	if (ret)
		return ret;

	/* 3A stats links */
	return media_create_pad_link(&rkisp1->isp.sd.entity,
				     RKISP1_ISP_PAD_SOURCE_STATS,
				     &rkisp1->stats.vnode.vdev.entity, 0,
				     MEDIA_LNK_FL_ENABLED |
				     MEDIA_LNK_FL_IMMUTABLE);
}

static void rkisp1_entities_unregister(struct rkisp1_device *rkisp1)
{
	if (rkisp1_has_feature(rkisp1, MIPI_CSI2))
		rkisp1_csi_unregister(rkisp1);
	rkisp1_params_unregister(rkisp1);
	rkisp1_stats_unregister(rkisp1);
	rkisp1_capture_devs_unregister(rkisp1);
	rkisp1_resizer_devs_unregister(rkisp1);
	rkisp1_isp_unregister(rkisp1);
}

static int rkisp1_entities_register(struct rkisp1_device *rkisp1)
{
	int ret;

	ret = rkisp1_isp_register(rkisp1);
	if (ret)
		goto error;

	ret = rkisp1_resizer_devs_register(rkisp1);
	if (ret)
		goto error;

	ret = rkisp1_capture_devs_register(rkisp1);
	if (ret)
		goto error;

	ret = rkisp1_stats_register(rkisp1);
	if (ret)
		goto error;

	ret = rkisp1_params_register(rkisp1);
	if (ret)
		goto error;

	if (rkisp1_has_feature(rkisp1, MIPI_CSI2)) {
		ret = rkisp1_csi_register(rkisp1);
		if (ret)
			goto error;
	}

	ret = rkisp1_create_links(rkisp1);
	if (ret)
		goto error;

	return 0;

error:
	rkisp1_entities_unregister(rkisp1);
	return ret;
}

static irqreturn_t rkisp1_isr(int irq, void *ctx)
{
	irqreturn_t ret = IRQ_NONE;

	/*
	 * Call rkisp1_capture_isr() first to handle the frame that
	 * potentially completed using the current frame_sequence number before
	 * it is potentially incremented by rkisp1_isp_isr() in the vertical
	 * sync.
	 */

	if (rkisp1_capture_isr(irq, ctx) == IRQ_HANDLED)
		ret = IRQ_HANDLED;

	if (rkisp1_isp_isr(irq, ctx) == IRQ_HANDLED)
		ret = IRQ_HANDLED;

	if (rkisp1_csi_isr(irq, ctx) == IRQ_HANDLED)
		ret = IRQ_HANDLED;

	return ret;
}

static const char * const px30_isp_clks[] = {
	"isp",
	"aclk",
	"hclk",
	"pclk",
};

static const struct rkisp1_isr_data px30_isp_isrs[] = {
	{ "isp", rkisp1_isp_isr, BIT(RKISP1_IRQ_ISP) },
	{ "mi", rkisp1_capture_isr, BIT(RKISP1_IRQ_MI) },
	{ "mipi", rkisp1_csi_isr, BIT(RKISP1_IRQ_MIPI) },
};

static const struct rkisp1_info px30_isp_info = {
	.clks = px30_isp_clks,
	.clk_size = ARRAY_SIZE(px30_isp_clks),
	.isrs = px30_isp_isrs,
	.isr_size = ARRAY_SIZE(px30_isp_isrs),
	.isp_ver = RKISP1_V12,
	.features = RKISP1_FEATURE_MIPI_CSI2
		  | RKISP1_FEATURE_SELF_PATH
		  | RKISP1_FEATURE_DUAL_CROP,
};

static const char * const rk3399_isp_clks[] = {
	"isp",
	"aclk",
	"hclk",
};

static const struct rkisp1_isr_data rk3399_isp_isrs[] = {
	{ NULL, rkisp1_isr, BIT(RKISP1_IRQ_ISP) | BIT(RKISP1_IRQ_MI) | BIT(RKISP1_IRQ_MIPI) },
};

static const struct rkisp1_info rk3399_isp_info = {
	.clks = rk3399_isp_clks,
	.clk_size = ARRAY_SIZE(rk3399_isp_clks),
	.isrs = rk3399_isp_isrs,
	.isr_size = ARRAY_SIZE(rk3399_isp_isrs),
	.isp_ver = RKISP1_V10,
	.features = RKISP1_FEATURE_MIPI_CSI2
		  | RKISP1_FEATURE_SELF_PATH
		  | RKISP1_FEATURE_DUAL_CROP,
};

static const char * const imx8mp_isp_clks[] = {
	"isp",
	"hclk",
	"aclk",
};

static const struct rkisp1_isr_data imx8mp_isp_isrs[] = {
	{ NULL, rkisp1_isr, BIT(RKISP1_IRQ_ISP) | BIT(RKISP1_IRQ_MI) },
};

static const struct rkisp1_info imx8mp_isp_info = {
	.clks = imx8mp_isp_clks,
	.clk_size = ARRAY_SIZE(imx8mp_isp_clks),
	.isrs = imx8mp_isp_isrs,
	.isr_size = ARRAY_SIZE(imx8mp_isp_isrs),
	.isp_ver = RKISP1_V_IMX8MP,
	.features = RKISP1_FEATURE_MAIN_STRIDE
		  | RKISP1_FEATURE_DMA_34BIT,
};

static const struct of_device_id rkisp1_of_match[] = {
	{
		.compatible = "rockchip,px30-cif-isp",
		.data = &px30_isp_info,
	},
	{
		.compatible = "rockchip,rk3399-cif-isp",
		.data = &rk3399_isp_info,
	},
	{
		.compatible = "fsl,imx8mp-isp",
		.data = &imx8mp_isp_info,
	},
	{},
};
MODULE_DEVICE_TABLE(of, rkisp1_of_match);

static int rkisp1_probe(struct platform_device *pdev)
{
	const struct rkisp1_info *info;
	struct device *dev = &pdev->dev;
	struct rkisp1_device *rkisp1;
	struct v4l2_device *v4l2_dev;
	unsigned int i;
	u64 dma_mask;
	int ret, irq;
	u32 cif_id;

	rkisp1 = devm_kzalloc(dev, sizeof(*rkisp1), GFP_KERNEL);
	if (!rkisp1)
		return -ENOMEM;

	info = of_device_get_match_data(dev);
	rkisp1->info = info;

	dev_set_drvdata(dev, rkisp1);
	rkisp1->dev = dev;

	dma_mask = rkisp1_has_feature(rkisp1, DMA_34BIT) ? DMA_BIT_MASK(34) :
							   DMA_BIT_MASK(32);

	ret = dma_set_mask_and_coherent(dev, dma_mask);
	if (ret)
		return ret;

	mutex_init(&rkisp1->stream_lock);

	rkisp1->base_addr = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(rkisp1->base_addr))
		return PTR_ERR(rkisp1->base_addr);

	for (unsigned int il = 0; il < ARRAY_SIZE(rkisp1->irqs); ++il)
		rkisp1->irqs[il] = -1;

	for (i = 0; i < info->isr_size; i++) {
		irq = info->isrs[i].name
		    ? platform_get_irq_byname(pdev, info->isrs[i].name)
		    : platform_get_irq(pdev, i);
		if (irq < 0)
			return irq;

		for (unsigned int il = 0; il < ARRAY_SIZE(rkisp1->irqs); ++il) {
			if (info->isrs[i].line_mask & BIT(il))
				rkisp1->irqs[il] = irq;
		}

		ret = devm_request_irq(dev, irq, info->isrs[i].isr, IRQF_SHARED,
				       dev_driver_string(dev), dev);
		if (ret) {
			dev_err(dev, "request irq failed: %d\n", ret);
			return ret;
		}
	}

	for (i = 0; i < info->clk_size; i++)
		rkisp1->clks[i].id = info->clks[i];
	ret = devm_clk_bulk_get(dev, info->clk_size, rkisp1->clks);
	if (ret)
		return ret;
	rkisp1->clk_size = info->clk_size;

	if (info->isp_ver == RKISP1_V_IMX8MP) {
		unsigned int id;

		rkisp1->gasket = syscon_regmap_lookup_by_phandle_args(dev->of_node,
								      "fsl,blk-ctrl",
								      1, &id);
		if (IS_ERR(rkisp1->gasket)) {
			ret = PTR_ERR(rkisp1->gasket);
			dev_err(dev, "failed to get gasket: %d\n", ret);
			return ret;
		}

		rkisp1->gasket_id = id;
	}

	pm_runtime_enable(&pdev->dev);

	ret = pm_runtime_resume_and_get(&pdev->dev);
	if (ret)
		goto err_pm_runtime_disable;

	cif_id = rkisp1_read(rkisp1, RKISP1_CIF_VI_ID);
	dev_dbg(rkisp1->dev, "CIF_ID 0x%08x\n", cif_id);

	pm_runtime_put(&pdev->dev);

	rkisp1->media_dev.hw_revision = info->isp_ver;
	strscpy(rkisp1->media_dev.model, RKISP1_DRIVER_NAME,
		sizeof(rkisp1->media_dev.model));
	rkisp1->media_dev.dev = &pdev->dev;
	strscpy(rkisp1->media_dev.bus_info, RKISP1_BUS_INFO,
		sizeof(rkisp1->media_dev.bus_info));
	media_device_init(&rkisp1->media_dev);

	v4l2_dev = &rkisp1->v4l2_dev;
	v4l2_dev->mdev = &rkisp1->media_dev;
	strscpy(v4l2_dev->name, RKISP1_DRIVER_NAME, sizeof(v4l2_dev->name));

	ret = v4l2_device_register(rkisp1->dev, &rkisp1->v4l2_dev);
	if (ret)
		goto err_media_dev_cleanup;

	ret = media_device_register(&rkisp1->media_dev);
	if (ret) {
		dev_err(dev, "Failed to register media device: %d\n", ret);
		goto err_unreg_v4l2_dev;
	}

	if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) {
		ret = rkisp1_csi_init(rkisp1);
		if (ret)
			goto err_unreg_media_dev;
	}

	ret = rkisp1_entities_register(rkisp1);
	if (ret)
		goto err_cleanup_csi;

	ret = rkisp1_subdev_notifier_register(rkisp1);
	if (ret)
		goto err_unreg_entities;

	rkisp1_debug_init(rkisp1);

	return 0;

err_unreg_entities:
	rkisp1_entities_unregister(rkisp1);
err_cleanup_csi:
	if (rkisp1_has_feature(rkisp1, MIPI_CSI2))
		rkisp1_csi_cleanup(rkisp1);
err_unreg_media_dev:
	media_device_unregister(&rkisp1->media_dev);
err_unreg_v4l2_dev:
	v4l2_device_unregister(&rkisp1->v4l2_dev);
err_media_dev_cleanup:
	media_device_cleanup(&rkisp1->media_dev);
err_pm_runtime_disable:
	pm_runtime_disable(&pdev->dev);
	return ret;
}

static void rkisp1_remove(struct platform_device *pdev)
{
	struct rkisp1_device *rkisp1 = platform_get_drvdata(pdev);

	v4l2_async_nf_unregister(&rkisp1->notifier);
	v4l2_async_nf_cleanup(&rkisp1->notifier);

	rkisp1_entities_unregister(rkisp1);
	if (rkisp1_has_feature(rkisp1, MIPI_CSI2))
		rkisp1_csi_cleanup(rkisp1);
	rkisp1_debug_cleanup(rkisp1);

	media_device_unregister(&rkisp1->media_dev);
	v4l2_device_unregister(&rkisp1->v4l2_dev);

	media_device_cleanup(&rkisp1->media_dev);

	pm_runtime_disable(&pdev->dev);
}

static struct platform_driver rkisp1_drv = {
	.driver = {
		.name = RKISP1_DRIVER_NAME,
		.of_match_table = of_match_ptr(rkisp1_of_match),
		.pm = &rkisp1_pm_ops,
	},
	.probe = rkisp1_probe,
	.remove_new = rkisp1_remove,
};

module_platform_driver(rkisp1_drv);
MODULE_DESCRIPTION("Rockchip ISP1 platform driver");
MODULE_LICENSE("Dual MIT/GPL");