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