Merge tag 'v3.3.7' into 3.3/master
[zen-stable.git] / drivers / video / backlight / ams369fg06.c
blob7838a23fbdd124fb8c2b639092708a35892f8bb9
1 /*
2 * ams369fg06 AMOLED LCD panel driver.
4 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
5 * Author: Jingoo Han <jg1.han@samsung.com>
7 * Derived from drivers/video/s6e63m0.c
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.
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.
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.
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>
33 #define SLEEPMSEC 0x1000
34 #define ENDDEF 0x2000
35 #define DEFMASK 0xFF00
36 #define COMMAND_ONLY 0xFE
37 #define DATA_ONLY 0xFF
39 #define MAX_GAMMA_LEVEL 5
40 #define GAMMA_TABLE_COUNT 21
42 #define MIN_BRIGHTNESS 0
43 #define MAX_BRIGHTNESS 255
44 #define DEFAULT_BRIGHTNESS 150
46 struct 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;
55 static const unsigned short seq_display_on[] = {
56 0x14, 0x03,
57 ENDDEF, 0x0000
60 static const unsigned short seq_display_off[] = {
61 0x14, 0x00,
62 ENDDEF, 0x0000
65 static const unsigned short seq_stand_by_on[] = {
66 0x1D, 0xA1,
67 SLEEPMSEC, 200,
68 ENDDEF, 0x0000
71 static const unsigned short seq_stand_by_off[] = {
72 0x1D, 0xA0,
73 SLEEPMSEC, 250,
74 ENDDEF, 0x0000
77 static 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,
87 0xef, 0xd0,
88 DATA_ONLY, 0xe8,
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,
113 0x17, 0x22,
114 0x18, 0x33,
115 0x19, 0x03,
116 0x1a, 0x01,
117 0x22, 0xa4,
118 0x23, 0x00,
119 0x26, 0xa0,
121 0x1d, 0xa0,
122 SLEEPMSEC, 300,
124 0x14, 0x03,
126 ENDDEF, 0x0000
129 /* gamma value: 2.2 */
130 static 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,
136 static 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,
142 static 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,
148 static 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,
154 static 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,
160 struct ams369fg06_gamma {
161 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
164 static 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,
172 static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
174 u16 buf[1];
175 struct spi_message msg;
177 struct spi_transfer xfer = {
178 .len = 2,
179 .tx_buf = buf,
182 buf[0] = (addr << 8) | data;
184 spi_message_init(&msg);
185 spi_message_add_tail(&xfer, &msg);
187 return spi_sync(lcd->spi, &msg);
190 static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
191 unsigned char command)
193 int ret = 0;
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);
200 return ret;
203 static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
204 const unsigned short *wbuf)
206 int ret = 0, i = 0;
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;
218 return ret;
221 static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
222 const unsigned int *gamma)
224 unsigned int i = 0;
225 int ret = 0;
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;
237 gamma_err:
238 return ret;
241 static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
243 int ret = 0;
244 int gamma = 0;
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;
257 ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
259 return ret;
262 static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
264 int ret, i;
265 static const unsigned short *init_seq[] = {
266 seq_setting,
267 seq_stand_by_off,
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;
276 return ret;
279 static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
281 int ret, i;
282 static const unsigned short *init_seq[] = {
283 seq_stand_by_off,
284 seq_display_on,
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;
293 return ret;
296 static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
298 int ret, i;
300 static const unsigned short *init_seq[] = {
301 seq_display_off,
302 seq_stand_by_on,
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;
311 return ret;
314 static int ams369fg06_power_is_on(int power)
316 return ((power) <= FB_BLANK_NORMAL);
319 static int ams369fg06_power_on(struct ams369fg06 *lcd)
321 int ret = 0;
322 struct lcd_platform_data *pd = NULL;
323 struct backlight_device *bd = NULL;
325 pd = lcd->lcd_pd;
326 if (!pd) {
327 dev_err(lcd->dev, "platform data is NULL.\n");
328 return -EFAULT;
331 bd = lcd->bd;
332 if (!bd) {
333 dev_err(lcd->dev, "backlight device is NULL.\n");
334 return -EFAULT;
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);
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);
353 ret = ams369fg06_ldi_init(lcd);
354 if (ret) {
355 dev_err(lcd->dev, "failed to initialize ldi.\n");
356 return ret;
359 ret = ams369fg06_ldi_enable(lcd);
360 if (ret) {
361 dev_err(lcd->dev, "failed to enable ldi.\n");
362 return ret;
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;
372 return 0;
375 static int ams369fg06_power_off(struct ams369fg06 *lcd)
377 int ret = 0;
378 struct lcd_platform_data *pd = NULL;
380 pd = lcd->lcd_pd;
381 if (!pd) {
382 dev_err(lcd->dev, "platform data is NULL\n");
383 return -EFAULT;
386 ret = ams369fg06_ldi_disable(lcd);
387 if (ret) {
388 dev_err(lcd->dev, "lcd setting failed.\n");
389 return -EIO;
392 mdelay(pd->power_off_delay);
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);
400 return 0;
403 static int ams369fg06_power(struct ams369fg06 *lcd, int power)
405 int ret = 0;
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);
414 if (!ret)
415 lcd->power = power;
417 return ret;
420 static int ams369fg06_get_power(struct lcd_device *ld)
422 struct ams369fg06 *lcd = lcd_get_data(ld);
424 return lcd->power;
427 static int ams369fg06_set_power(struct lcd_device *ld, int power)
429 struct ams369fg06 *lcd = lcd_get_data(ld);
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;
437 return ams369fg06_power(lcd, power);
440 static int ams369fg06_get_brightness(struct backlight_device *bd)
442 return bd->props.brightness;
445 static int ams369fg06_set_brightness(struct backlight_device *bd)
447 int ret = 0;
448 int brightness = bd->props.brightness;
449 struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
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;
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;
464 return ret;
467 static struct lcd_ops ams369fg06_lcd_ops = {
468 .get_power = ams369fg06_get_power,
469 .set_power = ams369fg06_set_power,
472 static const struct backlight_ops ams369fg06_backlight_ops = {
473 .get_brightness = ams369fg06_get_brightness,
474 .update_status = ams369fg06_set_brightness,
477 static int __devinit ams369fg06_probe(struct spi_device *spi)
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;
485 lcd = kzalloc(sizeof(struct ams369fg06), GFP_KERNEL);
486 if (!lcd)
487 return -ENOMEM;
489 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
490 spi->bits_per_word = 16;
492 ret = spi_setup(spi);
493 if (ret < 0) {
494 dev_err(&spi->dev, "spi setup failed.\n");
495 goto out_free_lcd;
498 lcd->spi = spi;
499 lcd->dev = &spi->dev;
501 lcd->lcd_pd = spi->dev.platform_data;
502 if (!lcd->lcd_pd) {
503 dev_err(&spi->dev, "platform data is NULL\n");
504 goto out_free_lcd;
507 ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
508 &ams369fg06_lcd_ops);
509 if (IS_ERR(ld)) {
510 ret = PTR_ERR(ld);
511 goto out_free_lcd;
514 lcd->ld = ld;
516 memset(&props, 0, sizeof(struct backlight_properties));
517 props.type = BACKLIGHT_RAW;
518 props.max_brightness = MAX_BRIGHTNESS;
520 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
521 &ams369fg06_backlight_ops, &props);
522 if (IS_ERR(bd)) {
523 ret = PTR_ERR(bd);
524 goto out_lcd_unregister;
527 bd->props.brightness = DEFAULT_BRIGHTNESS;
528 lcd->bd = bd;
530 if (!lcd->lcd_pd->lcd_enabled) {
532 * if lcd panel was off from bootloader then
533 * current lcd status is powerdown and then
534 * it enables lcd panel.
536 lcd->power = FB_BLANK_POWERDOWN;
538 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
539 } else
540 lcd->power = FB_BLANK_UNBLANK;
542 dev_set_drvdata(&spi->dev, lcd);
544 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
546 return 0;
548 out_lcd_unregister:
549 lcd_device_unregister(ld);
550 out_free_lcd:
551 kfree(lcd);
552 return ret;
555 static int __devexit ams369fg06_remove(struct spi_device *spi)
557 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
559 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
560 backlight_device_unregister(lcd->bd);
561 lcd_device_unregister(lcd->ld);
562 kfree(lcd);
564 return 0;
567 #if defined(CONFIG_PM)
568 static unsigned int before_power;
570 static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
572 int ret = 0;
573 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
575 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
577 before_power = lcd->power;
580 * when lcd panel is suspend, lcd panel becomes off
581 * regardless of status.
583 ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
585 return ret;
588 static int ams369fg06_resume(struct spi_device *spi)
590 int ret = 0;
591 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
594 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
595 * (at that time, before_power is FB_BLANK_UNBLANK) then
596 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
598 if (before_power == FB_BLANK_UNBLANK)
599 lcd->power = FB_BLANK_POWERDOWN;
601 dev_dbg(&spi->dev, "before_power = %d\n", before_power);
603 ret = ams369fg06_power(lcd, before_power);
605 return ret;
607 #else
608 #define ams369fg06_suspend NULL
609 #define ams369fg06_resume NULL
610 #endif
612 static void ams369fg06_shutdown(struct spi_device *spi)
614 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
616 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
619 static struct spi_driver ams369fg06_driver = {
620 .driver = {
621 .name = "ams369fg06",
622 .bus = &spi_bus_type,
623 .owner = THIS_MODULE,
625 .probe = ams369fg06_probe,
626 .remove = __devexit_p(ams369fg06_remove),
627 .shutdown = ams369fg06_shutdown,
628 .suspend = ams369fg06_suspend,
629 .resume = ams369fg06_resume,
632 static int __init ams369fg06_init(void)
634 return spi_register_driver(&ams369fg06_driver);
637 static void __exit ams369fg06_exit(void)
639 spi_unregister_driver(&ams369fg06_driver);
642 module_init(ams369fg06_init);
643 module_exit(ams369fg06_exit);
645 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
646 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
647 MODULE_LICENSE("GPL");