]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/media/platform/msm/camss-8x16/camss.c
Merge remote-tracking branch 'todor/release/qcomlt-4.4-camss-demo2' into release...
[karo-tx-linux.git] / drivers / media / platform / msm / camss-8x16 / camss.c
diff --git a/drivers/media/platform/msm/camss-8x16/camss.c b/drivers/media/platform/msm/camss-8x16/camss.c
new file mode 100644 (file)
index 0000000..0644bc6
--- /dev/null
@@ -0,0 +1,703 @@
+/*
+ * camss.c
+ *
+ * Qualcomm MSM Camera Subsystem - Core
+ *
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2015-2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+#include "camss.h"
+
+#define CAMSS_CSIPHY_NUM 2
+#define CAMSS_CSID_NUM 2
+
+static struct resources csiphy_res[] = {
+       /* CSIPHY0 */
+       {
+               .regulator = { NULL },
+               .clock = { "csiphy0_timer_src_clk", "csiphy0_timer_clk", "camss_ahb_src" },
+               .clock_rate = { 200000000, 0, 0 },
+               .reg = { "csiphy0", "csiphy0_clk_mux" },
+               .interrupt = { "csiphy0" }
+       },
+
+       /* CSIPHY1 */
+       {
+               .regulator = { NULL },
+               .clock = { "csiphy1_timer_src_clk", "csiphy1_timer_clk", "camss_ahb_src" },
+               .clock_rate = { 200000000, 0, 0 },
+               .reg = { "csiphy1", "csiphy1_clk_mux" },
+               .interrupt = { "csiphy1" }
+       }
+};
+
+static struct resources csid_res[] = {
+       /* CSID0 */
+       {
+               .regulator = { "vdda" },
+               .clock = { "camss_top_ahb_clk", "ispif_ahb_clk", "csi0_ahb_clk",
+                          "csi0_src_clk", "csi0_clk", "csi0_phy_clk",
+                          "csi0_pix_clk", "csi0_rdi_clk", "camss_ahb_clk" },
+               .clock_rate = { 0, 0, 0, 200000000, 0, 0, 0, 0, 0 },
+               .reg = { "csid0" },
+               .interrupt = { "csid0" }
+       },
+
+       /* CSID1 */
+       {
+               .regulator = { "vdda" },
+               .clock = { "camss_top_ahb_clk", "ispif_ahb_clk", "csi1_ahb_clk",
+                          "csi1_src_clk", "csi1_clk", "csi1_phy_clk",
+                          "csi1_pix_clk", "csi1_rdi_clk", "camss_ahb_clk" },
+               .clock_rate = { 0, 0, 0, 200000000, 0, 0, 0, 0, 0 },
+               .reg = { "csid1" },
+               .interrupt = { "csid1" }
+       },
+};
+
+static struct resources_ispif ispif_res = {
+       /* ISPIF */
+       .clock = { "camss_ahb_src", "ispif_ahb_clk", "csi0_src_clk",
+                  "csi0_clk", "csi0_pix_clk", "csi0_rdi_clk",
+                  "csi1_src_clk", "csi1_clk", "csi1_pix_clk",
+                  "csi1_rdi_clk", "vfe_clk_src", "camss_vfe_vfe_clk",
+                  "camss_csi_vfe_clk"
+       },
+       .clock_for_reset = { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+       .reg = { "ispif", "csi_clk_mux" },
+       .interrupt = "ispif"
+
+};
+
+/*
+ * camss_pipeline_pm_use_count - Count the number of users of a pipeline
+ * @entity: The entity
+ *
+ * Return the total number of users of all video device nodes in the pipeline.
+ */
+static int camss_pipeline_pm_use_count(struct media_entity *entity)
+{
+       struct media_entity_graph graph;
+       int use = 0;
+
+       media_entity_graph_walk_start(&graph, entity);
+
+       while ((entity = media_entity_graph_walk_next(&graph))) {
+               if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE)
+                       use += entity->use_count;
+       }
+
+       return use;
+}
+
+/*
+ * camss_pipeline_pm_power_one - Apply power change to an entity
+ * @entity: The entity
+ * @change: Use count change
+ *
+ * Change the entity use count by @change. If the entity is a subdev update its
+ * power state by calling the core::s_power operation when the use count goes
+ * from 0 to != 0 or from != 0 to 0.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+static int camss_pipeline_pm_power_one(struct media_entity *entity, int change)
+{
+       struct v4l2_subdev *subdev;
+       int ret;
+
+       subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV
+              ? media_entity_to_v4l2_subdev(entity) : NULL;
+
+       if (entity->use_count == 0 && change > 0 && subdev != NULL) {
+               ret = v4l2_subdev_call(subdev, core, s_power, 1);
+               if (ret < 0 && ret != -ENOIOCTLCMD)
+                       return ret;
+       }
+
+       entity->use_count += change;
+       WARN_ON(entity->use_count < 0);
+
+       if (entity->use_count == 0 && change < 0 && subdev != NULL)
+               v4l2_subdev_call(subdev, core, s_power, 0);
+
+       return 0;
+}
+
+/*
+ * camss_pipeline_pm_power - Apply power change to all entities in a pipeline
+ * @entity: The entity
+ * @change: Use count change
+ *
+ * Walk the pipeline to update the use count and the power state of all non-node
+ * entities.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+static int camss_pipeline_pm_power(struct media_entity *entity, int change)
+{
+       struct media_entity_graph graph;
+       struct media_entity *first = entity;
+       int ret = 0;
+
+       if (!change)
+               return 0;
+
+       media_entity_graph_walk_start(&graph, entity);
+
+       while (!ret && (entity = media_entity_graph_walk_next(&graph)))
+               if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE)
+                       ret = camss_pipeline_pm_power_one(entity, change);
+
+       if (!ret)
+               return 0;
+
+       media_entity_graph_walk_start(&graph, first);
+
+       while ((first = media_entity_graph_walk_next(&graph))
+              && first != entity)
+               if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE)
+                       camss_pipeline_pm_power_one(first, -change);
+
+       return ret;
+}
+
+/*
+ * msm_camss_pipeline_pm_use - Update the use count of an entity
+ * @entity: The entity
+ * @use: Use (1) or stop using (0) the entity
+ *
+ * Update the use count of all entities in the pipeline and power entities on or
+ * off accordingly.
+ *
+ * Return 0 on success or a negative error code on failure. Powering entities
+ * off is assumed to never fail. No failure can occur when the use parameter is
+ * set to 0.
+ */
+int msm_camss_pipeline_pm_use(struct media_entity *entity, int use)
+{
+       int change = use ? 1 : -1;
+       int ret;
+
+       mutex_lock(&entity->parent->graph_mutex);
+
+       /* Apply use count to node. */
+       entity->use_count += change;
+       WARN_ON(entity->use_count < 0);
+
+       /* Apply power change to connected non-nodes. */
+       ret = camss_pipeline_pm_power(entity, change);
+       if (ret < 0)
+               entity->use_count -= change;
+
+       mutex_unlock(&entity->parent->graph_mutex);
+
+       return ret;
+}
+
+/*
+ * camss_pipeline_link_notify - Link management notification callback
+ * @link: The link
+ * @flags: New link flags that will be applied
+ * @notification: The link's state change notification type (MEDIA_DEV_NOTIFY_*)
+ *
+ * React to link management on powered pipelines by updating the use count of
+ * all entities in the source and sink sides of the link. Entities are powered
+ * on or off accordingly.
+ *
+ * Return 0 on success or a negative error code on failure. Powering entities
+ * off is assumed to never fail. This function will not fail for disconnection
+ * events.
+ */
+static int camss_pipeline_link_notify(struct media_link *link, u32 flags,
+                                   unsigned int notification)
+{
+       struct media_entity *source = link->source->entity;
+       struct media_entity *sink = link->sink->entity;
+       int source_use = camss_pipeline_pm_use_count(source);
+       int sink_use = camss_pipeline_pm_use_count(sink);
+       int ret;
+
+       if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
+           !(flags & MEDIA_LNK_FL_ENABLED)) {
+               /* Powering off entities is assumed to never fail. */
+               camss_pipeline_pm_power(source, -sink_use);
+               camss_pipeline_pm_power(sink, -source_use);
+               return 0;
+       }
+
+       if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH &&
+               (flags & MEDIA_LNK_FL_ENABLED)) {
+
+               ret = camss_pipeline_pm_power(source, sink_use);
+               if (ret < 0)
+                       return ret;
+
+               ret = camss_pipeline_pm_power(sink, source_use);
+               if (ret < 0)
+                       camss_pipeline_pm_power(source, -sink_use);
+
+               return ret;
+       }
+
+       return 0;
+}
+
+static int camss_alloc(struct device *dev, struct camss **c)
+{
+       struct camss *camss;
+
+       *c = devm_kzalloc(dev, sizeof(**c), GFP_KERNEL);
+       if (!*c) {
+               dev_err(dev, "Failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       camss = *c;
+       camss->csiphy_num = CAMSS_CSIPHY_NUM;
+       camss->csiphy = devm_kzalloc(dev,
+                                    camss->csiphy_num * sizeof(*camss->csiphy),
+                                    GFP_KERNEL);
+
+       camss->csid_num = CAMSS_CSID_NUM;
+       camss->csid = devm_kzalloc(dev,
+                                  camss->csid_num * sizeof(*camss->csid),
+                                  GFP_KERNEL);
+       if (!camss->csiphy || !camss->csid) {
+               dev_err(dev, "Failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static int camss_of_parse_node(struct device *dev, struct device_node *node,
+                              struct camss_async_subdev *csd)
+{
+       struct camss_csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lanecfg;
+       int *settle_cnt = &csd->interface.csi2.settle_cnt;
+       struct v4l2_of_endpoint vep;
+       unsigned int i;
+
+       v4l2_of_parse_endpoint(node, &vep);
+
+       dev_dbg(dev, "parsing endpoint %s\n", node->full_name);
+
+       csd->interface.id = vep.base.port;
+
+       lncfg->clk.pos = vep.bus.mipi_csi2.clock_lane;
+       lncfg->clk.pol = vep.bus.mipi_csi2.lane_polarities[0];
+       dev_dbg(dev, "clock lane polarity %u, pos %u\n",
+               lncfg->clk.pol, lncfg->clk.pos);
+
+       lncfg->num_data = vep.bus.mipi_csi2.num_data_lanes;
+
+       lncfg->data = devm_kzalloc(dev, lncfg->num_data * sizeof(*lncfg->data),
+                                                               GFP_KERNEL);
+       if (!lncfg->data) {
+               dev_err(dev, "Failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < lncfg->num_data; i++) {
+               lncfg->data[i].pos = vep.bus.mipi_csi2.data_lanes[i];
+               lncfg->data[i].pol =
+                               vep.bus.mipi_csi2.lane_polarities[i + 1];
+               dev_dbg(dev, "data lane %u polarity %u, pos %u\n", i,
+                       lncfg->data[i].pol, lncfg->data[i].pos);
+       }
+
+       of_property_read_u32(node, "qcom,settle-cnt", settle_cnt);
+
+       return 0;
+}
+
+static int camss_of_parse_nodes(struct device *dev,
+                               struct v4l2_async_notifier *notifier)
+{
+       struct device_node *node = NULL;
+       int size, i;
+       int ret;
+
+       while ((node = of_graph_get_next_endpoint(dev->of_node, node))) {
+               notifier->num_subdevs++;
+       }
+       dev_err(dev, "notifier->num_subdevs = %u\n", notifier->num_subdevs);
+
+       size = sizeof(*notifier->subdevs) * notifier->num_subdevs;
+       notifier->subdevs = devm_kzalloc(dev, size, GFP_KERNEL);
+       if (!notifier->subdevs) {
+               dev_err(dev, "Failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       i = 0;
+       while ((node = of_graph_get_next_endpoint(dev->of_node, node))) {
+               struct camss_async_subdev *csd;
+
+               csd = devm_kzalloc(dev, sizeof(*csd), GFP_KERNEL);
+               if (!csd) {
+                       of_node_put(node);
+                       dev_err(dev, "Failed to allocate memory\n");
+                       return -ENOMEM;
+               }
+
+               notifier->subdevs[i++] = &csd->asd;
+
+               ret = camss_of_parse_node(dev, node, csd);
+               if (ret < 0) {
+                       of_node_put(node);
+                       return ret;
+               }
+
+               csd->asd.match.of.node = of_graph_get_remote_port_parent(node);
+               of_node_put(node);
+               if (!csd->asd.match.of.node) {
+                       dev_warn(dev, "bad remote port parent\n");
+                       return -EINVAL;
+               }
+
+               csd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+       }
+
+       return notifier->num_subdevs;
+}
+
+static int camss_init_subdevices(struct camss *camss)
+{
+       int i;
+       int ret;
+
+       for (i = 0; i < camss->csiphy_num; i++) {
+               ret = msm_csiphy_subdev_init(&camss->csiphy[i], camss,
+                                            &csiphy_res[i], i);
+               if (ret < 0) {
+                       dev_err(camss->dev,
+                               "Failed to init csiphy[%d] sub-device\n", i);
+                       return ret;
+               }
+       }
+
+       for (i = 0; i < camss->csid_num; i++) {
+               ret = msm_csid_subdev_init(&camss->csid[i], camss,
+                                          &csid_res[i], i);
+               if (ret < 0) {
+                       dev_err(camss->dev,
+                               "Failed to init csid[%d] sub-device\n", i);
+                       return ret;
+               }
+       }
+
+       ret = msm_ispif_subdev_init(&camss->ispif, camss, &ispif_res);
+       if (ret < 0) {
+               dev_err(camss->dev, "Failed to init ispif sub-device\n");
+               return ret;
+       }
+
+       camss->vfe_init.num_cids = 1;
+       camss->vfe_init.cid[0] = -1;
+       ret = msm_vfe_subdev_init(&camss->vfe, camss, &camss->vfe_init);
+       if (ret < 0) {
+               dev_err(camss->dev, "Fail to init vfe sub-device\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int camss_register_entities(struct camss *camss)
+{
+       int i, j;
+       int ret;
+
+       for (i = 0; i < camss->csiphy_num; i++) {
+               ret = msm_csiphy_register_entities(&camss->csiphy[i],
+                                                  &camss->v4l2_dev);
+               if (ret < 0) {
+                       dev_err(camss->dev,
+                               "Failed to register csiphy[%d] entity\n", i);
+                       goto err_reg_csiphy;
+               }
+       }
+
+       for (i = 0; i < camss->csiphy_num; i++) {
+               ret = msm_csid_register_entities(&camss->csid[i],
+                                                &camss->v4l2_dev);
+               if (ret < 0) {
+                       dev_err(camss->dev,
+                               "Failed to register csid[%d] entity\n", i);
+                       goto err_reg_csid;
+               }
+       }
+
+       ret = msm_ispif_register_entities(&camss->ispif, &camss->v4l2_dev);
+       if (ret < 0) {
+               dev_err(camss->dev, "Fail to register ispif entities\n");
+               goto err_reg_ispif;
+       }
+
+       ret = msm_vfe_register_entities(&camss->vfe, &camss->v4l2_dev);
+       if (ret < 0) {
+               dev_err(camss->dev, "Fail to register vfe entities\n");
+               goto err_reg_vfe;
+       }
+
+       for (i = 0; i < camss->csiphy_num; i++) {
+               for (j = 0; j < camss->csid_num; j++) {
+                       ret = media_entity_create_link(
+                               &camss->csiphy[i].subdev.entity,
+                               MSM_CSIPHY_PAD_SRC,
+                               &camss->csid[j].subdev.entity,
+                               MSM_CSID_PAD_SINK,
+                               0);
+                       if (ret < 0) {
+                               dev_err(camss->dev,
+                                       "Fail to link %s->%s entities\n",
+                                       camss->csiphy[i].subdev.entity.name,
+                                       camss->csid[j].subdev.entity.name);
+                               goto err_link;
+                       }
+               }
+       }
+
+       for (i = 0; i < camss->csid_num; i++) {
+               ret = media_entity_create_link(
+                       &camss->csid[i].subdev.entity, MSM_CSID_PAD_SRC,
+                       &camss->ispif.subdev.entity, MSM_ISPIF_PAD_SINK, 0);
+               if (ret < 0) {
+                       dev_err(camss->dev, "Fail to link %s->%s entities\n",
+                               camss->csid[i].subdev.entity.name,
+                               camss->ispif.subdev.entity.name);
+                       goto err_link;
+               }
+       }
+
+       ret = media_entity_create_link(
+                       &camss->ispif.subdev.entity, MSM_ISPIF_PAD_SRC,
+                       &camss->vfe.subdev.entity, MSM_VFE_PAD_SINK,
+                       MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+       if (ret < 0) {
+               dev_err(camss->dev, "Fail to link %s->%s entities\n",
+                       camss->ispif.subdev.entity.name,
+                       camss->vfe.subdev.entity.name);
+               goto err_link;
+       }
+
+       return 0;
+
+err_link:
+       msm_vfe_unregister_entities(&camss->vfe);
+err_reg_vfe:
+       msm_ispif_unregister_entities(&camss->ispif);
+err_reg_ispif:
+
+       i = camss->csid_num;
+err_reg_csid:
+       for (i--; i >= 0; i--) {
+               msm_csid_unregister_entities(&camss->csid[i]);
+       }
+
+       i = camss->csiphy_num;
+err_reg_csiphy:
+       for (i--; i >= 0; i--) {
+               msm_csiphy_unregister_entities(&camss->csiphy[i]);
+       }
+
+       return ret;
+}
+
+static void camss_unregister_entities(struct camss *camss)
+{
+       int i;
+
+       for (i = camss->csiphy_num - 1; i >= 0; i--)
+               msm_csiphy_unregister_entities(&camss->csiphy[i]);
+
+       for (i = camss->csid_num - 1; i >= 0; i--)
+               msm_csid_unregister_entities(&camss->csid[i]);
+
+       msm_ispif_unregister_entities(&camss->ispif);
+       msm_vfe_unregister_entities(&camss->vfe);
+}
+
+static int camss_subdev_notifier_bound(struct v4l2_async_notifier *async,
+                                      struct v4l2_subdev *subdev,
+                                      struct v4l2_async_subdev *asd)
+{
+       struct media_entity *sensor = &subdev->entity;
+       struct camss *camss = container_of(async, struct camss, notifier);
+       struct camss_async_subdev *csd =
+               container_of(asd, struct camss_async_subdev, asd);
+       enum camss_csiphy id = csd->interface.id;
+       struct csiphy_device *csiphy = &camss->csiphy[id];
+       struct media_entity *input = &csiphy->subdev.entity;
+       unsigned int flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
+       unsigned int pad = MSM_CSIPHY_PAD_SINK;
+       unsigned int i;
+       int ret;
+
+       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(camss->dev, "%s: no source pad in external entity\n",
+                       __func__);
+               return -EINVAL;
+       }
+
+       ret = media_entity_create_link(sensor, i, input, pad, flags);
+       if (ret < 0) {
+               dev_err(camss->dev, "Fail to link %s->%s entities\n",
+                       sensor->name, input->name);
+               return ret;
+       }
+
+       csiphy->cfg.csi2 = &csd->interface.csi2;
+
+       return 0;
+}
+
+static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async)
+{
+       struct camss *camss = container_of(async, struct camss, notifier);
+
+       return v4l2_device_register_subdev_nodes(&camss->v4l2_dev);
+}
+
+static int camss_probe(struct platform_device *pdev)
+{
+       struct camss *camss;
+       int ret;
+
+       dev_dbg(&pdev->dev, "Enter\n");
+
+       ret = camss_alloc(&pdev->dev, &camss);
+       if (ret < 0)
+               return ret;
+
+       camss->dev = &pdev->dev;
+       platform_set_drvdata(pdev, camss);
+
+       ret = camss_of_parse_nodes(&pdev->dev, &camss->notifier);
+       if (ret < 0)
+               return ret;
+
+       ret = camss_init_subdevices(camss);
+       if (ret < 0)
+               return ret;
+
+       camss->media_dev.dev = camss->dev;
+       strlcpy(camss->media_dev.model, "QC MSM CAMSS",
+               sizeof(camss->media_dev.model));
+       camss->media_dev.driver_version = CAMSS_VERSION;
+       camss->media_dev.link_notify = camss_pipeline_link_notify;
+       ret = media_device_register(&camss->media_dev);
+       if (ret < 0) {
+               dev_err(&pdev->dev,
+                       "%s: Media device registration failed (%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       camss->v4l2_dev.mdev = &camss->media_dev;
+       ret = v4l2_device_register(camss->dev, &camss->v4l2_dev);
+       if (ret < 0) {
+               dev_err(&pdev->dev,
+                       "%s: V4L2 device registration failed (%d)\n",
+                       __func__, ret);
+               goto err_register_v4l2;
+       }
+
+       ret = camss_register_entities(camss);
+       if (ret < 0)
+               goto err_register_entities;
+
+       if (camss->notifier.num_subdevs) {
+               camss->notifier.bound = camss_subdev_notifier_bound;
+               camss->notifier.complete = camss_subdev_notifier_complete;
+
+               ret = v4l2_async_notifier_register(&camss->v4l2_dev,
+                                                  &camss->notifier);
+               if (ret) {
+                       dev_err(&pdev->dev,
+                               "%s: V4L2 async notifier registration failed (%d)\n",
+                               __func__, ret);
+                       goto err_register_subdevs;
+               }
+       } else {
+               ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev);
+               if (ret < 0) {
+                       dev_err(&pdev->dev,
+                               "%s: V4L2 subdev nodes registration failed (%d)\n",
+                               __func__, ret);
+                       goto err_register_subdevs;
+               }
+       }
+
+       dev_dbg(&pdev->dev, "camss driver registered successfully!\n");
+
+       return 0;
+
+err_register_subdevs:
+       camss_unregister_entities(camss);
+err_register_entities:
+       v4l2_device_unregister(&camss->v4l2_dev);
+err_register_v4l2:
+       media_device_unregister(&camss->media_dev);
+
+       return ret;
+}
+
+static int camss_remove(struct platform_device *pdev)
+{
+       struct camss *camss = platform_get_drvdata(pdev);
+
+       v4l2_async_notifier_unregister(&camss->notifier);
+       camss_unregister_entities(camss);
+       v4l2_device_unregister(&camss->v4l2_dev);
+       media_device_unregister(&camss->media_dev);
+
+       return 0;
+}
+
+static const struct of_device_id camss_dt_match[] = {
+       { .compatible = "qcom,msm-camss" },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, camss_dt_match);
+
+static struct platform_driver qcom_camss_driver = {
+       .probe = camss_probe,
+       .remove = camss_remove,
+       .driver = {
+               .name = "qcom-camss",
+               .of_match_table = camss_dt_match,
+       },
+};
+
+module_platform_driver(qcom_camss_driver);
+
+MODULE_ALIAS("platform:qcom-camss");
+MODULE_DESCRIPTION("Qualcomm camera subsystem driver");
+MODULE_LICENSE("GPL");