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