564 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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.c9 */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 0x100019#define ENDDEF 0x200020#define DEFMASK 0xFF0021#define COMMAND_ONLY 0xFE22#define DATA_ONLY 0xFF23 24#define MAX_GAMMA_LEVEL 525#define GAMMA_TABLE_COUNT 2126 27#define MIN_BRIGHTNESS 028#define MAX_BRIGHTNESS 25529#define DEFAULT_BRIGHTNESS 15030 31struct 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 40static const unsigned short seq_display_on[] = {41 0x14, 0x03,42 ENDDEF, 0x000043};44 45static const unsigned short seq_display_off[] = {46 0x14, 0x00,47 ENDDEF, 0x000048};49 50static const unsigned short seq_stand_by_on[] = {51 0x1D, 0xA1,52 SLEEPMSEC, 200,53 ENDDEF, 0x000054};55 56static const unsigned short seq_stand_by_off[] = {57 0x1D, 0xA0,58 SLEEPMSEC, 250,59 ENDDEF, 0x000060};61 62static 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, 0x0000112};113 114/* gamma value: 2.2 */115static 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 121static 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 127static 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 133static 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 139static 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 145struct ams369fg06_gamma {146 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];147};148 149static 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 157static 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 175static 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 188static 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 207static 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 223gamma_err:224 return ret;225}226 227static 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 248static 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 265static 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 282static 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 300static int ams369fg06_power_is_on(int power)301{302 return power <= BACKLIGHT_POWER_REDUCED;303}304 305static 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 349static 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 370static 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 387static int ams369fg06_get_power(struct lcd_device *ld)388{389 struct ams369fg06 *lcd = lcd_get_data(ld);390 391 return lcd->power;392}393 394static 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 407static 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 429static const struct lcd_ops ams369fg06_lcd_ops = {430 .get_power = ams369fg06_get_power,431 .set_power = ams369fg06_set_power,432};433 434static const struct backlight_ops ams369fg06_backlight_ops = {435 .update_status = ams369fg06_set_brightness,436};437 438static 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 then491 * current lcd status is powerdown and then492 * 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 508static 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_SLEEP516static 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 off524 * regardless of status.525 */526 return ams369fg06_power(lcd, BACKLIGHT_POWER_OFF);527}528 529static 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#endif538 539static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,540 ams369fg06_resume);541 542static 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 549static 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 559module_spi_driver(ams369fg06_driver);560 561MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");562MODULE_DESCRIPTION("ams369fg06 LCD Driver");563MODULE_LICENSE("GPL");564