4794a9aba3fa77c71b885a2e710437c6e674a17d
[platform/adaptation/renesas_rcar/renesas_kernel.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  * 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>
25 #include <linux/module.h>
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
46 struct 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
55 static const unsigned short seq_display_on[] = {
56         0x14, 0x03,
57         ENDDEF, 0x0000
58 };
59
60 static const unsigned short seq_display_off[] = {
61         0x14, 0x00,
62         ENDDEF, 0x0000
63 };
64
65 static const unsigned short seq_stand_by_on[] = {
66         0x1D, 0xA1,
67         SLEEPMSEC, 200,
68         ENDDEF, 0x0000
69 };
70
71 static const unsigned short seq_stand_by_off[] = {
72         0x1D, 0xA0,
73         SLEEPMSEC, 250,
74         ENDDEF, 0x0000
75 };
76
77 static 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 */
130 static 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
136 static 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
142 static 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
148 static 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
154 static 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
160 struct ams369fg06_gamma {
161         unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
162 };
163
164 static 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
172 static 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
190 static 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
203 static 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                         msleep(wbuf[i+1]);
215                 }
216                 i += 2;
217         }
218
219         return ret;
220 }
221
222 static 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
238 gamma_err:
239         return ret;
240 }
241
242 static 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
263 static 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
280 static 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
297 static 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
315 static int ams369fg06_power_is_on(int power)
316 {
317         return power <= FB_BLANK_NORMAL;
318 }
319
320 static int ams369fg06_power_on(struct ams369fg06 *lcd)
321 {
322         int ret = 0;
323         struct lcd_platform_data *pd;
324         struct backlight_device *bd;
325
326         pd = lcd->lcd_pd;
327         bd = lcd->bd;
328
329         if (!pd->power_on) {
330                 dev_err(lcd->dev, "power_on is NULL.\n");
331                 return -EINVAL;
332         } else {
333                 pd->power_on(lcd->ld, 1);
334                 msleep(pd->power_on_delay);
335         }
336
337         if (!pd->reset) {
338                 dev_err(lcd->dev, "reset is NULL.\n");
339                 return -EINVAL;
340         } else {
341                 pd->reset(lcd->ld);
342                 msleep(pd->reset_delay);
343         }
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
367 static int ams369fg06_power_off(struct ams369fg06 *lcd)
368 {
369         int ret;
370         struct lcd_platform_data *pd;
371
372         pd = lcd->lcd_pd;
373
374         ret = ams369fg06_ldi_disable(lcd);
375         if (ret) {
376                 dev_err(lcd->dev, "lcd setting failed.\n");
377                 return -EIO;
378         }
379
380         msleep(pd->power_off_delay);
381
382         pd->power_on(lcd->ld, 0);
383
384         return 0;
385 }
386
387 static 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
404 static int ams369fg06_get_power(struct lcd_device *ld)
405 {
406         struct ams369fg06 *lcd = lcd_get_data(ld);
407
408         return lcd->power;
409 }
410
411 static 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
424 static int ams369fg06_get_brightness(struct backlight_device *bd)
425 {
426         return bd->props.brightness;
427 }
428
429 static 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
451 static struct lcd_ops ams369fg06_lcd_ops = {
452         .get_power = ams369fg06_get_power,
453         .set_power = ams369fg06_set_power,
454 };
455
456 static const struct backlight_ops ams369fg06_backlight_ops = {
457         .get_brightness = ams369fg06_get_brightness,
458         .update_status = ams369fg06_set_brightness,
459 };
460
461 static int ams369fg06_probe(struct spi_device *spi)
462 {
463         int ret = 0;
464         struct ams369fg06 *lcd = NULL;
465         struct lcd_device *ld = NULL;
466         struct backlight_device *bd = NULL;
467         struct backlight_properties props;
468
469         lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
470         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");
479                 return ret;
480         }
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");
488                 return -EINVAL;
489         }
490
491         ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
492                 &ams369fg06_lcd_ops);
493         if (IS_ERR(ld))
494                 return PTR_ERR(ld);
495
496         lcd->ld = ld;
497
498         memset(&props, 0, sizeof(struct backlight_properties));
499         props.type = BACKLIGHT_RAW;
500         props.max_brightness = MAX_BRIGHTNESS;
501
502         bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
503                 &ams369fg06_backlight_ops, &props);
504         if (IS_ERR(bd)) {
505                 ret =  PTR_ERR(bd);
506                 goto out_lcd_unregister;
507         }
508
509         bd->props.brightness = DEFAULT_BRIGHTNESS;
510         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);
521         } else {
522                 lcd->power = FB_BLANK_UNBLANK;
523         }
524
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
531 out_lcd_unregister:
532         lcd_device_unregister(ld);
533         return ret;
534 }
535
536 static int ams369fg06_remove(struct spi_device *spi)
537 {
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);
543
544         return 0;
545 }
546
547 #if defined(CONFIG_PM)
548 static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
549 {
550         struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
551
552         dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
553
554         /*
555          * when lcd panel is suspend, lcd panel becomes off
556          * regardless of status.
557          */
558         return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
559 }
560
561 static int ams369fg06_resume(struct spi_device *spi)
562 {
563         struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
564
565         lcd->power = FB_BLANK_POWERDOWN;
566
567         return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
568 }
569 #else
570 #define ams369fg06_suspend      NULL
571 #define ams369fg06_resume       NULL
572 #endif
573
574 static void ams369fg06_shutdown(struct spi_device *spi)
575 {
576         struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
577
578         ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
579 }
580
581 static struct spi_driver ams369fg06_driver = {
582         .driver = {
583                 .name   = "ams369fg06",
584                 .owner  = THIS_MODULE,
585         },
586         .probe          = ams369fg06_probe,
587         .remove         = ams369fg06_remove,
588         .shutdown       = ams369fg06_shutdown,
589         .suspend        = ams369fg06_suspend,
590         .resume         = ams369fg06_resume,
591 };
592
593 module_spi_driver(ams369fg06_driver);
594
595 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
596 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
597 MODULE_LICENSE("GPL");