blob: 3729238e70963d7e2d6d7e25ec79cf5a5f7288db [file] [log] [blame]
Jingoo Hana4c8aaa2011-07-25 17:12:00 -07001/*
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>
Paul Gortmaker355b2002011-07-03 16:17:28 -040025#include <linux/module.h>
Jingoo Hana4c8aaa2011-07-25 17:12:00 -070026#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;
Axel Linef22f6a2011-07-25 17:12:01 -0700483 struct backlight_properties props;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700484
Jingoo Han80629ef2012-05-29 15:07:21 -0700485 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700486 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");
Jingoo Han80629ef2012-05-29 15:07:21 -0700495 return ret;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700496 }
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");
Jingoo Han80629ef2012-05-29 15:07:21 -0700504 return -EFAULT;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700505 }
506
507 ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
508 &ams369fg06_lcd_ops);
Jingoo Han80629ef2012-05-29 15:07:21 -0700509 if (IS_ERR(ld))
510 return PTR_ERR(ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700511
512 lcd->ld = ld;
513
Axel Linef22f6a2011-07-25 17:12:01 -0700514 memset(&props, 0, sizeof(struct backlight_properties));
515 props.type = BACKLIGHT_RAW;
516 props.max_brightness = MAX_BRIGHTNESS;
517
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700518 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
Axel Linef22f6a2011-07-25 17:12:01 -0700519 &ams369fg06_backlight_ops, &props);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700520 if (IS_ERR(bd)) {
521 ret = PTR_ERR(bd);
522 goto out_lcd_unregister;
523 }
524
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700525 bd->props.brightness = DEFAULT_BRIGHTNESS;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700526 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);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700548 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);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700558
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",
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700617 .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
Axel Lin462dd832012-03-23 15:01:59 -0700626module_spi_driver(ams369fg06_driver);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700627
628MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
629MODULE_DESCRIPTION("ams369fg06 LCD Driver");
630MODULE_LICENSE("GPL");