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