summaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c')
-rw-r--r--drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c661
1 files changed, 661 insertions, 0 deletions
diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
new file mode 100644
index 000000000..2b76339f9
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
@@ -0,0 +1,661 @@
+// 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/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.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);
+};
+
+/* ----------------------------------------------------------------------------
+ * Sensor DT bindings
+ */
+
+static int rkisp1_subdev_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct rkisp1_device *rkisp1 =
+ container_of(notifier, struct rkisp1_device, notifier);
+ struct rkisp1_sensor_async *s_asd =
+ container_of(asd, 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_subdev *asd)
+{
+ struct rkisp1_sensor_async *rk_asd =
+ container_of(asd, 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);
+
+ 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->info->features & RKISP1_FEATURE_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(&rkisp1->v4l2_dev, 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);
+
+ 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;
+
+ 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 i;
+ int ret;
+
+ if (rkisp1->info->features & RKISP1_FEATURE_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 < 2; 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->info->features & RKISP1_FEATURE_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->info->features & RKISP1_FEATURE_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)
+{
+ /*
+ * 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.
+ */
+ rkisp1_capture_isr(irq, ctx);
+ rkisp1_isp_isr(irq, ctx);
+ rkisp1_csi_isr(irq, ctx);
+
+ return IRQ_HANDLED;
+}
+
+static const char * const px30_isp_clks[] = {
+ "isp",
+ "aclk",
+ "hclk",
+ "pclk",
+};
+
+static const struct rkisp1_isr_data px30_isp_isrs[] = {
+ { "isp", rkisp1_isp_isr },
+ { "mi", rkisp1_capture_isr },
+ { "mipi", rkisp1_csi_isr },
+};
+
+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,
+};
+
+static const char * const rk3399_isp_clks[] = {
+ "isp",
+ "aclk",
+ "hclk",
+};
+
+static const struct rkisp1_isr_data rk3399_isp_isrs[] = {
+ { NULL, rkisp1_isr },
+};
+
+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,
+};
+
+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,
+ },
+ {},
+};
+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;
+ 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;
+
+ 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 (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;
+
+ 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;
+
+ 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->info->features & RKISP1_FEATURE_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 int 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->info->features & RKISP1_FEATURE_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);
+
+ return 0;
+}
+
+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 = rkisp1_remove,
+};
+
+module_platform_driver(rkisp1_drv);
+MODULE_DESCRIPTION("Rockchip ISP1 platform driver");
+MODULE_LICENSE("Dual MIT/GPL");