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