ams369fg06.c revision c9023492b49b2a9506ae2f054167da02c71ef2b6
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 46struct 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 55static const unsigned short seq_display_on[] = { 56 0x14, 0x03, 57 ENDDEF, 0x0000 58}; 59 60static const unsigned short seq_display_off[] = { 61 0x14, 0x00, 62 ENDDEF, 0x0000 63}; 64 65static const unsigned short seq_stand_by_on[] = { 66 0x1D, 0xA1, 67 SLEEPMSEC, 200, 68 ENDDEF, 0x0000 69}; 70 71static const unsigned short seq_stand_by_off[] = { 72 0x1D, 0xA0, 73 SLEEPMSEC, 250, 74 ENDDEF, 0x0000 75}; 76 77static 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 */ 130static 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 136static 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 142static 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 148static 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 154static 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 160struct ams369fg06_gamma { 161 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL]; 162}; 163 164static 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 172static 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 190static 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 203static 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 msleep(wbuf[i+1]); 215 } 216 i += 2; 217 } 218 219 return ret; 220} 221 222static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd, 223 const unsigned int *gamma) 224{ 225 unsigned int i = 0; 226 int ret = 0; 227 228 for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) { 229 ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]); 230 ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]); 231 ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]); 232 if (ret) { 233 dev_err(lcd->dev, "failed to set gamma table.\n"); 234 goto gamma_err; 235 } 236 } 237 238gamma_err: 239 return ret; 240} 241 242static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness) 243{ 244 int ret = 0; 245 int gamma = 0; 246 247 if ((brightness >= 0) && (brightness <= 50)) 248 gamma = 0; 249 else if ((brightness > 50) && (brightness <= 100)) 250 gamma = 1; 251 else if ((brightness > 100) && (brightness <= 150)) 252 gamma = 2; 253 else if ((brightness > 150) && (brightness <= 200)) 254 gamma = 3; 255 else if ((brightness > 200) && (brightness <= 255)) 256 gamma = 4; 257 258 ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]); 259 260 return ret; 261} 262 263static int ams369fg06_ldi_init(struct ams369fg06 *lcd) 264{ 265 int ret, i; 266 static const unsigned short *init_seq[] = { 267 seq_setting, 268 seq_stand_by_off, 269 }; 270 271 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 272 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 273 if (ret) 274 break; 275 } 276 277 return ret; 278} 279 280static int ams369fg06_ldi_enable(struct ams369fg06 *lcd) 281{ 282 int ret, i; 283 static const unsigned short *init_seq[] = { 284 seq_stand_by_off, 285 seq_display_on, 286 }; 287 288 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 289 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 290 if (ret) 291 break; 292 } 293 294 return ret; 295} 296 297static int ams369fg06_ldi_disable(struct ams369fg06 *lcd) 298{ 299 int ret, i; 300 301 static const unsigned short *init_seq[] = { 302 seq_display_off, 303 seq_stand_by_on, 304 }; 305 306 for (i = 0; i < ARRAY_SIZE(init_seq); i++) { 307 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]); 308 if (ret) 309 break; 310 } 311 312 return ret; 313} 314 315static int ams369fg06_power_is_on(int power) 316{ 317 return power <= FB_BLANK_NORMAL; 318} 319 320static int ams369fg06_power_on(struct ams369fg06 *lcd) 321{ 322 int ret = 0; 323 struct lcd_platform_data *pd; 324 struct backlight_device *bd; 325 326 pd = lcd->lcd_pd; 327 bd = lcd->bd; 328 329 if (!pd->power_on) { 330 dev_err(lcd->dev, "power_on is NULL.\n"); 331 return -EINVAL; 332 } else { 333 pd->power_on(lcd->ld, 1); 334 msleep(pd->power_on_delay); 335 } 336 337 if (!pd->reset) { 338 dev_err(lcd->dev, "reset is NULL.\n"); 339 return -EINVAL; 340 } else { 341 pd->reset(lcd->ld); 342 msleep(pd->reset_delay); 343 } 344 345 ret = ams369fg06_ldi_init(lcd); 346 if (ret) { 347 dev_err(lcd->dev, "failed to initialize ldi.\n"); 348 return ret; 349 } 350 351 ret = ams369fg06_ldi_enable(lcd); 352 if (ret) { 353 dev_err(lcd->dev, "failed to enable ldi.\n"); 354 return ret; 355 } 356 357 /* set brightness to current value after power on or resume. */ 358 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 359 if (ret) { 360 dev_err(lcd->dev, "lcd gamma setting failed.\n"); 361 return ret; 362 } 363 364 return 0; 365} 366 367static int ams369fg06_power_off(struct ams369fg06 *lcd) 368{ 369 int ret; 370 struct lcd_platform_data *pd; 371 372 pd = lcd->lcd_pd; 373 374 ret = ams369fg06_ldi_disable(lcd); 375 if (ret) { 376 dev_err(lcd->dev, "lcd setting failed.\n"); 377 return -EIO; 378 } 379 380 msleep(pd->power_off_delay); 381 382 pd->power_on(lcd->ld, 0); 383 384 return 0; 385} 386 387static int ams369fg06_power(struct ams369fg06 *lcd, int power) 388{ 389 int ret = 0; 390 391 if (ams369fg06_power_is_on(power) && 392 !ams369fg06_power_is_on(lcd->power)) 393 ret = ams369fg06_power_on(lcd); 394 else if (!ams369fg06_power_is_on(power) && 395 ams369fg06_power_is_on(lcd->power)) 396 ret = ams369fg06_power_off(lcd); 397 398 if (!ret) 399 lcd->power = power; 400 401 return ret; 402} 403 404static int ams369fg06_get_power(struct lcd_device *ld) 405{ 406 struct ams369fg06 *lcd = lcd_get_data(ld); 407 408 return lcd->power; 409} 410 411static int ams369fg06_set_power(struct lcd_device *ld, int power) 412{ 413 struct ams369fg06 *lcd = lcd_get_data(ld); 414 415 if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && 416 power != FB_BLANK_NORMAL) { 417 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); 418 return -EINVAL; 419 } 420 421 return ams369fg06_power(lcd, power); 422} 423 424static int ams369fg06_get_brightness(struct backlight_device *bd) 425{ 426 return bd->props.brightness; 427} 428 429static int ams369fg06_set_brightness(struct backlight_device *bd) 430{ 431 int ret = 0; 432 int brightness = bd->props.brightness; 433 struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev); 434 435 if (brightness < MIN_BRIGHTNESS || 436 brightness > bd->props.max_brightness) { 437 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n", 438 MIN_BRIGHTNESS, MAX_BRIGHTNESS); 439 return -EINVAL; 440 } 441 442 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness); 443 if (ret) { 444 dev_err(&bd->dev, "lcd brightness setting failed.\n"); 445 return -EIO; 446 } 447 448 return ret; 449} 450 451static struct lcd_ops ams369fg06_lcd_ops = { 452 .get_power = ams369fg06_get_power, 453 .set_power = ams369fg06_set_power, 454}; 455 456static const struct backlight_ops ams369fg06_backlight_ops = { 457 .get_brightness = ams369fg06_get_brightness, 458 .update_status = ams369fg06_set_brightness, 459}; 460 461static int ams369fg06_probe(struct spi_device *spi) 462{ 463 int ret = 0; 464 struct ams369fg06 *lcd = NULL; 465 struct lcd_device *ld = NULL; 466 struct backlight_device *bd = NULL; 467 struct backlight_properties props; 468 469 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL); 470 if (!lcd) 471 return -ENOMEM; 472 473 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */ 474 spi->bits_per_word = 16; 475 476 ret = spi_setup(spi); 477 if (ret < 0) { 478 dev_err(&spi->dev, "spi setup failed.\n"); 479 return ret; 480 } 481 482 lcd->spi = spi; 483 lcd->dev = &spi->dev; 484 485 lcd->lcd_pd = spi->dev.platform_data; 486 if (!lcd->lcd_pd) { 487 dev_err(&spi->dev, "platform data is NULL\n"); 488 return -EINVAL; 489 } 490 491 ld = lcd_device_register("ams369fg06", &spi->dev, lcd, 492 &ams369fg06_lcd_ops); 493 if (IS_ERR(ld)) 494 return PTR_ERR(ld); 495 496 lcd->ld = ld; 497 498 memset(&props, 0, sizeof(struct backlight_properties)); 499 props.type = BACKLIGHT_RAW; 500 props.max_brightness = MAX_BRIGHTNESS; 501 502 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd, 503 &ams369fg06_backlight_ops, &props); 504 if (IS_ERR(bd)) { 505 ret = PTR_ERR(bd); 506 goto out_lcd_unregister; 507 } 508 509 bd->props.brightness = DEFAULT_BRIGHTNESS; 510 lcd->bd = bd; 511 512 if (!lcd->lcd_pd->lcd_enabled) { 513 /* 514 * if lcd panel was off from bootloader then 515 * current lcd status is powerdown and then 516 * it enables lcd panel. 517 */ 518 lcd->power = FB_BLANK_POWERDOWN; 519 520 ams369fg06_power(lcd, FB_BLANK_UNBLANK); 521 } else { 522 lcd->power = FB_BLANK_UNBLANK; 523 } 524 525 dev_set_drvdata(&spi->dev, lcd); 526 527 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n"); 528 529 return 0; 530 531out_lcd_unregister: 532 lcd_device_unregister(ld); 533 return ret; 534} 535 536static int ams369fg06_remove(struct spi_device *spi) 537{ 538 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 539 540 ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 541 backlight_device_unregister(lcd->bd); 542 lcd_device_unregister(lcd->ld); 543 544 return 0; 545} 546 547#if defined(CONFIG_PM) 548static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg) 549{ 550 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 551 552 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); 553 554 /* 555 * when lcd panel is suspend, lcd panel becomes off 556 * regardless of status. 557 */ 558 return ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 559} 560 561static int ams369fg06_resume(struct spi_device *spi) 562{ 563 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 564 565 lcd->power = FB_BLANK_POWERDOWN; 566 567 return ams369fg06_power(lcd, FB_BLANK_UNBLANK); 568} 569#else 570#define ams369fg06_suspend NULL 571#define ams369fg06_resume NULL 572#endif 573 574static void ams369fg06_shutdown(struct spi_device *spi) 575{ 576 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); 577 578 ams369fg06_power(lcd, FB_BLANK_POWERDOWN); 579} 580 581static struct spi_driver ams369fg06_driver = { 582 .driver = { 583 .name = "ams369fg06", 584 .owner = THIS_MODULE, 585 }, 586 .probe = ams369fg06_probe, 587 .remove = ams369fg06_remove, 588 .shutdown = ams369fg06_shutdown, 589 .suspend = ams369fg06_suspend, 590 .resume = ams369fg06_resume, 591}; 592 593module_spi_driver(ams369fg06_driver); 594 595MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); 596MODULE_DESCRIPTION("ams369fg06 LCD Driver"); 597MODULE_LICENSE("GPL"); 598