1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
22a05177dSHoegeun Kwon /*
32a05177dSHoegeun Kwon  * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
42a05177dSHoegeun Kwon  *
52a05177dSHoegeun Kwon  * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
62a05177dSHoegeun Kwon  *
72a05177dSHoegeun Kwon  * Inki Dae <inki.dae@samsung.com>
82a05177dSHoegeun Kwon  * Hoegeun Kwon <hoegeun.kwon@samsung.com>
92a05177dSHoegeun Kwon  */
102a05177dSHoegeun Kwon 
112a05177dSHoegeun Kwon #include <drm/drmP.h>
122a05177dSHoegeun Kwon #include <drm/drm_mipi_dsi.h>
132a05177dSHoegeun Kwon #include <drm/drm_panel.h>
142a05177dSHoegeun Kwon #include <linux/backlight.h>
152a05177dSHoegeun Kwon #include <linux/gpio/consumer.h>
162a05177dSHoegeun Kwon #include <linux/regulator/consumer.h>
172a05177dSHoegeun Kwon #include <video/mipi_display.h>
182a05177dSHoegeun Kwon 
192a05177dSHoegeun Kwon #define MCS_LEVEL2_KEY		0xf0
202a05177dSHoegeun Kwon #define MCS_MTP_KEY		0xf1
212a05177dSHoegeun Kwon #define MCS_MTP_SET3		0xd4
222a05177dSHoegeun Kwon 
232a05177dSHoegeun Kwon #define MAX_BRIGHTNESS		100
242a05177dSHoegeun Kwon #define DEFAULT_BRIGHTNESS	80
252a05177dSHoegeun Kwon 
262a05177dSHoegeun Kwon #define NUM_GAMMA_STEPS		9
272a05177dSHoegeun Kwon #define GAMMA_CMD_CNT		28
282a05177dSHoegeun Kwon 
292a05177dSHoegeun Kwon #define FIRST_COLUMN 20
302a05177dSHoegeun Kwon 
312a05177dSHoegeun Kwon struct s6e63j0x03 {
322a05177dSHoegeun Kwon 	struct device *dev;
332a05177dSHoegeun Kwon 	struct drm_panel panel;
342a05177dSHoegeun Kwon 	struct backlight_device *bl_dev;
352a05177dSHoegeun Kwon 
362a05177dSHoegeun Kwon 	struct regulator_bulk_data supplies[2];
372a05177dSHoegeun Kwon 	struct gpio_desc *reset_gpio;
382a05177dSHoegeun Kwon };
392a05177dSHoegeun Kwon 
402a05177dSHoegeun Kwon static const struct drm_display_mode default_mode = {
412a05177dSHoegeun Kwon 	.clock = 4649,
422a05177dSHoegeun Kwon 	.hdisplay = 320,
432a05177dSHoegeun Kwon 	.hsync_start = 320 + 1,
442a05177dSHoegeun Kwon 	.hsync_end = 320 + 1 + 1,
452a05177dSHoegeun Kwon 	.htotal = 320 + 1 + 1 + 1,
462a05177dSHoegeun Kwon 	.vdisplay = 320,
472a05177dSHoegeun Kwon 	.vsync_start = 320 + 150,
482a05177dSHoegeun Kwon 	.vsync_end = 320 + 150 + 1,
492a05177dSHoegeun Kwon 	.vtotal = 320 + 150 + 1 + 2,
502a05177dSHoegeun Kwon 	.vrefresh = 30,
512a05177dSHoegeun Kwon 	.flags = 0,
522a05177dSHoegeun Kwon };
532a05177dSHoegeun Kwon 
542a05177dSHoegeun Kwon static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {
552a05177dSHoegeun Kwon 	{	/* Gamma 10 */
562a05177dSHoegeun Kwon 		MCS_MTP_SET3,
572a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,
582a05177dSHoegeun Kwon 		0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,
592a05177dSHoegeun Kwon 		0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf
602a05177dSHoegeun Kwon 	},
612a05177dSHoegeun Kwon 	{	/* gamma 30 */
622a05177dSHoegeun Kwon 		MCS_MTP_SET3,
632a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,
642a05177dSHoegeun Kwon 		0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,
652a05177dSHoegeun Kwon 		0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc
662a05177dSHoegeun Kwon 	},
672a05177dSHoegeun Kwon 	{	/* gamma 60 */
682a05177dSHoegeun Kwon 		MCS_MTP_SET3,
692a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,
702a05177dSHoegeun Kwon 		0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,
712a05177dSHoegeun Kwon 		0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5
722a05177dSHoegeun Kwon 	},
732a05177dSHoegeun Kwon 	{	/* gamma 90 */
742a05177dSHoegeun Kwon 		MCS_MTP_SET3,
752a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,
762a05177dSHoegeun Kwon 		0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,
772a05177dSHoegeun Kwon 		0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09
782a05177dSHoegeun Kwon 	},
792a05177dSHoegeun Kwon 	{	/* gamma 120 */
802a05177dSHoegeun Kwon 		MCS_MTP_SET3,
812a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,
822a05177dSHoegeun Kwon 		0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,
832a05177dSHoegeun Kwon 		0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b
842a05177dSHoegeun Kwon 	},
852a05177dSHoegeun Kwon 	{	/* gamma 150 */
862a05177dSHoegeun Kwon 		MCS_MTP_SET3,
872a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,
882a05177dSHoegeun Kwon 		0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,
892a05177dSHoegeun Kwon 		0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29
902a05177dSHoegeun Kwon 	},
912a05177dSHoegeun Kwon 	{	/* gamma 200 */
922a05177dSHoegeun Kwon 		MCS_MTP_SET3,
932a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,
942a05177dSHoegeun Kwon 		0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,
952a05177dSHoegeun Kwon 		0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b
962a05177dSHoegeun Kwon 	},
972a05177dSHoegeun Kwon 	{	/* gamma 240 */
982a05177dSHoegeun Kwon 		MCS_MTP_SET3,
992a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,
1002a05177dSHoegeun Kwon 		0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,
1012a05177dSHoegeun Kwon 		0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47
1022a05177dSHoegeun Kwon 	},
1032a05177dSHoegeun Kwon 	{	/* gamma 300 */
1042a05177dSHoegeun Kwon 		MCS_MTP_SET3,
1052a05177dSHoegeun Kwon 		0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,
1062a05177dSHoegeun Kwon 		0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,
1072a05177dSHoegeun Kwon 		0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57
1082a05177dSHoegeun Kwon 	}
1092a05177dSHoegeun Kwon };
1102a05177dSHoegeun Kwon 
1112a05177dSHoegeun Kwon static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
1122a05177dSHoegeun Kwon {
1132a05177dSHoegeun Kwon 	return container_of(panel, struct s6e63j0x03, panel);
1142a05177dSHoegeun Kwon }
1152a05177dSHoegeun Kwon 
1162a05177dSHoegeun Kwon static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,
1172a05177dSHoegeun Kwon 					const void *seq, size_t len)
1182a05177dSHoegeun Kwon {
1192a05177dSHoegeun Kwon 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
1202a05177dSHoegeun Kwon 
1212a05177dSHoegeun Kwon 	return mipi_dsi_dcs_write_buffer(dsi, seq, len);
1222a05177dSHoegeun Kwon }
1232a05177dSHoegeun Kwon 
1242a05177dSHoegeun Kwon #define s6e63j0x03_dcs_write_seq_static(ctx, seq...)			\
1252a05177dSHoegeun Kwon 	({								\
1262a05177dSHoegeun Kwon 		static const u8 d[] = { seq };				\
1272a05177dSHoegeun Kwon 		s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d));	\
1282a05177dSHoegeun Kwon 	})
1292a05177dSHoegeun Kwon 
1302a05177dSHoegeun Kwon static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)
1312a05177dSHoegeun Kwon {
1322a05177dSHoegeun Kwon 	return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);
1332a05177dSHoegeun Kwon }
1342a05177dSHoegeun Kwon 
1352a05177dSHoegeun Kwon static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
1362a05177dSHoegeun Kwon {
1372a05177dSHoegeun Kwon 	if (on)
1382a05177dSHoegeun Kwon 		return s6e63j0x03_dcs_write_seq_static(ctx,
1392a05177dSHoegeun Kwon 				MCS_MTP_KEY, 0x5a, 0x5a);
1402a05177dSHoegeun Kwon 
1412a05177dSHoegeun Kwon 	return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
1422a05177dSHoegeun Kwon }
1432a05177dSHoegeun Kwon 
1442a05177dSHoegeun Kwon static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
1452a05177dSHoegeun Kwon {
1462a05177dSHoegeun Kwon 	int ret;
1472a05177dSHoegeun Kwon 
1482a05177dSHoegeun Kwon 	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
1492a05177dSHoegeun Kwon 	if (ret < 0)
1502a05177dSHoegeun Kwon 		return ret;
1512a05177dSHoegeun Kwon 
1522a05177dSHoegeun Kwon 	msleep(30);
1532a05177dSHoegeun Kwon 
1542a05177dSHoegeun Kwon 	gpiod_set_value(ctx->reset_gpio, 1);
1552a05177dSHoegeun Kwon 	usleep_range(1000, 2000);
1562a05177dSHoegeun Kwon 	gpiod_set_value(ctx->reset_gpio, 0);
1572a05177dSHoegeun Kwon 	usleep_range(5000, 6000);
1582a05177dSHoegeun Kwon 
1592a05177dSHoegeun Kwon 	return 0;
1602a05177dSHoegeun Kwon }
1612a05177dSHoegeun Kwon 
1622a05177dSHoegeun Kwon static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
1632a05177dSHoegeun Kwon {
1642a05177dSHoegeun Kwon 	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
1652a05177dSHoegeun Kwon }
1662a05177dSHoegeun Kwon 
1672a05177dSHoegeun Kwon static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)
1682a05177dSHoegeun Kwon {
1692a05177dSHoegeun Kwon 	unsigned int index;
1702a05177dSHoegeun Kwon 
1712a05177dSHoegeun Kwon 	index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);
1722a05177dSHoegeun Kwon 
1732a05177dSHoegeun Kwon 	if (index >= NUM_GAMMA_STEPS)
1742a05177dSHoegeun Kwon 		index = NUM_GAMMA_STEPS - 1;
1752a05177dSHoegeun Kwon 
1762a05177dSHoegeun Kwon 	return index;
1772a05177dSHoegeun Kwon }
1782a05177dSHoegeun Kwon 
1792a05177dSHoegeun Kwon static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
1802a05177dSHoegeun Kwon 					unsigned int brightness)
1812a05177dSHoegeun Kwon {
1822a05177dSHoegeun Kwon 	struct backlight_device *bl_dev = ctx->bl_dev;
1832a05177dSHoegeun Kwon 	unsigned int index = s6e63j0x03_get_brightness_index(brightness);
1842a05177dSHoegeun Kwon 	int ret;
1852a05177dSHoegeun Kwon 
1862a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, true);
1872a05177dSHoegeun Kwon 	if (ret < 0)
1882a05177dSHoegeun Kwon 		return ret;
1892a05177dSHoegeun Kwon 
1902a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
1912a05177dSHoegeun Kwon 	if (ret < 0)
1922a05177dSHoegeun Kwon 		return ret;
1932a05177dSHoegeun Kwon 
1942a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, false);
1952a05177dSHoegeun Kwon 	if (ret < 0)
1962a05177dSHoegeun Kwon 		return ret;
1972a05177dSHoegeun Kwon 
1982a05177dSHoegeun Kwon 	bl_dev->props.brightness = brightness;
1992a05177dSHoegeun Kwon 
2002a05177dSHoegeun Kwon 	return 0;
2012a05177dSHoegeun Kwon }
2022a05177dSHoegeun Kwon 
2032a05177dSHoegeun Kwon static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
2042a05177dSHoegeun Kwon {
2052a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = bl_get_data(bl_dev);
2062a05177dSHoegeun Kwon 	unsigned int brightness = bl_dev->props.brightness;
2072a05177dSHoegeun Kwon 
2082a05177dSHoegeun Kwon 	return s6e63j0x03_update_gamma(ctx, brightness);
2092a05177dSHoegeun Kwon }
2102a05177dSHoegeun Kwon 
2112a05177dSHoegeun Kwon static const struct backlight_ops s6e63j0x03_bl_ops = {
2122a05177dSHoegeun Kwon 	.update_status = s6e63j0x03_set_brightness,
2132a05177dSHoegeun Kwon };
2142a05177dSHoegeun Kwon 
2152a05177dSHoegeun Kwon static int s6e63j0x03_disable(struct drm_panel *panel)
2162a05177dSHoegeun Kwon {
2172a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
2182a05177dSHoegeun Kwon 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
2192a05177dSHoegeun Kwon 	int ret;
2202a05177dSHoegeun Kwon 
2212a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_display_off(dsi);
2222a05177dSHoegeun Kwon 	if (ret < 0)
2232a05177dSHoegeun Kwon 		return ret;
2242a05177dSHoegeun Kwon 
2252a05177dSHoegeun Kwon 	ctx->bl_dev->props.power = FB_BLANK_NORMAL;
2262a05177dSHoegeun Kwon 
2272a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
2282a05177dSHoegeun Kwon 	if (ret < 0)
2292a05177dSHoegeun Kwon 		return ret;
2302a05177dSHoegeun Kwon 
2312a05177dSHoegeun Kwon 	msleep(120);
2322a05177dSHoegeun Kwon 
2332a05177dSHoegeun Kwon 	return 0;
2342a05177dSHoegeun Kwon }
2352a05177dSHoegeun Kwon 
2362a05177dSHoegeun Kwon static int s6e63j0x03_unprepare(struct drm_panel *panel)
2372a05177dSHoegeun Kwon {
2382a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
2392a05177dSHoegeun Kwon 	int ret;
2402a05177dSHoegeun Kwon 
2412a05177dSHoegeun Kwon 	ret = s6e63j0x03_power_off(ctx);
2422a05177dSHoegeun Kwon 	if (ret < 0)
2432a05177dSHoegeun Kwon 		return ret;
2442a05177dSHoegeun Kwon 
2452a05177dSHoegeun Kwon 	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
2462a05177dSHoegeun Kwon 
2472a05177dSHoegeun Kwon 	return 0;
2482a05177dSHoegeun Kwon }
2492a05177dSHoegeun Kwon 
2502a05177dSHoegeun Kwon static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)
2512a05177dSHoegeun Kwon {
2522a05177dSHoegeun Kwon 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
2532a05177dSHoegeun Kwon 	int ret;
2542a05177dSHoegeun Kwon 
2552a05177dSHoegeun Kwon 	ret = s6e63j0x03_enable_lv2_command(ctx);
2562a05177dSHoegeun Kwon 	if (ret < 0)
2572a05177dSHoegeun Kwon 		return ret;
2582a05177dSHoegeun Kwon 
2592a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, true);
2602a05177dSHoegeun Kwon 	if (ret < 0)
2612a05177dSHoegeun Kwon 		return ret;
2622a05177dSHoegeun Kwon 
2632a05177dSHoegeun Kwon 	/* set porch adjustment */
2642a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
2652a05177dSHoegeun Kwon 	if (ret < 0)
2662a05177dSHoegeun Kwon 		return ret;
2672a05177dSHoegeun Kwon 
2682a05177dSHoegeun Kwon 	/* set frame freq */
2692a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
2702a05177dSHoegeun Kwon 	if (ret < 0)
2712a05177dSHoegeun Kwon 		return ret;
2722a05177dSHoegeun Kwon 
2732a05177dSHoegeun Kwon 	/* set caset, paset */
2742a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,
2752a05177dSHoegeun Kwon 		default_mode.hdisplay - 1 + FIRST_COLUMN);
2762a05177dSHoegeun Kwon 	if (ret < 0)
2772a05177dSHoegeun Kwon 		return ret;
2782a05177dSHoegeun Kwon 
2792a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);
2802a05177dSHoegeun Kwon 	if (ret < 0)
2812a05177dSHoegeun Kwon 		return ret;
2822a05177dSHoegeun Kwon 
2832a05177dSHoegeun Kwon 	/* set ltps timming 0, 1 */
2842a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,
2852a05177dSHoegeun Kwon 		0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
2862a05177dSHoegeun Kwon 	if (ret < 0)
2872a05177dSHoegeun Kwon 		return ret;
2882a05177dSHoegeun Kwon 
2892a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
2902a05177dSHoegeun Kwon 	if (ret < 0)
2912a05177dSHoegeun Kwon 		return ret;
2922a05177dSHoegeun Kwon 
2932a05177dSHoegeun Kwon 	/* set param pos te_edge */
2942a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
2952a05177dSHoegeun Kwon 	if (ret < 0)
2962a05177dSHoegeun Kwon 		return ret;
2972a05177dSHoegeun Kwon 
2982a05177dSHoegeun Kwon 	/* set te rising edge */
2992a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
3002a05177dSHoegeun Kwon 	if (ret < 0)
3012a05177dSHoegeun Kwon 		return ret;
3022a05177dSHoegeun Kwon 
3032a05177dSHoegeun Kwon 	/* set param pos default */
3042a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
3052a05177dSHoegeun Kwon 	if (ret < 0)
3062a05177dSHoegeun Kwon 		return ret;
3072a05177dSHoegeun Kwon 
3082a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
3092a05177dSHoegeun Kwon 	if (ret < 0)
3102a05177dSHoegeun Kwon 		return ret;
3112a05177dSHoegeun Kwon 
3122a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, false);
3132a05177dSHoegeun Kwon 	if (ret < 0)
3142a05177dSHoegeun Kwon 		return ret;
3152a05177dSHoegeun Kwon 
3162a05177dSHoegeun Kwon 	return 0;
3172a05177dSHoegeun Kwon }
3182a05177dSHoegeun Kwon 
3192a05177dSHoegeun Kwon static int s6e63j0x03_prepare(struct drm_panel *panel)
3202a05177dSHoegeun Kwon {
3212a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
3222a05177dSHoegeun Kwon 	int ret;
3232a05177dSHoegeun Kwon 
3242a05177dSHoegeun Kwon 	ret = s6e63j0x03_power_on(ctx);
3252a05177dSHoegeun Kwon 	if (ret < 0)
3262a05177dSHoegeun Kwon 		return ret;
3272a05177dSHoegeun Kwon 
3282a05177dSHoegeun Kwon 	ret = s6e63j0x03_panel_init(ctx);
3292a05177dSHoegeun Kwon 	if (ret < 0)
3302a05177dSHoegeun Kwon 		goto err;
3312a05177dSHoegeun Kwon 
3322a05177dSHoegeun Kwon 	ctx->bl_dev->props.power = FB_BLANK_NORMAL;
3332a05177dSHoegeun Kwon 
3342a05177dSHoegeun Kwon 	return 0;
3352a05177dSHoegeun Kwon 
3362a05177dSHoegeun Kwon err:
3372a05177dSHoegeun Kwon 	s6e63j0x03_power_off(ctx);
3382a05177dSHoegeun Kwon 	return ret;
3392a05177dSHoegeun Kwon }
3402a05177dSHoegeun Kwon 
3412a05177dSHoegeun Kwon static int s6e63j0x03_enable(struct drm_panel *panel)
3422a05177dSHoegeun Kwon {
3432a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
3442a05177dSHoegeun Kwon 	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
3452a05177dSHoegeun Kwon 	int ret;
3462a05177dSHoegeun Kwon 
3472a05177dSHoegeun Kwon 	msleep(120);
3482a05177dSHoegeun Kwon 
3492a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, true);
3502a05177dSHoegeun Kwon 	if (ret < 0)
3512a05177dSHoegeun Kwon 		return ret;
3522a05177dSHoegeun Kwon 
3532a05177dSHoegeun Kwon 	/* set elvss_cond */
3542a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
3552a05177dSHoegeun Kwon 	if (ret < 0)
3562a05177dSHoegeun Kwon 		return ret;
3572a05177dSHoegeun Kwon 
3582a05177dSHoegeun Kwon 	/* set pos */
3592a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx,
3602a05177dSHoegeun Kwon 		MIPI_DCS_SET_ADDRESS_MODE, 0x40);
3612a05177dSHoegeun Kwon 	if (ret < 0)
3622a05177dSHoegeun Kwon 		return ret;
3632a05177dSHoegeun Kwon 
3642a05177dSHoegeun Kwon 	/* set default white brightness */
3652a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
3662a05177dSHoegeun Kwon 	if (ret < 0)
3672a05177dSHoegeun Kwon 		return ret;
3682a05177dSHoegeun Kwon 
3692a05177dSHoegeun Kwon 	/* set white ctrl */
3702a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx,
3712a05177dSHoegeun Kwon 		MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
3722a05177dSHoegeun Kwon 	if (ret < 0)
3732a05177dSHoegeun Kwon 		return ret;
3742a05177dSHoegeun Kwon 
3752a05177dSHoegeun Kwon 	/* set acl off */
3762a05177dSHoegeun Kwon 	ret = s6e63j0x03_dcs_write_seq_static(ctx,
3772a05177dSHoegeun Kwon 		MIPI_DCS_WRITE_POWER_SAVE, 0x00);
3782a05177dSHoegeun Kwon 	if (ret < 0)
3792a05177dSHoegeun Kwon 		return ret;
3802a05177dSHoegeun Kwon 
3812a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
3822a05177dSHoegeun Kwon 	if (ret < 0)
3832a05177dSHoegeun Kwon 		return ret;
3842a05177dSHoegeun Kwon 
3852a05177dSHoegeun Kwon 	ret = s6e63j0x03_apply_mtp_key(ctx, false);
3862a05177dSHoegeun Kwon 	if (ret < 0)
3872a05177dSHoegeun Kwon 		return ret;
3882a05177dSHoegeun Kwon 
3892a05177dSHoegeun Kwon 	ret = mipi_dsi_dcs_set_display_on(dsi);
3902a05177dSHoegeun Kwon 	if (ret < 0)
3912a05177dSHoegeun Kwon 		return ret;
3922a05177dSHoegeun Kwon 
3932a05177dSHoegeun Kwon 	ctx->bl_dev->props.power = FB_BLANK_UNBLANK;
3942a05177dSHoegeun Kwon 
3952a05177dSHoegeun Kwon 	return 0;
3962a05177dSHoegeun Kwon }
3972a05177dSHoegeun Kwon 
3982a05177dSHoegeun Kwon static int s6e63j0x03_get_modes(struct drm_panel *panel)
3992a05177dSHoegeun Kwon {
4002a05177dSHoegeun Kwon 	struct drm_connector *connector = panel->connector;
4012a05177dSHoegeun Kwon 	struct drm_display_mode *mode;
4022a05177dSHoegeun Kwon 
4032a05177dSHoegeun Kwon 	mode = drm_mode_duplicate(panel->drm, &default_mode);
4042a05177dSHoegeun Kwon 	if (!mode) {
4052a05177dSHoegeun Kwon 		DRM_ERROR("failed to add mode %ux%ux@%u\n",
4062a05177dSHoegeun Kwon 			default_mode.hdisplay, default_mode.vdisplay,
4072a05177dSHoegeun Kwon 			default_mode.vrefresh);
4082a05177dSHoegeun Kwon 		return -ENOMEM;
4092a05177dSHoegeun Kwon 	}
4102a05177dSHoegeun Kwon 
4112a05177dSHoegeun Kwon 	drm_mode_set_name(mode);
4122a05177dSHoegeun Kwon 
4132a05177dSHoegeun Kwon 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
4142a05177dSHoegeun Kwon 	drm_mode_probed_add(connector, mode);
4152a05177dSHoegeun Kwon 
4162a05177dSHoegeun Kwon 	connector->display_info.width_mm = 29;
4172a05177dSHoegeun Kwon 	connector->display_info.height_mm = 29;
4182a05177dSHoegeun Kwon 
4192a05177dSHoegeun Kwon 	return 1;
4202a05177dSHoegeun Kwon }
4212a05177dSHoegeun Kwon 
4222a05177dSHoegeun Kwon static const struct drm_panel_funcs s6e63j0x03_funcs = {
4232a05177dSHoegeun Kwon 	.disable = s6e63j0x03_disable,
4242a05177dSHoegeun Kwon 	.unprepare = s6e63j0x03_unprepare,
4252a05177dSHoegeun Kwon 	.prepare = s6e63j0x03_prepare,
4262a05177dSHoegeun Kwon 	.enable = s6e63j0x03_enable,
4272a05177dSHoegeun Kwon 	.get_modes = s6e63j0x03_get_modes,
4282a05177dSHoegeun Kwon };
4292a05177dSHoegeun Kwon 
4302a05177dSHoegeun Kwon static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
4312a05177dSHoegeun Kwon {
4322a05177dSHoegeun Kwon 	struct device *dev = &dsi->dev;
4332a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx;
4342a05177dSHoegeun Kwon 	int ret;
4352a05177dSHoegeun Kwon 
4362a05177dSHoegeun Kwon 	ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
4372a05177dSHoegeun Kwon 	if (!ctx)
4382a05177dSHoegeun Kwon 		return -ENOMEM;
4392a05177dSHoegeun Kwon 
4402a05177dSHoegeun Kwon 	mipi_dsi_set_drvdata(dsi, ctx);
4412a05177dSHoegeun Kwon 
4422a05177dSHoegeun Kwon 	ctx->dev = dev;
4432a05177dSHoegeun Kwon 
4442a05177dSHoegeun Kwon 	dsi->lanes = 1;
4452a05177dSHoegeun Kwon 	dsi->format = MIPI_DSI_FMT_RGB888;
4462a05177dSHoegeun Kwon 	dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET;
4472a05177dSHoegeun Kwon 
4482a05177dSHoegeun Kwon 	ctx->supplies[0].supply = "vdd3";
4492a05177dSHoegeun Kwon 	ctx->supplies[1].supply = "vci";
4502a05177dSHoegeun Kwon 	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
4512a05177dSHoegeun Kwon 				      ctx->supplies);
4522a05177dSHoegeun Kwon 	if (ret < 0) {
4532a05177dSHoegeun Kwon 		dev_err(dev, "failed to get regulators: %d\n", ret);
4542a05177dSHoegeun Kwon 		return ret;
4552a05177dSHoegeun Kwon 	}
4562a05177dSHoegeun Kwon 
4572a05177dSHoegeun Kwon 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
4582a05177dSHoegeun Kwon 	if (IS_ERR(ctx->reset_gpio)) {
4592a05177dSHoegeun Kwon 		dev_err(dev, "cannot get reset-gpio: %ld\n",
4602a05177dSHoegeun Kwon 				PTR_ERR(ctx->reset_gpio));
4612a05177dSHoegeun Kwon 		return PTR_ERR(ctx->reset_gpio);
4622a05177dSHoegeun Kwon 	}
4632a05177dSHoegeun Kwon 
4642a05177dSHoegeun Kwon 	drm_panel_init(&ctx->panel);
4652a05177dSHoegeun Kwon 	ctx->panel.dev = dev;
4662a05177dSHoegeun Kwon 	ctx->panel.funcs = &s6e63j0x03_funcs;
4672a05177dSHoegeun Kwon 
4682a05177dSHoegeun Kwon 	ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
4692a05177dSHoegeun Kwon 						&s6e63j0x03_bl_ops, NULL);
4702a05177dSHoegeun Kwon 	if (IS_ERR(ctx->bl_dev)) {
4712a05177dSHoegeun Kwon 		dev_err(dev, "failed to register backlight device\n");
4722a05177dSHoegeun Kwon 		return PTR_ERR(ctx->bl_dev);
4732a05177dSHoegeun Kwon 	}
4742a05177dSHoegeun Kwon 
4752a05177dSHoegeun Kwon 	ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
4762a05177dSHoegeun Kwon 	ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
4772a05177dSHoegeun Kwon 	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
4782a05177dSHoegeun Kwon 
4792a05177dSHoegeun Kwon 	ret = drm_panel_add(&ctx->panel);
4802a05177dSHoegeun Kwon 	if (ret < 0)
4812a05177dSHoegeun Kwon 		goto unregister_backlight;
4822a05177dSHoegeun Kwon 
4832a05177dSHoegeun Kwon 	ret = mipi_dsi_attach(dsi);
4842a05177dSHoegeun Kwon 	if (ret < 0)
4852a05177dSHoegeun Kwon 		goto remove_panel;
4862a05177dSHoegeun Kwon 
4872a05177dSHoegeun Kwon 	return ret;
4882a05177dSHoegeun Kwon 
4892a05177dSHoegeun Kwon remove_panel:
4902a05177dSHoegeun Kwon 	drm_panel_remove(&ctx->panel);
4912a05177dSHoegeun Kwon 
4922a05177dSHoegeun Kwon unregister_backlight:
4932a05177dSHoegeun Kwon 	backlight_device_unregister(ctx->bl_dev);
4942a05177dSHoegeun Kwon 
4952a05177dSHoegeun Kwon 	return ret;
4962a05177dSHoegeun Kwon }
4972a05177dSHoegeun Kwon 
4982a05177dSHoegeun Kwon static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
4992a05177dSHoegeun Kwon {
5002a05177dSHoegeun Kwon 	struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
5012a05177dSHoegeun Kwon 
5022a05177dSHoegeun Kwon 	mipi_dsi_detach(dsi);
5032a05177dSHoegeun Kwon 	drm_panel_remove(&ctx->panel);
5042a05177dSHoegeun Kwon 
5052a05177dSHoegeun Kwon 	backlight_device_unregister(ctx->bl_dev);
5062a05177dSHoegeun Kwon 
5072a05177dSHoegeun Kwon 	return 0;
5082a05177dSHoegeun Kwon }
5092a05177dSHoegeun Kwon 
5102a05177dSHoegeun Kwon static const struct of_device_id s6e63j0x03_of_match[] = {
5112a05177dSHoegeun Kwon 	{ .compatible = "samsung,s6e63j0x03" },
5122a05177dSHoegeun Kwon 	{ }
5132a05177dSHoegeun Kwon };
5142a05177dSHoegeun Kwon MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
5152a05177dSHoegeun Kwon 
5162a05177dSHoegeun Kwon static struct mipi_dsi_driver s6e63j0x03_driver = {
5172a05177dSHoegeun Kwon 	.probe = s6e63j0x03_probe,
5182a05177dSHoegeun Kwon 	.remove = s6e63j0x03_remove,
5192a05177dSHoegeun Kwon 	.driver = {
5202a05177dSHoegeun Kwon 		.name = "panel_samsung_s6e63j0x03",
5212a05177dSHoegeun Kwon 		.of_match_table = s6e63j0x03_of_match,
5222a05177dSHoegeun Kwon 	},
5232a05177dSHoegeun Kwon };
5242a05177dSHoegeun Kwon module_mipi_dsi_driver(s6e63j0x03_driver);
5252a05177dSHoegeun Kwon 
5262a05177dSHoegeun Kwon MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
5272a05177dSHoegeun Kwon MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
5282a05177dSHoegeun Kwon MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
5292a05177dSHoegeun Kwon MODULE_LICENSE("GPL v2");
530