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