1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver. 4 * 5 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd 6 * 7 * Inki Dae <inki.dae@samsung.com> 8 * Hoegeun Kwon <hoegeun.kwon@samsung.com> 9 */ 10 11 #include <linux/backlight.h> 12 #include <linux/delay.h> 13 #include <linux/gpio/consumer.h> 14 #include <linux/module.h> 15 #include <linux/regulator/consumer.h> 16 17 #include <video/mipi_display.h> 18 19 #include <drm/drm_mipi_dsi.h> 20 #include <drm/drm_modes.h> 21 #include <drm/drm_panel.h> 22 #include <drm/drm_print.h> 23 24 #define MCS_LEVEL2_KEY 0xf0 25 #define MCS_MTP_KEY 0xf1 26 #define MCS_MTP_SET3 0xd4 27 28 #define MAX_BRIGHTNESS 100 29 #define DEFAULT_BRIGHTNESS 80 30 31 #define NUM_GAMMA_STEPS 9 32 #define GAMMA_CMD_CNT 28 33 34 #define FIRST_COLUMN 20 35 36 struct s6e63j0x03 { 37 struct device *dev; 38 struct drm_panel panel; 39 struct backlight_device *bl_dev; 40 41 struct regulator_bulk_data supplies[2]; 42 struct gpio_desc *reset_gpio; 43 }; 44 45 static const struct drm_display_mode default_mode = { 46 .clock = 4649, 47 .hdisplay = 320, 48 .hsync_start = 320 + 1, 49 .hsync_end = 320 + 1 + 1, 50 .htotal = 320 + 1 + 1 + 1, 51 .vdisplay = 320, 52 .vsync_start = 320 + 150, 53 .vsync_end = 320 + 150 + 1, 54 .vtotal = 320 + 150 + 1 + 2, 55 .vrefresh = 30, 56 .flags = 0, 57 }; 58 59 static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = { 60 { /* Gamma 10 */ 61 MCS_MTP_SET3, 62 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26, 63 0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36, 64 0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf 65 }, 66 { /* gamma 30 */ 67 MCS_MTP_SET3, 68 0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26, 69 0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34, 70 0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc 71 }, 72 { /* gamma 60 */ 73 MCS_MTP_SET3, 74 0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a, 75 0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33, 76 0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5 77 }, 78 { /* gamma 90 */ 79 MCS_MTP_SET3, 80 0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29, 81 0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31, 82 0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09 83 }, 84 { /* gamma 120 */ 85 MCS_MTP_SET3, 86 0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28, 87 0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31, 88 0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b 89 }, 90 { /* gamma 150 */ 91 MCS_MTP_SET3, 92 0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28, 93 0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31, 94 0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29 95 }, 96 { /* gamma 200 */ 97 MCS_MTP_SET3, 98 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29, 99 0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30, 100 0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b 101 }, 102 { /* gamma 240 */ 103 MCS_MTP_SET3, 104 0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a, 105 0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30, 106 0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47 107 }, 108 { /* gamma 300 */ 109 MCS_MTP_SET3, 110 0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28, 111 0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f, 112 0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57 113 } 114 }; 115 116 static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel) 117 { 118 return container_of(panel, struct s6e63j0x03, panel); 119 } 120 121 static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx, 122 const void *seq, size_t len) 123 { 124 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); 125 126 return mipi_dsi_dcs_write_buffer(dsi, seq, len); 127 } 128 129 #define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \ 130 ({ \ 131 static const u8 d[] = { seq }; \ 132 s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d)); \ 133 }) 134 135 static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx) 136 { 137 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a); 138 } 139 140 static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on) 141 { 142 if (on) 143 return s6e63j0x03_dcs_write_seq_static(ctx, 144 MCS_MTP_KEY, 0x5a, 0x5a); 145 146 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5); 147 } 148 149 static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx) 150 { 151 int ret; 152 153 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 154 if (ret < 0) 155 return ret; 156 157 msleep(30); 158 159 gpiod_set_value(ctx->reset_gpio, 1); 160 usleep_range(1000, 2000); 161 gpiod_set_value(ctx->reset_gpio, 0); 162 usleep_range(5000, 6000); 163 164 return 0; 165 } 166 167 static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx) 168 { 169 return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 170 } 171 172 static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness) 173 { 174 unsigned int index; 175 176 index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS); 177 178 if (index >= NUM_GAMMA_STEPS) 179 index = NUM_GAMMA_STEPS - 1; 180 181 return index; 182 } 183 184 static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx, 185 unsigned int brightness) 186 { 187 struct backlight_device *bl_dev = ctx->bl_dev; 188 unsigned int index = s6e63j0x03_get_brightness_index(brightness); 189 int ret; 190 191 ret = s6e63j0x03_apply_mtp_key(ctx, true); 192 if (ret < 0) 193 return ret; 194 195 ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT); 196 if (ret < 0) 197 return ret; 198 199 ret = s6e63j0x03_apply_mtp_key(ctx, false); 200 if (ret < 0) 201 return ret; 202 203 bl_dev->props.brightness = brightness; 204 205 return 0; 206 } 207 208 static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev) 209 { 210 struct s6e63j0x03 *ctx = bl_get_data(bl_dev); 211 unsigned int brightness = bl_dev->props.brightness; 212 213 return s6e63j0x03_update_gamma(ctx, brightness); 214 } 215 216 static const struct backlight_ops s6e63j0x03_bl_ops = { 217 .update_status = s6e63j0x03_set_brightness, 218 }; 219 220 static int s6e63j0x03_disable(struct drm_panel *panel) 221 { 222 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel); 223 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); 224 int ret; 225 226 ret = mipi_dsi_dcs_set_display_off(dsi); 227 if (ret < 0) 228 return ret; 229 230 ctx->bl_dev->props.power = FB_BLANK_NORMAL; 231 232 ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 233 if (ret < 0) 234 return ret; 235 236 msleep(120); 237 238 return 0; 239 } 240 241 static int s6e63j0x03_unprepare(struct drm_panel *panel) 242 { 243 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel); 244 int ret; 245 246 ret = s6e63j0x03_power_off(ctx); 247 if (ret < 0) 248 return ret; 249 250 ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; 251 252 return 0; 253 } 254 255 static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx) 256 { 257 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); 258 int ret; 259 260 ret = s6e63j0x03_enable_lv2_command(ctx); 261 if (ret < 0) 262 return ret; 263 264 ret = s6e63j0x03_apply_mtp_key(ctx, true); 265 if (ret < 0) 266 return ret; 267 268 /* set porch adjustment */ 269 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28); 270 if (ret < 0) 271 return ret; 272 273 /* set frame freq */ 274 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00); 275 if (ret < 0) 276 return ret; 277 278 /* set caset, paset */ 279 ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN, 280 default_mode.hdisplay - 1 + FIRST_COLUMN); 281 if (ret < 0) 282 return ret; 283 284 ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1); 285 if (ret < 0) 286 return ret; 287 288 /* set ltps timming 0, 1 */ 289 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17, 290 0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00); 291 if (ret < 0) 292 return ret; 293 294 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02); 295 if (ret < 0) 296 return ret; 297 298 /* set param pos te_edge */ 299 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01); 300 if (ret < 0) 301 return ret; 302 303 /* set te rising edge */ 304 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f); 305 if (ret < 0) 306 return ret; 307 308 /* set param pos default */ 309 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00); 310 if (ret < 0) 311 return ret; 312 313 ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 314 if (ret < 0) 315 return ret; 316 317 ret = s6e63j0x03_apply_mtp_key(ctx, false); 318 if (ret < 0) 319 return ret; 320 321 return 0; 322 } 323 324 static int s6e63j0x03_prepare(struct drm_panel *panel) 325 { 326 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel); 327 int ret; 328 329 ret = s6e63j0x03_power_on(ctx); 330 if (ret < 0) 331 return ret; 332 333 ret = s6e63j0x03_panel_init(ctx); 334 if (ret < 0) 335 goto err; 336 337 ctx->bl_dev->props.power = FB_BLANK_NORMAL; 338 339 return 0; 340 341 err: 342 s6e63j0x03_power_off(ctx); 343 return ret; 344 } 345 346 static int s6e63j0x03_enable(struct drm_panel *panel) 347 { 348 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel); 349 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); 350 int ret; 351 352 msleep(120); 353 354 ret = s6e63j0x03_apply_mtp_key(ctx, true); 355 if (ret < 0) 356 return ret; 357 358 /* set elvss_cond */ 359 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09); 360 if (ret < 0) 361 return ret; 362 363 /* set pos */ 364 ret = s6e63j0x03_dcs_write_seq_static(ctx, 365 MIPI_DCS_SET_ADDRESS_MODE, 0x40); 366 if (ret < 0) 367 return ret; 368 369 /* set default white brightness */ 370 ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff); 371 if (ret < 0) 372 return ret; 373 374 /* set white ctrl */ 375 ret = s6e63j0x03_dcs_write_seq_static(ctx, 376 MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); 377 if (ret < 0) 378 return ret; 379 380 /* set acl off */ 381 ret = s6e63j0x03_dcs_write_seq_static(ctx, 382 MIPI_DCS_WRITE_POWER_SAVE, 0x00); 383 if (ret < 0) 384 return ret; 385 386 ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 387 if (ret < 0) 388 return ret; 389 390 ret = s6e63j0x03_apply_mtp_key(ctx, false); 391 if (ret < 0) 392 return ret; 393 394 ret = mipi_dsi_dcs_set_display_on(dsi); 395 if (ret < 0) 396 return ret; 397 398 ctx->bl_dev->props.power = FB_BLANK_UNBLANK; 399 400 return 0; 401 } 402 403 static int s6e63j0x03_get_modes(struct drm_panel *panel, 404 struct drm_connector *connector) 405 { 406 struct drm_display_mode *mode; 407 408 mode = drm_mode_duplicate(connector->dev, &default_mode); 409 if (!mode) { 410 DRM_ERROR("failed to add mode %ux%ux@%u\n", 411 default_mode.hdisplay, default_mode.vdisplay, 412 default_mode.vrefresh); 413 return -ENOMEM; 414 } 415 416 drm_mode_set_name(mode); 417 418 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 419 drm_mode_probed_add(connector, mode); 420 421 connector->display_info.width_mm = 29; 422 connector->display_info.height_mm = 29; 423 424 return 1; 425 } 426 427 static const struct drm_panel_funcs s6e63j0x03_funcs = { 428 .disable = s6e63j0x03_disable, 429 .unprepare = s6e63j0x03_unprepare, 430 .prepare = s6e63j0x03_prepare, 431 .enable = s6e63j0x03_enable, 432 .get_modes = s6e63j0x03_get_modes, 433 }; 434 435 static int s6e63j0x03_probe(struct mipi_dsi_device *dsi) 436 { 437 struct device *dev = &dsi->dev; 438 struct s6e63j0x03 *ctx; 439 int ret; 440 441 ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL); 442 if (!ctx) 443 return -ENOMEM; 444 445 mipi_dsi_set_drvdata(dsi, ctx); 446 447 ctx->dev = dev; 448 449 dsi->lanes = 1; 450 dsi->format = MIPI_DSI_FMT_RGB888; 451 dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET; 452 453 ctx->supplies[0].supply = "vdd3"; 454 ctx->supplies[1].supply = "vci"; 455 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), 456 ctx->supplies); 457 if (ret < 0) { 458 dev_err(dev, "failed to get regulators: %d\n", ret); 459 return ret; 460 } 461 462 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 463 if (IS_ERR(ctx->reset_gpio)) { 464 dev_err(dev, "cannot get reset-gpio: %ld\n", 465 PTR_ERR(ctx->reset_gpio)); 466 return PTR_ERR(ctx->reset_gpio); 467 } 468 469 drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs, 470 DRM_MODE_CONNECTOR_DSI); 471 472 ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx, 473 &s6e63j0x03_bl_ops, NULL); 474 if (IS_ERR(ctx->bl_dev)) { 475 dev_err(dev, "failed to register backlight device\n"); 476 return PTR_ERR(ctx->bl_dev); 477 } 478 479 ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS; 480 ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS; 481 ctx->bl_dev->props.power = FB_BLANK_POWERDOWN; 482 483 ret = drm_panel_add(&ctx->panel); 484 if (ret < 0) 485 goto unregister_backlight; 486 487 ret = mipi_dsi_attach(dsi); 488 if (ret < 0) 489 goto remove_panel; 490 491 return ret; 492 493 remove_panel: 494 drm_panel_remove(&ctx->panel); 495 496 unregister_backlight: 497 backlight_device_unregister(ctx->bl_dev); 498 499 return ret; 500 } 501 502 static int s6e63j0x03_remove(struct mipi_dsi_device *dsi) 503 { 504 struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi); 505 506 mipi_dsi_detach(dsi); 507 drm_panel_remove(&ctx->panel); 508 509 backlight_device_unregister(ctx->bl_dev); 510 511 return 0; 512 } 513 514 static const struct of_device_id s6e63j0x03_of_match[] = { 515 { .compatible = "samsung,s6e63j0x03" }, 516 { } 517 }; 518 MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match); 519 520 static struct mipi_dsi_driver s6e63j0x03_driver = { 521 .probe = s6e63j0x03_probe, 522 .remove = s6e63j0x03_remove, 523 .driver = { 524 .name = "panel_samsung_s6e63j0x03", 525 .of_match_table = s6e63j0x03_of_match, 526 }, 527 }; 528 module_mipi_dsi_driver(s6e63j0x03_driver); 529 530 MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); 531 MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>"); 532 MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver"); 533 MODULE_LICENSE("GPL v2"); 534