1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * ams369fg06 AMOLED LCD panel driver. 4 * 5 * Copyright (c) 2011 Samsung Electronics Co., Ltd. 6 * Author: Jingoo Han <jg1.han@samsung.com> 7 * 8 * Derived from drivers/video/s6e63m0.c 9 */ 10 11 #include <linux/backlight.h> 12 #include <linux/delay.h> 13 #include <linux/fb.h> 14 #include <linux/lcd.h> 15 #include <linux/module.h> 16 #include <linux/spi/spi.h> 17 #include <linux/wait.h> 18 19 #define SLEEPMSEC 0x1000 20 #define ENDDEF 0x2000 21 #define DEFMASK 0xFF00 22 #define COMMAND_ONLY 0xFE 23 #define DATA_ONLY 0xFF 24 25 #define MAX_GAMMA_LEVEL 5 26 #define GAMMA_TABLE_COUNT 21 27 28 #define MIN_BRIGHTNESS 0 29 #define MAX_BRIGHTNESS 255 30 #define DEFAULT_BRIGHTNESS 150 31 32 struct ams369fg06 { 33 struct device *dev; 34 struct spi_device *spi; 35 unsigned int power; 36 struct lcd_device *ld; 37 struct backlight_device *bd; 38 struct lcd_platform_data *lcd_pd; 39 }; 40 41 static const unsigned short seq_display_on[] = { 42 0x14, 0x03, 43 ENDDEF, 0x0000 44 }; 45 46 static const unsigned short seq_display_off[] = { 47 0x14, 0x00, 48 ENDDEF, 0x0000 49 }; 50 51 static const unsigned short seq_stand_by_on[] = { 52 0x1D, 0xA1, 53 SLEEPMSEC, 200, 54 ENDDEF, 0x0000 55 }; 56 57 static const unsigned short seq_stand_by_off[] = { 58 0x1D, 0xA0, 59 SLEEPMSEC, 250, 60 ENDDEF, 0x0000 61 }; 62 63 static const unsigned short seq_setting[] = { 64 0x31, 0x08, 65 0x32, 0x14, 66 0x30, 0x02, 67 0x27, 0x01, 68 0x12, 0x08, 69 0x13, 0x08, 70 0x15, 0x00, 71 0x16, 0x00, 72 73 0xef, 0xd0, 74 DATA_ONLY, 0xe8, 75 76 0x39, 0x44, 77 0x40, 0x00, 78 0x41, 0x3f, 79 0x42, 0x2a, 80 0x43, 0x27, 81 0x44, 0x27, 82 0x45, 0x1f, 83 0x46, 0x44, 84 0x50, 0x00, 85 0x51, 0x00, 86 0x52, 0x17, 87 0x53, 0x24, 88 0x54, 0x26, 89 0x55, 0x1f, 90 0x56, 0x43, 91 0x60, 0x00, 92 0x61, 0x3f, 93 0x62, 0x2a, 94 0x63, 0x25, 95 0x64, 0x24, 96 0x65, 0x1b, 97 0x66, 0x5c, 98 99 0x17, 0x22, 100 0x18, 0x33, 101 0x19, 0x03, 102 0x1a, 0x01, 103 0x22, 0xa4, 104 0x23, 0x00, 105 0x26, 0xa0, 106 107 0x1d, 0xa0, 108 SLEEPMSEC, 300, 109 110 0x14, 0x03, 111 112 ENDDEF, 0x0000 113 }; 114 115 /* gamma value: 2.2 */ 116 static const unsigned int ams369fg06_22_250[] = { 117 0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44, 118 0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43, 119 0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c, 120 }; 121 122 static const unsigned int ams369fg06_22_200[] = { 123 0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e, 124 0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d, 125 0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53, 126 }; 127 128 static const unsigned int ams369fg06_22_150[] = { 129 0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37, 130 0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36, 131 0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a, 132 }; 133 134 static const unsigned int ams369fg06_22_100[] = { 135 0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f, 136 0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e, 137 0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f, 138 }; 139 140 static const unsigned int ams369fg06_22_50[] = { 141 0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24, 142 0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23, 143 0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31, 144 }; 145 146 struct ams369fg06_gamma { 147 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL]; 148 }; 149 150 static struct ams369fg06_gamma gamma_table = { 151 .gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50, 152 .gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100, 153 .gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150, 154 .gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200, 155 .gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250, 156 }; 157 158 static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data) 159 { 160 u16 buf[1]; 161 struct spi_message msg; 162 163 struct spi_transfer xfer = { 164 .len = 2, 165 .tx_buf = buf, 166 }; 167 168 buf[0] = (addr << 8) | data; 169 170 spi_message_init(&msg); 171 spi_message_add_tail(&xfer, &msg); 172 173 return spi_sync(lcd->spi, &msg); 174 } 175 176 static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address, 177 unsigned char command) 178 { 179 int ret = 0; 180 181 if (address != DATA_ONLY) 182 ret = ams369fg06_spi_write_byte(lcd, 0x70, address); 183 if (command != COMMAND_ONLY) 184 ret = ams369fg06_spi_write_byte(lcd, 0x72, command); 185 186 return ret; 187 } 188 189 static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd, 190 const unsigned short *wbuf) 191 { 192 int ret = 0, i = 0; 193 194 while ((wbuf[i] & DEFMASK) != ENDDEF) { 195 if ((wbuf[i] & DEFMASK) != SLEEPMSEC) { 196 ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]); 197 if (ret) 198 break; 199 } else { 200 msleep(wbuf[i+1]); 201 } 202 i += 2; 203 } 204 205 return ret; 206 } 207 208 static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd, 209 const unsigned int *gamma) 210 { 211 unsigned int i = 0; 212 int ret = 0; 213 214 for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) { 215 ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]); 216 ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]); 217 ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]); 218 if (ret) { 219 dev_err(lcd->dev, "failed to set gamma table.\n"); 220 goto gamma_err; 221 } 222 } 223 224 gamma_err: 225 return ret; 226 } 227 228 static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness) 229 { 230 int ret = 0; 231 int gamma = 0; 232 233 if ((brightness >= 0) && (brightness <= 50)) 234 gamma = 0; 235 else if ((brightness > 50) && (brightness <= 100)) 236 gamma = 1; 237 else if ((brightness > 100) && (brightness <= 150)) 238 gamma = 2; 239 else if ((brightness > 150) && (brightness <= 200)) 240 gamma = 3; 241 else if ((brightness > 200) && (brightness <= 255)) 242 gamma = 4; 243 244 ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]); 245 246 return ret; 247 } 248 249 static int ams369fg06_ldi_init(struct ams369fg06 *lcd) 250 { 251 int ret, i; 252 static const unsigned short *init_seq[] = { 253 seq_setting, 254 seq_stand_by_off, 255 }; 256 257 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 258 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 259 if (ret) 260 break; 261 } 262 263 return ret; 264 } 265 266 static int ams369fg06_ldi_enable(struct ams369fg06 *lcd) 267 { 268 int ret, i; 269 static const unsigned short *init_seq[] = { 270 seq_stand_by_off, 271 seq_display_on, 272 }; 273 274 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 275 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 276 if (ret) 277 break; 278 } 279 280 return ret; 281 } 282 283 static int ams369fg06_ldi_disable(struct ams369fg06 *lcd) 284 { 285 int ret, i; 286 287 static const unsigned short *init_seq[] = { 288 seq_display_off, 289 seq_stand_by_on, 290 }; 291 292 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 293 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 294 if (ret) 295 break; 296 } 297 298 return ret; 299 } 300 301 static int ams369fg06_power_is_on(int power) 302 { 303 return power <= FB_BLANK_NORMAL; 304 } 305 306 static int ams369fg06_power_on(struct ams369fg06 *lcd) 307 { 308 int ret = 0; 309 struct lcd_platform_data *pd; 310 struct backlight_device *bd; 311 312 pd = lcd->lcd_pd; 313 bd = lcd->bd; 314 315 if (pd->power_on) { 316 pd->power_on(lcd->ld, 1); 317 msleep(pd->power_on_delay); 318 } 319 320 if (!pd->reset) { 321 dev_err(lcd->dev, "reset is NULL.\n"); 322 return -EINVAL; 323 } 324 325 pd->reset(lcd->ld); 326 msleep(pd->reset_delay); 327 328 ret = ams369fg06_ldi_init(lcd); 329 if (ret) { 330 dev_err(lcd->dev, "failed to initialize ldi.\n"); 331 return ret; 332 } 333 334 ret = ams369fg06_ldi_enable(lcd); 335 if (ret) { 336 dev_err(lcd->dev, "failed to enable ldi.\n"); 337 return ret; 338 } 339 340 /* set brightness to current value after power on or resume. */ 341 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 342 if (ret) { 343 dev_err(lcd->dev, "lcd gamma setting failed.\n"); 344 return ret; 345 } 346 347 return 0; 348 } 349 350 static int ams369fg06_power_off(struct ams369fg06 *lcd) 351 { 352 int ret; 353 struct lcd_platform_data *pd; 354 355 pd = lcd->lcd_pd; 356 357 ret = ams369fg06_ldi_disable(lcd); 358 if (ret) { 359 dev_err(lcd->dev, "lcd setting failed.\n"); 360 return -EIO; 361 } 362 363 msleep(pd->power_off_delay); 364 365 if (pd->power_on) 366 pd->power_on(lcd->ld, 0); 367 368 return 0; 369 } 370 371 static int ams369fg06_power(struct ams369fg06 *lcd, int power) 372 { 373 int ret = 0; 374 375 if (ams369fg06_power_is_on(power) && 376 !ams369fg06_power_is_on(lcd->power)) 377 ret = ams369fg06_power_on(lcd); 378 else if (!ams369fg06_power_is_on(power) && 379 ams369fg06_power_is_on(lcd->power)) 380 ret = ams369fg06_power_off(lcd); 381 382 if (!ret) 383 lcd->power = power; 384 385 return ret; 386 } 387 388 static int ams369fg06_get_power(struct lcd_device *ld) 389 { 390 struct ams369fg06 *lcd = lcd_get_data(ld); 391 392 return lcd->power; 393 } 394 395 static int ams369fg06_set_power(struct lcd_device *ld, int power) 396 { 397 struct ams369fg06 *lcd = lcd_get_data(ld); 398 399 if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && 400 power != FB_BLANK_NORMAL) { 401 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); 402 return -EINVAL; 403 } 404 405 return ams369fg06_power(lcd, power); 406 } 407 408 static int ams369fg06_set_brightness(struct backlight_device *bd) 409 { 410 int ret = 0; 411 int brightness = bd->props.brightness; 412 struct ams369fg06 *lcd = bl_get_data(bd); 413 414 if (brightness < MIN_BRIGHTNESS || 415 brightness > bd->props.max_brightness) { 416 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n", 417 MIN_BRIGHTNESS, MAX_BRIGHTNESS); 418 return -EINVAL; 419 } 420 421 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 422 if (ret) { 423 dev_err(&bd->dev, "lcd brightness setting failed.\n"); 424 return -EIO; 425 } 426 427 return ret; 428 } 429 430 static const struct lcd_ops ams369fg06_lcd_ops = { 431 .get_power = ams369fg06_get_power, 432 .set_power = ams369fg06_set_power, 433 }; 434 435 static const struct backlight_ops ams369fg06_backlight_ops = { 436 .update_status = ams369fg06_set_brightness, 437 }; 438 439 static int ams369fg06_probe(struct spi_device *spi) 440 { 441 int ret = 0; 442 struct ams369fg06 *lcd = NULL; 443 struct lcd_device *ld = NULL; 444 struct backlight_device *bd = NULL; 445 struct backlight_properties props; 446 447 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL); 448 if (!lcd) 449 return -ENOMEM; 450 451 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */ 452 spi->bits_per_word = 16; 453 454 ret = spi_setup(spi); 455 if (ret < 0) { 456 dev_err(&spi->dev, "spi setup failed.\n"); 457 return ret; 458 } 459 460 lcd->spi = spi; 461 lcd->dev = &spi->dev; 462 463 lcd->lcd_pd = dev_get_platdata(&spi->dev); 464 if (!lcd->lcd_pd) { 465 dev_err(&spi->dev, "platform data is NULL\n"); 466 return -EINVAL; 467 } 468 469 ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd, 470 &ams369fg06_lcd_ops); 471 if (IS_ERR(ld)) 472 return PTR_ERR(ld); 473 474 lcd->ld = ld; 475 476 memset(&props, 0, sizeof(struct backlight_properties)); 477 props.type = BACKLIGHT_RAW; 478 props.max_brightness = MAX_BRIGHTNESS; 479 480 bd = devm_backlight_device_register(&spi->dev, "ams369fg06-bl", 481 &spi->dev, lcd, 482 &ams369fg06_backlight_ops, &props); 483 if (IS_ERR(bd)) 484 return PTR_ERR(bd); 485 486 bd->props.brightness = DEFAULT_BRIGHTNESS; 487 lcd->bd = bd; 488 489 if (!lcd->lcd_pd->lcd_enabled) { 490 /* 491 * if lcd panel was off from bootloader then 492 * current lcd status is powerdown and then 493 * it enables lcd panel. 494 */ 495 lcd->power = FB_BLANK_POWERDOWN; 496 497 ams369fg06_power(lcd, FB_BLANK_UNBLANK); 498 } else { 499 lcd->power = FB_BLANK_UNBLANK; 500 } 501 502 spi_set_drvdata(spi, lcd); 503 504 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n"); 505 506 return 0; 507 } 508 509 static void ams369fg06_remove(struct spi_device *spi) 510 { 511 struct ams369fg06 *lcd = spi_get_drvdata(spi); 512 513 ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 514 } 515 516 #ifdef CONFIG_PM_SLEEP 517 static int ams369fg06_suspend(struct device *dev) 518 { 519 struct ams369fg06 *lcd = dev_get_drvdata(dev); 520 521 dev_dbg(dev, "lcd->power = %d\n", lcd->power); 522 523 /* 524 * when lcd panel is suspend, lcd panel becomes off 525 * regardless of status. 526 */ 527 return ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 528 } 529 530 static int ams369fg06_resume(struct device *dev) 531 { 532 struct ams369fg06 *lcd = dev_get_drvdata(dev); 533 534 lcd->power = FB_BLANK_POWERDOWN; 535 536 return ams369fg06_power(lcd, FB_BLANK_UNBLANK); 537 } 538 #endif 539 540 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend, 541 ams369fg06_resume); 542 543 static void ams369fg06_shutdown(struct spi_device *spi) 544 { 545 struct ams369fg06 *lcd = spi_get_drvdata(spi); 546 547 ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 548 } 549 550 static struct spi_driver ams369fg06_driver = { 551 .driver = { 552 .name = "ams369fg06", 553 .pm = &ams369fg06_pm_ops, 554 }, 555 .probe = ams369fg06_probe, 556 .remove = ams369fg06_remove, 557 .shutdown = ams369fg06_shutdown, 558 }; 559 560 module_spi_driver(ams369fg06_driver); 561 562 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); 563 MODULE_DESCRIPTION("ams369fg06 LCD Driver"); 564 MODULE_LICENSE("GPL"); 565