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