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