]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/video/backlight/ams369fg06.c
Merge tag 'char-misc-3.3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[mirror_ubuntu-artful-kernel.git] / drivers / video / backlight / ams369fg06.c
CommitLineData
a4c8aaa5
JH
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>
355b200b 25#include <linux/module.h>
a4c8aaa5
JH
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;
ef22f6a7 483 struct backlight_properties props;
a4c8aaa5
JH
484
485 lcd = kzalloc(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 goto out_free_lcd;
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 goto out_free_lcd;
505 }
506
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;
512 }
513
514 lcd->ld = ld;
515
ef22f6a7
AL
516 memset(&props, 0, sizeof(struct backlight_properties));
517 props.type = BACKLIGHT_RAW;
518 props.max_brightness = MAX_BRIGHTNESS;
519
a4c8aaa5 520 bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
ef22f6a7 521 &ams369fg06_backlight_ops, &props);
a4c8aaa5
JH
522 if (IS_ERR(bd)) {
523 ret = PTR_ERR(bd);
524 goto out_lcd_unregister;
525 }
526
a4c8aaa5 527 bd->props.brightness = DEFAULT_BRIGHTNESS;
a4c8aaa5
JH
528 lcd->bd = bd;
529
530 if (!lcd->lcd_pd->lcd_enabled) {
531 /*
532 * if lcd panel was off from bootloader then
533 * current lcd status is powerdown and then
534 * it enables lcd panel.
535 */
536 lcd->power = FB_BLANK_POWERDOWN;
537
538 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
539 } else
540 lcd->power = FB_BLANK_UNBLANK;
541
542 dev_set_drvdata(&spi->dev, lcd);
543
544 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
545
546 return 0;
547
548out_lcd_unregister:
549 lcd_device_unregister(ld);
550out_free_lcd:
551 kfree(lcd);
552 return ret;
553}
554
555static int __devexit ams369fg06_remove(struct spi_device *spi)
556{
557 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
558
559 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
560 backlight_device_unregister(lcd->bd);
561 lcd_device_unregister(lcd->ld);
562 kfree(lcd);
563
564 return 0;
565}
566
567#if defined(CONFIG_PM)
568static unsigned int before_power;
569
570static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
571{
572 int ret = 0;
573 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
574
575 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
576
577 before_power = lcd->power;
578
579 /*
580 * when lcd panel is suspend, lcd panel becomes off
581 * regardless of status.
582 */
583 ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
584
585 return ret;
586}
587
588static int ams369fg06_resume(struct spi_device *spi)
589{
590 int ret = 0;
591 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
592
593 /*
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.
597 */
598 if (before_power == FB_BLANK_UNBLANK)
599 lcd->power = FB_BLANK_POWERDOWN;
600
601 dev_dbg(&spi->dev, "before_power = %d\n", before_power);
602
603 ret = ams369fg06_power(lcd, before_power);
604
605 return ret;
606}
607#else
608#define ams369fg06_suspend NULL
609#define ams369fg06_resume NULL
610#endif
611
612static void ams369fg06_shutdown(struct spi_device *spi)
613{
614 struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
615
616 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
617}
618
619static struct spi_driver ams369fg06_driver = {
620 .driver = {
621 .name = "ams369fg06",
622 .bus = &spi_bus_type,
623 .owner = THIS_MODULE,
624 },
625 .probe = ams369fg06_probe,
626 .remove = __devexit_p(ams369fg06_remove),
627 .shutdown = ams369fg06_shutdown,
628 .suspend = ams369fg06_suspend,
629 .resume = ams369fg06_resume,
630};
631
632static int __init ams369fg06_init(void)
633{
634 return spi_register_driver(&ams369fg06_driver);
635}
636
637static void __exit ams369fg06_exit(void)
638{
639 spi_unregister_driver(&ams369fg06_driver);
640}
641
642module_init(ams369fg06_init);
643module_exit(ams369fg06_exit);
644
645MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
646MODULE_DESCRIPTION("ams369fg06 LCD Driver");
647MODULE_LICENSE("GPL");