From 2ad7c4e5d34bf6d4ac107e8fd39719a90daf6922 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Otto=20Pfl=C3=BCger?= <otto.pflueger@abscue.de>
Date: Sun, 16 Oct 2022 15:42:14 +0200
Subject: [PATCH 09/11] Add display support for Samsung Galaxy Note 2
 (GT-N710x)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

There are two displays, so n710x.dts is split into two files
(n710x-ea8061.dts and n710x-s6evr02.dts).

NOTE: I didn't write these drivers myself. They were taken from
https://github.com/fourkbomb/linux and adapted to work with the current
mainline Linux version. This patch is based on the older patch by
Jack Knightly, which only added support for the s6evr02 variant.

Signed-off-by: Otto Pflüger <otto.pflueger@abscue.de>
---
 arch/arm/boot/dts/Makefile                    |   3 +-
 arch/arm/boot/dts/exynos4412-n710x-ea8061.dts |  16 +
 .../arm/boot/dts/exynos4412-n710x-s6evr02.dts |  16 +
 ...os4412-n710x.dts => exynos4412-n710x.dtsi} | 100 ++-
 drivers/gpu/drm/panel/Kconfig                 |  12 +
 drivers/gpu/drm/panel/Makefile                |   2 +
 .../gpu/drm/panel/panel-magnachip-ea8061.c    | 810 ++++++++++++++++++
 drivers/gpu/drm/panel/panel-samsung-s6evr02.c | 761 ++++++++++++++++
 8 files changed, 1718 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/boot/dts/exynos4412-n710x-ea8061.dts
 create mode 100644 arch/arm/boot/dts/exynos4412-n710x-s6evr02.dts
 rename arch/arm/boot/dts/{exynos4412-n710x.dts => exynos4412-n710x.dtsi} (56%)
 create mode 100644 drivers/gpu/drm/panel/panel-magnachip-ea8061.c
 create mode 100644 drivers/gpu/drm/panel/panel-samsung-s6evr02.c

diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index 6aa7dc4db2fc..afde0e21fc71 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -217,7 +217,8 @@ dtb-$(CONFIG_ARCH_EXYNOS4) += \
 	exynos4412-i9300.dtb \
 	exynos4412-i9305.dtb \
 	exynos4412-itop-elite.dtb \
-	exynos4412-n710x.dtb \
+	exynos4412-n710x-ea8061.dtb \
+	exynos4412-n710x-s6evr02.dtb \
 	exynos4412-odroidu3.dtb \
 	exynos4412-odroidx.dtb \
 	exynos4412-odroidx2.dtb \
diff --git a/arch/arm/boot/dts/exynos4412-n710x-ea8061.dts b/arch/arm/boot/dts/exynos4412-n710x-ea8061.dts
new file mode 100644
index 000000000000..134dca2649ea
--- /dev/null
+++ b/arch/arm/boot/dts/exynos4412-n710x-ea8061.dts
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#include "exynos4412-n710x.dtsi"
+
+&ea8061 {
+	status = "okay";
+};
+
+&dsi_in_ea8061 {
+	status = "okay";
+};
+
+&dsi_out {
+	status = "okay";
+	remote-endpoint = <&dsi_in_ea8061>;
+};
diff --git a/arch/arm/boot/dts/exynos4412-n710x-s6evr02.dts b/arch/arm/boot/dts/exynos4412-n710x-s6evr02.dts
new file mode 100644
index 000000000000..ae799e0a3e80
--- /dev/null
+++ b/arch/arm/boot/dts/exynos4412-n710x-s6evr02.dts
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#include "exynos4412-n710x.dtsi"
+
+&s6evr02 {
+	status = "okay";
+};
+
+&dsi_in_s6evr02 {
+	status = "okay";
+};
+
+&dsi_out {
+	status = "okay";
+	remote-endpoint = <&dsi_in_s6evr02>;
+};
diff --git a/arch/arm/boot/dts/exynos4412-n710x.dts b/arch/arm/boot/dts/exynos4412-n710x.dtsi
similarity index 56%
rename from arch/arm/boot/dts/exynos4412-n710x.dts
rename to arch/arm/boot/dts/exynos4412-n710x.dtsi
index 5f2888816e4a..d67c83a9ab0b 100644
--- a/arch/arm/boot/dts/exynos4412-n710x.dts
+++ b/arch/arm/boot/dts/exynos4412-n710x.dtsi
@@ -1,5 +1,4 @@
 // SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
 #include "exynos4412-midas.dtsi"
 
 / {
@@ -38,6 +37,105 @@ &cam_io_reg {
 	status = "okay";
 };
 
+&dsi_0 {
+	vddcore-supply = <&ldo8_reg>;
+	vddio-supply = <&ldo10_reg>;
+	samsung,burst-clock-frequency = <500000000>;
+	samsung,esc-clock-frequency = <20000000>;
+	samsung,pll-clock-frequency = <24000000>;
+	status = "okay";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@1 {
+			reg = <1>;
+
+			dsi_out: endpoint@0 {
+				samsung,burst-clock-frequency = <500000000>;
+				samsung,esc-clock-frequency = <20000000>;
+				status = "disabled";
+			};
+
+		};
+
+	};
+
+	s6evr02: panel-s6evr02@0 {
+		compatible = "samsung,s6evr02";
+		reg = <0>;
+		vdd3-supply = <&ldo13_reg>;
+		vci-supply = <&ldo25_reg>;
+		reset-gpios = <&gpf2 1 GPIO_ACTIVE_HIGH>;
+		power-on-delay = <50>;
+		reset-delay = <100>;
+		init-delay = <100>;
+		panel-width-mm = <69>;
+		panel-height-mm = <123>;
+		// HIGH means s6evr02
+		present-gpios = <&gpf1 0 GPIO_ACTIVE_HIGH>;
+		status = "disabled";
+
+		display-timings {
+			timing0_s6evr02: timing-0 {
+				clock-frequency = <62614944>;
+				hactive = <720>;
+				vactive = <1280>;
+				hfront-porch = <70>;
+				hback-porch = <40>;
+				hsync-len = <3>;
+				vfront-porch = <13>;
+				vback-porch = <1>;
+				vsync-len = <2>;
+			};
+		};
+
+		port {
+			dsi_in_s6evr02: endpoint {
+				remote-endpoint = <&dsi_out>;
+				status = "disabled";
+			};
+		};
+	};
+
+	ea8061: panel-ea8061@0 {
+		compatible = "magnachip,ea8061";
+		reg = <0>;
+		vdd3-supply = <&ldo13_reg>;
+		vci-supply = <&ldo25_reg>;
+		reset-gpios = <&gpf2 1 GPIO_ACTIVE_HIGH>;
+		power-on-delay = <50>;
+		reset-delay = <100>;
+		init-delay = <100>;
+		panel-width-mm = <69>;
+		panel-height-mm = <123>;
+		// LOW means ea8061
+		present-gpios = <&gpf1 0 GPIO_ACTIVE_LOW>;
+		status = "disabled";
+
+		display-timings {
+			timing0_ea8061: timing-0 {
+				clock-frequency = <67425696>;
+				hactive = <720>;
+				vactive = <1280>;
+				hfront-porch = <52>;
+				hback-porch = <121>;
+				hsync-len = <4>;
+				vfront-porch = <13>;
+				vback-porch = <1>;
+				vsync-len = <2>;
+			};
+		};
+
+		port {
+			dsi_in_ea8061: endpoint {
+				status = "disabled";
+			};
+		};
+	};
+};
+
 &i2c_3 {
 	samsung,i2c-sda-delay = <100>;
 	samsung,i2c-slave-addr = <0x10>;
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index a582ddd583c2..f80c48c6ac4e 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -357,6 +357,12 @@ config DRM_PANEL_NOVATEK_NT39016
 	  Say Y here if you want to enable support for the panels built
 	  around the Novatek NT39016 display controller.
 
+config DRM_PANEL_MAGNACHIP_EA8061
+	tristate "Magnachip EA8061 DSI video mode panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 config DRM_PANEL_MANTIX_MLAF057WE51
 	tristate "Mantix MLAF057WE51-X MIPI-DSI LCD panel"
 	depends on OF
@@ -537,6 +543,12 @@ config DRM_PANEL_SAMSUNG_S6E8AA0
 	select DRM_MIPI_DSI
 	select VIDEOMODE_HELPERS
 
+config DRM_PANEL_SAMSUNG_S6EVR02
+	tristate "Samsung S6EVR02 DSI video mode panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 config DRM_PANEL_SAMSUNG_SOFEF00
 	tristate "Samsung sofef00/s6e3fc2x01 OnePlus 6/6T DSI cmd mode panels"
 	depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 34e717382dbb..2d6183d8cc9a 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o
+obj-$(CONFIG_DRM_PANEL_MAGNACHIP_EA8061) += panel-magnachip-ea8061.o
 obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o
 obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
 obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
@@ -54,6 +55,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_SPI) += panel-samsung-s6e63m0-spi.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0_DSI) += panel-samsung-s6e63m0-dsi.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01) += panel-samsung-s6e88a0-ams452ef01.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o
+obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6EVR02) += panel-samsung-s6evr02.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_SOFEF00) += panel-samsung-sofef00.o
 obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
diff --git a/drivers/gpu/drm/panel/panel-magnachip-ea8061.c b/drivers/gpu/drm/panel/panel-magnachip-ea8061.c
new file mode 100644
index 000000000000..c4521c42986c
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-magnachip-ea8061.c
@@ -0,0 +1,810 @@
+/*
+ * MIPI-DSI based ea8061 AMOLED LCD 5.5 inch panel driver.
+ *
+ * Copyright (c) 2017 Simon Shields, <simon@lineageos.org>
+ *
+ * Based on the s6e8aa0 panel driver,
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd
+ *
+ * Inki Dae, <inki.dae@samsung.com>
+ * Donghwa Lee, <dh09.lee@samsung.com>
+ * Joongmock Shin <jmock.shin@samsung.com>
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ * Tomasz Figa <t.figa@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/backlight.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define M4_VERSION 0x13
+
+#define AID_TABLE_LEN			2
+#define GAMMA_LEVEL_NUM			33
+#define GAMMA_TABLE_LEN			33
+
+typedef u8 ea8061_gamma_table[GAMMA_TABLE_LEN];
+typedef u8 ea8061_aid_table[AID_TABLE_LEN];
+
+#define EA8061_STATE_BIT_ENABLED	0
+
+struct ea8061 {
+	struct device *dev;
+	struct drm_panel panel;
+
+	struct regulator_bulk_data supplies[2];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 reset_delay;
+	u32 init_delay;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+
+	unsigned long state;
+	u8 version;
+	u8 id;
+	int brightness;
+
+	/* This field is tested by functions directly accessing DSI bus before
+	 * transfer, transfer is skipped if it is set. In case of transfer
+	 * failure or unexpected response the field is set to error value.
+	 * Such construct allows to eliminate many checks in higher level
+	 * functions.
+	 */
+	int error;
+};
+
+
+static const u8 ea8061_elvss_param[GAMMA_LEVEL_NUM] = {
+	0x20, 0x20, 0x20, 0x1f, 0x1f, 0x1f, 0x1e, 0x1e, /* 20-90cd */
+	0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1b, 0x19, 0x17, /* 100-130cd */
+	0x16, 0x14, 0x12, 0x10, 0x0f, 0x0f, 0x0f, 0x0f, /* 140-186cd */
+	0x0f, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x10, /* 188-250cd */
+	0x0b, /* 300cd */
+};
+
+static const ea8061_aid_table ea8061_aid_tables[GAMMA_LEVEL_NUM] = {
+	{0x04, 0x45}, {0x03, 0xdf}, {0x03, 0x74}, {0x03, 0x0a}, /* 20-50cd */
+	{0x02, 0x95}, {0x02, 0x25}, {0x01, 0xab}, {0x01, 0x36}, /* 60-90cd */
+	{0x00, 0xbc}, {0x00, 0xfe}, {0x01, 0x40}, {0x01, 0x82}, /* 100-106cd */
+	{0x01, 0xc4}, {0x02, 0x06}, {0x02, 0x06}, {0x02, 0x06}, /* 108-130cd */
+	{0x02, 0x06}, {0x02, 0x06}, {0x02, 0x06}, {0x02, 0x06}, /* 140-170cd */
+	{0x02, 0x06}, {0x01, 0x9f}, {0x01, 0x37}, {0x00, 0xcf}, /* 180-186cd */
+	{0x00, 0x68}, {0x00, 0x0a}, {0x00, 0x0a}, {0x00, 0x0a}, /* 188-210cd */
+	{0x00, 0x0a}, {0x00, 0x0a}, {0x00, 0x0a}, {0x00, 0x0a}, /* 220-250cd */
+	{0x00, 0x0a}, /* 300cd */
+};
+
+static const ea8061_gamma_table ea8061_gamma_tables[GAMMA_LEVEL_NUM] = {
+	{
+		/* 20cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x87, 0x88, 0x87,
+		0x88, 0x87, 0x88, 0x86, 0x89, 0x88, 0x87, 0x88, 0x89, 0x74,
+		0x65, 0x81, 0x68, 0x66, 0x83, 0x68, 0x6f, 0xa9, 0x37, 0x42,
+		0x2e, 0x31, 0x02,
+	}, {
+		/* 30cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x87, 0x88, 0x87,
+		0x87, 0x87, 0x87, 0x85, 0x88, 0x87, 0x87, 0x87, 0x88, 0x7e,
+		0x77, 0x83, 0x6d, 0x66, 0x7c, 0x68, 0x6f, 0xa1, 0x37, 0x42,
+		0x2e, 0x31, 0x02,
+	}, {
+		/* 40cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x87, 0x87,
+		0x88, 0x87, 0x88, 0x84, 0x87, 0x86, 0x88, 0x89, 0x89, 0x7f,
+		0x83, 0x81, 0x70, 0x63, 0x79, 0x65, 0x67, 0x98, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 50cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x87, 0x87,
+		0x87, 0x87, 0x87, 0x85, 0x88, 0x87, 0x86, 0x87, 0x87, 0x81,
+		0x85, 0x83, 0x75, 0x6a, 0x79, 0x6b, 0x67, 0x93, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 60cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x87, 0x87,
+		0x87, 0x87, 0x87, 0x84, 0x87, 0x86, 0x85, 0x86, 0x87, 0x80,
+		0x83, 0x82, 0x7b, 0x6e, 0x7c, 0x6d, 0x67, 0x8e, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 70cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x86, 0x86,
+		0x87, 0x87, 0x87, 0x85, 0x88, 0x87, 0x85, 0x86, 0x87, 0x80,
+		0x83, 0x82, 0x79, 0x6d, 0x78, 0x73, 0x6b, 0x8f, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 80cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x86, 0x86,
+		0x87, 0x87, 0x87, 0x84, 0x86, 0x85, 0x86, 0x87, 0x88, 0x80,
+		0x83, 0x82, 0x7b, 0x6f, 0x78, 0x74, 0x6b, 0x8c, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 90cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x86, 0x86,
+		0x87, 0x87, 0x87, 0x84, 0x86, 0x85, 0x84, 0x85, 0x86, 0x7e,
+		0x81, 0x80, 0x7f, 0x74, 0x7c, 0x75, 0x6b, 0x8a, 0x3b, 0x48,
+		0x31, 0x31, 0x02,
+	}, {
+		/* 100cd */
+		0xca, 0x00, 0xd9, 0x00, 0xd5, 0x00, 0xd3, 0x86, 0x86, 0x86,
+		0x87, 0x86, 0x86, 0x85, 0x87, 0x86, 0x84, 0x85, 0x86, 0x7e,
+		0x81, 0x80, 0x7f, 0x77, 0x7c, 0x70, 0x5f, 0x83, 0x40, 0x4f,
+		0x35, 0x31, 0x02,
+	}, {
+		/* 102cd */
+		0xca, 0x00, 0xdb, 0x00, 0xd7, 0x00, 0xd6, 0x85, 0x86, 0x86, 
+		0x87, 0x86, 0x86, 0x83, 0x85, 0x85, 0x86, 0x87, 0x87, 0x7e,
+		0x81, 0x80, 0x7f, 0x77, 0x7c, 0x7b, 0x73, 0x8a, 0x37, 0x42,
+		0x2e, 0x31, 0x02,
+	}, {
+		/* 104cd */
+		0xca, 0x00, 0xde, 0x00, 0xda, 0x00, 0xd9, 0x85, 0x86, 0x85,
+		0x87, 0x86, 0x86, 0x84, 0x85, 0x85, 0x85, 0x85, 0x86, 0x7f,
+		0x83, 0x81, 0x7c, 0x73, 0x79, 0x7b, 0x72, 0x8a, 0x37, 0x42,
+		0x2e, 0x31, 0x02,
+	}, {
+		/* 106cd */
+		0xca, 0x00, 0xe1, 0x00, 0xdd, 0x00, 0xdc, 0x84, 0x85, 0x84,
+		0x86, 0x85, 0x85, 0x83, 0x84, 0x84, 0x84, 0x84, 0x85, 0x7f,
+		0x83, 0x81, 0x7e, 0x76, 0x7a, 0x80, 0x7a, 0x8b, 0x33, 0x3c,
+		0x2b, 0x31, 0x02,
+	}, {
+		/* 108cd */
+		0xca, 0x00, 0xe4, 0x00, 0xe1, 0x00, 0xe0, 0x84, 0x86, 0x85,
+		0x84, 0x83, 0x84, 0x83, 0x85, 0x84, 0x84, 0x84, 0x84, 0x81,
+		0x84, 0x83, 0x7d, 0x77, 0x7a, 0x7d, 0x74, 0x87, 0x33, 0x3c,
+		0x2b, 0x31, 0x02,
+	}, {
+		/* 110cd */
+		0xca, 0x00, 0xe7, 0x00, 0xe4, 0x00, 0xe3, 0x84, 0x84, 0x84,
+		0x84, 0x83, 0x84, 0x83, 0x84, 0x84, 0x83, 0x83, 0x84, 0x80,
+		0x83, 0x82, 0x7f, 0x79, 0x7d, 0x7a, 0x70, 0x84, 0x33, 0x3c,
+		0x2b, 0x31, 0x02,
+	}, {
+		/* 120cd */
+		0xca, 0x00, 0xeb, 0x00, 0xe8, 0x00, 0xe7, 0x83, 0x84, 0x84,
+		0x83, 0x82, 0x83, 0x83, 0x84, 0x83, 0x83, 0x83, 0x83, 0x7f,
+		0x82, 0x81, 0x7d, 0x77, 0x7a, 0x80, 0x7c, 0x89, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 130cd */
+		0xca, 0x00, 0xee, 0x00, 0xec, 0x00, 0xec, 0x83, 0x83, 0x83,
+		0x82, 0x82, 0x82, 0x81, 0x82, 0x82, 0x84, 0x84, 0x84, 0x7f,
+		0x80, 0x7f, 0x7e, 0x7a, 0x7c, 0x7d, 0x78, 0x86, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 140cd */
+		0xca, 0x00, 0xf1, 0x00, 0xef, 0x00, 0xef, 0x82, 0x82, 0x82,
+		0x82, 0x82, 0x82, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7f,
+		0x81, 0x80, 0x7f, 0x7c, 0x7e, 0x7b, 0x74, 0x82, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 150cd */
+		0xca, 0x00, 0xf4, 0x00, 0xf2, 0x00, 0xf2, 0x81, 0x82, 0x82,
+		0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80,
+		0x81, 0x80, 0x7d, 0x7b, 0x7c, 0x81, 0x7f, 0x88, 0x85,
+		0x87,0x84, 0x31, 0x02,
+	}, {
+		/* 160cd */
+		0xca, 0x00, 0xf7, 0x00, 0xf6, 0x00, 0xf5, 0x80, 0x81, 0x81,
+		0x82, 0x81, 0x81, 0x80, 0x81, 0x80, 0x82, 0x82, 0x82, 0x7f,
+		0x80, 0x7f, 0x7f, 0x7d, 0x7e, 0x7e, 0x7b, 0x84, 0x85, 0x87,
+		0x84, 0x31, 0x02,
+	}, {
+		/* 170cd */
+		0xca, 0x00, 0xf9, 0x00, 0xf9, 0x00, 0xf8, 0x80, 0x81, 0x80,
+		0x81, 0x80, 0x80, 0x81, 0x82, 0x81, 0x80, 0x80, 0x81, 0x80,
+		0x80, 0x80, 0x80, 0x7f, 0x80, 0x7b, 0x77, 0x81, 0x85, 0x87,
+		0x84, 0x31, 0x02,
+	}, {
+		/* 180cd */
+		0xca, 0x00, 0xfc, 0x00, 0xfb, 0x00, 0xfb, 0x80, 0x80, 0x80,
+		0x80, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81, 0x80,
+		0x81, 0x81, 0x7e, 0x7d, 0x7e, 0x81, 0x81, 0x86, 0x7f, 0x80,
+		0x7f, 0x31, 0x02,
+	}, {
+		/* 182cd */
+		0xca, 0x00, 0xf8, 0x00, 0xf7, 0x00, 0xf6, 0x81, 0x81, 0x81,
+		0x81, 0x81, 0x81, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f,
+		0x80, 0x7f, 0x7f, 0x7d, 0x7e, 0x7f, 0x7c, 0x83, 0x85, 0x87,
+		0x84, 0x31, 0x02,
+	}, {
+		/* 184cd */
+		0xca, 0x00, 0xf4, 0x00, 0xf2, 0x00, 0xf2, 0x81, 0x82, 0x82,
+		0x81, 0x81, 0x81, 0x80, 0x81, 0x81, 0x82, 0x83, 0x83, 0x7e,
+		0x7f, 0x7f, 0x7f, 0x7c, 0x7e, 0x7c, 0x76, 0x80, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 186cd */
+		0xca, 0x00, 0xf1, 0x00, 0xef, 0x00, 0xee, 0x82, 0x82, 0x82,
+		0x82, 0x82, 0x82, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7f,
+		0x80, 0x7f, 0x7e, 0x7a, 0x7c, 0x7e, 0x7a, 0x83, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 188cd */
+		0xca, 0x00, 0xee, 0x00, 0xec, 0x00, 0xec, 0x82, 0x83, 0x82,
+		0x82, 0x82, 0x82, 0x81, 0x82, 0x81, 0x82, 0x82, 0x82, 0x7f,
+		0x82, 0x81, 0x7d, 0x77, 0x7a, 0x81, 0x7e, 0x86, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 190cd */
+		0xca, 0x00, 0xeb, 0x00, 0xe8, 0x00, 0xe8, 0x82, 0x83, 0x83,
+		0x83, 0x82, 0x83, 0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x7f,
+		0x82, 0x80, 0x7f, 0x79, 0x7d, 0x7b, 0x72, 0x81, 0x33, 0x3c,
+		0x2b, 0x31, 0x02,
+	}, {
+		/* 200cd */
+		0xca, 0x00, 0xee, 0x00, 0xec, 0x00, 0xeb, 0x82, 0x83, 0x82,
+		0x82, 0x82, 0x82, 0x80, 0x81, 0x81, 0x83, 0x83, 0x83, 0x7f,
+		0x82, 0x81, 0x7d, 0x77, 0x7a, 0x7b, 0x72, 0x81, 0x33, 0x3c,
+		0x2b, 0x31, 0x02,
+	}, {
+		/* 210cd */
+		0xca, 0x00, 0xf0, 0x00, 0xee, 0x00, 0xed, 0x82, 0x82, 0x82,
+		0x82, 0x81, 0x81, 0x80, 0x81, 0x81, 0x82, 0x83, 0x83, 0x7e,
+		0x80, 0x7f, 0x80, 0x7c, 0x7e, 0x7e, 0x7a, 0x83, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 220cd */
+		0xca, 0x00, 0xf2, 0x00, 0xf0, 0x00, 0xf0, 0x80, 0x81, 0x81,
+		0x82, 0x82, 0x82, 0x80, 0x81, 0x81, 0x82, 0x82, 0x82, 0x7f,
+		0x80, 0x7f, 0x7e, 0x7a, 0x7c, 0x7e, 0x7a, 0x83, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 230cd */
+		0xca, 0x00, 0xf4, 0x00, 0xf2, 0x00, 0xf2, 0x81, 0x81, 0x81,
+		0x81, 0x81, 0x80, 0x80, 0x81, 0x80, 0x82, 0x82, 0x82, 0x7f,
+		0x81, 0x80, 0x7c, 0x78, 0x7a, 0x7e, 0x7a, 0x83, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 240cd */
+		0xca, 0x00, 0xf6, 0x00, 0xf4, 0x00, 0xf4, 0x80, 0x81, 0x81,
+		0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f,
+		0x81, 0x80, 0x7f, 0x7c, 0x7e, 0x7c, 0x76, 0x7f, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 250cd */
+		0xca, 0x00, 0xf8, 0x00, 0xf7, 0x00, 0xf6, 0x80, 0x80, 0x80,
+		0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80,
+		0x81, 0x80, 0x7d, 0x7b, 0x7c, 0x7c, 0x76, 0x7f, 0x2e, 0x37,
+		0x28, 0x31, 0x02,
+	}, {
+		/* 300cd */
+		0xca, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x7f, 0x7f, 0x7f,
+		0x80, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x80,
+		0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80,
+		0x7f, 0x31, 0x02,
+	},
+};
+
+static inline struct ea8061 *panel_to_ea8061(struct drm_panel *panel)
+{
+	return container_of(panel, struct ea8061, panel);
+}
+
+static int ea8061_clear_error(struct ea8061 *ctx)
+{
+	int ret = ctx->error;
+
+	ctx->error = 0;
+	return ret;
+}
+
+static void ea8061_dcs_write(struct ea8061 *ctx, const void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	ssize_t ret;
+
+	if (ctx->error < 0)
+		return;
+
+	ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+	if (ret < 0) {
+		dev_err(ctx->dev, "error %zd writing dcs seq: %*ph\n", ret,
+			(int)len, data);
+		ctx->error = ret;
+	}
+}
+
+static int ea8061_dcs_read(struct ea8061 *ctx, u8 cmd, void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	if (ctx->error < 0)
+		return ctx->error;
+
+	ret = mipi_dsi_dcs_read(dsi, cmd, data, len);
+	if (ret < 0) {
+		dev_err(ctx->dev, "error %d reading dcs seq(%#x)\n", ret, cmd);
+		ctx->error = ret;
+	}
+
+	return ret;
+}
+
+#define ea8061_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	ea8061_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void ea8061_apply_level_2_key(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xf0, 0x5a, 0x5a);
+}
+
+static void ea8061_panel_cond_set(struct ea8061 *ctx)
+{
+	if (ctx->version == M4_VERSION)
+		ea8061_dcs_write_seq_static(ctx,
+				0xc4, 0x4e, 0xbd, 0x00, 0x00, 0x58, 0xa7, 
+				0x0b, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 
+				0x0b, 0x92, 0x0b, 0x92, 0x08, 0x08, 0x07,
+				0x30, 0x50, 0x30, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x02, 0x04, 0x04);
+	else
+		ea8061_dcs_write_seq_static(ctx,
+				0xC4, 0x4E, 0xBD, 0x00, 0x00, 0x58, 0xA7,
+				0x0B, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00,
+				0x0B, 0x92, 0x0B, 0x92, 0x08, 0x08, 0x07,
+				0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x02, 0x04, 0x04);
+}
+
+static void ea8061_display_condition_set(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0x36, 0x02, 0x00);
+}
+
+static void ea8061_etc_power_control(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0x55, 0x00, 0x00);
+}
+
+static void ea8061_etc_elvss_control(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xb2, 0x0e, 0xb4, 0xa0, 0x00,
+			0x00, 0x00, 0x00);
+}
+
+static void ea8061_apply_level_3_key(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xfc, 0x5a, 0x5a);
+}
+
+static void ea8061_aid_set(struct ea8061 *ctx)
+{
+	u8 aid_cmd[AID_TABLE_LEN + 1] = {
+		0xb3, 0x00, 0x00,
+	};
+	if (ctx->error)
+		return;
+
+	aid_cmd[1] = ea8061_aid_tables[ctx->brightness][0];
+	aid_cmd[2] = ea8061_aid_tables[ctx->brightness][1];
+
+	ea8061_dcs_write(ctx, aid_cmd, ARRAY_SIZE(aid_cmd));
+}
+
+static void ea8061_acl_set(struct ea8061 *ctx)
+{
+	u8 acl_seq[] = {0x55, 0x02, 0x00};
+	if (ctx->error)
+		return;
+
+	switch (ctx->brightness) {
+	case 0: /* <30cd */
+		acl_seq[1] = 0x00;
+		break;
+	case 1: /* 30cd */
+		acl_seq[1] = 0x01;
+		break;
+	}
+
+	ea8061_dcs_write(ctx, acl_seq, ARRAY_SIZE(acl_seq));
+}
+
+static void ea8061_elvss_set(struct ea8061 *ctx)
+{
+	u8 elvss_cmd[] = {
+		0xb2, 0x00, 0xb4, 0xa0, 0x00, 0x00, 0x00, 0x00,
+	};
+
+	if (ctx->error)
+		return;
+
+	elvss_cmd[1] = ea8061_elvss_param[ctx->brightness];
+	ea8061_dcs_write(ctx, elvss_cmd, ARRAY_SIZE(elvss_cmd));
+}
+
+static void ea8061_brightness_set(struct ea8061 *ctx)
+{
+	const u8 *gamma;
+
+	if (ctx->error)
+		return;
+
+	gamma = ea8061_gamma_tables[ctx->brightness];
+
+	/* prepare for gamma table update */
+	ea8061_dcs_write_seq_static(ctx, 0xf7, 0x5a, 0x5a);
+
+	ea8061_dcs_write(ctx, gamma, GAMMA_TABLE_LEN);
+
+	/* apply gamma table update. */
+	ea8061_dcs_write_seq_static(ctx, 0xf7, 0xa5, 0xa5);
+
+	ea8061_aid_set(ctx);
+	ea8061_acl_set(ctx);
+	ea8061_elvss_set(ctx);
+}
+
+static void ea8061_magna_gp(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xb0, 0x0e);
+}
+
+static void ea8061_magna_refresh_disable(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xdd, 0x00, 0x00);
+}
+
+static void ea8061_ltps_aid(struct ea8061 *ctx)
+{
+	ea8061_dcs_write_seq_static(ctx, 0xb3, 0x0, 0x0a);
+}
+
+static void ea8061_slew(struct ea8061 *ctx)
+{
+	if (ctx->version == M4_VERSION)
+		ea8061_dcs_write_seq_static(ctx, 0xb4, 0x33, 0x0d, 0x00);
+	else {
+		if (ctx->id == 0x00 || ctx->id == 0x01)
+			ea8061_dcs_write_seq_static(ctx, 0xb4, 0x33, 0x0e, 0x00);
+		else if (ctx->id == 0x02)
+			ea8061_dcs_write_seq_static(ctx, 0xb4, 0x33, 0x09, 0x00);
+		else if (ctx->id == 0x03)
+			ea8061_dcs_write_seq_static(ctx, 0xb4, 0x33, 0x0d, 0x00);
+		else
+			ea8061_dcs_write_seq_static(ctx, 0xb4, 0x33, 0x0a, 0x00);
+	}
+}
+
+static void ea8061_panel_init(struct ea8061 *ctx)
+{
+	ea8061_apply_level_2_key(ctx);
+	ea8061_apply_level_3_key(ctx);
+	msleep(20);
+
+	ea8061_magna_gp(ctx);
+	ea8061_magna_refresh_disable(ctx);
+
+	ea8061_panel_cond_set(ctx);
+	ea8061_display_condition_set(ctx);
+	ea8061_brightness_set(ctx);
+	ea8061_ltps_aid(ctx);
+	ea8061_etc_elvss_control(ctx);
+	ea8061_etc_power_control(ctx);
+	ea8061_slew(ctx);
+	msleep(ctx->init_delay);
+	
+	ea8061_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(40);
+
+	ea8061_dcs_write_seq_static(ctx, 0x29, 0x00, 0x00);
+}
+
+static void ea8061_set_maximum_return_packet_size(struct ea8061 *ctx,
+						   u16 size)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	if (ctx->error < 0)
+		return;
+
+	ret = mipi_dsi_set_maximum_return_packet_size(dsi, size);
+	if (ret < 0) {
+		dev_err(ctx->dev,
+			"error %d setting maximum return packet size to %d\n",
+			ret, size);
+		ctx->error = ret;
+	}
+}
+
+static void ea8061_read_mtp_id(struct ea8061 *ctx)
+{
+	u8 id[3];
+	int ret;
+
+	ea8061_dcs_write_seq_static(ctx, 0xfd, 0xd1);
+
+	ret = ea8061_dcs_read(ctx, 0xfe, id, ARRAY_SIZE(id));
+	if (ret < ARRAY_SIZE(id) || id[0] == 0x00) {
+		dev_err(ctx->dev, "read id failed\n");
+		ctx->error = -EIO;
+		return;
+	}
+
+	dev_info(ctx->dev, "ID: 0x%2x, 0x%2x, 0x%2x\n", id[0], id[1], id[2]);
+
+	ctx->version = id[1];
+	ctx->id = id[2];
+}
+
+static void ea8061_set_sequence(struct ea8061 *ctx)
+{
+	ea8061_set_maximum_return_packet_size(ctx, 3);
+	ea8061_read_mtp_id(ctx);
+	ea8061_panel_init(ctx);
+	ea8061_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int ea8061_power_on(struct ea8061 *ctx)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	msleep(ctx->power_on_delay);
+
+	gpiod_set_value(ctx->reset_gpio, 0);
+	usleep_range(10000, 11000);
+	gpiod_set_value(ctx->reset_gpio, 1);
+
+	msleep(ctx->reset_delay);
+
+	return 0;
+}
+
+static int ea8061_power_off(struct ea8061 *ctx)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int ea8061_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int ea8061_unprepare(struct drm_panel *panel)
+{
+	struct ea8061 *ctx = panel_to_ea8061(panel);
+
+	clear_bit(EA8061_STATE_BIT_ENABLED, &ctx->state);
+	ea8061_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+	ea8061_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+	msleep(40);
+
+	ea8061_clear_error(ctx);
+
+	return ea8061_power_off(ctx);
+}
+
+static int ea8061_prepare(struct drm_panel *panel)
+{
+	struct ea8061 *ctx = panel_to_ea8061(panel);
+	int ret;
+
+	ret = ea8061_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	ea8061_set_sequence(ctx);
+	ret = ctx->error;
+
+	if (ret < 0)
+		ea8061_unprepare(panel);
+	else
+		set_bit(EA8061_STATE_BIT_ENABLED, &ctx->state);
+
+	return ret;
+}
+
+static int ea8061_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int ea8061_get_modes(struct drm_panel *panel, struct drm_connector *connector)
+{
+	struct ea8061 *ctx = panel_to_ea8061(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		dev_err(ctx->dev, "failed to create a new display mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&ctx->vm, mode);
+	mode->width_mm = ctx->width_mm;
+	mode->height_mm = ctx->height_mm;
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static const struct drm_panel_funcs ea8061_drm_funcs = {
+	.disable = ea8061_disable,
+	.unprepare = ea8061_unprepare,
+	.prepare = ea8061_prepare,
+	.enable = ea8061_enable,
+	.get_modes = ea8061_get_modes,
+};
+
+static int ea8061_get_brightness(struct backlight_device *bd)
+{
+	return bd->props.brightness;
+}
+
+static int ea8061_set_brightness(struct backlight_device *bd)
+{
+	struct ea8061 *ctx = bl_get_data(bd);
+
+	bd->props.power = FB_BLANK_UNBLANK;
+	if (ctx->brightness != bd->props.brightness) {
+		ctx->brightness = bd->props.brightness;
+		if (test_bit(EA8061_STATE_BIT_ENABLED, &ctx->state))
+			ea8061_brightness_set(ctx);
+	}
+
+	return ea8061_clear_error(ctx);
+}
+
+static const struct backlight_ops ea8061_backlight_ops = {
+	.get_brightness = ea8061_get_brightness,
+	.update_status = ea8061_set_brightness,
+};
+
+static void ea8061_backlight_register(struct ea8061 *ctx)
+{
+	struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.brightness = ctx->brightness,
+		.max_brightness = GAMMA_LEVEL_NUM - 1
+	};
+	struct device *dev = ctx->dev;
+	struct backlight_device *bd;
+
+	bd = devm_backlight_device_register(dev, "panel", dev, ctx,
+					    &ea8061_backlight_ops, &props);
+	if (IS_ERR(bd))
+		dev_err(dev, "error registering backlight device (%ld)\n",
+			PTR_ERR(bd));
+}
+
+static int ea8061_parse_dt(struct ea8061 *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	ret = of_get_videomode(np, &ctx->vm, 0);
+	if (ret < 0)
+		return ret;
+
+	of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "init-delay", &ctx->init_delay);
+	of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+	of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+	return 0;
+}
+
+static int ea8061_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct ea8061 *ctx;
+	int ret;
+
+	ctx = devm_kzalloc(dev, sizeof(struct ea8061), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	pr_err("ea8061 probing\n");
+
+	mipi_dsi_set_drvdata(dsi, ctx);
+
+	ctx->dev = dev;
+
+	dsi->lanes = 4;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST
+		| MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP
+		| MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET
+		| MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_AUTO_VERT;
+
+	ret = ea8061_parse_dt(ctx);
+	if (ret < 0)
+		return ret;
+
+	ctx->supplies[0].supply = "vdd3";
+	ctx->supplies[1].supply = "vci";
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
+				      ctx->supplies);
+	if (ret < 0) {
+		dev_err(dev, "failed to get regulators: %d\n", ret);
+		return ret;
+	}
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(ctx->reset_gpio)) {
+		dev_err(dev, "cannot get reset-gpios %ld\n",
+			PTR_ERR(ctx->reset_gpio));
+		return PTR_ERR(ctx->reset_gpio);
+	}
+
+	ctx->brightness = GAMMA_LEVEL_NUM - 1;
+
+	drm_panel_init(&ctx->panel, dev, &ea8061_drm_funcs, DRM_MODE_CONNECTOR_DSI);
+
+	drm_panel_add(&ctx->panel);
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0)
+		drm_panel_remove(&ctx->panel);
+
+	ea8061_backlight_register(ctx);
+
+	return ret;
+}
+
+static void ea8061_remove(struct mipi_dsi_device *dsi)
+{
+	struct ea8061 *ctx = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id ea8061_of_match[] = {
+	{ .compatible = "magnachip,ea8061" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ea8061_of_match);
+
+static struct mipi_dsi_driver ea8061_driver = {
+	.probe = ea8061_probe,
+	.remove = ea8061_remove,
+	.driver = {
+		.name = "panel-magnachip-ea8061",
+		.of_match_table = ea8061_of_match,
+	},
+};
+module_mipi_dsi_driver(ea8061_driver);
+
+MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
+MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>");
+MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>");
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based ea8061 AMOLED LCD Panel Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-samsung-s6evr02.c b/drivers/gpu/drm/panel/panel-samsung-s6evr02.c
new file mode 100644
index 000000000000..4081e0691c94
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-samsung-s6evr02.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MIPI-DSI based S6EVR02 AMOLED LCD 5.5 inch panel driver.
+ *
+ * Copyright (c) 2017 Simon Shields, <simon@lineageos.org>
+ *
+ * Based on the s6e8aa0 panel driver,
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd
+ *
+ * Inki Dae, <inki.dae@samsung.com>
+ * Donghwa Lee, <dh09.lee@samsung.com>
+ * Joongmock Shin <jmock.shin@samsung.com>
+ * Eunchul Kim <chulspro.kim@samsung.com>
+ * Tomasz Figa <t.figa@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.com>
+*/
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/backlight.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define UB_VERSION 0x10
+
+#define LDI_MTP_LENGTH			24
+#define GAMMA_LEVEL_NUM			33
+#define GAMMA_TABLE_LEN			34
+
+typedef u8 s6evr02_gamma_table[GAMMA_TABLE_LEN];
+
+#define S6EVR02_STATE_BIT_ENABLED	0
+
+struct s6evr02 {
+	struct device *dev;
+	struct drm_panel panel;
+
+	struct regulator_bulk_data supplies[2];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 reset_delay;
+	u32 init_delay;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+
+	unsigned long state;
+	u8 version;
+	u8 id;
+	int brightness;
+
+	/* This field is tested by functions directly accessing DSI bus before
+	 * transfer, transfer is skipped if it is set. In case of transfer
+	 * failure or unexpected response the field is set to error value.
+	 * Such construct allows to eliminate many checks in higher level
+	 * functions.
+	 */
+	int error;
+};
+
+static const s6evr02_gamma_table s6evr02_gamma_tables[GAMMA_LEVEL_NUM] = {
+	{
+		/* 20cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x86, 0x87, 0x87,
+		0x86, 0x86, 0x85, 0x88, 0x8a, 0x88, 0x86, 0x85, 0x88, 0x75,
+		0x63, 0x83, 0x63, 0x60, 0x7d, 0x78, 0x85, 0xb8, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	}, {
+		/* 30cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x86, 0x87, 0x87,
+		0x86, 0x85, 0x84, 0x88, 0x8a, 0x87, 0x85, 0x84, 0x87, 0x80,
+		0x76, 0x85, 0x68, 0x60, 0x76, 0x78, 0x85, 0xb0, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	},  {
+		/* 40cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x85, 0x86, 0x87,
+		0x86, 0x86, 0x85, 0x87, 0x88, 0x86, 0x86, 0x85, 0x89, 0x80,
+		0x80, 0x83, 0x6a, 0x5c, 0x71, 0x77, 0x81, 0xaa, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 50cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x85, 0x86, 0x87,
+		0x85, 0x85, 0x84, 0x88, 0x89, 0x87, 0x84, 0x82, 0x86, 0x83,
+		0x84, 0x85, 0x6f, 0x63, 0x71, 0x7d, 0x81, 0xa5, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 60cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x85, 0x86, 0x87,
+		0x85, 0x85, 0x84, 0x86, 0x88, 0x85, 0x83, 0x81, 0x85, 0x81,
+		0x80, 0x83, 0x77, 0x69, 0x76, 0x7f, 0x81, 0xa0, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 70cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x87, 0x89, 0x87, 0x83, 0x81, 0x85, 0x81,
+		0x80, 0x83, 0x71, 0x64, 0x6f, 0x89, 0x88, 0xa3, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 80cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x86, 0x87, 0x85, 0x84, 0x82, 0x87, 0x81,
+		0x80, 0x83, 0x73, 0x66, 0x6f, 0x8a, 0x88, 0xa0, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 90cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x86, 0x87, 0x85, 0x81, 0x7f, 0x85, 0x7e,
+		0x7c, 0x80, 0x7a, 0x6d, 0x75, 0x8b, 0x88, 0x9e, 0x37, 0x45,
+		0x2f, 0x02, 0x03, 0x02
+	},  {
+		/* 100cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x84, 0x83, 0x83, 0x87, 0x88, 0x86, 0x81, 0x7f, 0x85, 0x7e,
+		0x7c, 0x80, 0x7a, 0x70, 0x75, 0x86, 0x7d, 0x98, 0x3b, 0x4b,
+		0x33, 0x02, 0x03, 0x02
+	},  {
+		/* 102cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x86, 0x87, 0x85, 0x81, 0x7f, 0x85, 0x7e,
+		0x7c, 0x80, 0x7a, 0x70, 0x75, 0x94, 0x93, 0xa1, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	},  {
+		/* 104cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x86, 0x87, 0x85, 0x84, 0x82, 0x87, 0x81,
+		0x80, 0x83, 0x74, 0x6a, 0x6f, 0x94, 0x92, 0xa7, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	},  {
+		/* 106cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x84, 0x85, 0x86,
+		0x85, 0x85, 0x84, 0x87, 0x89, 0x87, 0x83, 0x81, 0x85, 0x81,
+		0x80, 0x83, 0x74, 0x6a, 0x6f, 0x95, 0x92, 0x9d, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	},  {
+		/* 108cd */
+		0xca, 0x00, 0xc1, 0x00, 0xba, 0x00, 0xbb, 0x85, 0x86, 0x87,
+		0x84, 0x84, 0x83, 0x87, 0x89, 0x87, 0x83, 0x81, 0x85, 0x81,
+		0x80, 0x83, 0x74, 0x6a, 0x6f, 0x95, 0x91, 0x9d, 0x33, 0x3f,
+		0x2c, 0x02, 0x03, 0x02
+	},  {
+		/* 110cd */
+		0xca, 0x00, 0xd8, 0x00, 0xd4, 0x00, 0xd3, 0x82, 0x84, 0x85,
+		0x82, 0x82, 0x81, 0x84, 0x85, 0x84, 0x82, 0x80, 0x83, 0x81,
+		0x82, 0x83, 0x7b, 0x75, 0x78, 0x85, 0x80, 0x8e, 0x2f, 0x39,
+		0x29, 0x02, 0x03, 0x02
+	},  {
+		/* 120cd */
+		0xca, 0x00, 0xdf, 0x00, 0xdb, 0x00, 0xda, 0x82, 0x83, 0x84,
+		0x81, 0x81, 0x80, 0x83, 0x83, 0x82, 0x82, 0x81, 0x83, 0x80,
+		0x80, 0x82, 0x7e, 0x7a, 0x7b, 0x86, 0x83, 0x8d, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	}, {
+		/* 130cd */
+		0xca, 0x00, 0xe4, 0x00, 0xe0, 0x00, 0xe0, 0x82, 0x83, 0x84,
+		0x81, 0x80, 0x7f, 0x82, 0x82, 0x81, 0x83, 0x82, 0x84, 0x7e,
+		0x7f, 0x80, 0x7b, 0x76, 0x79, 0x86, 0x83, 0x8d, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 140cd */
+		0xca, 0x00, 0xe8, 0x00, 0xe5, 0x00, 0xe5, 0x81, 0x82, 0x82,
+		0x80, 0x80, 0x7f, 0x82, 0x82, 0x82, 0x81, 0x80, 0x82, 0x7f,
+		0x7f, 0x81, 0x7e, 0x7a, 0x7c, 0x81, 0x7d, 0x88, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 150cd */
+		0xca, 0x00, 0xec, 0x00, 0xea, 0x00, 0xe9, 0x81, 0x82, 0x82,
+		0x80, 0x80, 0x7f, 0x81, 0x82, 0x81, 0x81, 0x80, 0x81, 0x80,
+		0x80, 0x81, 0x7a, 0x77, 0x79, 0x87, 0x86, 0x8d, 0x84, 0x86,
+		0x83, 0x02, 0x03, 0x02
+	},  {
+		/* 160cd */
+		0xca, 0x00, 0xf1, 0x00, 0xef, 0x00, 0xef, 0x80, 0x80, 0x81,
+		0x80, 0x80, 0x7f, 0x80, 0x81, 0x80, 0x82, 0x81, 0x82, 0x7f,
+		0x7f, 0x7f, 0x7d, 0x7b, 0x7c, 0x82, 0x80, 0x88, 0x84, 0x86,
+		0x83, 0x02, 0x03, 0x02
+	},  {
+		/* 170cd */
+		0xca, 0x00, 0xf5, 0x00, 0xf3, 0x00, 0xf3, 0x80, 0x80, 0x81,
+		0x7f, 0x7f, 0x7f, 0x82, 0x82, 0x82, 0x80, 0x80, 0x80, 0x80,
+		0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7d, 0x7b, 0x83, 0x84, 0x86,
+		0x83, 0x02, 0x03, 0x02
+	},  {
+		/* 180cd */
+		0xca, 0x00, 0xf9, 0x00, 0xf8, 0x00, 0xf8, 0x80, 0x80, 0x80,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x81, 0x81,
+		0x81, 0x81, 0x7d, 0x7c, 0x7d, 0x83, 0x83, 0x87, 0x7f, 0x7f,
+		0x7f, 0x02, 0x03, 0x02
+	},  {
+		/* 182cd */
+		0xca, 0x00, 0xf2, 0x00, 0xf1, 0x00, 0xf0, 0x80, 0x81, 0x81,
+		0x80, 0x7f, 0x7f, 0x81, 0x81, 0x81, 0x81, 0x80, 0x81, 0x7f,
+		0x7f, 0x7f, 0x7d, 0x7b, 0x7c, 0x83, 0x81, 0x87, 0x84, 0x86,
+		0x83, 0x02, 0x03, 0x02
+	},  {
+		/* 184cd */
+		0xca, 0x00, 0xec, 0x00, 0xea, 0x00, 0xe9, 0x81, 0x82, 0x82,
+		0x7f, 0x7f, 0x7f, 0x81, 0x81, 0x81, 0x82, 0x81, 0x83, 0x7d,
+		0x7d, 0x7f, 0x7e, 0x7a, 0x7c, 0x82, 0x7f, 0x86, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 186cd */
+		0xca, 0x00, 0xe8, 0x00, 0xe4, 0x00, 0xe4, 0x81, 0x82, 0x83,
+		0x80, 0x80, 0x7f, 0x82, 0x82, 0x81, 0x80, 0x80, 0x81, 0x7e,
+		0x7f, 0x80, 0x7b, 0x76, 0x79, 0x87, 0x85, 0x8a, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 188cd */
+		0xca, 0x00, 0xe4, 0x00, 0xe0, 0x00, 0xe0, 0x81, 0x82, 0x83,
+		0x80, 0x80, 0x7f, 0x81, 0x82, 0x81, 0x80, 0x7f, 0x82, 0x80,
+		0x80, 0x82, 0x78, 0x72, 0x75, 0x8c, 0x8b, 0x90, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 190cd */
+		0xca, 0x00, 0xde, 0x00, 0xda, 0x00, 0xda, 0x81, 0x82, 0x83,
+		0x81, 0x81, 0x80, 0x83, 0x84, 0x82, 0x81, 0x80, 0x83, 0x7f,
+		0x7f, 0x81, 0x7b, 0x75, 0x78, 0x86, 0x82, 0x8b, 0x2f, 0x39,
+		0x29, 0x02, 0x03, 0x02
+	},  {
+		/* 200cd */
+		0xca, 0x00, 0xe3, 0x00, 0xdf, 0x00, 0xdf, 0x81, 0x82, 0x83,
+		0x80, 0x80, 0x7f, 0x81, 0x82, 0x81, 0x82, 0x81, 0x83, 0x80,
+		0x80, 0x82, 0x78, 0x72, 0x75, 0x86, 0x82, 0x8b, 0x2f, 0x39,
+		0x29, 0x02, 0x03, 0x02
+	},  {
+		/* 210cd */
+		0xca, 0x00, 0xe6, 0x00, 0xe3, 0x00, 0xe3, 0x81, 0x82, 0x82,
+		0x80, 0x7f, 0x7f, 0x81, 0x81, 0x81, 0x81, 0x80, 0x82, 0x7d,
+		0x7e, 0x7f, 0x7e, 0x7a, 0x7b, 0x87, 0x85, 0x8a, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 220cd */
+		0xca, 0x00, 0xea, 0x00, 0xe7, 0x00, 0xe6, 0x80, 0x80, 0x81,
+		0x80, 0x80, 0x7f, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x7e,
+		0x7f, 0x80, 0x7b, 0x76, 0x79, 0x87, 0x85, 0x8a, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	}, {
+		/* 230cd */
+		0xca, 0x00, 0xec, 0x00, 0xea, 0x00, 0xe9, 0x80, 0x81, 0x82,
+		0x7f, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x81, 0x80, 0x82, 0x7f,
+		0x7f, 0x81, 0x78, 0x73, 0x76, 0x87, 0x85, 0x8a, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 240cd */
+		0xca, 0x00, 0xef, 0x00, 0xed, 0x00, 0xed, 0x80, 0x80, 0x81,
+		0x80, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x7e, 0x7e, 0x7f, 0x7f,
+		0x7f, 0x81, 0x7e, 0x7a, 0x7c, 0x82, 0x7f, 0x85, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 250cd */
+		0xca, 0x00, 0xf2, 0x00, 0xf1, 0x00, 0xf0, 0x80, 0x80, 0x81,
+		0x7e, 0x7e, 0x7e, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x80,
+		0x80, 0x81, 0x7a, 0x77, 0x79, 0x82, 0x7f, 0x85, 0x2b, 0x34,
+		0x27, 0x02, 0x03, 0x02
+	},  {
+		/* 300cd */
+		0xca, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x80, 0x7f, 0x7f,
+		0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f,
+		0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f,
+		0x7f, 0x02, 0x03, 0x02
+	},
+};
+
+static inline struct s6evr02 *panel_to_s6evr02(struct drm_panel *panel)
+{
+	return container_of(panel, struct s6evr02, panel);
+}
+
+static int s6evr02_clear_error(struct s6evr02 *ctx)
+{
+	int ret = ctx->error;
+
+	ctx->error = 0;
+	return ret;
+}
+
+static void s6evr02_dcs_write(struct s6evr02 *ctx, const void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	ssize_t ret;
+
+	if (ctx->error < 0)
+		return;
+
+	ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+	if (ret < 0) {
+		dev_err(ctx->dev, "error %zd writing dcs seq: %*ph\n", ret,
+			(int)len, data);
+		ctx->error = ret;
+	}
+}
+
+static int s6evr02_read(struct s6evr02 *ctx, u8 cmd, void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	if (ctx->error < 0)
+		return ctx->error;
+
+	ret = mipi_dsi_generic_read(dsi, &cmd, 1, data, len);
+	if (ret < 0) {
+		dev_err(ctx->dev, "error %d reading dcs seq(%#x)\n", ret, cmd);
+		ctx->error = ret;
+	}
+
+	return ret;
+}
+
+#define s6evr02_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	s6evr02_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void s6evr02_apply_level_2_key(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0xf0, 0x5a, 0x5a);
+}
+
+static void s6evr02_etc_elvss_control(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0xb6, 0x08, 0x07);
+}
+
+static void s6evr02_aid_set(struct s6evr02 *ctx)
+{
+	u8 aid_cmd[] = {
+		0x51, 0xff, 0x00,
+	};
+
+	const u8 aid_arg[GAMMA_LEVEL_NUM] = {
+		0x28, 0x3c, 0x51, 0x66, 0x7d, 0x93, 0xab, 0xc2, /* 20-90cd */
+		0xda, 0xcd, 0xc0, 0xb3, 0xa6, 0x99, 0x99, 0x99, /* 100-130cd */
+		0x99, 0x99, 0x99, 0x99, 0x99, 0xad, 0xc2, 0xd6, /* 140-186cd */
+		0xea, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 188-250cd */
+		0xff, /* 300cd */
+	};
+
+	if (ctx->error)
+		return;
+
+	aid_cmd[2] = aid_arg[ctx->brightness];
+
+	s6evr02_dcs_write(ctx, aid_cmd, ARRAY_SIZE(aid_cmd));
+}
+
+static void s6evr02_acl_set(struct s6evr02 *ctx)
+{
+	u8 acl_seq[] = {0x55, 0x02, 0x00};
+	if (ctx->error)
+		return;
+
+	switch (ctx->brightness) {
+	case 0:
+		acl_seq[1] = 0x00;
+		break;
+	case 1:
+		acl_seq[1] = 0x01;
+		break;
+	}
+
+	s6evr02_dcs_write(ctx, acl_seq, ARRAY_SIZE(acl_seq));
+}
+
+static void s6evr02_elvss_set(struct s6evr02 *ctx)
+{
+	u8 elvss_cmd[] = {
+		0xb6, 0x08, 0x00,
+	};
+
+	const u8 elvss_arg[GAMMA_LEVEL_NUM] = {
+		0x20, 0x20, 0x20, 0x1f, 0x1f, 0x1f, 0x1e, 0x1c, /* 20-100cd */
+		0x1c, 0x1c, 0x1c, 0x1c, 0x1b, 0x19, 0x17, 0x16, /* 102-140cd */
+		0x14, 0x12, 0x10, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, /* 150-188cd */
+		0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x10, 0x0b, /* 190-300cd */
+	};
+
+	if (ctx->error)
+		return;
+
+	elvss_cmd[2] = elvss_arg[ctx->brightness];
+
+	s6evr02_dcs_write(ctx, elvss_cmd, ARRAY_SIZE(elvss_cmd));
+}
+
+
+static void s6evr02_brightness_set(struct s6evr02 *ctx)
+{
+	const u8 *gamma;
+
+	if (ctx->error)
+		return;
+
+	gamma = s6evr02_gamma_tables[ctx->brightness];
+
+	s6evr02_dcs_write(ctx, gamma, GAMMA_TABLE_LEN);
+
+	/* apply gamma table update. */
+	s6evr02_dcs_write_seq_static(ctx, 0xf7, 0x03, 0x00);
+
+	s6evr02_aid_set(ctx);
+	s6evr02_acl_set(ctx);
+	s6evr02_elvss_set(ctx);
+}
+
+static void s6evr02_gamma_cond_set(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx,
+			0xca, 0x01, 0x27, 0x01, 0x3d, 0x01, 0x47, 0xd1, 0xd7, 0xd1,
+			0xca, 0xce, 0xcc, 0xc4, 0xb3, 0xb1, 0xa1, 0xb9, 0xb8, 0xa2,
+			0xce, 0xba, 0xc8, 0xc9, 0xad, 0x9b, 0x85, 0x53, 0x6a, 0x7e,
+			0xe3, 0x09, 0x09, 0x0b);
+}
+
+static void s6evr02_gamma_update(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0xf7, 0x03, 0x00);
+}
+
+static void s6evr02_brightness_control_on(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0x53, 0x20, 0x00);
+}
+
+static void s6evr02_aor_control(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0x51, 0xff, 0x00);
+}
+
+static void s6evr02_acl_off(struct s6evr02 *ctx)
+{
+	s6evr02_dcs_write_seq_static(ctx, 0x55, 0x00, 0x00);
+}
+
+static void s6evr02_panel_init(struct s6evr02 *ctx)
+{
+	s6evr02_apply_level_2_key(ctx);
+	s6evr02_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(20);
+
+	if (ctx->version == UB_VERSION) {
+		s6evr02_gamma_cond_set(ctx);
+		s6evr02_gamma_update(ctx);
+	}
+
+	s6evr02_brightness_control_on(ctx);
+	s6evr02_aor_control(ctx);
+	s6evr02_etc_elvss_control(ctx);
+	s6evr02_acl_off(ctx);
+}
+
+static void s6evr02_set_maximum_return_packet_size(struct s6evr02 *ctx,
+						   u16 size)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	if (ctx->error < 0)
+		return;
+
+	ret = mipi_dsi_set_maximum_return_packet_size(dsi, size);
+	if (ret < 0) {
+		dev_err(ctx->dev,
+			"error %d setting maximum return packet size to %d\n",
+			ret, size);
+		ctx->error = ret;
+	}
+}
+
+static void s6evr02_read_mtp_id(struct s6evr02 *ctx)
+{
+	u8 id[3] = {0, 0, 0};
+	int ret;
+
+	/* the panel seems not to respond properly
+	 * to read commands until we write something to it
+	 */
+	s6evr02_apply_level_2_key(ctx);
+
+	ret = s6evr02_read(ctx, 0xd7, id, ARRAY_SIZE(id));
+	if (ret < 0 || ret < ARRAY_SIZE(id) || id[0] == 0x00) {
+		dev_err(ctx->dev, "failed to read ID from panel\n");
+		ctx->error = -EIO;
+		return;
+	}
+
+	dev_info(ctx->dev, "ID: 0x%2x, 0x%2x, 0x%2x\n", id[0], id[1], id[2]);
+
+	ctx->version = id[1];
+	ctx->id = id[2];
+}
+
+static void s6evr02_set_sequence(struct s6evr02 *ctx)
+{
+	s6evr02_set_maximum_return_packet_size(ctx, 3);
+	s6evr02_read_mtp_id(ctx);
+	s6evr02_panel_init(ctx);
+	s6evr02_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int s6evr02_power_on(struct s6evr02 *ctx)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	msleep(ctx->power_on_delay);
+
+	gpiod_set_value(ctx->reset_gpio, 0);
+	usleep_range(5000, 6000);
+	gpiod_set_value(ctx->reset_gpio, 1);
+	usleep_range(5000, 6000);
+
+	msleep(ctx->reset_delay);
+
+	return 0;
+}
+
+static int s6evr02_power_off(struct s6evr02 *ctx)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int s6evr02_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int s6evr02_unprepare(struct drm_panel *panel)
+{
+	struct s6evr02 *ctx = panel_to_s6evr02(panel);
+
+	clear_bit(S6EVR02_STATE_BIT_ENABLED, &ctx->state);
+	s6evr02_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+	s6evr02_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+	msleep(40);
+
+	s6evr02_clear_error(ctx);
+
+	return s6evr02_power_off(ctx);
+}
+
+static int s6evr02_prepare(struct drm_panel *panel)
+{
+	struct s6evr02 *ctx = panel_to_s6evr02(panel);
+	int ret;
+
+	ret = s6evr02_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	s6evr02_set_sequence(ctx);
+	ret = ctx->error;
+
+	if (ret < 0)
+		s6evr02_unprepare(panel);
+	else
+		set_bit(S6EVR02_STATE_BIT_ENABLED, &ctx->state);
+
+	return ret;
+}
+
+static int s6evr02_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int s6evr02_get_modes(struct drm_panel *panel,
+			     struct drm_connector *connector)
+{
+	struct s6evr02 *ctx = panel_to_s6evr02(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		dev_err(panel->dev, "failed to create a new display mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&ctx->vm, mode);
+	mode->width_mm = ctx->width_mm;
+	mode->height_mm = ctx->height_mm;
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static const struct drm_panel_funcs s6evr02_drm_funcs = {
+	.disable = s6evr02_disable,
+	.unprepare = s6evr02_unprepare,
+	.prepare = s6evr02_prepare,
+	.enable = s6evr02_enable,
+	.get_modes = s6evr02_get_modes,
+};
+
+static int s6evr02_get_brightness(struct backlight_device *bd)
+{
+	return bd->props.brightness;
+}
+
+static int s6evr02_set_brightness(struct backlight_device *bd)
+{
+	struct s6evr02 *ctx = bl_get_data(bd);
+
+	bd->props.power = FB_BLANK_UNBLANK;
+	if (ctx->brightness != bd->props.brightness) {
+		ctx->brightness = bd->props.brightness;
+		if (test_bit(S6EVR02_STATE_BIT_ENABLED, &ctx->state))
+			s6evr02_brightness_set(ctx);
+	}
+
+	return s6evr02_clear_error(ctx);
+}
+
+static const struct backlight_ops s6evr02_backlight_ops = {
+	.get_brightness = s6evr02_get_brightness,
+	.update_status = s6evr02_set_brightness,
+};
+
+static void s6evr02_backlight_register(struct s6evr02 *ctx)
+{
+	struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.brightness = ctx->brightness,
+		.max_brightness = GAMMA_LEVEL_NUM - 1
+	};
+	struct device *dev = ctx->dev;
+	struct backlight_device *bd;
+
+	bd = devm_backlight_device_register(dev, "panel", dev, ctx,
+					    &s6evr02_backlight_ops, &props);
+	if (IS_ERR(bd))
+		dev_err(dev, "error registering backlight device (%ld)\n",
+			PTR_ERR(bd));
+}
+
+static int s6evr02_parse_dt(struct s6evr02 *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	ret = of_get_videomode(np, &ctx->vm, 0);
+	if (ret < 0)
+		return ret;
+
+	of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "init-delay", &ctx->init_delay);
+	of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+	of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+	return 0;
+}
+
+static int s6evr02_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct s6evr02 *ctx;
+	int ret = 0;
+
+	ctx = devm_kzalloc(dev, sizeof(struct s6evr02), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	pr_err("s6evr02 probing\n");
+
+	mipi_dsi_set_drvdata(dsi, ctx);
+
+	ctx->dev = dev;
+
+	dsi->lanes = 4;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST
+		| MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP
+		| MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET
+		| MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_AUTO_VERT;
+
+	ret = s6evr02_parse_dt(ctx);
+	if (ret < 0)
+		return ret;
+
+	ctx->supplies[0].supply = "vdd3";
+	ctx->supplies[1].supply = "vci";
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
+				      ctx->supplies);
+	if (ret < 0) {
+		dev_err(dev, "failed to get regulators: %d\n", ret);
+		return ret;
+	}
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(ctx->reset_gpio)) {
+		dev_err(dev, "cannot get reset-gpios %ld\n",
+			PTR_ERR(ctx->reset_gpio));
+		return PTR_ERR(ctx->reset_gpio);
+	}
+
+	ctx->brightness = GAMMA_LEVEL_NUM - 1;
+
+	drm_panel_init(&ctx->panel, dev, &s6evr02_drm_funcs, DRM_MODE_CONNECTOR_DSI);
+
+	drm_panel_add(&ctx->panel);
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0)
+		drm_panel_remove(&ctx->panel);
+
+	s6evr02_backlight_register(ctx);
+
+	return ret;
+}
+
+static void s6evr02_remove(struct mipi_dsi_device *dsi)
+{
+	struct s6evr02 *ctx = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id s6evr02_of_match[] = {
+	{ .compatible = "samsung,s6evr02" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, s6evr02_of_match);
+
+static struct mipi_dsi_driver s6evr02_driver = {
+	.probe = s6evr02_probe,
+	.remove = s6evr02_remove,
+	.driver = {
+		.name = "panel-samsung-s6evr02",
+		.of_match_table = s6evr02_of_match,
+	},
+};
+module_mipi_dsi_driver(s6evr02_driver);
+
+MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
+MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>");
+MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>");
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based s6evr02 AMOLED LCD Panel Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.39.0

