1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver. 4 * 5 * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com> 6 */ 7 8 #include <linux/backlight.h> 9 #include <linux/delay.h> 10 #include <linux/gpio/consumer.h> 11 #include <linux/module.h> 12 #include <linux/regulator/consumer.h> 13 #include <linux/of.h> 14 15 #include <video/mipi_display.h> 16 #include <drm/drm_mipi_dsi.h> 17 #include <drm/drm_modes.h> 18 #include <drm/drm_panel.h> 19 20 /* Manufacturer command set */ 21 #define MCS_BL_CTL 0xc3 22 #define MCS_OTP_RELOAD 0xd0 23 #define MCS_PASSWD1 0xf0 24 #define MCS_PASSWD2 0xf1 25 #define MCS_PASSWD3 0xfc 26 27 struct s6d7aa0 { 28 struct drm_panel panel; 29 struct mipi_dsi_device *dsi; 30 struct gpio_desc *reset_gpio; 31 struct regulator_bulk_data supplies[2]; 32 const struct s6d7aa0_panel_desc *desc; 33 }; 34 35 struct s6d7aa0_panel_desc { 36 unsigned int panel_type; 37 void (*init_func)(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx); 38 void (*off_func)(struct mipi_dsi_multi_context *dsi_ctx); 39 const struct drm_display_mode *drm_mode; 40 unsigned long mode_flags; 41 u32 bus_flags; 42 bool has_backlight; 43 bool use_passwd3; 44 }; 45 46 enum s6d7aa0_panels { 47 S6D7AA0_PANEL_LSL080AL02, 48 S6D7AA0_PANEL_LSL080AL03, 49 S6D7AA0_PANEL_LTL101AT01, 50 }; 51 52 static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel) 53 { 54 return container_of(panel, struct s6d7aa0, panel); 55 } 56 57 static void s6d7aa0_reset(struct s6d7aa0 *ctx) 58 { 59 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 60 msleep(50); 61 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 62 msleep(50); 63 } 64 65 static void s6d7aa0_lock(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx, bool lock) 66 { 67 if (lock) { 68 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); 69 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0xa5, 0xa5); 70 if (ctx->desc->use_passwd3) 71 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0x5a, 0x5a); 72 } else { 73 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); 74 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0x5a, 0x5a); 75 if (ctx->desc->use_passwd3) 76 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0xa5, 0xa5); 77 } 78 } 79 80 static int s6d7aa0_on(struct s6d7aa0 *ctx) 81 { 82 struct mipi_dsi_device *dsi = ctx->dsi; 83 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 84 85 ctx->desc->init_func(ctx, &dsi_ctx); 86 87 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 88 89 return dsi_ctx.accum_err; 90 } 91 92 static void s6d7aa0_off(struct s6d7aa0 *ctx) 93 { 94 struct mipi_dsi_device *dsi = ctx->dsi; 95 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 96 97 ctx->desc->off_func(&dsi_ctx); 98 99 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 100 mipi_dsi_msleep(&dsi_ctx, 64); 101 102 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 103 104 mipi_dsi_msleep(&dsi_ctx, 120); 105 } 106 107 static int s6d7aa0_prepare(struct drm_panel *panel) 108 { 109 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 110 int ret; 111 112 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 113 if (ret < 0) 114 return ret; 115 116 s6d7aa0_reset(ctx); 117 118 ret = s6d7aa0_on(ctx); 119 if (ret < 0) { 120 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 121 return ret; 122 } 123 124 return 0; 125 } 126 127 static int s6d7aa0_disable(struct drm_panel *panel) 128 { 129 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 130 131 s6d7aa0_off(ctx); 132 133 return 0; 134 } 135 136 static int s6d7aa0_unprepare(struct drm_panel *panel) 137 { 138 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 139 140 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 141 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 142 143 return 0; 144 } 145 146 /* Backlight control code */ 147 148 static int s6d7aa0_bl_update_status(struct backlight_device *bl) 149 { 150 struct mipi_dsi_device *dsi = bl_get_data(bl); 151 u16 brightness = backlight_get_brightness(bl); 152 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 153 154 mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, brightness); 155 156 return dsi_ctx.accum_err; 157 } 158 159 static int s6d7aa0_bl_get_brightness(struct backlight_device *bl) 160 { 161 struct mipi_dsi_device *dsi = bl_get_data(bl); 162 u16 brightness; 163 int ret; 164 165 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); 166 if (ret < 0) 167 return ret; 168 169 return brightness & 0xff; 170 } 171 172 static const struct backlight_ops s6d7aa0_bl_ops = { 173 .update_status = s6d7aa0_bl_update_status, 174 .get_brightness = s6d7aa0_bl_get_brightness, 175 }; 176 177 static struct backlight_device * 178 s6d7aa0_create_backlight(struct mipi_dsi_device *dsi) 179 { 180 struct device *dev = &dsi->dev; 181 const struct backlight_properties props = { 182 .type = BACKLIGHT_RAW, 183 .brightness = 255, 184 .max_brightness = 255, 185 }; 186 187 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 188 &s6d7aa0_bl_ops, &props); 189 } 190 191 /* Initialization code and structures for LSL080AL02 panel */ 192 193 static void s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) 194 { 195 mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); 196 197 s6d7aa0_lock(ctx, dsi_ctx, false); 198 199 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_OTP_RELOAD, 0x00, 0x10); 200 mipi_dsi_usleep_range(dsi_ctx, 1000, 1500); 201 202 /* SEQ_B6_PARAM_8_R01 */ 203 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb6, 0x10); 204 205 /* BL_CTL_ON */ 206 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x28); 207 208 mipi_dsi_usleep_range(dsi_ctx, 5000, 6000); 209 210 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x04); 211 212 mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); 213 214 mipi_dsi_msleep(dsi_ctx, 120); 215 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); 216 217 s6d7aa0_lock(ctx, dsi_ctx, true); 218 219 mipi_dsi_dcs_set_display_on_multi(dsi_ctx); 220 } 221 222 static void s6d7aa0_lsl080al02_off(struct mipi_dsi_multi_context *dsi_ctx) 223 { 224 /* BL_CTL_OFF */ 225 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x20); 226 } 227 228 static const struct drm_display_mode s6d7aa0_lsl080al02_mode = { 229 .clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000, 230 .hdisplay = 800, 231 .hsync_start = 800 + 16, 232 .hsync_end = 800 + 16 + 4, 233 .htotal = 800 + 16 + 4 + 140, 234 .vdisplay = 1280, 235 .vsync_start = 1280 + 8, 236 .vsync_end = 1280 + 8 + 4, 237 .vtotal = 1280 + 8 + 4 + 4, 238 .width_mm = 108, 239 .height_mm = 173, 240 }; 241 242 static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = { 243 .panel_type = S6D7AA0_PANEL_LSL080AL02, 244 .init_func = s6d7aa0_lsl080al02_init, 245 .off_func = s6d7aa0_lsl080al02_off, 246 .drm_mode = &s6d7aa0_lsl080al02_mode, 247 .mode_flags = MIPI_DSI_MODE_VIDEO_NO_HFP, 248 .bus_flags = 0, 249 250 .has_backlight = false, 251 .use_passwd3 = false, 252 }; 253 254 /* Initialization code and structures for LSL080AL03 panel */ 255 256 static void s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) 257 { 258 mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); 259 260 s6d7aa0_lock(ctx, dsi_ctx, false); 261 262 if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) { 263 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0xc7, 0x00, 0x29); 264 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0xa0); 265 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, 266 0x09); 267 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, 268 0x80, 0x78); 269 } else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) { 270 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x08); 271 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0x0b); 272 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, 273 0x09); 274 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, 275 0x80, 0x68); 276 } 277 278 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x51); 279 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); 280 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf2, 0x02, 0x08, 0x08); 281 282 mipi_dsi_usleep_range(dsi_ctx, 10000, 11000); 283 284 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x80, 0x80, 0x30); 285 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, 286 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 287 0x2e, 0x2e, 0x2e, 0x2e, 0x2e); 288 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xce, 289 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 290 0x00, 0x00, 0x00, 0x00, 0x00); 291 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x03); 292 293 mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); 294 s6d7aa0_lock(ctx, dsi_ctx, true); 295 mipi_dsi_dcs_set_display_on_multi(dsi_ctx); 296 } 297 298 static void s6d7aa0_lsl080al03_off(struct mipi_dsi_multi_context *dsi_ctx) 299 { 300 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x22, 0x00); 301 } 302 303 static const struct drm_display_mode s6d7aa0_lsl080al03_mode = { 304 .clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000, 305 .hdisplay = 768, 306 .hsync_start = 768 + 18, 307 .hsync_end = 768 + 18 + 16, 308 .htotal = 768 + 18 + 16 + 126, 309 .vdisplay = 1024, 310 .vsync_start = 1024 + 8, 311 .vsync_end = 1024 + 8 + 2, 312 .vtotal = 1024 + 8 + 2 + 6, 313 .width_mm = 122, 314 .height_mm = 163, 315 }; 316 317 static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = { 318 .panel_type = S6D7AA0_PANEL_LSL080AL03, 319 .init_func = s6d7aa0_lsl080al03_init, 320 .off_func = s6d7aa0_lsl080al03_off, 321 .drm_mode = &s6d7aa0_lsl080al03_mode, 322 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, 323 .bus_flags = 0, 324 325 .has_backlight = true, 326 .use_passwd3 = true, 327 }; 328 329 /* Initialization structures for LTL101AT01 panel */ 330 331 static const struct drm_display_mode s6d7aa0_ltl101at01_mode = { 332 .clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000, 333 .hdisplay = 768, 334 .hsync_start = 768 + 96, 335 .hsync_end = 768 + 96 + 16, 336 .htotal = 768 + 96 + 16 + 184, 337 .vdisplay = 1024, 338 .vsync_start = 1024 + 8, 339 .vsync_end = 1024 + 8 + 2, 340 .vtotal = 1024 + 8 + 2 + 6, 341 .width_mm = 148, 342 .height_mm = 197, 343 }; 344 345 static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = { 346 .panel_type = S6D7AA0_PANEL_LTL101AT01, 347 .init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */ 348 .off_func = s6d7aa0_lsl080al03_off, 349 .drm_mode = &s6d7aa0_ltl101at01_mode, 350 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, 351 .bus_flags = 0, 352 353 .has_backlight = true, 354 .use_passwd3 = true, 355 }; 356 357 static int s6d7aa0_get_modes(struct drm_panel *panel, 358 struct drm_connector *connector) 359 { 360 struct drm_display_mode *mode; 361 struct s6d7aa0 *ctx; 362 363 ctx = container_of(panel, struct s6d7aa0, panel); 364 if (!ctx) 365 return -EINVAL; 366 367 mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode); 368 if (!mode) 369 return -ENOMEM; 370 371 drm_mode_set_name(mode); 372 373 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 374 connector->display_info.width_mm = mode->width_mm; 375 connector->display_info.height_mm = mode->height_mm; 376 connector->display_info.bus_flags = ctx->desc->bus_flags; 377 drm_mode_probed_add(connector, mode); 378 379 return 1; 380 } 381 382 static const struct drm_panel_funcs s6d7aa0_panel_funcs = { 383 .disable = s6d7aa0_disable, 384 .prepare = s6d7aa0_prepare, 385 .unprepare = s6d7aa0_unprepare, 386 .get_modes = s6d7aa0_get_modes, 387 }; 388 389 static int s6d7aa0_probe(struct mipi_dsi_device *dsi) 390 { 391 struct device *dev = &dsi->dev; 392 struct s6d7aa0 *ctx; 393 int ret; 394 395 ctx = devm_drm_panel_alloc(dev, struct s6d7aa0, panel, 396 &s6d7aa0_panel_funcs, 397 DRM_MODE_CONNECTOR_DSI); 398 if (IS_ERR(ctx)) 399 return PTR_ERR(ctx); 400 401 ctx->desc = of_device_get_match_data(dev); 402 if (!ctx->desc) 403 return -ENODEV; 404 405 ctx->supplies[0].supply = "power"; 406 ctx->supplies[1].supply = "vmipi"; 407 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), 408 ctx->supplies); 409 if (ret < 0) 410 return dev_err_probe(dev, ret, "Failed to get regulators\n"); 411 412 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 413 if (IS_ERR(ctx->reset_gpio)) 414 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 415 "Failed to get reset-gpios\n"); 416 417 ctx->dsi = dsi; 418 mipi_dsi_set_drvdata(dsi, ctx); 419 420 dsi->lanes = 4; 421 dsi->format = MIPI_DSI_FMT_RGB888; 422 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST 423 | ctx->desc->mode_flags; 424 425 ctx->panel.prepare_prev_first = true; 426 427 ret = drm_panel_of_backlight(&ctx->panel); 428 if (ret) 429 return dev_err_probe(dev, ret, "Failed to get backlight\n"); 430 431 /* Use DSI-based backlight as fallback if available */ 432 if (ctx->desc->has_backlight && !ctx->panel.backlight) { 433 ctx->panel.backlight = s6d7aa0_create_backlight(dsi); 434 if (IS_ERR(ctx->panel.backlight)) 435 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 436 "Failed to create backlight\n"); 437 } 438 439 drm_panel_add(&ctx->panel); 440 441 ret = mipi_dsi_attach(dsi); 442 if (ret < 0) { 443 dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 444 drm_panel_remove(&ctx->panel); 445 return ret; 446 } 447 448 return 0; 449 } 450 451 static void s6d7aa0_remove(struct mipi_dsi_device *dsi) 452 { 453 struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi); 454 int ret; 455 456 ret = mipi_dsi_detach(dsi); 457 if (ret < 0) 458 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 459 460 drm_panel_remove(&ctx->panel); 461 } 462 463 static const struct of_device_id s6d7aa0_of_match[] = { 464 { 465 .compatible = "samsung,lsl080al02", 466 .data = &s6d7aa0_lsl080al02_desc 467 }, 468 { 469 .compatible = "samsung,lsl080al03", 470 .data = &s6d7aa0_lsl080al03_desc 471 }, 472 { 473 .compatible = "samsung,ltl101at01", 474 .data = &s6d7aa0_ltl101at01_desc 475 }, 476 { /* sentinel */ } 477 }; 478 MODULE_DEVICE_TABLE(of, s6d7aa0_of_match); 479 480 static struct mipi_dsi_driver s6d7aa0_driver = { 481 .probe = s6d7aa0_probe, 482 .remove = s6d7aa0_remove, 483 .driver = { 484 .name = "panel-samsung-s6d7aa0", 485 .of_match_table = s6d7aa0_of_match, 486 }, 487 }; 488 module_mipi_dsi_driver(s6d7aa0_driver); 489 490 MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>"); 491 MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver"); 492 MODULE_LICENSE("GPL"); 493