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