]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/video/backlight/ams369fg06.c
5f897f99cc9b68c154207bfaad4461137977ea97
[karo-tx-linux.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                 pd->power_on(lcd->ld, 1);
322                 msleep(pd->power_on_delay);
323         }
324
325         if (!pd->reset) {
326                 dev_err(lcd->dev, "reset is NULL.\n");
327                 return -EINVAL;
328         }
329
330         pd->reset(lcd->ld);
331         msleep(pd->reset_delay);
332
333         ret = ams369fg06_ldi_init(lcd);
334         if (ret) {
335                 dev_err(lcd->dev, "failed to initialize ldi.\n");
336                 return ret;
337         }
338
339         ret = ams369fg06_ldi_enable(lcd);
340         if (ret) {
341                 dev_err(lcd->dev, "failed to enable ldi.\n");
342                 return ret;
343         }
344
345         /* set brightness to current value after power on or resume. */
346         ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
347         if (ret) {
348                 dev_err(lcd->dev, "lcd gamma setting failed.\n");
349                 return ret;
350         }
351
352         return 0;
353 }
354
355 static int ams369fg06_power_off(struct ams369fg06 *lcd)
356 {
357         int ret;
358         struct lcd_platform_data *pd;
359
360         pd = lcd->lcd_pd;
361
362         ret = ams369fg06_ldi_disable(lcd);
363         if (ret) {
364                 dev_err(lcd->dev, "lcd setting failed.\n");
365                 return -EIO;
366         }
367
368         msleep(pd->power_off_delay);
369
370         if (pd->power_on)
371                 pd->power_on(lcd->ld, 0);
372
373         return 0;
374 }
375
376 static int ams369fg06_power(struct ams369fg06 *lcd, int power)
377 {
378         int ret = 0;
379
380         if (ams369fg06_power_is_on(power) &&
381                 !ams369fg06_power_is_on(lcd->power))
382                 ret = ams369fg06_power_on(lcd);
383         else if (!ams369fg06_power_is_on(power) &&
384                 ams369fg06_power_is_on(lcd->power))
385                 ret = ams369fg06_power_off(lcd);
386
387         if (!ret)
388                 lcd->power = power;
389
390         return ret;
391 }
392
393 static int ams369fg06_get_power(struct lcd_device *ld)
394 {
395         struct ams369fg06 *lcd = lcd_get_data(ld);
396
397         return lcd->power;
398 }
399
400 static int ams369fg06_set_power(struct lcd_device *ld, int power)
401 {
402         struct ams369fg06 *lcd = lcd_get_data(ld);
403
404         if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
405                 power != FB_BLANK_NORMAL) {
406                 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
407                 return -EINVAL;
408         }
409
410         return ams369fg06_power(lcd, power);
411 }
412
413 static int ams369fg06_set_brightness(struct backlight_device *bd)
414 {
415         int ret = 0;
416         int brightness = bd->props.brightness;
417         struct ams369fg06 *lcd = bl_get_data(bd);
418
419         if (brightness < MIN_BRIGHTNESS ||
420                 brightness > bd->props.max_brightness) {
421                 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
422                         MIN_BRIGHTNESS, MAX_BRIGHTNESS);
423                 return -EINVAL;
424         }
425
426         ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
427         if (ret) {
428                 dev_err(&bd->dev, "lcd brightness setting failed.\n");
429                 return -EIO;
430         }
431
432         return ret;
433 }
434
435 static struct lcd_ops ams369fg06_lcd_ops = {
436         .get_power = ams369fg06_get_power,
437         .set_power = ams369fg06_set_power,
438 };
439
440 static const struct backlight_ops ams369fg06_backlight_ops = {
441         .update_status = ams369fg06_set_brightness,
442 };
443
444 static int ams369fg06_probe(struct spi_device *spi)
445 {
446         int ret = 0;
447         struct ams369fg06 *lcd = NULL;
448         struct lcd_device *ld = NULL;
449         struct backlight_device *bd = NULL;
450         struct backlight_properties props;
451
452         lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
453         if (!lcd)
454                 return -ENOMEM;
455
456         /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
457         spi->bits_per_word = 16;
458
459         ret = spi_setup(spi);
460         if (ret < 0) {
461                 dev_err(&spi->dev, "spi setup failed.\n");
462                 return ret;
463         }
464
465         lcd->spi = spi;
466         lcd->dev = &spi->dev;
467
468         lcd->lcd_pd = dev_get_platdata(&spi->dev);
469         if (!lcd->lcd_pd) {
470                 dev_err(&spi->dev, "platform data is NULL\n");
471                 return -EINVAL;
472         }
473
474         ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd,
475                                         &ams369fg06_lcd_ops);
476         if (IS_ERR(ld))
477                 return PTR_ERR(ld);
478
479         lcd->ld = ld;
480
481         memset(&props, 0, sizeof(struct backlight_properties));
482         props.type = BACKLIGHT_RAW;
483         props.max_brightness = MAX_BRIGHTNESS;
484
485         bd = devm_backlight_device_register(&spi->dev, "ams369fg06-bl",
486                                         &spi->dev, lcd,
487                                         &ams369fg06_backlight_ops, &props);
488         if (IS_ERR(bd))
489                 return PTR_ERR(bd);
490
491         bd->props.brightness = DEFAULT_BRIGHTNESS;
492         lcd->bd = bd;
493
494         if (!lcd->lcd_pd->lcd_enabled) {
495                 /*
496                  * if lcd panel was off from bootloader then
497                  * current lcd status is powerdown and then
498                  * it enables lcd panel.
499                  */
500                 lcd->power = FB_BLANK_POWERDOWN;
501
502                 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
503         } else {
504                 lcd->power = FB_BLANK_UNBLANK;
505         }
506
507         spi_set_drvdata(spi, lcd);
508
509         dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
510
511         return 0;
512 }
513
514 static int ams369fg06_remove(struct spi_device *spi)
515 {
516         struct ams369fg06 *lcd = spi_get_drvdata(spi);
517
518         ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
519         return 0;
520 }
521
522 #ifdef CONFIG_PM_SLEEP
523 static int ams369fg06_suspend(struct device *dev)
524 {
525         struct ams369fg06 *lcd = dev_get_drvdata(dev);
526
527         dev_dbg(dev, "lcd->power = %d\n", lcd->power);
528
529         /*
530          * when lcd panel is suspend, lcd panel becomes off
531          * regardless of status.
532          */
533         return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
534 }
535
536 static int ams369fg06_resume(struct device *dev)
537 {
538         struct ams369fg06 *lcd = dev_get_drvdata(dev);
539
540         lcd->power = FB_BLANK_POWERDOWN;
541
542         return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
543 }
544 #endif
545
546 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
547                         ams369fg06_resume);
548
549 static void ams369fg06_shutdown(struct spi_device *spi)
550 {
551         struct ams369fg06 *lcd = spi_get_drvdata(spi);
552
553         ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
554 }
555
556 static struct spi_driver ams369fg06_driver = {
557         .driver = {
558                 .name   = "ams369fg06",
559                 .owner  = THIS_MODULE,
560                 .pm     = &ams369fg06_pm_ops,
561         },
562         .probe          = ams369fg06_probe,
563         .remove         = ams369fg06_remove,
564         .shutdown       = ams369fg06_shutdown,
565 };
566
567 module_spi_driver(ams369fg06_driver);
568
569 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
570 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
571 MODULE_LICENSE("GPL");