bd8a320022002484962f8ec5ec85832a8a7e5101
[~shefty/rdma-dev.git] / drivers / video / backlight / ams369fg06.c
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
15 #include <linux/backlight.h>
16 #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>
23
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
37 struct 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
46 static const unsigned short seq_display_on[] = {
47         0x14, 0x03,
48         ENDDEF, 0x0000
49 };
50
51 static const unsigned short seq_display_off[] = {
52         0x14, 0x00,
53         ENDDEF, 0x0000
54 };
55
56 static const unsigned short seq_stand_by_on[] = {
57         0x1D, 0xA1,
58         SLEEPMSEC, 200,
59         ENDDEF, 0x0000
60 };
61
62 static const unsigned short seq_stand_by_off[] = {
63         0x1D, 0xA0,
64         SLEEPMSEC, 250,
65         ENDDEF, 0x0000
66 };
67
68 static 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 */
121 static 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
127 static 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
133 static 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
139 static 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
145 static 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
151 struct ams369fg06_gamma {
152         unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
153 };
154
155 static 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
163 static 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
181 static 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
194 static 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;
204                 } else {
205                         msleep(wbuf[i+1]);
206                 }
207                 i += 2;
208         }
209
210         return ret;
211 }
212
213 static 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
229 gamma_err:
230         return ret;
231 }
232
233 static 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
254 static 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
271 static 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
288 static 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
306 static int ams369fg06_power_is_on(int power)
307 {
308         return power <= FB_BLANK_NORMAL;
309 }
310
311 static int ams369fg06_power_on(struct ams369fg06 *lcd)
312 {
313         int ret = 0;
314         struct lcd_platform_data *pd;
315         struct backlight_device *bd;
316
317         pd = lcd->lcd_pd;
318         bd = lcd->bd;
319
320         if (!pd->power_on) {
321                 dev_err(lcd->dev, "power_on is NULL.\n");
322                 return -EINVAL;
323         } else {
324                 pd->power_on(lcd->ld, 1);
325                 msleep(pd->power_on_delay);
326         }
327
328         if (!pd->reset) {
329                 dev_err(lcd->dev, "reset is NULL.\n");
330                 return -EINVAL;
331         } else {
332                 pd->reset(lcd->ld);
333                 msleep(pd->reset_delay);
334         }
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
358 static int ams369fg06_power_off(struct ams369fg06 *lcd)
359 {
360         int ret;
361         struct lcd_platform_data *pd;
362
363         pd = lcd->lcd_pd;
364
365         ret = ams369fg06_ldi_disable(lcd);
366         if (ret) {
367                 dev_err(lcd->dev, "lcd setting failed.\n");
368                 return -EIO;
369         }
370
371         msleep(pd->power_off_delay);
372
373         pd->power_on(lcd->ld, 0);
374
375         return 0;
376 }
377
378 static 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
395 static int ams369fg06_get_power(struct lcd_device *ld)
396 {
397         struct ams369fg06 *lcd = lcd_get_data(ld);
398
399         return lcd->power;
400 }
401
402 static 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
415 static int ams369fg06_get_brightness(struct backlight_device *bd)
416 {
417         return bd->props.brightness;
418 }
419
420 static int ams369fg06_set_brightness(struct backlight_device *bd)
421 {
422         int ret = 0;
423         int brightness = bd->props.brightness;
424         struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
425
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
442 static struct lcd_ops ams369fg06_lcd_ops = {
443         .get_power = ams369fg06_get_power,
444         .set_power = ams369fg06_set_power,
445 };
446
447 static const struct backlight_ops ams369fg06_backlight_ops = {
448         .get_brightness = ams369fg06_get_brightness,
449         .update_status = ams369fg06_set_brightness,
450 };
451
452 static int ams369fg06_probe(struct spi_device *spi)
453 {
454         int ret = 0;
455         struct ams369fg06 *lcd = NULL;
456         struct lcd_device *ld = NULL;
457         struct backlight_device *bd = NULL;
458         struct backlight_properties props;
459
460         lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
461         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");
470                 return ret;
471         }
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");
479                 return -EINVAL;
480         }
481
482         ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
483                 &ams369fg06_lcd_ops);
484         if (IS_ERR(ld))
485                 return PTR_ERR(ld);
486
487         lcd->ld = ld;
488
489         memset(&props, 0, sizeof(struct backlight_properties));
490         props.type = BACKLIGHT_RAW;
491         props.max_brightness = MAX_BRIGHTNESS;
492
493         bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
494                 &ams369fg06_backlight_ops, &props);
495         if (IS_ERR(bd)) {
496                 ret =  PTR_ERR(bd);
497                 goto out_lcd_unregister;
498         }
499
500         bd->props.brightness = DEFAULT_BRIGHTNESS;
501         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);
512         } else {
513                 lcd->power = FB_BLANK_UNBLANK;
514         }
515
516         spi_set_drvdata(spi, lcd);
517
518         dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
519
520         return 0;
521
522 out_lcd_unregister:
523         lcd_device_unregister(ld);
524         return ret;
525 }
526
527 static int ams369fg06_remove(struct spi_device *spi)
528 {
529         struct ams369fg06 *lcd = spi_get_drvdata(spi);
530
531         ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
532         backlight_device_unregister(lcd->bd);
533         lcd_device_unregister(lcd->ld);
534
535         return 0;
536 }
537
538 #if defined(CONFIG_PM)
539 static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
540 {
541         struct ams369fg06 *lcd = spi_get_drvdata(spi);
542
543         dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
544
545         /*
546          * when lcd panel is suspend, lcd panel becomes off
547          * regardless of status.
548          */
549         return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
550 }
551
552 static int ams369fg06_resume(struct spi_device *spi)
553 {
554         struct ams369fg06 *lcd = spi_get_drvdata(spi);
555
556         lcd->power = FB_BLANK_POWERDOWN;
557
558         return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
559 }
560 #else
561 #define ams369fg06_suspend      NULL
562 #define ams369fg06_resume       NULL
563 #endif
564
565 static void ams369fg06_shutdown(struct spi_device *spi)
566 {
567         struct ams369fg06 *lcd = spi_get_drvdata(spi);
568
569         ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
570 }
571
572 static struct spi_driver ams369fg06_driver = {
573         .driver = {
574                 .name   = "ams369fg06",
575                 .owner  = THIS_MODULE,
576         },
577         .probe          = ams369fg06_probe,
578         .remove         = ams369fg06_remove,
579         .shutdown       = ams369fg06_shutdown,
580         .suspend        = ams369fg06_suspend,
581         .resume         = ams369fg06_resume,
582 };
583
584 module_spi_driver(ams369fg06_driver);
585
586 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
587 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
588 MODULE_LICENSE("GPL");