aboutsummaryrefslogtreecommitdiff
path: root/drivers/media/platform/hisi/isp/isp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/hisi/isp/isp.c')
-rw-r--r--drivers/media/platform/hisi/isp/isp.c709
1 files changed, 709 insertions, 0 deletions
diff --git a/drivers/media/platform/hisi/isp/isp.c b/drivers/media/platform/hisi/isp/isp.c
new file mode 100644
index 000000000000..e57806c801a9
--- /dev/null
+++ b/drivers/media/platform/hisi/isp/isp.c
@@ -0,0 +1,709 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * isp.c
+ *
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/media-bus-format.h>
+#include <linux/media.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-fwnode.h>
+
+#include "isp.h"
+
+#define DOVDD_VOLTAGE 1800000
+#define ISP_CLK_FREQ 24000000
+
+const struct resources isp_res[] = {
+ {
+ .reg = "ispss_ctrl",
+ },
+ {
+ .reg = "smmu_ctrl",
+ },
+ {
+ .reg = "irq_merger2",
+ },
+};
+
+const struct resources csiphy_res[] = {
+ {
+ .reg = "csi0",
+ },
+ {
+ .reg = "csi1",
+ },
+};
+
+const struct resources sr_res[] = {
+ {
+ .reg = "sr",
+ },
+};
+
+const struct resources cvdr_res[] = {
+ {
+ .reg = "cvdr_rt",
+ },
+ {
+ .reg = "cvdr_srt",
+ },
+ {
+ .reg = "sub_ctrl",
+ },
+};
+
+void isp_writel(void __iomem *base , u32 reg, u32 value)
+{
+ writel(value, base + reg);
+}
+
+u32 isp_readl(void __iomem *base , u32 reg)
+{
+ return readl(base + reg);
+}
+
+u32 isp_clear_irq(struct isp *isp, enum IRQ_MERGER_TYPE irq)
+{
+ u32 val;
+
+ val = isp_readl(isp->irq_merger2, irq + IRQ_MERGER_FUNC_RIS);
+ isp_writel(isp->irq_merger2, irq+IRQ_MERGER_FUNC_ICR, val);
+
+ return val;
+}
+
+int frame_num2Offset(struct isp *isp, int frame_num)
+{
+ return (isp->frame_size * (frame_num % isp->frame_count));
+}
+
+void isp_config_smmu_bypass(struct isp *isp)
+{
+ isp_writel(isp->smmu_ctrl, 0x0, 0x1);
+}
+
+void isp_ispss_enable_irq(struct isp *isp)
+{
+ isp_writel(isp->irq_merger2, IRQ_MERGER_IMSC_DEBUG1, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_IMSC_FRPROC0, 0xFFFFFFFF);
+}
+
+void isp_ispss_clear_irq_state(struct isp *isp)
+{
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_DEBUG0, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_DEBUG1, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_DEBUG2, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_DEBUG3, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_ERROR0, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_ERROR1, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_FRPROC0, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_FRPROC1, 0xFFFFFFFF);
+ isp_writel(isp->irq_merger2, IRQ_MERGER_ICR_FRPROC2, 0xFFFFFFFF);
+}
+
+/*
+ * isp_enable_clocks - Enable multiple clocks
+ * @nclocks: Number of clocks in clock array
+ * @clock: Clock array
+ * @dev: Device
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int isp_enable_clocks(struct isp_clock *clks, struct device *dev)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < ISP_NUM_CLKS; i++) {
+ ret = clk_prepare_enable(clks[i].clk);
+ if (ret) {
+ dev_err(dev, "clock enable failed: %d\n", ret);
+ goto error;
+ }
+ }
+
+ return 0;
+
+error:
+ for (i--; i >= 0; i--)
+ clk_disable_unprepare(clks[i].clk);
+
+ return ret;
+}
+
+/*
+ * isp_disable_clocks - Disable multiple clocks
+ * @nclocks: Number of clocks in clock array
+ * @clock: Clock array
+ */
+void isp_disable_clocks(struct isp_clock *clks)
+{
+ int i;
+
+ for (i = ISP_NUM_CLKS - 1; i >= 0; i--)
+ clk_disable_unprepare(clks[i].clk);
+}
+
+/*
+ * isp_find_sensor - Find a linked media entity which represents a sensor
+ * @entity: Media entity to start searching from
+ *
+ * Return a pointer to sensor media entity or NULL if not found
+ */
+static struct media_entity *isp_find_sensor(struct media_entity *entity)
+{
+ struct media_pad *pad;
+
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ return NULL;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ return NULL;
+
+ entity = pad->entity;
+
+ if (entity->function == MEDIA_ENT_F_CAM_SENSOR)
+ return entity;
+ }
+}
+
+/*
+ * isp_get_pixel_clock - Get pixel clock rate from sensor
+ * @entity: Media entity in the current pipeline
+ * @pixel_clock: Received pixel clock value
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int isp_get_pixel_clock(struct media_entity *entity, u32 *pixel_clock)
+{
+ struct media_entity *sensor;
+ struct v4l2_subdev *subdev;
+ struct v4l2_ctrl *ctrl;
+
+ sensor = isp_find_sensor(entity);
+ if (!sensor)
+ return -ENODEV;
+
+ subdev = media_entity_to_v4l2_subdev(sensor);
+
+ ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE);
+
+ if (!ctrl)
+ return -EINVAL;
+
+ *pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl);
+
+ return 0;
+}
+
+/*
+ * isp_of_parse_endpoint_node - Parse port endpoint node
+ * @dev: Device
+ * @node: Device node to be parsed
+ * @csd: Parsed data from port endpoint node
+ *
+ * Return 0 on success or a negative error code on failure
+ */
+static int isp_of_parse_endpoint_node(struct device *dev,
+ struct device_node *node,
+ struct isp_async_subdev *csd)
+{
+ struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg;
+ struct v4l2_fwnode_bus_mipi_csi2 *mipi_csi2;
+ struct v4l2_fwnode_endpoint vep = { { 0 } };
+ unsigned int i;
+
+ v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep);
+
+ csd->interface.csiphy_id = vep.base.port;
+
+ mipi_csi2 = &vep.bus.mipi_csi2;
+ lncfg->clk.pos = mipi_csi2->clock_lane;
+ lncfg->clk.pol = mipi_csi2->lane_polarities[0];
+ lncfg->num_data = mipi_csi2->num_data_lanes;
+
+ lncfg->data = devm_kcalloc(dev,
+ lncfg->num_data, sizeof(*lncfg->data),
+ GFP_KERNEL);
+ if (!lncfg->data)
+ return -ENOMEM;
+
+ for (i = 0; i < lncfg->num_data; i++) {
+ lncfg->data[i].pos = mipi_csi2->data_lanes[i];
+ lncfg->data[i].pol = mipi_csi2->lane_polarities[i + 1];
+ }
+
+ return 0;
+}
+
+/*
+ * isp_of_parse_ports - Parse ports node
+ * @dev: Device
+ * @notifier: v4l2_device notifier data
+ *
+ * Return number of "port" nodes found in "ports" node
+ */
+static int isp_of_parse_ports(struct isp *isp)
+{
+ struct device *dev = isp->dev;
+ struct device_node *node = NULL;
+ struct device_node *remote = NULL;
+ int ret, num_subdevs = 0;
+
+ for_each_endpoint_of_node(dev->of_node, node) {
+ struct isp_async_subdev *csd;
+ struct v4l2_async_subdev *asd;
+
+ if (!of_device_is_available(node))
+ continue;
+
+ remote = of_graph_get_remote_port_parent(node);
+ if (!remote) {
+ dev_err(dev, "Cannot get remote parent\n");
+ ret = -EINVAL;
+ goto err_cleanup;
+ }
+
+ asd = v4l2_async_notifier_add_fwnode_subdev(
+ &isp->notifier, of_fwnode_handle(remote),
+ sizeof(*csd));
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ of_node_put(remote);
+ goto err_cleanup;
+ }
+
+ csd = container_of(asd, struct isp_async_subdev, asd);
+
+ ret = isp_of_parse_endpoint_node(dev, node, csd);
+ if (ret < 0)
+ goto err_cleanup;
+
+ num_subdevs++;
+ }
+
+ return num_subdevs;
+
+err_cleanup:
+ v4l2_async_notifier_cleanup(&isp->notifier);
+ of_node_put(node);
+ return ret;
+}
+
+/*
+ * isp_init_subdevices - Initialize subdev structures and resources
+ *
+ * Return 0 on success or a negative error code on failure
+ */
+static int isp_init_subdevices(struct isp *isp)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < isp->csiphy_num; i++) {
+ ret = isp_csiphy_subdev_init(isp, &isp->csiphy[i],
+ &csiphy_res[i], i);
+ if (ret < 0) {
+ dev_err(isp->dev,
+ "Failed to init csiphy%d sub-device: %d\n",
+ i, ret);
+ return ret;
+ }
+ }
+
+ ret = isp_sr_subdev_init(isp, isp->sr, sr_res);
+ if (ret < 0) {
+ dev_err(isp->dev, "Failed to init SR sub-device: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = isp_cvdr_subdev_init(isp, isp->cvdr, cvdr_res);
+ if (ret < 0) {
+ dev_err(isp->dev, "Failed to init CVDR sub-device: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * isp_register_entities - Register subdev nodes and create links
+ *
+ * Return 0 on success or a negative error code on failure
+ */
+static int isp_register_entities(struct isp *isp)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < isp->csiphy_num; i++) {
+ ret = isp_csiphy_register_entity(&isp->csiphy[i],
+ &isp->v4l2_dev);
+ if (ret < 0) {
+ dev_err(isp->dev,
+ "Failed to register csiphy%d entity: %d\n",
+ i, ret);
+ goto err_reg_csiphy;
+ }
+ }
+
+ ret = isp_sr_register_entity(isp->sr, &isp->v4l2_dev);
+ if (ret < 0) {
+ dev_err(isp->dev, "Failed to register sr entity: %d\n",
+ ret);
+ goto err_reg_sr;
+ }
+
+ ret = isp_cvdr_register_entity(isp->cvdr, &isp->v4l2_dev);
+ if (ret < 0) {
+ dev_err(isp->dev, "Failed to register cvdr entity: %d\n",
+ ret);
+ goto err_reg_sr;
+ }
+
+ for (i = 0; i < isp->csiphy_num; i++) {
+ ret = media_create_pad_link(
+ &isp->csiphy[i].subdev.entity,
+ ISP_CSIPHY_PAD_SRC,
+ &isp->sr->subdev.entity,
+ ISP_SR_PAD_SINK,
+ 0);
+ if (ret < 0) {
+ dev_err(isp->dev,
+ "Failed to link %s->%s entities: %d\n",
+ isp->csiphy[i].subdev.entity.name,
+ isp->sr->subdev.entity.name,
+ ret);
+ goto err_reg_sr;
+ }
+ }
+
+ ret = media_create_pad_link(&isp->sr->subdev.entity,
+ ISP_SR_PAD_SRC,
+ &isp->cvdr->subdev.entity,
+ ISP_CVDR_PAD_SINK,
+ 0);
+ if (ret < 0) {
+ dev_err(isp->dev, "Failed to link %s->%s entities: %d\n",
+ isp->sr->subdev.entity.name,
+ isp->cvdr->subdev.entity.name,
+ ret);
+ goto err_reg_sr;
+ }
+
+ return 0;
+
+err_reg_sr:
+ isp_sr_unregister_entity(isp->sr);
+
+ i = isp->csiphy_num;
+err_reg_csiphy:
+ for (i--; i >= 0; i--)
+ isp_csiphy_unregister_entity(&isp->csiphy[i]);
+
+ return ret;
+}
+
+static void isp_unregister_entities(struct isp *isp)
+{
+ unsigned int i;
+
+ for (i = 0; i < isp->csiphy_num; i++)
+ isp_csiphy_unregister_entity(&isp->csiphy[i]);
+ isp_sr_unregister_entity(isp->sr);
+ isp_cvdr_unregister_entity(isp->cvdr);
+}
+
+static int isp_subdev_notifier_bound(struct v4l2_async_notifier *async,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct isp *isp = container_of(async, struct isp, notifier);
+ struct isp_async_subdev *csd =
+ container_of(asd, struct isp_async_subdev, asd);
+ u8 id = csd->interface.csiphy_id;
+ struct csiphy_device *csiphy = &isp->csiphy[id];
+
+ csiphy->cfg.csi2 = &csd->interface.csi2;
+ subdev->host_priv = csiphy;
+
+ return 0;
+}
+
+static int isp_subdev_notifier_complete(struct v4l2_async_notifier *async)
+{
+ struct isp *isp = container_of(async, struct isp, notifier);
+ struct v4l2_device *v4l2_dev = &isp->v4l2_dev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
+ if (sd->host_priv) {
+ struct media_entity *sensor = &sd->entity;
+ struct csiphy_device *csiphy =
+ (struct csiphy_device *) sd->host_priv;
+ struct media_entity *input = &csiphy->subdev.entity;
+ unsigned int i;
+
+ for (i = 0; i < sensor->num_pads; i++) {
+ if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE)
+ break;
+ }
+ if (i == sensor->num_pads) {
+ dev_err(isp->dev,
+ "No source pad in external entity\n");
+ return -EINVAL;
+ }
+
+ ret = media_create_pad_link(sensor, i,
+ input, ISP_CSIPHY_PAD_SINK,
+ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+ if (ret < 0) {
+ dev_err(isp->dev,
+ "Failed to link %s->%s entities: %d\n",
+ sensor->name, input->name, ret);
+ return ret;
+ }
+ }
+ }
+
+ ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ return media_device_register(&isp->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations isp_subdev_notifier_ops = {
+ .bound = isp_subdev_notifier_bound,
+ .complete = isp_subdev_notifier_complete,
+};
+
+static const struct media_device_ops isp_media_ops = {
+ .link_notify = v4l2_pipeline_link_notify,
+};
+
+/*
+ * isp_probe - Probe ISP platform device
+ * @pdev: Pointer to ISP platform device
+ *
+ * Return 0 on success or a negative error code on failure
+ */
+static int isp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct isp *isp;
+ struct resource *r;
+ int num_subdevs, ret, i;
+
+ isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
+ if (!isp)
+ return -ENOMEM;
+
+ isp->dev = dev;
+ platform_set_drvdata(pdev, isp);
+
+ isp->csiphy_num = 2;
+ isp->csiphy = devm_kcalloc(dev, isp->csiphy_num,
+ sizeof(*isp->csiphy), GFP_KERNEL);
+ if (!isp->csiphy)
+ return -ENOMEM;
+
+ isp->sr = devm_kzalloc(dev, sizeof(*isp->sr), GFP_KERNEL);
+ if (!isp->sr)
+ return -ENOMEM;
+
+ isp->cvdr = devm_kzalloc(dev, sizeof(*isp->cvdr), GFP_KERNEL);
+ if (!isp->cvdr)
+ return -ENOMEM;
+
+ v4l2_async_notifier_init(&isp->notifier);
+ pr_info("%s: %d\n", __func__, __LINE__);
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, isp_res[0].reg);
+ isp->ispss_ctrl = devm_ioremap_resource(dev, r);
+ if (IS_ERR(isp->ispss_ctrl)) {
+ dev_err(dev, "could not map ispss_ctrl memory\n");
+ ret = PTR_ERR(isp->ispss_ctrl);
+ goto err_cleanup;
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, isp_res[1].reg);
+ isp->smmu_ctrl = devm_ioremap_resource(dev, r);
+ if (IS_ERR(isp->smmu_ctrl)) {
+ dev_err(dev, "could not map smmu_ctrl memory\n");
+ ret = PTR_ERR(isp->smmu_ctrl);
+ goto err_cleanup;
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, isp_res[2].reg);
+ isp->irq_merger2 = devm_ioremap_resource(dev, r);
+ if (IS_ERR(isp->irq_merger2)) {
+ dev_err(dev, "could not map irq_merger2 memory\n");
+ ret = PTR_ERR(isp->irq_merger2);
+ goto err_cleanup;
+ }
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ isp->clks[0].name = "isp_snclk0";
+ isp->clks[0].freq = ISP_CLK_FREQ;
+ isp->clks[1].name = "isp_snclk1";
+ isp->clks[1].freq = ISP_CLK_FREQ;
+
+ for (i = 0; i < ISP_NUM_CLKS; i++) {
+ isp->clks[i].clk = devm_clk_get(&pdev->dev, isp->clks[i].name);
+ if (IS_ERR(isp->clks[i].clk)) {
+ dev_err(isp->dev, "could not get clock: %s\n",
+ isp->clks[i].name);
+ ret = -EINVAL;
+ goto err_cleanup;
+ }
+
+ clk_set_rate(isp->clks[i].clk, isp->clks[i].freq);
+ }
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ ret = of_property_read_u32(dev->of_node, "pool-size", &isp->pool_size);
+ if (ret < 0)
+ goto err_cleanup;
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ isp->virt_addr = dma_alloc_coherent(dev, isp->pool_size,
+ &(isp->hw_addr), GFP_KERNEL);
+ if (!isp->virt_addr) {
+ pr_err("dma_alloc_coherent (ISP) alloc err!\n");
+ ret = -ENOMEM;
+ goto err_cleanup;
+ }
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ memset(isp->virt_addr, 0, isp->pool_size);
+
+ num_subdevs = isp_of_parse_ports(isp);
+ if (num_subdevs < 0) {
+ ret = num_subdevs;
+ goto err_cleanup;
+ }
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ ret = isp_init_subdevices(isp);
+ if (ret < 0)
+ goto err_cleanup;
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ isp->media_dev.dev = isp->dev;
+ strscpy(isp->media_dev.model, "HiSilicon ISP",
+ sizeof(isp->media_dev.model));
+ isp->media_dev.ops = &isp_media_ops;
+ media_device_init(&isp->media_dev);
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ /* FIXME: Get these from userspace */
+ isp->frame_size = 0x3F4800;
+ isp->frame_count = 4;
+ isp->frame_num = 0;
+ isp->v4l2_dev.mdev = &isp->media_dev;
+ ret = v4l2_device_register(isp->dev, &isp->v4l2_dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register V4L2 device: %d\n", ret);
+ goto err_cleanup;
+ }
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ ret = isp_register_entities(isp);
+ if (ret < 0)
+ goto err_register_entities;
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ if (num_subdevs) {
+ isp->notifier.ops = &isp_subdev_notifier_ops;
+
+ ret = v4l2_async_notifier_register(&isp->v4l2_dev,
+ &isp->notifier);
+ if (ret) {
+ dev_err(dev,
+ "Failed to register async subdev nodes: %d\n",
+ ret);
+ goto err_register_subdevs;
+ }
+ } else {
+ ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register subdev nodes: %d\n",
+ ret);
+ goto err_register_subdevs;
+ }
+
+ ret = media_device_register(&isp->media_dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register media device: %d\n",
+ ret);
+ goto err_register_subdevs;
+ }
+ }
+
+ pr_info("**********ISP registered*******\n");
+ return 0;
+
+err_register_subdevs:
+ isp_unregister_entities(isp);
+err_register_entities:
+ v4l2_device_unregister(&isp->v4l2_dev);
+err_cleanup:
+ v4l2_async_notifier_cleanup(&isp->notifier);
+
+ return ret;
+}
+
+void isp_delete(struct isp *isp)
+{
+ v4l2_device_unregister(&isp->v4l2_dev);
+ media_device_unregister(&isp->media_dev);
+ media_device_cleanup(&isp->media_dev);
+}
+
+static int isp_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id isp_dt_match[] = {
+ { .compatible = "hisilicon,hisi-isp" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, isp_dt_match);
+
+static struct platform_driver hisi_isp_driver = {
+ .probe = isp_probe,
+ .remove = isp_remove,
+ .driver = {
+ .name = "hisi-isp",
+ .of_match_table = isp_dt_match,
+ },
+};
+
+module_platform_driver(hisi_isp_driver);
+
+MODULE_LICENSE("GPL v2");