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