blob: 22756dda8682a07e1466046ec50570e06908897f [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;
Jingoo Han51976432013-02-21 16:43:27 -0800213 } else {
214 msleep(wbuf[i+1]);
215 }
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700216 i += 2;
217 }
218
219 return ret;
220}
221
222static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
223 const unsigned int *gamma)
224{
225 unsigned int i = 0;
226 int ret = 0;
227
228 for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
229 ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
230 ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
231 ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
232 if (ret) {
233 dev_err(lcd->dev, "failed to set gamma table.\n");
234 goto gamma_err;
235 }
236 }
237
238gamma_err:
239 return ret;
240}
241
242static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
243{
244 int ret = 0;
245 int gamma = 0;
246
247 if ((brightness >= 0) && (brightness <= 50))
248 gamma = 0;
249 else if ((brightness > 50) && (brightness <= 100))
250 gamma = 1;
251 else if ((brightness > 100) && (brightness <= 150))
252 gamma = 2;
253 else if ((brightness > 150) && (brightness <= 200))
254 gamma = 3;
255 else if ((brightness > 200) && (brightness <= 255))
256 gamma = 4;
257
258 ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
259
260 return ret;
261}
262
263static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
264{
265 int ret, i;
266 static const unsigned short *init_seq[] = {
267 seq_setting,
268 seq_stand_by_off,
269 };
270
271 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
272 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
273 if (ret)
274 break;
275 }
276
277 return ret;
278}
279
280static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
281{
282 int ret, i;
283 static const unsigned short *init_seq[] = {
284 seq_stand_by_off,
285 seq_display_on,
286 };
287
288 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
289 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
290 if (ret)
291 break;
292 }
293
294 return ret;
295}
296
297static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
298{
299 int ret, i;
300
301 static const unsigned short *init_seq[] = {
302 seq_display_off,
303 seq_stand_by_on,
304 };
305
306 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
307 ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
308 if (ret)
309 break;
310 }
311
312 return ret;
313}
314
315static int ams369fg06_power_is_on(int power)
316{
Jingoo Han66ecc112013-02-21 16:43:28 -0800317 return power <= FB_BLANK_NORMAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700318}
319
320static int ams369fg06_power_on(struct ams369fg06 *lcd)
321{
322 int ret = 0;
Jingoo Han66ecc112013-02-21 16:43:28 -0800323 struct lcd_platform_data *pd;
324 struct backlight_device *bd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700325
326 pd = lcd->lcd_pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700327 bd = lcd->bd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700328
329 if (!pd->power_on) {
330 dev_err(lcd->dev, "power_on is NULL.\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800331 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700332 } else {
333 pd->power_on(lcd->ld, 1);
Jingoo Han51976432013-02-21 16:43:27 -0800334 msleep(pd->power_on_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700335 }
336
337 if (!pd->reset) {
338 dev_err(lcd->dev, "reset is NULL.\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800339 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700340 } else {
341 pd->reset(lcd->ld);
Jingoo Han51976432013-02-21 16:43:27 -0800342 msleep(pd->reset_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700343 }
344
345 ret = ams369fg06_ldi_init(lcd);
346 if (ret) {
347 dev_err(lcd->dev, "failed to initialize ldi.\n");
348 return ret;
349 }
350
351 ret = ams369fg06_ldi_enable(lcd);
352 if (ret) {
353 dev_err(lcd->dev, "failed to enable ldi.\n");
354 return ret;
355 }
356
357 /* set brightness to current value after power on or resume. */
358 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
359 if (ret) {
360 dev_err(lcd->dev, "lcd gamma setting failed.\n");
361 return ret;
362 }
363
364 return 0;
365}
366
367static int ams369fg06_power_off(struct ams369fg06 *lcd)
368{
Jingoo Han66ecc112013-02-21 16:43:28 -0800369 int ret;
370 struct lcd_platform_data *pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700371
372 pd = lcd->lcd_pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700373
374 ret = ams369fg06_ldi_disable(lcd);
375 if (ret) {
376 dev_err(lcd->dev, "lcd setting failed.\n");
377 return -EIO;
378 }
379
Jingoo Han51976432013-02-21 16:43:27 -0800380 msleep(pd->power_off_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700381
Jingoo Han66ecc112013-02-21 16:43:28 -0800382 pd->power_on(lcd->ld, 0);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700383
384 return 0;
385}
386
387static int ams369fg06_power(struct ams369fg06 *lcd, int power)
388{
389 int ret = 0;
390
391 if (ams369fg06_power_is_on(power) &&
392 !ams369fg06_power_is_on(lcd->power))
393 ret = ams369fg06_power_on(lcd);
394 else if (!ams369fg06_power_is_on(power) &&
395 ams369fg06_power_is_on(lcd->power))
396 ret = ams369fg06_power_off(lcd);
397
398 if (!ret)
399 lcd->power = power;
400
401 return ret;
402}
403
404static int ams369fg06_get_power(struct lcd_device *ld)
405{
406 struct ams369fg06 *lcd = lcd_get_data(ld);
407
408 return lcd->power;
409}
410
411static int ams369fg06_set_power(struct lcd_device *ld, int power)
412{
413 struct ams369fg06 *lcd = lcd_get_data(ld);
414
415 if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
416 power != FB_BLANK_NORMAL) {
417 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
418 return -EINVAL;
419 }
420
421 return ams369fg06_power(lcd, power);
422}
423
424static int ams369fg06_get_brightness(struct backlight_device *bd)
425{
426 return bd->props.brightness;
427}
428
429static int ams369fg06_set_brightness(struct backlight_device *bd)
430{
431 int ret = 0;
432 int brightness = bd->props.brightness;
433 struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
434
435 if (brightness < MIN_BRIGHTNESS ||
436 brightness > bd->props.max_brightness) {
437 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
438 MIN_BRIGHTNESS, MAX_BRIGHTNESS);
439 return -EINVAL;
440 }
441
442 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
443 if (ret) {
444 dev_err(&bd->dev, "lcd brightness setting failed.\n");
445 return -EIO;
446 }
447
448 return ret;
449}
450
451static struct lcd_ops ams369fg06_lcd_ops = {
452 .get_power = ams369fg06_get_power,
453 .set_power = ams369fg06_set_power,
454};
455
456static const struct backlight_ops ams369fg06_backlight_ops = {
457 .get_brightness = ams369fg06_get_brightness,
458 .update_status = ams369fg06_set_brightness,
459};
460
Bill Pemberton1b9e4502012-11-19 13:21:46 -0500461static int ams369fg06_probe(struct spi_device *spi)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700462{
463 int ret = 0;
464 struct ams369fg06 *lcd = NULL;
465 struct lcd_device *ld = NULL;
466 struct backlight_device *bd = NULL;
Axel Linef22f6a2011-07-25 17:12:01 -0700467 struct backlight_properties props;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700468
Jingoo Han80629ef2012-05-29 15:07:21 -0700469 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700470 if (!lcd)
471 return -ENOMEM;
472
473 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
474 spi->bits_per_word = 16;
475
476 ret = spi_setup(spi);
477 if (ret < 0) {
478 dev_err(&spi->dev, "spi setup failed.\n");
Jingoo Han80629ef2012-05-29 15:07:21 -0700479 return ret;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700480 }
481
482 lcd->spi = spi;
483 lcd->dev = &spi->dev;
484
485 lcd->lcd_pd = spi->dev.platform_data;
486 if (!lcd->lcd_pd) {
487 dev_err(&spi->dev, "platform data is NULL\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800488 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700489 }
490
491 ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
492 &ams369fg06_lcd_ops);
Jingoo Han80629ef2012-05-29 15:07:21 -0700493 if (IS_ERR(ld))
494 return PTR_ERR(ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700495
496 lcd->ld = ld;
497
Axel Linef22f6a2011-07-25 17:12:01 -0700498 memset(&props, 0, sizeof(struct backlight_properties));
499 props.type = BACKLIGHT_RAW;
500 props.max_brightness = MAX_BRIGHTNESS;
501
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700502 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
Axel Linef22f6a2011-07-25 17:12:01 -0700503 &ams369fg06_backlight_ops, &props);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700504 if (IS_ERR(bd)) {
505 ret = PTR_ERR(bd);
506 goto out_lcd_unregister;
507 }
508
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700509 bd->props.brightness = DEFAULT_BRIGHTNESS;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700510 lcd->bd = bd;
511
512 if (!lcd->lcd_pd->lcd_enabled) {
513 /*
514 * if lcd panel was off from bootloader then
515 * current lcd status is powerdown and then
516 * it enables lcd panel.
517 */
518 lcd->power = FB_BLANK_POWERDOWN;
519
520 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
Jingoo Han66ecc112013-02-21 16:43:28 -0800521 } else {
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700522 lcd->power = FB_BLANK_UNBLANK;
Jingoo Han66ecc112013-02-21 16:43:28 -0800523 }
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700524
525 dev_set_drvdata(&spi->dev, lcd);
526
527 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
528
529 return 0;
530
531out_lcd_unregister:
532 lcd_device_unregister(ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700533 return ret;
534}
535
Bill Pemberton7e4b9d02012-11-19 13:26:34 -0500536static int ams369fg06_remove(struct spi_device *spi)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700537{
538 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
539
540 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
541 backlight_device_unregister(lcd->bd);
542 lcd_device_unregister(lcd->ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700543
544 return 0;
545}
546
547#if defined(CONFIG_PM)
548static unsigned int before_power;
549
550static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
551{
552 int ret = 0;
553 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
554
555 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
556
557 before_power = lcd->power;
558
559 /*
560 * when lcd panel is suspend, lcd panel becomes off
561 * regardless of status.
562 */
563 ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
564
565 return ret;
566}
567
568static int ams369fg06_resume(struct spi_device *spi)
569{
570 int ret = 0;
571 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
572
573 /*
574 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
575 * (at that time, before_power is FB_BLANK_UNBLANK) then
576 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
577 */
578 if (before_power == FB_BLANK_UNBLANK)
579 lcd->power = FB_BLANK_POWERDOWN;
580
581 dev_dbg(&spi->dev, "before_power = %d\n", before_power);
582
583 ret = ams369fg06_power(lcd, before_power);
584
585 return ret;
586}
587#else
588#define ams369fg06_suspend NULL
589#define ams369fg06_resume NULL
590#endif
591
592static void ams369fg06_shutdown(struct spi_device *spi)
593{
594 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
595
596 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
597}
598
599static struct spi_driver ams369fg06_driver = {
600 .driver = {
601 .name = "ams369fg06",
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700602 .owner = THIS_MODULE,
603 },
604 .probe = ams369fg06_probe,
Bill Pembertond1723fa2012-11-19 13:21:09 -0500605 .remove = ams369fg06_remove,
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700606 .shutdown = ams369fg06_shutdown,
607 .suspend = ams369fg06_suspend,
608 .resume = ams369fg06_resume,
609};
610
Axel Lin462dd832012-03-23 15:01:59 -0700611module_spi_driver(ams369fg06_driver);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700612
613MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
614MODULE_DESCRIPTION("ams369fg06 LCD Driver");
615MODULE_LICENSE("GPL");