blob: d29e49443f299cd6234d4f26167beac3bb6f2263 [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.
Jingoo Hana4c8aaa2011-07-25 17:12:00 -070013 */
14
Jingoo Hana4c8aaa2011-07-25 17:12:00 -070015#include <linux/backlight.h>
Jingoo Han1a5b1af2013-02-21 16:43:31 -080016#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>
Jingoo Hana4c8aaa2011-07-25 17:12:00 -070023
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;
Jingoo Han51976432013-02-21 16:43:27 -0800204 } else {
205 msleep(wbuf[i+1]);
206 }
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700207 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{
Jingoo Han66ecc112013-02-21 16:43:28 -0800308 return power <= FB_BLANK_NORMAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700309}
310
311static int ams369fg06_power_on(struct ams369fg06 *lcd)
312{
313 int ret = 0;
Jingoo Han66ecc112013-02-21 16:43:28 -0800314 struct lcd_platform_data *pd;
315 struct backlight_device *bd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700316
317 pd = lcd->lcd_pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700318 bd = lcd->bd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700319
320 if (!pd->power_on) {
321 dev_err(lcd->dev, "power_on is NULL.\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800322 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700323 } else {
324 pd->power_on(lcd->ld, 1);
Jingoo Han51976432013-02-21 16:43:27 -0800325 msleep(pd->power_on_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700326 }
327
328 if (!pd->reset) {
329 dev_err(lcd->dev, "reset is NULL.\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800330 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700331 } else {
332 pd->reset(lcd->ld);
Jingoo Han51976432013-02-21 16:43:27 -0800333 msleep(pd->reset_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700334 }
335
336 ret = ams369fg06_ldi_init(lcd);
337 if (ret) {
338 dev_err(lcd->dev, "failed to initialize ldi.\n");
339 return ret;
340 }
341
342 ret = ams369fg06_ldi_enable(lcd);
343 if (ret) {
344 dev_err(lcd->dev, "failed to enable ldi.\n");
345 return ret;
346 }
347
348 /* set brightness to current value after power on or resume. */
349 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
350 if (ret) {
351 dev_err(lcd->dev, "lcd gamma setting failed.\n");
352 return ret;
353 }
354
355 return 0;
356}
357
358static int ams369fg06_power_off(struct ams369fg06 *lcd)
359{
Jingoo Han66ecc112013-02-21 16:43:28 -0800360 int ret;
361 struct lcd_platform_data *pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700362
363 pd = lcd->lcd_pd;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700364
365 ret = ams369fg06_ldi_disable(lcd);
366 if (ret) {
367 dev_err(lcd->dev, "lcd setting failed.\n");
368 return -EIO;
369 }
370
Jingoo Han51976432013-02-21 16:43:27 -0800371 msleep(pd->power_off_delay);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700372
Jingoo Han66ecc112013-02-21 16:43:28 -0800373 pd->power_on(lcd->ld, 0);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700374
375 return 0;
376}
377
378static int ams369fg06_power(struct ams369fg06 *lcd, int power)
379{
380 int ret = 0;
381
382 if (ams369fg06_power_is_on(power) &&
383 !ams369fg06_power_is_on(lcd->power))
384 ret = ams369fg06_power_on(lcd);
385 else if (!ams369fg06_power_is_on(power) &&
386 ams369fg06_power_is_on(lcd->power))
387 ret = ams369fg06_power_off(lcd);
388
389 if (!ret)
390 lcd->power = power;
391
392 return ret;
393}
394
395static int ams369fg06_get_power(struct lcd_device *ld)
396{
397 struct ams369fg06 *lcd = lcd_get_data(ld);
398
399 return lcd->power;
400}
401
402static int ams369fg06_set_power(struct lcd_device *ld, int power)
403{
404 struct ams369fg06 *lcd = lcd_get_data(ld);
405
406 if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
407 power != FB_BLANK_NORMAL) {
408 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
409 return -EINVAL;
410 }
411
412 return ams369fg06_power(lcd, power);
413}
414
415static int ams369fg06_get_brightness(struct backlight_device *bd)
416{
417 return bd->props.brightness;
418}
419
420static int ams369fg06_set_brightness(struct backlight_device *bd)
421{
422 int ret = 0;
423 int brightness = bd->props.brightness;
Jingoo Han232f5a002013-02-21 16:43:47 -0800424 struct ams369fg06 *lcd = bl_get_data(bd);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700425
426 if (brightness < MIN_BRIGHTNESS ||
427 brightness > bd->props.max_brightness) {
428 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
429 MIN_BRIGHTNESS, MAX_BRIGHTNESS);
430 return -EINVAL;
431 }
432
433 ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
434 if (ret) {
435 dev_err(&bd->dev, "lcd brightness setting failed.\n");
436 return -EIO;
437 }
438
439 return ret;
440}
441
442static struct lcd_ops ams369fg06_lcd_ops = {
443 .get_power = ams369fg06_get_power,
444 .set_power = ams369fg06_set_power,
445};
446
447static const struct backlight_ops ams369fg06_backlight_ops = {
448 .get_brightness = ams369fg06_get_brightness,
449 .update_status = ams369fg06_set_brightness,
450};
451
Bill Pemberton1b9e4502012-11-19 13:21:46 -0500452static int ams369fg06_probe(struct spi_device *spi)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700453{
454 int ret = 0;
455 struct ams369fg06 *lcd = NULL;
456 struct lcd_device *ld = NULL;
457 struct backlight_device *bd = NULL;
Axel Linef22f6a2011-07-25 17:12:01 -0700458 struct backlight_properties props;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700459
Jingoo Han80629ef2012-05-29 15:07:21 -0700460 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700461 if (!lcd)
462 return -ENOMEM;
463
464 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
465 spi->bits_per_word = 16;
466
467 ret = spi_setup(spi);
468 if (ret < 0) {
469 dev_err(&spi->dev, "spi setup failed.\n");
Jingoo Han80629ef2012-05-29 15:07:21 -0700470 return ret;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700471 }
472
473 lcd->spi = spi;
474 lcd->dev = &spi->dev;
475
476 lcd->lcd_pd = spi->dev.platform_data;
477 if (!lcd->lcd_pd) {
478 dev_err(&spi->dev, "platform data is NULL\n");
Jingoo Han1d7976b2013-02-21 16:43:29 -0800479 return -EINVAL;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700480 }
481
482 ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
483 &ams369fg06_lcd_ops);
Jingoo Han80629ef2012-05-29 15:07:21 -0700484 if (IS_ERR(ld))
485 return PTR_ERR(ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700486
487 lcd->ld = ld;
488
Axel Linef22f6a2011-07-25 17:12:01 -0700489 memset(&props, 0, sizeof(struct backlight_properties));
490 props.type = BACKLIGHT_RAW;
491 props.max_brightness = MAX_BRIGHTNESS;
492
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700493 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
Axel Linef22f6a2011-07-25 17:12:01 -0700494 &ams369fg06_backlight_ops, &props);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700495 if (IS_ERR(bd)) {
496 ret = PTR_ERR(bd);
497 goto out_lcd_unregister;
498 }
499
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700500 bd->props.brightness = DEFAULT_BRIGHTNESS;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700501 lcd->bd = bd;
502
503 if (!lcd->lcd_pd->lcd_enabled) {
504 /*
505 * if lcd panel was off from bootloader then
506 * current lcd status is powerdown and then
507 * it enables lcd panel.
508 */
509 lcd->power = FB_BLANK_POWERDOWN;
510
511 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
Jingoo Han66ecc112013-02-21 16:43:28 -0800512 } else {
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700513 lcd->power = FB_BLANK_UNBLANK;
Jingoo Han66ecc112013-02-21 16:43:28 -0800514 }
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700515
Jingoo Hanc7855f152013-02-21 16:43:39 -0800516 spi_set_drvdata(spi, lcd);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700517
518 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
519
520 return 0;
521
522out_lcd_unregister:
523 lcd_device_unregister(ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700524 return ret;
525}
526
Bill Pemberton7e4b9d02012-11-19 13:26:34 -0500527static int ams369fg06_remove(struct spi_device *spi)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700528{
Jingoo Hanc7855f152013-02-21 16:43:39 -0800529 struct ams369fg06 *lcd = spi_get_drvdata(spi);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700530
531 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
532 backlight_device_unregister(lcd->bd);
533 lcd_device_unregister(lcd->ld);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700534
535 return 0;
536}
537
538#if defined(CONFIG_PM)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700539static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
540{
Jingoo Hanc7855f152013-02-21 16:43:39 -0800541 struct ams369fg06 *lcd = spi_get_drvdata(spi);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700542
543 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
544
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700545 /*
546 * when lcd panel is suspend, lcd panel becomes off
547 * regardless of status.
548 */
Jingoo Hanc9023492013-02-21 16:43:30 -0800549 return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700550}
551
552static int ams369fg06_resume(struct spi_device *spi)
553{
Jingoo Hanc7855f152013-02-21 16:43:39 -0800554 struct ams369fg06 *lcd = spi_get_drvdata(spi);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700555
Jingoo Hanc9023492013-02-21 16:43:30 -0800556 lcd->power = FB_BLANK_POWERDOWN;
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700557
Jingoo Hanc9023492013-02-21 16:43:30 -0800558 return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700559}
560#else
561#define ams369fg06_suspend NULL
562#define ams369fg06_resume NULL
563#endif
564
565static void ams369fg06_shutdown(struct spi_device *spi)
566{
Jingoo Hanc7855f152013-02-21 16:43:39 -0800567 struct ams369fg06 *lcd = spi_get_drvdata(spi);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700568
569 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
570}
571
572static struct spi_driver ams369fg06_driver = {
573 .driver = {
574 .name = "ams369fg06",
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700575 .owner = THIS_MODULE,
576 },
577 .probe = ams369fg06_probe,
Bill Pembertond1723fa2012-11-19 13:21:09 -0500578 .remove = ams369fg06_remove,
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700579 .shutdown = ams369fg06_shutdown,
580 .suspend = ams369fg06_suspend,
581 .resume = ams369fg06_resume,
582};
583
Axel Lin462dd832012-03-23 15:01:59 -0700584module_spi_driver(ams369fg06_driver);
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700585
586MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
587MODULE_DESCRIPTION("ams369fg06 LCD Driver");
588MODULE_LICENSE("GPL");