diff mbox

[2/3] drm/panel: add s6e63j0x03 LCD Panel driver

Message ID 1417174791-7339-3-git-send-email-inki.dae@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Inki Dae Nov. 28, 2014, 11:39 a.m. UTC
This patch adds MIPI-DSI based S6E63J0X03 AMOLED LCD Panel driver
which uses mipi_dsi bus to communicate with Panel.

Signed-off-by: Inki Dae <inki.dae@samsung.com>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/gpu/drm/panel/Kconfig            |    6 +
 drivers/gpu/drm/panel/Makefile           |    1 +
 drivers/gpu/drm/panel/panel-s6e63j0x03.c |  693 ++++++++++++++++++++++++++++++
 3 files changed, 700 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-s6e63j0x03.c

Comments

Thierry Reding Dec. 1, 2014, 10:43 a.m. UTC | #1
On Fri, Nov 28, 2014 at 08:39:50PM +0900, Inki Dae wrote:
> This patch adds MIPI-DSI based S6E63J0X03 AMOLED LCD Panel driver
> which uses mipi_dsi bus to communicate with Panel.

s/Panel/panel/. Also I prefer to have more information in the commit
message than this. Physical size and resolution would be good. Also a
small comment as to which device this panel can be found in would be
good.

> +config DRM_PANEL_S6E63J0X03
> +	tristate "S6E63J0X03 DSI video mode panel"
> +	depends on OF
> +	select DRM_MIPI_DSI
> +	select VIDEOMODE_HELPERS
> +
>  endmenu
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index 8b92921..7f36dc2 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -1,3 +1,4 @@
>  obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
>  obj-$(CONFIG_DRM_PANEL_LD9040) += panel-ld9040.o
>  obj-$(CONFIG_DRM_PANEL_S6E8AA0) += panel-s6e8aa0.o
> +obj-$(CONFIG_DRM_PANEL_S6E63J0X03) += panel-s6e63j0x03.o
> diff --git a/drivers/gpu/drm/panel/panel-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-s6e63j0x03.c
[...]
> +struct s6e63j0x03 {
> +	struct device *dev;
> +	struct drm_panel panel;
> +	struct backlight_device *bl_dev;
> +
> +	struct regulator_bulk_data supplies[2];
> +	struct gpio_desc *reset_gpio;
> +	u32 power_on_delay;
> +	u32 power_off_delay;
> +	u32 reset_delay;
> +	u32 init_delay;
> +	bool flip_horizontal;
> +	bool flip_vertical;
> +	struct videomode vm;

Specifying the video mode in DT should be optional and only used to
override the default mode provided by the driver.

> +	u32 width_mm;
> +	u32 height_mm;

There's no need for these to be sized types. unsigned int will do just
fine.

> +	int power;

What's the difference between this and bl_dev->properties.power?

> +
> +	/* 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.
> +	 */

The format of this comment is wrong, it should be:

	/*
	 * ...
	 */

> +	int error;
> +};
> +
> +static const unsigned char GAMMA_10[] = {
...

These array names should not be all-caps.

> +static const unsigned char *gamma_tbl[MAX_GAMMA_CNT] = {
> +	GAMMA_10,
> +	GAMMA_30,
> +	GAMMA_60,
> +	GAMMA_90,
> +	GAMMA_120,
> +	GAMMA_150,
> +	GAMMA_200,
> +	GAMMA_200,
> +	GAMMA_240,
> +	GAMMA_300,
> +	GAMMA_300
> +};

Why not just do this using a two-dimensional array?

> +
> +static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
> +{
> +	return container_of(panel, struct s6e63j0x03, panel);
> +}
> +
> +static int s6e63j0x03_clear_error(struct s6e63j0x03 *ctx)
> +{
> +	int ret = ctx->error;
> +
> +	ctx->error = 0;
> +	return ret;
> +}

The only place where you ever call this ignores the return value.

> +
> +static void s6e63j0x03_dcs_write(struct s6e63j0x03 *ctx, const void *data, size_t len)
> +{
> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
> +	int ret;
> +
> +	if (ctx->error < 0)
> +		return;
> +
> +	ret = mipi_dsi_dcs_write(dsi, data, len);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret, len,
> +			data);
> +		ctx->error = ret;
> +	}
> +}

The only reason for these wrappers seems to be the ctx->error handling.
But that error handling is flawed because it silently ignores errors.
The only location where you check for errors is in .prepare(), but you
only do so after all commands have been executed, so you loose all
context about what exactly went wrong.

I'd prefer if the driver just did straightforward checking of all
commands that could possibly fail.

> +static void s6e63j0x03_set_maximum_return_packet_size(struct s6e63j0x03 *ctx,
> +							unsigned int size)
> +{
> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
> +	const struct mipi_dsi_host_ops *ops = dsi->host->ops;
> +
> +	if (ops && ops->transfer) {
> +		unsigned char buf[] = {size, 0};
> +		struct mipi_dsi_msg msg = {
> +			.channel = dsi->channel,
> +			.type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
> +			.tx_len = sizeof(buf),
> +			.tx_buf = buf
> +		};
> +		int ret;
> +
> +		ret = ops->transfer(dsi->host, &msg);
> +		if (ret < 0) {
> +			dev_err(ctx->dev, "failed to transfer message.\n");
> +			ctx->error = ret;
> +		}
> +	}
> +}

Use mipi_dsi_set_maximum_return_packet_size().

> +static void s6e63j0x03_set_sequence(struct s6e63j0x03 *ctx)

Can you find a better name for this? Also this is really only called by
->prepare() below, so why not just inline it there?

> +{
> +	/* Test key enable */
> +	s6e63j0x03_test_level_1_key_on(ctx);
> +	s6e63j0x03_test_level_2_key_on(ctx);
> +
> +	s6e63j0x03_porch_adjustment(ctx);
> +	s6e63j0x03_frame_freq(ctx);
> +	s6e63j0x03_frame_freq(ctx);
> +	s6e63j0x03_mem_addr_set_0(ctx);
> +	s6e63j0x03_mem_addr_set_1(ctx);
> +
> +	s6e63j0x03_ltps_timming_set_0_60hz(ctx);
> +	s6e63j0x03_ltps_timming_set_1(ctx);
> +
> +	s6e63j0x03_param_pos_te_edge(ctx);
> +	s6e63j0x03_te_rising_edge(ctx);
> +	s6e63j0x03_param_pos_default(ctx);
> +
> +	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);

Use mipi_dsi_dcs_exit_sleep_mode().

> +	msleep(120);
> +
> +	/* etc condition setting */
> +	s6e63j0x03_elvss_cond(ctx);
> +	s6e63j0x03_set_pos(ctx);
> +
> +	/* brightness setting */
> +	s6e63j0x03_white_brightness_default(ctx);
> +	s6e63j0x03_white_ctrl(ctx);
> +	s6e63j0x03_acl_off(ctx);
> +
> +	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_TEAR_ON);

Use mipi_dsi_dcs_set_tear_on().

> +	s6e63j0x03_test_level_2_key_off(ctx);
> +
> +	/* display on */
> +	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);

Use mipi_dsi_dcs_set_display_on().

> +static int s6e63j0x03_get_brightness(struct backlight_device *bl_dev)
> +{
> +	return bl_dev->props.brightness;
> +}
> +
> +static int s6e63j0x03_get_brightness_index(unsigned int brightness)

Should return unsigned int.

> +{
> +	int index;
> +
> +	switch (brightness) {
> +	case 0 ... 20:
> +		index = 0;
> +		break;
> +	case 21 ... 40:
> +		index = 2;
> +		break;
> +	case 41 ... 60:
> +		index = 4;
> +		break;
> +	case 61 ... 80:
> +		index = 6;
> +		break;
> +	default:
> +		index = 10;
> +		break;
> +	}
> +
> +	return index;
> +}
> +
> +static void s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
> +					unsigned int brightness)
> +{
> +	int index = s6e63j0x03_get_brightness_index(brightness);

And this should be unsigned, too, otherwise you'll need to test that
it's non-negative before indexing the array.

> +static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
> +{
> +	struct s6e63j0x03 *ctx = (struct s6e63j0x03 *)bl_get_data(bl_dev);
> +	unsigned int brightness = bl_dev->props.brightness;
> +
> +	if (brightness < MIN_BRIGHTNESS ||
> +		brightness > bl_dev->props.max_brightness) {
> +		dev_err(ctx->dev, "brightness[%u] is invalid value\n",
> +			brightness);

Perhaps a more canonical error message would read: "Invalid brightness
%u\n".

> +		return -EINVAL;
> +	}
> +
> +	if (ctx->power > FB_BLANK_UNBLANK) {
> +		dev_err(ctx->dev, "panel is not in fb unblank state\n");
> +		return -EPERM;
> +	}

What does that have to do with anything? If the screen is blanked you
could still set the brightness and have it in effect when the screen is
unblanked, I presume?

> +
> +	s6e63j0x03_update_gamma(ctx, brightness);
> +
> +	return 0;
> +}
> +
> +static const struct backlight_ops s6e63j0x03_bl_ops = {
> +	.get_brightness = s6e63j0x03_get_brightness,
> +	.update_status = s6e63j0x03_set_brightness,
> +};
> +
> +

There's a gratuitous blank line here.

> +static int s6e63j0x03_enable(struct drm_panel *panel)
> +{
> +	return 0;
> +}
> +
> +static int s6e63j0x03_disable(struct drm_panel *panel)
> +{
> +	return 0;
> +}

These should typically enable or disable the backlight.

> +static int s6e63j0x03_prepare(struct drm_panel *panel)
> +{
> +	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
> +	int ret;
> +
> +	ret = s6e63j0x03_power_on(ctx);
> +	if (ret < 0)
> +		return ret;
> +
> +	s6e63j0x03_read_mtp_id(ctx);
> +
> +	s6e63j0x03_set_sequence(ctx);
> +	ret = ctx->error;
> +
> +	if (ret < 0)
> +		return s6e63j0x03_disable(panel);

No, you can't do this. While there's nothing enforcing this (yet), the
panel can't be disabled before it's been prepared.

> +
> +	ctx->power = FB_BLANK_UNBLANK;
> +
> +	return ret;
> +}
> +
> +static int s6e63j0x03_unprepare(struct drm_panel *panel)
> +{
> +	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
> +	int ret;
> +
> +	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);

Use mipi_dsi_dcs_set_display_off().

> +	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);

Use mipi_dsi_dcs_enter_sleep_mode().

> +
> +	s6e63j0x03_clear_error(ctx);
> +
> +	usleep_range(ctx->power_off_delay * 1000,
> +			(ctx->power_off_delay + 1) * 1000);
> +
> +	ret = s6e63j0x03_power_off(ctx);
> +	if (ret < 0)
> +		return s6e63j0x03_prepare(panel);

Huh? Why?

> +
> +	ctx->power = FB_BLANK_POWERDOWN;
> +
> +	return ret;
> +}
> +
> +static const struct drm_panel_funcs s6e63j0x03_drm_funcs = {

No need for "_drm" in the variable name here.

> +	.enable = s6e63j0x03_enable,
> +	.disable = s6e63j0x03_disable,
> +	.prepare= s6e63j0x03_prepare,

Missing space between '.prepare' and '='.

> +	.unprepare = s6e63j0x03_unprepare,
> +	.get_modes = s6e63j0x03_get_modes,

Please order these in the same way as they are declared in the
structure.

> +static int s6e63j0x03_parse_dt(struct s6e63j0x03 *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, "power-off-delay", &ctx->power_off_delay);
> +	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
> +	of_property_read_u32(np, "init-delay", &ctx->init_delay);

I take it that these are optional (I can't tell because you haven't
provided a DT binding for this panel)?

> +static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
> +{
> +	struct device *dev = &dsi->dev;
> +	struct s6e63j0x03 *ctx;
> +	int ret;
> +
> +	ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
> +	if (!ctx)
> +		return -ENOMEM;
> +
> +	mipi_dsi_set_drvdata(dsi, ctx);
> +
> +	ctx->dev = dev;
> +
> +	dsi->lanes = 1;
> +	dsi->format = MIPI_DSI_FMT_RGB888;
> +	dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_BURST;
> +
> +	ret = s6e63j0x03_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));

"reset GPIO" since it's only one. Also please separate the error code
from the rest of the message using a colon to make it more obvious that
the number isn't the GPIO that could be gotten.

> +	ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
> +	ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
> +
> +	ret = drm_panel_add(&ctx->panel);
> +	if (ret < 0)
> +		goto err_unregister_backlight;
> +
> +	ret = mipi_dsi_attach(dsi);
> +	if (ret < 0)
> +		goto err_remove_panel;
> +
> +	return ret;
> +
> +err_remove_panel:
> +		drm_panel_remove(&ctx->panel);
> +
> +err_unregister_backlight:

No need for the err_ prefix here.

> +	backlight_device_unregister(ctx->bl_dev);
> +
> +	return ret;
> +}
> +
> +static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
> +{
> +	struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
> +
> +	mipi_dsi_detach(dsi);
> +	drm_panel_remove(&ctx->panel);
> +
> +	backlight_device_unregister(ctx->bl_dev);
> +
> +	return 0;
> +}
> +
> +static struct of_device_id s6e63j0x03_of_match[] = {

static const please.

> +	{ .compatible = "samsung,s6e63j0x03" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
> +
> +static struct mipi_dsi_driver s6e63j0x03_driver = {
> +	.probe = s6e63j0x03_probe,
> +	.remove = s6e63j0x03_remove,
> +	.driver = {
> +		.name = "panel_s6e63j0x03",
> +		.owner = THIS_MODULE,

It is no longer necessary to specify this explicitly.

> +		.of_match_table = s6e63j0x03_of_match,
> +	},
> +};
> +module_mipi_dsi_driver(s6e63j0x03_driver);

You're missing MODULE_AUTHOR, MODULE_DESCRIPTION and MODULE_LICENSE
here.

Thierry
diff mbox

Patch

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index bee9f72..bc133e2 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -27,4 +27,10 @@  config DRM_PANEL_S6E8AA0
 	select DRM_MIPI_DSI
 	select VIDEOMODE_HELPERS
 
+config DRM_PANEL_S6E63J0X03
+	tristate "S6E63J0X03 DSI video mode panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 8b92921..7f36dc2 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -1,3 +1,4 @@ 
 obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
 obj-$(CONFIG_DRM_PANEL_LD9040) += panel-ld9040.o
 obj-$(CONFIG_DRM_PANEL_S6E8AA0) += panel-s6e8aa0.o
+obj-$(CONFIG_DRM_PANEL_S6E63J0X03) += panel-s6e63j0x03.o
diff --git a/drivers/gpu/drm/panel/panel-s6e63j0x03.c b/drivers/gpu/drm/panel/panel-s6e63j0x03.c
new file mode 100644
index 0000000..7b8d8ca
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-s6e63j0x03.c
@@ -0,0 +1,693 @@ 
+/*
+ * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * Inki Dae, <inki.dae@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 <drm/drmP.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/backlight.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#define GAMMA_LEVEL_NUM		30
+#define MTP_ID_LEN		6
+/* Manufacturer Command Set */
+#define MCS_MTP_ID		0xD3
+
+#define MCS_MTP_SET3		0xd4
+#define MCS_MTP_KEY		0xf1
+
+#define MIN_BRIGHTNESS		0
+#define MAX_BRIGHTNESS		100
+#define DEFAULT_BRIGHTNESS	80
+
+#define GAMMA_CMD_CNT	28
+#define MAX_GAMMA_CNT	11
+
+struct s6e63j0x03 {
+	struct device *dev;
+	struct drm_panel panel;
+	struct backlight_device *bl_dev;
+
+	struct regulator_bulk_data supplies[2];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 power_off_delay;
+	u32 reset_delay;
+	u32 init_delay;
+	bool flip_horizontal;
+	bool flip_vertical;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+
+	int power;
+
+	/* 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 unsigned char GAMMA_10[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26, 0x28, 0x2d,
+	0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36, 0x35, 0x00, 0xab, 0x00,
+	0xae, 0x00, 0xbf
+};
+
+static const unsigned char GAMMA_30[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26, 0x27, 0x2a,
+	0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34, 0x35, 0x00, 0xc4, 0x00,
+	0xca, 0x00, 0xdc
+};
+
+static const unsigned char GAMMA_60[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a, 0x28, 0x29,
+	0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33, 0x34, 0x00, 0xd9, 0x00,
+	0xe4, 0x00, 0xf5
+};
+
+static const unsigned char GAMMA_90[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29, 0x28, 0x28,
+	0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31, 0x31, 0x00, 0xea, 0x00,
+	0xf6, 0x01, 0x09
+};
+
+static const unsigned char GAMMA_120[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28, 0x28, 0x27,
+	0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31, 0x30, 0x00, 0xf9, 0x01,
+	0x05, 0x01, 0x1b
+};
+
+static const unsigned char GAMMA_150[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28, 0x29, 0x27,
+	0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31, 0x30, 0x01, 0x04, 0x01,
+	0x11, 0x01, 0x29
+};
+
+static const unsigned char GAMMA_200[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29, 0x2a, 0x28,
+	0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30, 0x30, 0x01, 0x14, 0x01,
+	0x23, 0x01, 0x3b
+};
+
+static const unsigned char GAMMA_240[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a, 0x2c, 0x29,
+	0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30, 0x30, 0x01, 0x1e, 0x01,
+	0x2f, 0x01, 0x47
+};
+
+static const unsigned char GAMMA_300[] = {
+	MCS_MTP_SET3,
+	0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28, 0x2a, 0x27,
+	0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f, 0x30, 0x01, 0x2d, 0x01,
+	0x3f, 0x01, 0x57
+};
+
+static const unsigned char *gamma_tbl[MAX_GAMMA_CNT] = {
+	GAMMA_10,
+	GAMMA_30,
+	GAMMA_60,
+	GAMMA_90,
+	GAMMA_120,
+	GAMMA_150,
+	GAMMA_200,
+	GAMMA_200,
+	GAMMA_240,
+	GAMMA_300,
+	GAMMA_300
+};
+
+static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
+{
+	return container_of(panel, struct s6e63j0x03, panel);
+}
+
+static int s6e63j0x03_clear_error(struct s6e63j0x03 *ctx)
+{
+	int ret = ctx->error;
+
+	ctx->error = 0;
+	return ret;
+}
+
+static void s6e63j0x03_dcs_write(struct s6e63j0x03 *ctx, const void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	if (ctx->error < 0)
+		return;
+
+	ret = mipi_dsi_dcs_write(dsi, data, len);
+	if (ret < 0) {
+		dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret, len,
+			data);
+		ctx->error = ret;
+	}
+}
+
+static int s6e63j0x03_dcs_read(struct s6e63j0x03 *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 s6e63j0x03_dcs_write_seq(ctx, seq...) \
+({\
+	const u8 d[] = { seq };\
+	BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too big for stack");\
+	s6e63j0x03_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+#define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	s6e63j0x03_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void s6e63j0x03_test_level_1_key_on(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xf0, 0x5a, 0x5a);
+}
+
+static void s6e63j0x03_test_level_2_key_on(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xf1, 0x5a, 0x5a);
+}
+
+static void s6e63j0x03_porch_adjustment(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
+}
+
+static void s6e63j0x03_frame_freq(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
+}
+
+static void s6e63j0x03_mem_addr_set_0(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x2a, 0x00, 0x14, 0x01, 0x53);
+}
+
+static void s6e63j0x03_mem_addr_set_1(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x2b, 0x00, 0x00, 0x01, 0x3f);
+}
+
+static void s6e63j0x03_ltps_timming_set_0_60hz(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx,
+			0xf8, 0x08, 0x08, 0x08, 0x17, 0x00, 0x2a, 0x02,
+			0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
+}
+
+static void s6e63j0x03_ltps_timming_set_1(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
+}
+
+static void s6e63j0x03_param_pos_te_edge(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
+}
+
+static void s6e63j0x03_te_rising_edge(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
+}
+
+static void s6e63j0x03_param_pos_default(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
+}
+
+static void s6e63j0x03_elvss_cond(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
+}
+
+static void s6e63j0x03_set_pos(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x36, 0x40);
+}
+
+static void s6e63j0x03_white_brightness_default(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x51, 0xff);
+}
+
+static void s6e63j0x03_white_ctrl(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x53, 0x20);
+}
+
+static void s6e63j0x03_acl_off(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0x55, 0x00);
+}
+
+static void s6e63j0x03_test_level_2_key_off(struct s6e63j0x03 *ctx)
+{
+	s6e63j0x03_dcs_write_seq_static(ctx, 0xf1, 0xa5, 0xa5);
+}
+
+static void s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
+{
+	if (on)
+		s6e63j0x03_dcs_write_seq(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
+	else
+		s6e63j0x03_dcs_write_seq(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
+}
+
+static void s6e63j0x03_set_maximum_return_packet_size(struct s6e63j0x03 *ctx,
+							unsigned int size)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+	if (ops && ops->transfer) {
+		unsigned char buf[] = {size, 0};
+		struct mipi_dsi_msg msg = {
+			.channel = dsi->channel,
+			.type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
+			.tx_len = sizeof(buf),
+			.tx_buf = buf
+		};
+		int ret;
+
+		ret = ops->transfer(dsi->host, &msg);
+		if (ret < 0) {
+			dev_err(ctx->dev, "failed to transfer message.\n");
+			ctx->error = ret;
+		}
+	}
+}
+
+static void s6e63j0x03_read_mtp_id(struct s6e63j0x03 *ctx)
+{
+	unsigned char id[MTP_ID_LEN];
+	int ret;
+
+	s6e63j0x03_test_level_2_key_on(ctx);
+
+	s6e63j0x03_set_maximum_return_packet_size(ctx, MTP_ID_LEN);
+	ret = s6e63j0x03_dcs_read(ctx, MCS_MTP_ID, id, MTP_ID_LEN);
+	if (ret < ARRAY_SIZE(id) || id[0] == 0x00) {
+		dev_err(ctx->dev, "read id failed\n");
+		ctx->error = -EIO;
+		return;
+	}
+
+	s6e63j0x03_test_level_2_key_off(ctx);
+
+	dev_info(ctx->dev, "ID: 0x%02x, 0x%02x, 0x%02x\n", id[3], id[2], id[5]);
+}
+
+static void s6e63j0x03_set_sequence(struct s6e63j0x03 *ctx)
+{
+	/* Test key enable */
+	s6e63j0x03_test_level_1_key_on(ctx);
+	s6e63j0x03_test_level_2_key_on(ctx);
+
+	s6e63j0x03_porch_adjustment(ctx);
+	s6e63j0x03_frame_freq(ctx);
+	s6e63j0x03_frame_freq(ctx);
+	s6e63j0x03_mem_addr_set_0(ctx);
+	s6e63j0x03_mem_addr_set_1(ctx);
+
+	s6e63j0x03_ltps_timming_set_0_60hz(ctx);
+	s6e63j0x03_ltps_timming_set_1(ctx);
+
+	s6e63j0x03_param_pos_te_edge(ctx);
+	s6e63j0x03_te_rising_edge(ctx);
+	s6e63j0x03_param_pos_default(ctx);
+
+	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(120);
+
+	/* etc condition setting */
+	s6e63j0x03_elvss_cond(ctx);
+	s6e63j0x03_set_pos(ctx);
+
+	/* brightness setting */
+	s6e63j0x03_white_brightness_default(ctx);
+	s6e63j0x03_white_ctrl(ctx);
+	s6e63j0x03_acl_off(ctx);
+
+	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_TEAR_ON);
+	s6e63j0x03_test_level_2_key_off(ctx);
+
+	/* display on */
+	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
+}
+
+static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(ctx->power_on_delay * 1000,
+			(ctx->power_on_delay + 1) * 1000);
+
+	gpiod_set_value(ctx->reset_gpio, 0);
+	usleep_range(1000, 2000);
+	gpiod_set_value(ctx->reset_gpio, 1);
+
+	usleep_range(ctx->reset_delay * 1000,
+			(ctx->reset_delay + 1) * 1000);
+
+	return 0;
+}
+
+static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
+{
+	int ret;
+
+	gpiod_set_value(ctx->reset_gpio, 0);
+
+	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int s6e63j0x03_get_brightness(struct backlight_device *bl_dev)
+{
+	return bl_dev->props.brightness;
+}
+
+static int s6e63j0x03_get_brightness_index(unsigned int brightness)
+{
+	int index;
+
+	switch (brightness) {
+	case 0 ... 20:
+		index = 0;
+		break;
+	case 21 ... 40:
+		index = 2;
+		break;
+	case 41 ... 60:
+		index = 4;
+		break;
+	case 61 ... 80:
+		index = 6;
+		break;
+	default:
+		index = 10;
+		break;
+	}
+
+	return index;
+}
+
+static void s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
+					unsigned int brightness)
+{
+	int index = s6e63j0x03_get_brightness_index(brightness);
+
+	s6e63j0x03_apply_mtp_key(ctx, true);
+
+	s6e63j0x03_dcs_write(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
+
+	s6e63j0x03_apply_mtp_key(ctx, false);
+}
+
+static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
+{
+	struct s6e63j0x03 *ctx = (struct s6e63j0x03 *)bl_get_data(bl_dev);
+	unsigned int brightness = bl_dev->props.brightness;
+
+	if (brightness < MIN_BRIGHTNESS ||
+		brightness > bl_dev->props.max_brightness) {
+		dev_err(ctx->dev, "brightness[%u] is invalid value\n",
+			brightness);
+		return -EINVAL;
+	}
+
+	if (ctx->power > FB_BLANK_UNBLANK) {
+		dev_err(ctx->dev, "panel is not in fb unblank state\n");
+		return -EPERM;
+	}
+
+	s6e63j0x03_update_gamma(ctx, brightness);
+
+	return 0;
+}
+
+static const struct backlight_ops s6e63j0x03_bl_ops = {
+	.get_brightness = s6e63j0x03_get_brightness,
+	.update_status = s6e63j0x03_set_brightness,
+};
+
+
+static int s6e63j0x03_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int s6e63j0x03_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int s6e63j0x03_prepare(struct drm_panel *panel)
+{
+	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+	int ret;
+
+	ret = s6e63j0x03_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	s6e63j0x03_read_mtp_id(ctx);
+
+	s6e63j0x03_set_sequence(ctx);
+	ret = ctx->error;
+
+	if (ret < 0)
+		return s6e63j0x03_disable(panel);
+
+	ctx->power = FB_BLANK_UNBLANK;
+
+	return ret;
+}
+
+static int s6e63j0x03_unprepare(struct drm_panel *panel)
+{
+	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+	int ret;
+
+	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
+	s6e63j0x03_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+
+	s6e63j0x03_clear_error(ctx);
+
+	usleep_range(ctx->power_off_delay * 1000,
+			(ctx->power_off_delay + 1) * 1000);
+
+	ret = s6e63j0x03_power_off(ctx);
+	if (ret < 0)
+		return s6e63j0x03_prepare(panel);
+
+	ctx->power = FB_BLANK_POWERDOWN;
+
+	return ret;
+}
+
+static int s6e63j0x03_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		DRM_ERROR("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 s6e63j0x03_drm_funcs = {
+	.enable = s6e63j0x03_enable,
+	.disable = s6e63j0x03_disable,
+	.prepare= s6e63j0x03_prepare,
+	.unprepare = s6e63j0x03_unprepare,
+	.get_modes = s6e63j0x03_get_modes,
+};
+
+static int s6e63j0x03_parse_dt(struct s6e63j0x03 *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, "power-off-delay", &ctx->power_off_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "init-delay", &ctx->init_delay);
+
+	ctx->flip_horizontal = of_property_read_bool(np, "flip-horizontal");
+	ctx->flip_vertical = of_property_read_bool(np, "flip-vertical");
+
+	return ret;
+}
+
+static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct s6e63j0x03 *ctx;
+	int ret;
+
+	ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	mipi_dsi_set_drvdata(dsi, ctx);
+
+	ctx->dev = dev;
+
+	dsi->lanes = 1;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_BURST;
+
+	ret = s6e63j0x03_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->power = FB_BLANK_POWERDOWN;
+
+	drm_panel_init(&ctx->panel);
+	ctx->panel.dev = dev;
+	ctx->panel.funcs = &s6e63j0x03_drm_funcs;
+
+	ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
+						&s6e63j0x03_bl_ops, NULL);
+	if (IS_ERR(ctx->bl_dev)) {
+		dev_err(dev, "failed to register backlight device\n");
+		return PTR_ERR(ctx->bl_dev);
+	}
+
+	ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
+	ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
+
+	ret = drm_panel_add(&ctx->panel);
+	if (ret < 0)
+		goto err_unregister_backlight;
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0)
+		goto err_remove_panel;
+
+	return ret;
+
+err_remove_panel:
+		drm_panel_remove(&ctx->panel);
+
+err_unregister_backlight:
+	backlight_device_unregister(ctx->bl_dev);
+
+	return ret;
+}
+
+static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
+{
+	struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&ctx->panel);
+
+	backlight_device_unregister(ctx->bl_dev);
+
+	return 0;
+}
+
+static struct of_device_id s6e63j0x03_of_match[] = {
+	{ .compatible = "samsung,s6e63j0x03" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
+
+static struct mipi_dsi_driver s6e63j0x03_driver = {
+	.probe = s6e63j0x03_probe,
+	.remove = s6e63j0x03_remove,
+	.driver = {
+		.name = "panel_s6e63j0x03",
+		.owner = THIS_MODULE,
+		.of_match_table = s6e63j0x03_of_match,
+	},
+};
+module_mipi_dsi_driver(s6e63j0x03_driver);