msm: mdss: Add SPI display driver

Add SPI display driver for handle IOCTLs from usespace
and kickoff display pixels data.

Change-Id: I24b9215df439f30de5aa7f510ce09014739ccb78
Signed-off-by: Lei Chen <chenlei@codeaurora.org>
Signed-off-by: Raghavendra Ambadas <rambad@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/fb/mdss-spi-display.txt b/Documentation/devicetree/bindings/fb/mdss-spi-display.txt
new file mode 100644
index 0000000..2923ff1
--- /dev/null
+++ b/Documentation/devicetree/bindings/fb/mdss-spi-display.txt
@@ -0,0 +1,23 @@
+Qualcomm Technologies, Inc. mdss-spi-display
+
+mdss-spi-display is a spi interface display which support send frame
+data and command to panel, compatible with SPI interface specification.
+
+Required properties:
+- compatible:		Must be "qcom,mdss-spi-display"
+- qcom,mdss-fb-map:	pHandle that specifies the framebuffer to which the
+			interface is mapped.
+
+Optional properties:
+- label:		A string used to describe the controller used.
+Example:
+mdss_spi_display: qcom,mdss_spi_display {
+	compatible = "qcom,mdss-spi-display";
+	label = "mdss spi display";
+
+	mdss_fb0: qcom,mdss_fb_primary {
+		cell-index = <0>;
+		compatible = "qcom,mdss-fb";
+	};
+};
+
diff --git a/drivers/video/fbdev/msm/Kconfig b/drivers/video/fbdev/msm/Kconfig
index a5e0662..ad46979 100644
--- a/drivers/video/fbdev/msm/Kconfig
+++ b/drivers/video/fbdev/msm/Kconfig
@@ -83,14 +83,13 @@
 	uses USB connector to output HDMI content
 
 config FB_MSM_MDSS_SPI_PANEL
-        depends on FB_MSM_MDSS
-        bool "Support SPI panel feature"
-        default n
-        ---help---
-        The MDSS SPI Panel provides support for transmittimg SPI signals of
-        MDSS frame buffer data to connected panel. Limited by SPI rate, the
-        current max fps only reach to 27 fps, and limited by MDP hardware
-        architecture only supply on MDP3
+	depends on SPI_QUP
+	bool "Support SPI panel feature"
+	---help---
+	The MDSS SPI Panel provides support for transmittimg SPI signals of
+	MDSS frame buffer data to connected panel. Limited by SPI clock rate,
+	the current max fps only reach to ~30 fps with 240x320 resolution, and
+	limited by MDP hardware architecture only supply GPU compostition.
 
 config FB_MSM_MDSS_MHL3
 	depends on FB_MSM_MDSS_HDMI_PANEL
diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile
index 26a940c..6e8d9a8 100644
--- a/drivers/video/fbdev/msm/Makefile
+++ b/drivers/video/fbdev/msm/Makefile
@@ -66,9 +66,13 @@
 obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_cec.o
 obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_audio.o
 obj-$(CONFIG_FB_MSM_MDSS_HDMI_MHL_SII8334) += mhl_sii8334.o mhl_msc.o
+obj-$(CONFIG_FB_MSM_MDSS_SPI_PANEL) += mdss_spi_display.o
 ccflags-y += -DTARGET_HW_MDSS_HDMI
 endif
 
+obj-$(CONFIG_FB_MSM_MDSS_SPI_PANEL) += mdss_spi_client.o
+obj-$(CONFIG_FB_MSM_MDSS_SPI_PANEL) += mdss_spi_panel.o
+
 obj-$(CONFIG_FB_MSM_MDSS_WRITEBACK) += mdss_wb.o
 
 mdss-qpic-objs := mdss_qpic.o mdss_fb.o mdss_qpic_panel.o mdss_sync.o
diff --git a/drivers/video/fbdev/msm/mdss_fb.c b/drivers/video/fbdev/msm/mdss_fb.c
index 9d829e4..44db3ae 100644
--- a/drivers/video/fbdev/msm/mdss_fb.c
+++ b/drivers/video/fbdev/msm/mdss_fb.c
@@ -1295,6 +1295,7 @@
 
 	if (mfd->panel.type == SPI_PANEL)
 		mfd->fb_imgType = MDP_RGB_565;
+
 	if (mfd->panel.type == MIPI_VIDEO_PANEL || mfd->panel.type ==
 		MIPI_CMD_PANEL || mfd->panel.type == SPI_PANEL){
 		rc = of_property_read_string(pdev->dev.of_node,
@@ -1308,6 +1309,7 @@
 				mfd->fb_imgType = MDP_RGBA_8888;
 			}
 		}
+
 	mfd->split_fb_left = mfd->split_fb_right = 0;
 
 	mdss_fb_set_split_mode(mfd, pdata);
@@ -2215,8 +2217,9 @@
 
 	ion_unmap_kernel(mfd->fb_ion_client, mfd->fb_ion_handle);
 
-	if (mfd->mdp.fb_mem_get_iommu_domain && !(!mfd->fb_attachment ||
-		!mfd->fb_attachment->dmabuf ||
+	if ((mfd->mdp.fb_mem_get_iommu_domain ||
+		(mfd->panel.type == SPI_PANEL)) &&
+		!(!mfd->fb_attachment || !mfd->fb_attachment->dmabuf ||
 		!mfd->fb_attachment->dmabuf->ops)) {
 		dma_buf_unmap_attachment(mfd->fb_attachment, mfd->fb_table,
 				DMA_BIDIRECTIONAL);
@@ -2280,6 +2283,20 @@
 			rc = PTR_ERR(mfd->fb_table);
 			goto err_detach;
 		}
+	} else if (mfd->panel.type == SPI_PANEL) {
+		mfd->fb_attachment = dma_buf_attach(mfd->fbmem_buf,
+				&mfd->pdev->dev);
+		if (IS_ERR(mfd->fb_attachment)) {
+			rc = PTR_ERR(mfd->fb_attachment);
+			goto err_put;
+		}
+
+		mfd->fb_table = dma_buf_map_attachment(mfd->fb_attachment,
+			DMA_BIDIRECTIONAL);
+		if (IS_ERR(mfd->fb_table)) {
+			rc = PTR_ERR(mfd->fb_table);
+			goto err_detach;
+		}
 	} else {
 		pr_err("No IOMMU Domain\n");
 		rc = -EINVAL;
diff --git a/drivers/video/fbdev/msm/mdss_spi_display.c b/drivers/video/fbdev/msm/mdss_spi_display.c
new file mode 100644
index 0000000..3e138c2
--- /dev/null
+++ b/drivers/video/fbdev/msm/mdss_spi_display.c
@@ -0,0 +1,541 @@
+/* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved.
+ *
+ * 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/module.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/leds.h>
+#include <linux/pwm.h>
+#include <linux/of_device.h>
+#include <linux/uaccess.h>
+
+#include "mdss_panel.h"
+#include "mdss_spi_panel.h"
+#include "mdss_spi_client.h"
+#include "mdss_mdp.h"
+#include <linux/dma-buf.h>
+static int mdss_spi_get_img(struct spi_panel_data *ctrl_pdata,
+			struct mdp_layer_commit_v1 *commit, struct device *dev)
+{
+	struct msmfb_data image;
+	struct dma_buf *dmabuf;
+	void *vaddr;
+
+	memset(&image, 0, sizeof(image));
+	image.memory_id = commit->input_layers[0].buffer.planes[0].fd;
+	image.offset = commit->input_layers[0].buffer.planes[0].offset;
+
+	dmabuf = dma_buf_get(image.memory_id);
+	if (IS_ERR(dmabuf)) {
+		pr_err("%s : error on dma_buf_get\n", __func__);
+		return PTR_ERR(dmabuf);
+	}
+	ctrl_pdata->image_data.srcp_attachment =
+				dma_buf_attach(dmabuf, dev);
+	if (IS_ERR(ctrl_pdata->image_data.srcp_attachment))
+		goto err_put;
+
+	ctrl_pdata->image_data.srcp_table =
+		dma_buf_map_attachment(ctrl_pdata->image_data.srcp_attachment,
+		DMA_TO_DEVICE);
+	if (IS_ERR(ctrl_pdata->image_data.srcp_table))
+		goto err_detach;
+
+	dma_buf_begin_cpu_access(dmabuf, DMA_TO_DEVICE);
+
+	vaddr  = dma_buf_kmap(dmabuf, 0);
+	if (!vaddr) {
+		pr_err("%s:ion memory mapping failed\n", __func__);
+		goto err_unmap;
+	};
+
+	ctrl_pdata->image_data.addr = vaddr;
+	ctrl_pdata->image_data.len = dmabuf->size;
+	ctrl_pdata->image_data.mapped = true;
+	ctrl_pdata->image_data.srcp_dma_buf = dmabuf;
+
+	return 0;
+err_unmap:
+	dma_buf_unmap_attachment(ctrl_pdata->image_data.srcp_attachment,
+		ctrl_pdata->image_data.srcp_table, DMA_BIDIRECTIONAL);
+err_detach:
+	dma_buf_detach(ctrl_pdata->image_data.srcp_dma_buf,
+			ctrl_pdata->image_data.srcp_attachment);
+err_put:
+	dma_buf_put(ctrl_pdata->image_data.srcp_dma_buf);
+	return -EINVAL;
+}
+
+static void mdss_spi_put_img(struct spi_panel_data *ctrl_pdata)
+{
+	if (!ctrl_pdata->image_data.mapped)
+		return;
+	dma_buf_kunmap(ctrl_pdata->image_data.srcp_dma_buf, 0,
+				ctrl_pdata->image_data.addr);
+	dma_buf_end_cpu_access(ctrl_pdata->image_data.srcp_dma_buf,
+				DMA_BIDIRECTIONAL);
+	dma_buf_unmap_attachment(ctrl_pdata->image_data.srcp_attachment,
+			ctrl_pdata->image_data.srcp_table, DMA_TO_DEVICE);
+	dma_buf_detach(ctrl_pdata->image_data.srcp_dma_buf,
+				ctrl_pdata->image_data.srcp_attachment);
+	dma_buf_put(ctrl_pdata->image_data.srcp_dma_buf);
+
+	ctrl_pdata->image_data.srcp_dma_buf = NULL;
+	ctrl_pdata->image_data.addr = NULL;
+	ctrl_pdata->image_data.len = 0;
+	ctrl_pdata->image_data.mapped = false;
+}
+
+int mdss_spi_display_pre_commit(struct msm_fb_data_type *mfd,
+	struct file *file, struct mdp_layer_commit_v1 *commit)
+{
+	char *temp_buf;
+	int rc = 0, scan_count = 0;
+	int panel_yres, panel_xres;
+	int padding_length, byte_per_pixel;
+	int dma_stride, actual_stride;
+	struct mdss_panel_data *pdata;
+	struct spi_panel_data *ctrl_pdata = NULL;
+
+	if (commit->input_layer_cnt == 0) {
+		pr_err("SPI display doesn't support NULL commit\n");
+		return 0;
+	}
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	rc = mdss_spi_get_img(ctrl_pdata, commit, &mfd->pdev->dev);
+	if (rc) {
+		pr_err("mdss_spi_get_img failed\n");
+		return rc;
+	}
+
+	panel_xres = ctrl_pdata->panel_data.panel_info.xres;
+	panel_yres = ctrl_pdata->panel_data.panel_info.yres;
+	dma_stride = mfd->fbi->fix.line_length;
+	byte_per_pixel = ctrl_pdata->panel_data.panel_info.bpp / 8;
+	actual_stride = panel_xres * byte_per_pixel;
+	padding_length = dma_stride - actual_stride;
+
+	/* remove padding and copy to continuous buffer */
+	while (scan_count < panel_yres) {
+		memcpy((ctrl_pdata->back_buf + scan_count * actual_stride),
+			(ctrl_pdata->image_data.addr + scan_count *
+			(actual_stride + padding_length)), actual_stride);
+		scan_count++;
+	}
+
+	mdss_spi_put_img(ctrl_pdata);
+
+	/* wait for SPI transfer done */
+	rc = mdss_spi_wait_tx_done(ctrl_pdata);
+	if (!rc) {
+		pr_err("SPI transfer timeout\n");
+		return -EINVAL;
+	}
+
+	/* swap buffer */
+	temp_buf = ctrl_pdata->front_buf;
+	ctrl_pdata->front_buf = ctrl_pdata->back_buf;
+	ctrl_pdata->back_buf = temp_buf;
+
+	return 0;
+}
+
+int mdss_spi_display_atomic_validate(struct msm_fb_data_type *mfd,
+	struct file *file, struct mdp_layer_commit_v1 *commit)
+{
+	struct mdss_panel_data *pdata;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+
+	if ((commit->input_layers->dst_rect.w != pdata->panel_info.xres) &&
+		(commit->input_layers->dst_rect.h != pdata->panel_info.yres) &&
+		(commit->input_layer_cnt > 1)) {
+		WARN_ONCE(1, "%s:Only support GPU composition layer_cnt %d\n",
+				__func__, commit->input_layer_cnt);
+		return -EINVAL;
+	}
+
+	if (commit->input_layers[0].buffer.format != MDP_RGB_565) {
+		WARN_ONCE(1, "%s:SPI display only support RGB565 format %d\n",
+			__func__, commit->input_layers[0].buffer.format);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int mdss_spi_panel_kickoff(struct msm_fb_data_type *mfd,
+			struct mdp_display_commit *data)
+{
+	struct spi_panel_data *ctrl_pdata = NULL;
+	struct mdss_panel_data *pdata;
+	int rc = 0;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+	if (WARN_ON(!pdata))
+		return -EINVAL;
+
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	enable_spi_panel_te_irq(ctrl_pdata, true);
+	mutex_lock(&ctrl_pdata->spi_tx_mutex);
+	reinit_completion(&ctrl_pdata->spi_panel_te);
+	atomic_inc(&ctrl_pdata->koff_cnt);
+
+	rc = wait_for_completion_timeout(&ctrl_pdata->spi_panel_te,
+				msecs_to_jiffies(SPI_PANEL_TE_TIMEOUT));
+	if (rc == 0) {
+		pr_err("wait panel TE time out\n");
+		mutex_unlock(&ctrl_pdata->spi_tx_mutex);
+		return rc;
+	}
+
+	rc = mdss_spi_tx_pixel(ctrl_pdata->front_buf,
+				ctrl_pdata->byte_per_frame,
+				mdss_spi_tx_fb_complete, ctrl_pdata);
+
+	mutex_unlock(&ctrl_pdata->spi_tx_mutex);
+	enable_spi_panel_te_irq(ctrl_pdata, false);
+
+	return rc;
+}
+
+static int spi_display_get_metadata(struct msm_fb_data_type *mfd,
+				struct msmfb_metadata *metadata)
+{
+	int ret = 0;
+
+	switch (metadata->op) {
+	case metadata_op_frame_rate:
+		metadata->data.panel_frame_rate =
+			mfd->panel_info->spi.frame_rate;
+		break;
+	case metadata_op_get_caps:
+		metadata->data.caps.mdp_rev = 5;
+		metadata->data.caps.rgb_pipes = 0;
+		metadata->data.caps.vig_pipes = 0;
+		metadata->data.caps.dma_pipes = 1;
+		break;
+
+	default:
+		pr_warn("Unsupported request to GET META IOCTL %d\n",
+			metadata->op);
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int spi_display_ioctl_handler(struct msm_fb_data_type *mfd,
+					  u32 cmd, void __user *argp)
+{
+	int val, ret = 0;
+	struct mdss_panel_data *pdata;
+	struct msmfb_metadata metadata;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+
+	switch (cmd) {
+	case MSMFB_OVERLAY_VSYNC_CTRL:
+		if (!copy_from_user(&val, argp, sizeof(val))) {
+			mdss_spi_vsync_enable(pdata, val);
+		} else {
+			pr_err("overlay vsync ctrl copy from user failed\n");
+			ret = -EFAULT;
+		}
+		break;
+	case MSMFB_METADATA_GET:
+		ret = copy_from_user(&metadata, argp, sizeof(metadata));
+		if (ret) {
+			pr_err("get metadata from user failed (%d)\n", ret);
+			break;
+		}
+		ret = spi_display_get_metadata(mfd, &metadata);
+		if (ret) {
+			pr_err("spi_display_get_metadata failed (%d)\n", ret);
+			break;
+		}
+		ret = copy_to_user(argp, &metadata, sizeof(metadata));
+		if (ret)
+			pr_err("copy to user failed (%d)\n", ret);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static int mdss_spi_display_off(struct msm_fb_data_type *mfd)
+{
+	int rc = 0;
+	struct mdss_panel_data *pdata;
+	struct spi_panel_data *ctrl_pdata = NULL;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	ctrl_pdata->ctrl_state &= ~CTRL_STATE_PANEL_ACTIVE;
+
+	if (ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_INIT) {
+		rc = mdss_spi_panel_off(&ctrl_pdata->panel_data);
+		if (rc) {
+			pr_err("%s: Panel off failed\n", __func__);
+			return rc;
+		}
+		ctrl_pdata->ctrl_state &= ~CTRL_STATE_PANEL_INIT;
+	}
+	rc = mdss_spi_panel_power_ctrl(pdata, MDSS_PANEL_POWER_OFF);
+
+	return rc;
+}
+
+static int mdss_spi_display_on(struct msm_fb_data_type *mfd)
+{
+	int rc = 0;
+	struct mdss_panel_data *pdata;
+	struct spi_panel_data *ctrl_pdata = NULL;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	rc = mdss_spi_panel_power_ctrl(pdata, MDSS_PANEL_POWER_ON);
+	if (rc) {
+		pr_err("%s:Panel power on failed. rc=%d\n",
+			__func__, rc);
+		return rc;
+	}
+
+	mdss_spi_panel_pinctrl_set_state(ctrl_pdata, true);
+	mdss_spi_panel_reset(pdata, 1);
+	ctrl_pdata->ctrl_state |= CTRL_STATE_PANEL_ACTIVE;
+	rc = mdss_spi_panel_on(&ctrl_pdata->panel_data);
+	return rc;
+}
+
+u32 mdss_spi_display_fb_stride(u32 fb_index, u32 xres, int bpp)
+{
+	/*
+	 * The adreno GPU hardware requires that the pitch be aligned to
+	 * 32 pixels for color buffers, so for the cases where the GPU
+	 * is writing directly to fb0, the framebuffer pitch
+	 * also needs to be 32 pixels aligned
+	 */
+
+	if (fb_index == 0)
+		return ALIGN(xres, 32) * bpp;
+	else
+		return xres * bpp;
+}
+
+ssize_t mdss_spi_show_capabilities(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	size_t len = PAGE_SIZE;
+	int cnt = 0;
+
+	cnt += scnprintf(buf + cnt, len - cnt, "mdp_version=5\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "hw_rev=%d\n", 5);
+	cnt += scnprintf(buf + cnt, len - cnt, "pipe_count:%d\n", 1);
+	cnt += scnprintf(buf + cnt, len - cnt,
+		"pipe_num:3 pipe_type:rgb pipe_ndx:8 rects:1 pipe_is_handoff:0"
+		);
+	cnt += scnprintf(buf + cnt, len - cnt,
+		"display_id:0 fmts_supported:51,224,0,22,0,191,248,255,1,");
+	cnt += scnprintf(buf + cnt, len - cnt,
+		"0,0,0,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "rgb_pipes=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "vig_pipes=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "dma_pipes=%d\n", 1);
+	cnt += scnprintf(buf + cnt, len - cnt, "blending_stages=%d\n", 2);
+	cnt += scnprintf(buf + cnt, len - cnt, "cursor_pipes=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "max_cursor_size=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "smp_count=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "smp_size=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "smp_mb_per_pipe=%d\n", 0);
+	cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_low=3100000\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_high=3100000\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_pipe_width=2048\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_mixer_width=2048\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_bandwidth_low=3100000\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_pipe_bw=2300000\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_mdp_clk=320000000\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "rot_dwnscale_min=1\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "rot_dwnscale_max=1\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_downscale_ratio=1\n");
+	cnt += scnprintf(buf + cnt, len - cnt, "max_upscale_ratio=1\n");
+
+	return cnt;
+}
+
+static ssize_t mdss_spi_vsync_show_event(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct fb_info *fbi = dev_get_drvdata(dev);
+	struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
+	struct mdss_panel_data *pdata;
+	struct spi_panel_data *ctrl_pdata = NULL;
+	int rc = 0;
+	u64 vsync_ticks;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	if (!(ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_ACTIVE))
+		return -EAGAIN;
+
+	vsync_ticks = ktime_to_ns(ctrl_pdata->vsync_time);
+	pr_debug("fb%d vsync=%llu\n", mfd->index, vsync_ticks);
+	rc = scnprintf(buf, PAGE_SIZE, "VSYNC=%llu\n", vsync_ticks);
+
+	return rc;
+}
+
+static DEVICE_ATTR(vsync_event, 0444, mdss_spi_vsync_show_event, NULL);
+static DEVICE_ATTR(caps, 0444, mdss_spi_show_capabilities, NULL);
+
+static struct attribute *mdp_spi_sysfs_attrs[] = {
+	&dev_attr_caps.attr,
+	NULL,
+};
+
+static struct attribute *spi_vsync_fs_attr_group[] = {
+	&dev_attr_vsync_event.attr,
+	NULL,
+};
+
+static struct attribute_group mdp_spi_sysfs_group = {
+	.attrs = mdp_spi_sysfs_attrs,
+};
+
+static struct attribute_group spi_vsync_sysfs_group = {
+	.attrs = spi_vsync_fs_attr_group,
+};
+
+int mdss_spi_overlay_init(struct msm_fb_data_type *mfd)
+{
+	struct msm_mdp_interface *spi_display_interface = &mfd->mdp;
+	struct device *dev = mfd->fbi->dev;
+	struct mdss_data_type *spi_mdata;
+	struct mdss_panel_data *pdata;
+	struct spi_panel_data *ctrl_pdata = NULL;
+	int rc = 0;
+
+	pdata = dev_get_platdata(&mfd->pdev->dev);
+	ctrl_pdata = container_of(pdata, struct spi_panel_data, panel_data);
+
+	spi_mdata = dev_get_drvdata(mfd->pdev->dev.parent);
+	if (!spi_mdata) {
+		pr_err("unable to initialize spi mdata for fb%d\n", mfd->index);
+		return -ENODEV;
+	}
+
+	spi_display_interface->on_fnc = mdss_spi_display_on;
+	spi_display_interface->off_fnc = mdss_spi_display_off;
+	spi_display_interface->do_histogram = NULL;
+	spi_display_interface->cursor_update = NULL;
+
+	spi_display_interface->ioctl_handler = spi_display_ioctl_handler;
+	spi_display_interface->kickoff_fnc = mdss_spi_panel_kickoff;
+	spi_display_interface->pre_commit = mdss_spi_display_pre_commit;
+	spi_display_interface->atomic_validate =
+				mdss_spi_display_atomic_validate;
+	spi_display_interface->fb_mem_get_iommu_domain = NULL;
+	spi_display_interface->fb_stride = mdss_spi_display_fb_stride;
+	spi_display_interface->fb_mem_alloc_fnc = NULL;
+	spi_display_interface->check_dsi_status = NULL;
+
+	rc = sysfs_create_group(&dev->kobj, &spi_vsync_sysfs_group);
+	if (rc)
+		pr_err("spi vsync sysfs group creation failed, ret=%d\n", rc);
+
+	rc = sysfs_create_link_nowarn(&dev->kobj,
+			&spi_mdata->pdev->dev.kobj, "mdp");
+
+	ctrl_pdata->vsync_event_sd = sysfs_get_dirent(dev->kobj.sd,
+			"vsync_event");
+	if (!ctrl_pdata->vsync_event_sd)
+		pr_err("spi vsync_event sysfs lookup failed\n");
+
+	return rc;
+}
+
+static int mdss_spi_display_probe(struct platform_device *pdev)
+{
+	int rc = 0;
+	struct mdss_data_type *mdata;
+	static struct msm_mdp_interface spi_display_interface = {
+		.init_fnc = mdss_spi_overlay_init,
+		.fb_stride = mdss_spi_display_fb_stride,
+	};
+	struct device *dev = &pdev->dev;
+
+	if (!pdev->dev.of_node) {
+		pr_err("spi display driver only supports device tree probe\n");
+		return -ENOTSUPP;
+	}
+
+	mdata = devm_kzalloc(&pdev->dev, sizeof(*mdata), GFP_KERNEL);
+	if (mdata == NULL)
+		return -ENOMEM;
+
+	pdev->id = 0;
+	mdata->pdev = pdev;
+	platform_set_drvdata(pdev, mdata);
+
+	rc = mdss_fb_register_mdp_instance(&spi_display_interface);
+	if (rc) {
+		pr_err("unable to register SPI display instance\n");
+		return rc;
+	}
+
+	rc = sysfs_create_group(&dev->kobj, &mdp_spi_sysfs_group);
+	if (rc) {
+		pr_err("spi vsync sysfs group creation failed, ret=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id mdss_spi_display_match[] = {
+	{ .compatible = "qcom,mdss-spi-panel" },
+	{},
+};
+
+static struct platform_driver this_driver = {
+	.probe = mdss_spi_display_probe,
+	.driver = {
+		.name = "spi_display",
+		.owner  = THIS_MODULE,
+		.of_match_table = mdss_spi_display_match,
+	},
+};
+
+static int __init mdss_spi_panel_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&this_driver);
+	return ret;
+}
+
+module_init(mdss_spi_panel_init);
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(of, mdss_spi_display_match);