16810bb39SArtur Weber // SPDX-License-Identifier: GPL-2.0
26810bb39SArtur Weber /*
36810bb39SArtur Weber  * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver.
46810bb39SArtur Weber  *
56810bb39SArtur Weber  * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com>
66810bb39SArtur Weber  */
76810bb39SArtur Weber 
86810bb39SArtur Weber #include <linux/backlight.h>
96810bb39SArtur Weber #include <linux/delay.h>
106810bb39SArtur Weber #include <linux/gpio/consumer.h>
116810bb39SArtur Weber #include <linux/module.h>
126810bb39SArtur Weber #include <linux/regulator/consumer.h>
136810bb39SArtur Weber #include <linux/of.h>
146810bb39SArtur Weber 
156810bb39SArtur Weber #include <video/mipi_display.h>
166810bb39SArtur Weber #include <drm/drm_mipi_dsi.h>
176810bb39SArtur Weber #include <drm/drm_modes.h>
186810bb39SArtur Weber #include <drm/drm_panel.h>
196810bb39SArtur Weber 
206810bb39SArtur Weber /* Manufacturer command set */
216810bb39SArtur Weber #define MCS_BL_CTL		0xc3
226810bb39SArtur Weber #define MCS_OTP_RELOAD		0xd0
236810bb39SArtur Weber #define MCS_PASSWD1		0xf0
246810bb39SArtur Weber #define MCS_PASSWD2		0xf1
256810bb39SArtur Weber #define MCS_PASSWD3		0xfc
266810bb39SArtur Weber 
276810bb39SArtur Weber struct s6d7aa0 {
286810bb39SArtur Weber 	struct drm_panel panel;
296810bb39SArtur Weber 	struct mipi_dsi_device *dsi;
306810bb39SArtur Weber 	struct gpio_desc *reset_gpio;
316810bb39SArtur Weber 	struct regulator_bulk_data supplies[2];
326810bb39SArtur Weber 	const struct s6d7aa0_panel_desc *desc;
336810bb39SArtur Weber };
346810bb39SArtur Weber 
356810bb39SArtur Weber struct s6d7aa0_panel_desc {
366810bb39SArtur Weber 	unsigned int panel_type;
376810bb39SArtur Weber 	int (*init_func)(struct s6d7aa0 *ctx);
386810bb39SArtur Weber 	int (*off_func)(struct s6d7aa0 *ctx);
396a038f01SArtur Weber 	const struct drm_display_mode *drm_mode;
406810bb39SArtur Weber 	unsigned long mode_flags;
416810bb39SArtur Weber 	u32 bus_flags;
426810bb39SArtur Weber 	bool has_backlight;
436810bb39SArtur Weber 	bool use_passwd3;
446810bb39SArtur Weber };
456810bb39SArtur Weber 
466810bb39SArtur Weber enum s6d7aa0_panels {
476810bb39SArtur Weber 	S6D7AA0_PANEL_LSL080AL02,
486810bb39SArtur Weber 	S6D7AA0_PANEL_LSL080AL03,
496810bb39SArtur Weber 	S6D7AA0_PANEL_LTL101AT01,
506810bb39SArtur Weber };
516810bb39SArtur Weber 
panel_to_s6d7aa0(struct drm_panel * panel)526810bb39SArtur Weber static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel)
536810bb39SArtur Weber {
546810bb39SArtur Weber 	return container_of(panel, struct s6d7aa0, panel);
556810bb39SArtur Weber }
566810bb39SArtur Weber 
s6d7aa0_reset(struct s6d7aa0 * ctx)576810bb39SArtur Weber static void s6d7aa0_reset(struct s6d7aa0 *ctx)
586810bb39SArtur Weber {
596810bb39SArtur Weber 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
606810bb39SArtur Weber 	msleep(50);
616810bb39SArtur Weber 	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
626810bb39SArtur Weber 	msleep(50);
636810bb39SArtur Weber }
646810bb39SArtur Weber 
s6d7aa0_lock(struct s6d7aa0 * ctx,bool lock)656810bb39SArtur Weber static int s6d7aa0_lock(struct s6d7aa0 *ctx, bool lock)
666810bb39SArtur Weber {
676810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
686810bb39SArtur Weber 
696810bb39SArtur Weber 	if (lock) {
706810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0xa5, 0xa5);
716810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0xa5, 0xa5);
726810bb39SArtur Weber 		if (ctx->desc->use_passwd3)
736810bb39SArtur Weber 			mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0x5a, 0x5a);
746810bb39SArtur Weber 	} else {
756810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0x5a, 0x5a);
766810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0x5a, 0x5a);
776810bb39SArtur Weber 		if (ctx->desc->use_passwd3)
786810bb39SArtur Weber 			mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0xa5, 0xa5);
796810bb39SArtur Weber 	}
806810bb39SArtur Weber 
81c5dacfe2SWang Jianzheng 	return 0;
826810bb39SArtur Weber }
836810bb39SArtur Weber 
s6d7aa0_on(struct s6d7aa0 * ctx)846810bb39SArtur Weber static int s6d7aa0_on(struct s6d7aa0 *ctx)
856810bb39SArtur Weber {
866810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
876810bb39SArtur Weber 	struct device *dev = &dsi->dev;
886810bb39SArtur Weber 	int ret;
896810bb39SArtur Weber 
906810bb39SArtur Weber 	ret = ctx->desc->init_func(ctx);
916810bb39SArtur Weber 	if (ret < 0) {
926810bb39SArtur Weber 		dev_err(dev, "Failed to initialize panel: %d\n", ret);
936810bb39SArtur Weber 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
946810bb39SArtur Weber 		return ret;
956810bb39SArtur Weber 	}
966810bb39SArtur Weber 
976810bb39SArtur Weber 	ret = mipi_dsi_dcs_set_display_on(dsi);
986810bb39SArtur Weber 	if (ret < 0) {
996810bb39SArtur Weber 		dev_err(dev, "Failed to set display on: %d\n", ret);
1006810bb39SArtur Weber 		return ret;
1016810bb39SArtur Weber 	}
1026810bb39SArtur Weber 
1036810bb39SArtur Weber 	return 0;
1046810bb39SArtur Weber }
1056810bb39SArtur Weber 
s6d7aa0_off(struct s6d7aa0 * ctx)1066810bb39SArtur Weber static int s6d7aa0_off(struct s6d7aa0 *ctx)
1076810bb39SArtur Weber {
1086810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
1096810bb39SArtur Weber 	struct device *dev = &dsi->dev;
1106810bb39SArtur Weber 	int ret;
1116810bb39SArtur Weber 
1126810bb39SArtur Weber 	ret = ctx->desc->off_func(ctx);
1136810bb39SArtur Weber 	if (ret < 0) {
1146810bb39SArtur Weber 		dev_err(dev, "Panel-specific off function failed: %d\n", ret);
1156810bb39SArtur Weber 		return ret;
1166810bb39SArtur Weber 	}
1176810bb39SArtur Weber 
1186810bb39SArtur Weber 	ret = mipi_dsi_dcs_set_display_off(dsi);
1196810bb39SArtur Weber 	if (ret < 0) {
1206810bb39SArtur Weber 		dev_err(dev, "Failed to set display off: %d\n", ret);
1216810bb39SArtur Weber 		return ret;
1226810bb39SArtur Weber 	}
1236810bb39SArtur Weber 	msleep(64);
1246810bb39SArtur Weber 
1256810bb39SArtur Weber 	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
1266810bb39SArtur Weber 	if (ret < 0) {
1276810bb39SArtur Weber 		dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
1286810bb39SArtur Weber 		return ret;
1296810bb39SArtur Weber 	}
1306810bb39SArtur Weber 	msleep(120);
1316810bb39SArtur Weber 
1326810bb39SArtur Weber 	return 0;
1336810bb39SArtur Weber }
1346810bb39SArtur Weber 
s6d7aa0_prepare(struct drm_panel * panel)1356810bb39SArtur Weber static int s6d7aa0_prepare(struct drm_panel *panel)
1366810bb39SArtur Weber {
1376810bb39SArtur Weber 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
1386810bb39SArtur Weber 	struct device *dev = &ctx->dsi->dev;
1396810bb39SArtur Weber 	int ret;
1406810bb39SArtur Weber 
1416810bb39SArtur Weber 	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
1426810bb39SArtur Weber 	if (ret < 0) {
1436810bb39SArtur Weber 		dev_err(dev, "Failed to enable regulators: %d\n", ret);
1446810bb39SArtur Weber 		return ret;
1456810bb39SArtur Weber 	}
1466810bb39SArtur Weber 
1476810bb39SArtur Weber 	s6d7aa0_reset(ctx);
1486810bb39SArtur Weber 
1496810bb39SArtur Weber 	ret = s6d7aa0_on(ctx);
1506810bb39SArtur Weber 	if (ret < 0) {
1516810bb39SArtur Weber 		dev_err(dev, "Failed to initialize panel: %d\n", ret);
1526810bb39SArtur Weber 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
1536810bb39SArtur Weber 		return ret;
1546810bb39SArtur Weber 	}
1556810bb39SArtur Weber 
1566810bb39SArtur Weber 	return 0;
1576810bb39SArtur Weber }
1586810bb39SArtur Weber 
s6d7aa0_disable(struct drm_panel * panel)1596810bb39SArtur Weber static int s6d7aa0_disable(struct drm_panel *panel)
1606810bb39SArtur Weber {
1616810bb39SArtur Weber 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
1626810bb39SArtur Weber 	struct device *dev = &ctx->dsi->dev;
1636810bb39SArtur Weber 	int ret;
1646810bb39SArtur Weber 
1656810bb39SArtur Weber 	ret = s6d7aa0_off(ctx);
1666810bb39SArtur Weber 	if (ret < 0)
1676810bb39SArtur Weber 		dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
1686810bb39SArtur Weber 
1696810bb39SArtur Weber 	return 0;
1706810bb39SArtur Weber }
1716810bb39SArtur Weber 
s6d7aa0_unprepare(struct drm_panel * panel)1726810bb39SArtur Weber static int s6d7aa0_unprepare(struct drm_panel *panel)
1736810bb39SArtur Weber {
1746810bb39SArtur Weber 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
1756810bb39SArtur Weber 
1766810bb39SArtur Weber 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
1776810bb39SArtur Weber 	regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
1786810bb39SArtur Weber 
1796810bb39SArtur Weber 	return 0;
1806810bb39SArtur Weber }
1816810bb39SArtur Weber 
1826810bb39SArtur Weber /* Backlight control code */
1836810bb39SArtur Weber 
s6d7aa0_bl_update_status(struct backlight_device * bl)1846810bb39SArtur Weber static int s6d7aa0_bl_update_status(struct backlight_device *bl)
1856810bb39SArtur Weber {
1866810bb39SArtur Weber 	struct mipi_dsi_device *dsi = bl_get_data(bl);
1876810bb39SArtur Weber 	u16 brightness = backlight_get_brightness(bl);
1886810bb39SArtur Weber 	int ret;
1896810bb39SArtur Weber 
1906810bb39SArtur Weber 	ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);
1916810bb39SArtur Weber 	if (ret < 0)
1926810bb39SArtur Weber 		return ret;
1936810bb39SArtur Weber 
1946810bb39SArtur Weber 	return 0;
1956810bb39SArtur Weber }
1966810bb39SArtur Weber 
s6d7aa0_bl_get_brightness(struct backlight_device * bl)1976810bb39SArtur Weber static int s6d7aa0_bl_get_brightness(struct backlight_device *bl)
1986810bb39SArtur Weber {
1996810bb39SArtur Weber 	struct mipi_dsi_device *dsi = bl_get_data(bl);
2006810bb39SArtur Weber 	u16 brightness;
2016810bb39SArtur Weber 	int ret;
2026810bb39SArtur Weber 
2036810bb39SArtur Weber 	ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);
2046810bb39SArtur Weber 	if (ret < 0)
2056810bb39SArtur Weber 		return ret;
2066810bb39SArtur Weber 
2076810bb39SArtur Weber 	return brightness & 0xff;
2086810bb39SArtur Weber }
2096810bb39SArtur Weber 
2106810bb39SArtur Weber static const struct backlight_ops s6d7aa0_bl_ops = {
2116810bb39SArtur Weber 	.update_status = s6d7aa0_bl_update_status,
2126810bb39SArtur Weber 	.get_brightness = s6d7aa0_bl_get_brightness,
2136810bb39SArtur Weber };
2146810bb39SArtur Weber 
2156810bb39SArtur Weber static struct backlight_device *
s6d7aa0_create_backlight(struct mipi_dsi_device * dsi)2166810bb39SArtur Weber s6d7aa0_create_backlight(struct mipi_dsi_device *dsi)
2176810bb39SArtur Weber {
2186810bb39SArtur Weber 	struct device *dev = &dsi->dev;
2196810bb39SArtur Weber 	const struct backlight_properties props = {
2206810bb39SArtur Weber 		.type = BACKLIGHT_RAW,
2216810bb39SArtur Weber 		.brightness = 255,
2226810bb39SArtur Weber 		.max_brightness = 255,
2236810bb39SArtur Weber 	};
2246810bb39SArtur Weber 
2256810bb39SArtur Weber 	return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
2266810bb39SArtur Weber 					      &s6d7aa0_bl_ops, &props);
2276810bb39SArtur Weber }
2286810bb39SArtur Weber 
2296810bb39SArtur Weber /* Initialization code and structures for LSL080AL02 panel */
2306810bb39SArtur Weber 
s6d7aa0_lsl080al02_init(struct s6d7aa0 * ctx)2316810bb39SArtur Weber static int s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx)
2326810bb39SArtur Weber {
2336810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
2346810bb39SArtur Weber 	struct device *dev = &dsi->dev;
2356810bb39SArtur Weber 	int ret;
2366810bb39SArtur Weber 
2376810bb39SArtur Weber 	usleep_range(20000, 25000);
2386810bb39SArtur Weber 
2396810bb39SArtur Weber 	ret = s6d7aa0_lock(ctx, false);
2406810bb39SArtur Weber 	if (ret < 0) {
2416810bb39SArtur Weber 		dev_err(dev, "Failed to unlock registers: %d\n", ret);
2426810bb39SArtur Weber 		return ret;
2436810bb39SArtur Weber 	}
2446810bb39SArtur Weber 
2456810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MCS_OTP_RELOAD, 0x00, 0x10);
2466810bb39SArtur Weber 	usleep_range(1000, 1500);
2476810bb39SArtur Weber 
2486810bb39SArtur Weber 	/* SEQ_B6_PARAM_8_R01 */
2496810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xb6, 0x10);
2506810bb39SArtur Weber 
2516810bb39SArtur Weber 	/* BL_CTL_ON */
2526810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x28);
2536810bb39SArtur Weber 
2546810bb39SArtur Weber 	usleep_range(5000, 6000);
2556810bb39SArtur Weber 
2566810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x04);
2576810bb39SArtur Weber 
2586810bb39SArtur Weber 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
2596810bb39SArtur Weber 	if (ret < 0) {
2606810bb39SArtur Weber 		dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
2616810bb39SArtur Weber 		return ret;
2626810bb39SArtur Weber 	}
2636810bb39SArtur Weber 
2646810bb39SArtur Weber 	msleep(120);
2656810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
2666810bb39SArtur Weber 
2676810bb39SArtur Weber 	ret = s6d7aa0_lock(ctx, true);
2686810bb39SArtur Weber 	if (ret < 0) {
2696810bb39SArtur Weber 		dev_err(dev, "Failed to lock registers: %d\n", ret);
2706810bb39SArtur Weber 		return ret;
2716810bb39SArtur Weber 	}
2726810bb39SArtur Weber 
2736810bb39SArtur Weber 	ret = mipi_dsi_dcs_set_display_on(dsi);
2746810bb39SArtur Weber 	if (ret < 0) {
2756810bb39SArtur Weber 		dev_err(dev, "Failed to set display on: %d\n", ret);
2766810bb39SArtur Weber 		return ret;
2776810bb39SArtur Weber 	}
2786810bb39SArtur Weber 
2796810bb39SArtur Weber 	return 0;
2806810bb39SArtur Weber }
2816810bb39SArtur Weber 
s6d7aa0_lsl080al02_off(struct s6d7aa0 * ctx)2826810bb39SArtur Weber static int s6d7aa0_lsl080al02_off(struct s6d7aa0 *ctx)
2836810bb39SArtur Weber {
2846810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
2856810bb39SArtur Weber 
2866810bb39SArtur Weber 	/* BL_CTL_OFF */
2876810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x20);
2886810bb39SArtur Weber 
2896810bb39SArtur Weber 	return 0;
2906810bb39SArtur Weber }
2916810bb39SArtur Weber 
2926810bb39SArtur Weber static const struct drm_display_mode s6d7aa0_lsl080al02_mode = {
2936810bb39SArtur Weber 	.clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000,
2946810bb39SArtur Weber 	.hdisplay = 800,
2956810bb39SArtur Weber 	.hsync_start = 800 + 16,
2966810bb39SArtur Weber 	.hsync_end = 800 + 16 + 4,
2976810bb39SArtur Weber 	.htotal = 800 + 16 + 4 + 140,
2986810bb39SArtur Weber 	.vdisplay = 1280,
2996810bb39SArtur Weber 	.vsync_start = 1280 + 8,
3006810bb39SArtur Weber 	.vsync_end = 1280 + 8 + 4,
3016810bb39SArtur Weber 	.vtotal = 1280 + 8 + 4 + 4,
3026810bb39SArtur Weber 	.width_mm = 108,
3036810bb39SArtur Weber 	.height_mm = 173,
3046810bb39SArtur Weber };
3056810bb39SArtur Weber 
3066810bb39SArtur Weber static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = {
3076810bb39SArtur Weber 	.panel_type = S6D7AA0_PANEL_LSL080AL02,
3086810bb39SArtur Weber 	.init_func = s6d7aa0_lsl080al02_init,
3096810bb39SArtur Weber 	.off_func = s6d7aa0_lsl080al02_off,
3106a038f01SArtur Weber 	.drm_mode = &s6d7aa0_lsl080al02_mode,
3116810bb39SArtur Weber 	.mode_flags = MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_NO_HFP,
312*7ed0974cSArtur Weber 	.bus_flags = 0,
3136810bb39SArtur Weber 
3146810bb39SArtur Weber 	.has_backlight = false,
3156810bb39SArtur Weber 	.use_passwd3 = false,
3166810bb39SArtur Weber };
3176810bb39SArtur Weber 
3186810bb39SArtur Weber /* Initialization code and structures for LSL080AL03 panel */
3196810bb39SArtur Weber 
s6d7aa0_lsl080al03_init(struct s6d7aa0 * ctx)3206810bb39SArtur Weber static int s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx)
3216810bb39SArtur Weber {
3226810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
3236810bb39SArtur Weber 	struct device *dev = &dsi->dev;
3246810bb39SArtur Weber 	int ret;
3256810bb39SArtur Weber 
3266810bb39SArtur Weber 	usleep_range(20000, 25000);
3276810bb39SArtur Weber 
3286810bb39SArtur Weber 	ret = s6d7aa0_lock(ctx, false);
3296810bb39SArtur Weber 	if (ret < 0) {
3306810bb39SArtur Weber 		dev_err(dev, "Failed to unlock registers: %d\n", ret);
3316810bb39SArtur Weber 		return ret;
3326810bb39SArtur Weber 	}
3336810bb39SArtur Weber 
3346810bb39SArtur Weber 	if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) {
3356810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0xc7, 0x00, 0x29);
3366810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0xa0);
3376810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,
3386810bb39SArtur Weber 				       0x09);
3396810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,
3406810bb39SArtur Weber 				       0x80, 0x78);
3416810bb39SArtur Weber 	} else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) {
3426810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x08);
3436810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0x0b);
3446810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,
3456810bb39SArtur Weber 				       0x09);
3466810bb39SArtur Weber 		mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,
3476810bb39SArtur Weber 				       0x80, 0x68);
3486810bb39SArtur Weber 	}
3496810bb39SArtur Weber 
3506810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xb3, 0x51);
3516810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
3526810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x02, 0x08, 0x08);
3536810bb39SArtur Weber 
3546810bb39SArtur Weber 	usleep_range(10000, 11000);
3556810bb39SArtur Weber 
3566810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xc0, 0x80, 0x80, 0x30);
3576810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xcd,
3586810bb39SArtur Weber 			       0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
3596810bb39SArtur Weber 			       0x2e, 0x2e, 0x2e, 0x2e, 0x2e);
3606810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xce,
3616810bb39SArtur Weber 			       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3626810bb39SArtur Weber 			       0x00, 0x00, 0x00, 0x00, 0x00);
3636810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x03);
3646810bb39SArtur Weber 
3656810bb39SArtur Weber 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
3666810bb39SArtur Weber 	if (ret < 0) {
3676810bb39SArtur Weber 		dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
3686810bb39SArtur Weber 		return ret;
3696810bb39SArtur Weber 	}
3706810bb39SArtur Weber 
3716810bb39SArtur Weber 	ret = s6d7aa0_lock(ctx, true);
3726810bb39SArtur Weber 	if (ret < 0) {
3736810bb39SArtur Weber 		dev_err(dev, "Failed to lock registers: %d\n", ret);
3746810bb39SArtur Weber 		return ret;
3756810bb39SArtur Weber 	}
3766810bb39SArtur Weber 
3776810bb39SArtur Weber 	ret = mipi_dsi_dcs_set_display_on(dsi);
3786810bb39SArtur Weber 	if (ret < 0) {
3796810bb39SArtur Weber 		dev_err(dev, "Failed to set display on: %d\n", ret);
3806810bb39SArtur Weber 		return ret;
3816810bb39SArtur Weber 	}
3826810bb39SArtur Weber 
3836810bb39SArtur Weber 	return 0;
3846810bb39SArtur Weber }
3856810bb39SArtur Weber 
s6d7aa0_lsl080al03_off(struct s6d7aa0 * ctx)3866810bb39SArtur Weber static int s6d7aa0_lsl080al03_off(struct s6d7aa0 *ctx)
3876810bb39SArtur Weber {
3886810bb39SArtur Weber 	struct mipi_dsi_device *dsi = ctx->dsi;
3896810bb39SArtur Weber 
3906810bb39SArtur Weber 	mipi_dsi_dcs_write_seq(dsi, 0x22, 0x00);
3916810bb39SArtur Weber 
3926810bb39SArtur Weber 	return 0;
3936810bb39SArtur Weber }
3946810bb39SArtur Weber 
3956810bb39SArtur Weber static const struct drm_display_mode s6d7aa0_lsl080al03_mode = {
3966810bb39SArtur Weber 	.clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000,
3976810bb39SArtur Weber 	.hdisplay = 768,
3986810bb39SArtur Weber 	.hsync_start = 768 + 18,
3996810bb39SArtur Weber 	.hsync_end = 768 + 18 + 16,
4006810bb39SArtur Weber 	.htotal = 768 + 18 + 16 + 126,
4016810bb39SArtur Weber 	.vdisplay = 1024,
4026810bb39SArtur Weber 	.vsync_start = 1024 + 8,
4036810bb39SArtur Weber 	.vsync_end = 1024 + 8 + 2,
4046810bb39SArtur Weber 	.vtotal = 1024 + 8 + 2 + 6,
4056810bb39SArtur Weber 	.width_mm = 122,
4066810bb39SArtur Weber 	.height_mm = 163,
4076810bb39SArtur Weber };
4086810bb39SArtur Weber 
4096810bb39SArtur Weber static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = {
4106810bb39SArtur Weber 	.panel_type = S6D7AA0_PANEL_LSL080AL03,
4116810bb39SArtur Weber 	.init_func = s6d7aa0_lsl080al03_init,
4126810bb39SArtur Weber 	.off_func = s6d7aa0_lsl080al03_off,
4136a038f01SArtur Weber 	.drm_mode = &s6d7aa0_lsl080al03_mode,
4146810bb39SArtur Weber 	.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,
4156810bb39SArtur Weber 	.bus_flags = 0,
4166810bb39SArtur Weber 
4176810bb39SArtur Weber 	.has_backlight = true,
4186810bb39SArtur Weber 	.use_passwd3 = true,
4196810bb39SArtur Weber };
4206810bb39SArtur Weber 
4216810bb39SArtur Weber /* Initialization structures for LTL101AT01 panel */
4226810bb39SArtur Weber 
4236810bb39SArtur Weber static const struct drm_display_mode s6d7aa0_ltl101at01_mode = {
4246810bb39SArtur Weber 	.clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000,
4256810bb39SArtur Weber 	.hdisplay = 768,
4266810bb39SArtur Weber 	.hsync_start = 768 + 96,
4276810bb39SArtur Weber 	.hsync_end = 768 + 96 + 16,
4286810bb39SArtur Weber 	.htotal = 768 + 96 + 16 + 184,
4296810bb39SArtur Weber 	.vdisplay = 1024,
4306810bb39SArtur Weber 	.vsync_start = 1024 + 8,
4316810bb39SArtur Weber 	.vsync_end = 1024 + 8 + 2,
4326810bb39SArtur Weber 	.vtotal = 1024 + 8 + 2 + 6,
4336810bb39SArtur Weber 	.width_mm = 148,
4346810bb39SArtur Weber 	.height_mm = 197,
4356810bb39SArtur Weber };
4366810bb39SArtur Weber 
4376810bb39SArtur Weber static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = {
4386810bb39SArtur Weber 	.panel_type = S6D7AA0_PANEL_LTL101AT01,
4396810bb39SArtur Weber 	.init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */
4406810bb39SArtur Weber 	.off_func = s6d7aa0_lsl080al03_off,
4416a038f01SArtur Weber 	.drm_mode = &s6d7aa0_ltl101at01_mode,
4426810bb39SArtur Weber 	.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,
4436810bb39SArtur Weber 	.bus_flags = 0,
4446810bb39SArtur Weber 
4456810bb39SArtur Weber 	.has_backlight = true,
4466810bb39SArtur Weber 	.use_passwd3 = true,
4476810bb39SArtur Weber };
4486810bb39SArtur Weber 
s6d7aa0_get_modes(struct drm_panel * panel,struct drm_connector * connector)4496810bb39SArtur Weber static int s6d7aa0_get_modes(struct drm_panel *panel,
4506810bb39SArtur Weber 					struct drm_connector *connector)
4516810bb39SArtur Weber {
4526810bb39SArtur Weber 	struct drm_display_mode *mode;
4536810bb39SArtur Weber 	struct s6d7aa0 *ctx;
4546810bb39SArtur Weber 
4556810bb39SArtur Weber 	ctx = container_of(panel, struct s6d7aa0, panel);
4566810bb39SArtur Weber 	if (!ctx)
4576810bb39SArtur Weber 		return -EINVAL;
4586810bb39SArtur Weber 
4596a038f01SArtur Weber 	mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode);
4606810bb39SArtur Weber 	if (!mode)
4616810bb39SArtur Weber 		return -ENOMEM;
4626810bb39SArtur Weber 
4636810bb39SArtur Weber 	drm_mode_set_name(mode);
4646810bb39SArtur Weber 
4656810bb39SArtur Weber 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
4666810bb39SArtur Weber 	connector->display_info.width_mm = mode->width_mm;
4676810bb39SArtur Weber 	connector->display_info.height_mm = mode->height_mm;
4686810bb39SArtur Weber 	connector->display_info.bus_flags = ctx->desc->bus_flags;
4696810bb39SArtur Weber 	drm_mode_probed_add(connector, mode);
4706810bb39SArtur Weber 
4716810bb39SArtur Weber 	return 1;
4726810bb39SArtur Weber }
4736810bb39SArtur Weber 
4746810bb39SArtur Weber static const struct drm_panel_funcs s6d7aa0_panel_funcs = {
4756810bb39SArtur Weber 	.disable = s6d7aa0_disable,
4766810bb39SArtur Weber 	.prepare = s6d7aa0_prepare,
4776810bb39SArtur Weber 	.unprepare = s6d7aa0_unprepare,
4786810bb39SArtur Weber 	.get_modes = s6d7aa0_get_modes,
4796810bb39SArtur Weber };
4806810bb39SArtur Weber 
s6d7aa0_probe(struct mipi_dsi_device * dsi)4816810bb39SArtur Weber static int s6d7aa0_probe(struct mipi_dsi_device *dsi)
4826810bb39SArtur Weber {
4836810bb39SArtur Weber 	struct device *dev = &dsi->dev;
4846810bb39SArtur Weber 	struct s6d7aa0 *ctx;
4856810bb39SArtur Weber 	int ret;
4866810bb39SArtur Weber 
4876810bb39SArtur Weber 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
4886810bb39SArtur Weber 	if (!ctx)
4896810bb39SArtur Weber 		return -ENOMEM;
4906810bb39SArtur Weber 
4916810bb39SArtur Weber 	ctx->desc = of_device_get_match_data(dev);
4926810bb39SArtur Weber 	if (!ctx->desc)
4936810bb39SArtur Weber 		return -ENODEV;
4946810bb39SArtur Weber 
4956810bb39SArtur Weber 	ctx->supplies[0].supply = "power";
4966810bb39SArtur Weber 	ctx->supplies[1].supply = "vmipi";
4976810bb39SArtur Weber 	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
4986810bb39SArtur Weber 					      ctx->supplies);
4996810bb39SArtur Weber 	if (ret < 0)
5006810bb39SArtur Weber 		return dev_err_probe(dev, ret, "Failed to get regulators\n");
5016810bb39SArtur Weber 
5026810bb39SArtur Weber 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
5036810bb39SArtur Weber 	if (IS_ERR(ctx->reset_gpio))
5046810bb39SArtur Weber 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
5056810bb39SArtur Weber 				     "Failed to get reset-gpios\n");
5066810bb39SArtur Weber 
5076810bb39SArtur Weber 	ctx->dsi = dsi;
5086810bb39SArtur Weber 	mipi_dsi_set_drvdata(dsi, ctx);
5096810bb39SArtur Weber 
5106810bb39SArtur Weber 	dsi->lanes = 4;
5116810bb39SArtur Weber 	dsi->format = MIPI_DSI_FMT_RGB888;
5126810bb39SArtur Weber 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST
5136810bb39SArtur Weber 		| ctx->desc->mode_flags;
5146810bb39SArtur Weber 
5156810bb39SArtur Weber 	drm_panel_init(&ctx->panel, dev, &s6d7aa0_panel_funcs,
5166810bb39SArtur Weber 		       DRM_MODE_CONNECTOR_DSI);
5176810bb39SArtur Weber 	ctx->panel.prepare_prev_first = true;
5186810bb39SArtur Weber 
5196810bb39SArtur Weber 	ret = drm_panel_of_backlight(&ctx->panel);
5206810bb39SArtur Weber 	if (ret)
5216810bb39SArtur Weber 		return dev_err_probe(dev, ret, "Failed to get backlight\n");
5226810bb39SArtur Weber 
5236810bb39SArtur Weber 	/* Use DSI-based backlight as fallback if available */
5246810bb39SArtur Weber 	if (ctx->desc->has_backlight && !ctx->panel.backlight) {
5256810bb39SArtur Weber 		ctx->panel.backlight = s6d7aa0_create_backlight(dsi);
5266810bb39SArtur Weber 		if (IS_ERR(ctx->panel.backlight))
5276810bb39SArtur Weber 			return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
5286810bb39SArtur Weber 					     "Failed to create backlight\n");
5296810bb39SArtur Weber 	}
5306810bb39SArtur Weber 
5316810bb39SArtur Weber 	drm_panel_add(&ctx->panel);
5326810bb39SArtur Weber 
5336810bb39SArtur Weber 	ret = mipi_dsi_attach(dsi);
5346810bb39SArtur Weber 	if (ret < 0) {
5356810bb39SArtur Weber 		dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
5366810bb39SArtur Weber 		drm_panel_remove(&ctx->panel);
5376810bb39SArtur Weber 		return ret;
5386810bb39SArtur Weber 	}
5396810bb39SArtur Weber 
5406810bb39SArtur Weber 	return 0;
5416810bb39SArtur Weber }
5426810bb39SArtur Weber 
s6d7aa0_remove(struct mipi_dsi_device * dsi)5436810bb39SArtur Weber static void s6d7aa0_remove(struct mipi_dsi_device *dsi)
5446810bb39SArtur Weber {
5456810bb39SArtur Weber 	struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi);
5466810bb39SArtur Weber 	int ret;
5476810bb39SArtur Weber 
5486810bb39SArtur Weber 	ret = mipi_dsi_detach(dsi);
5496810bb39SArtur Weber 	if (ret < 0)
5506810bb39SArtur Weber 		dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
5516810bb39SArtur Weber 
5526810bb39SArtur Weber 	drm_panel_remove(&ctx->panel);
5536810bb39SArtur Weber }
5546810bb39SArtur Weber 
5556810bb39SArtur Weber static const struct of_device_id s6d7aa0_of_match[] = {
5566810bb39SArtur Weber 	{
5576810bb39SArtur Weber 		.compatible = "samsung,lsl080al02",
5586810bb39SArtur Weber 		.data = &s6d7aa0_lsl080al02_desc
5596810bb39SArtur Weber 	},
5606810bb39SArtur Weber 	{
5616810bb39SArtur Weber 		.compatible = "samsung,lsl080al03",
5626810bb39SArtur Weber 		.data = &s6d7aa0_lsl080al03_desc
5636810bb39SArtur Weber 	},
5646810bb39SArtur Weber 	{
5656810bb39SArtur Weber 		.compatible = "samsung,ltl101at01",
5666810bb39SArtur Weber 		.data = &s6d7aa0_ltl101at01_desc
5676810bb39SArtur Weber 	},
5686810bb39SArtur Weber 	{ /* sentinel */ }
5696810bb39SArtur Weber };
570c71b7aa8SNikita Travkin MODULE_DEVICE_TABLE(of, s6d7aa0_of_match);
5716810bb39SArtur Weber 
5726810bb39SArtur Weber static struct mipi_dsi_driver s6d7aa0_driver = {
5736810bb39SArtur Weber 	.probe = s6d7aa0_probe,
5746810bb39SArtur Weber 	.remove = s6d7aa0_remove,
5756810bb39SArtur Weber 	.driver = {
5766810bb39SArtur Weber 		.name = "panel-samsung-s6d7aa0",
5776810bb39SArtur Weber 		.of_match_table = s6d7aa0_of_match,
5786810bb39SArtur Weber 	},
5796810bb39SArtur Weber };
5806810bb39SArtur Weber module_mipi_dsi_driver(s6d7aa0_driver);
5816810bb39SArtur Weber 
5826810bb39SArtur Weber MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>");
5836810bb39SArtur Weber MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver");
5846810bb39SArtur Weber MODULE_LICENSE("GPL");
585