blob: cff45152dee3506fc0401b440e5b7501bab85478 [file] [log] [blame]
InKi Daeee378a52010-05-24 12:21:36 -07001/*
2 * S6E63M0 AMOLED LCD panel driver.
3 *
4 * Author: InKi Dae <inki.dae@samsung.com>
5 *
6 * Derived from drivers/video/omap/lcd-apollon.c
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23#include <linux/wait.h>
24#include <linux/fb.h>
25#include <linux/delay.h>
26#include <linux/gpio.h>
27#include <linux/spi/spi.h>
28#include <linux/irq.h>
29#include <linux/interrupt.h>
30#include <linux/kernel.h>
31#include <linux/lcd.h>
32#include <linux/backlight.h>
Paul Gortmaker355b2002011-07-03 16:17:28 -040033#include <linux/module.h>
InKi Daeee378a52010-05-24 12:21:36 -070034
35#include "s6e63m0_gamma.h"
36
37#define SLEEPMSEC 0x1000
38#define ENDDEF 0x2000
39#define DEFMASK 0xFF00
40#define COMMAND_ONLY 0xFE
41#define DATA_ONLY 0xFF
42
43#define MIN_BRIGHTNESS 0
44#define MAX_BRIGHTNESS 10
45
46#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
47
48struct s6e63m0 {
49 struct device *dev;
50 struct spi_device *spi;
51 unsigned int power;
52 unsigned int current_brightness;
53 unsigned int gamma_mode;
54 unsigned int gamma_table_count;
55 struct lcd_device *ld;
56 struct backlight_device *bd;
57 struct lcd_platform_data *lcd_pd;
58};
59
Jingoo Han4a959e52013-02-21 16:43:20 -080060static const unsigned short seq_panel_condition_set[] = {
InKi Daeee378a52010-05-24 12:21:36 -070061 0xF8, 0x01,
62 DATA_ONLY, 0x27,
63 DATA_ONLY, 0x27,
64 DATA_ONLY, 0x07,
65 DATA_ONLY, 0x07,
66 DATA_ONLY, 0x54,
67 DATA_ONLY, 0x9f,
68 DATA_ONLY, 0x63,
69 DATA_ONLY, 0x86,
70 DATA_ONLY, 0x1a,
71 DATA_ONLY, 0x33,
72 DATA_ONLY, 0x0d,
73 DATA_ONLY, 0x00,
74 DATA_ONLY, 0x00,
75
76 ENDDEF, 0x0000
77};
78
Jingoo Han4a959e52013-02-21 16:43:20 -080079static const unsigned short seq_display_condition_set[] = {
InKi Daeee378a52010-05-24 12:21:36 -070080 0xf2, 0x02,
81 DATA_ONLY, 0x03,
82 DATA_ONLY, 0x1c,
83 DATA_ONLY, 0x10,
84 DATA_ONLY, 0x10,
85
86 0xf7, 0x03,
87 DATA_ONLY, 0x00,
88 DATA_ONLY, 0x00,
89
90 ENDDEF, 0x0000
91};
92
Jingoo Han4a959e52013-02-21 16:43:20 -080093static const unsigned short seq_gamma_setting[] = {
InKi Daeee378a52010-05-24 12:21:36 -070094 0xfa, 0x00,
95 DATA_ONLY, 0x18,
96 DATA_ONLY, 0x08,
97 DATA_ONLY, 0x24,
98 DATA_ONLY, 0x64,
99 DATA_ONLY, 0x56,
100 DATA_ONLY, 0x33,
101 DATA_ONLY, 0xb6,
102 DATA_ONLY, 0xba,
103 DATA_ONLY, 0xa8,
104 DATA_ONLY, 0xac,
105 DATA_ONLY, 0xb1,
106 DATA_ONLY, 0x9d,
107 DATA_ONLY, 0xc1,
108 DATA_ONLY, 0xc1,
109 DATA_ONLY, 0xb7,
110 DATA_ONLY, 0x00,
111 DATA_ONLY, 0x9c,
112 DATA_ONLY, 0x00,
113 DATA_ONLY, 0x9f,
114 DATA_ONLY, 0x00,
115 DATA_ONLY, 0xd6,
116
117 0xfa, 0x01,
118
119 ENDDEF, 0x0000
120};
121
Jingoo Han4a959e52013-02-21 16:43:20 -0800122static const unsigned short seq_etc_condition_set[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700123 0xf6, 0x00,
124 DATA_ONLY, 0x8c,
125 DATA_ONLY, 0x07,
126
127 0xb3, 0xc,
128
129 0xb5, 0x2c,
130 DATA_ONLY, 0x12,
131 DATA_ONLY, 0x0c,
132 DATA_ONLY, 0x0a,
133 DATA_ONLY, 0x10,
134 DATA_ONLY, 0x0e,
135 DATA_ONLY, 0x17,
136 DATA_ONLY, 0x13,
137 DATA_ONLY, 0x1f,
138 DATA_ONLY, 0x1a,
139 DATA_ONLY, 0x2a,
140 DATA_ONLY, 0x24,
141 DATA_ONLY, 0x1f,
142 DATA_ONLY, 0x1b,
143 DATA_ONLY, 0x1a,
144 DATA_ONLY, 0x17,
145
146 DATA_ONLY, 0x2b,
147 DATA_ONLY, 0x26,
148 DATA_ONLY, 0x22,
149 DATA_ONLY, 0x20,
150 DATA_ONLY, 0x3a,
151 DATA_ONLY, 0x34,
152 DATA_ONLY, 0x30,
153 DATA_ONLY, 0x2c,
154 DATA_ONLY, 0x29,
155 DATA_ONLY, 0x26,
156 DATA_ONLY, 0x25,
157 DATA_ONLY, 0x23,
158 DATA_ONLY, 0x21,
159 DATA_ONLY, 0x20,
160 DATA_ONLY, 0x1e,
161 DATA_ONLY, 0x1e,
162
163 0xb6, 0x00,
164 DATA_ONLY, 0x00,
165 DATA_ONLY, 0x11,
166 DATA_ONLY, 0x22,
167 DATA_ONLY, 0x33,
168 DATA_ONLY, 0x44,
169 DATA_ONLY, 0x44,
170 DATA_ONLY, 0x44,
171
172 DATA_ONLY, 0x55,
173 DATA_ONLY, 0x55,
174 DATA_ONLY, 0x66,
175 DATA_ONLY, 0x66,
176 DATA_ONLY, 0x66,
177 DATA_ONLY, 0x66,
178 DATA_ONLY, 0x66,
179 DATA_ONLY, 0x66,
180
181 0xb7, 0x2c,
182 DATA_ONLY, 0x12,
183 DATA_ONLY, 0x0c,
184 DATA_ONLY, 0x0a,
185 DATA_ONLY, 0x10,
186 DATA_ONLY, 0x0e,
187 DATA_ONLY, 0x17,
188 DATA_ONLY, 0x13,
189 DATA_ONLY, 0x1f,
190 DATA_ONLY, 0x1a,
191 DATA_ONLY, 0x2a,
192 DATA_ONLY, 0x24,
193 DATA_ONLY, 0x1f,
194 DATA_ONLY, 0x1b,
195 DATA_ONLY, 0x1a,
196 DATA_ONLY, 0x17,
197
198 DATA_ONLY, 0x2b,
199 DATA_ONLY, 0x26,
200 DATA_ONLY, 0x22,
201 DATA_ONLY, 0x20,
202 DATA_ONLY, 0x3a,
203 DATA_ONLY, 0x34,
204 DATA_ONLY, 0x30,
205 DATA_ONLY, 0x2c,
206 DATA_ONLY, 0x29,
207 DATA_ONLY, 0x26,
208 DATA_ONLY, 0x25,
209 DATA_ONLY, 0x23,
210 DATA_ONLY, 0x21,
211 DATA_ONLY, 0x20,
212 DATA_ONLY, 0x1e,
213 DATA_ONLY, 0x1e,
214
215 0xb8, 0x00,
216 DATA_ONLY, 0x00,
217 DATA_ONLY, 0x11,
218 DATA_ONLY, 0x22,
219 DATA_ONLY, 0x33,
220 DATA_ONLY, 0x44,
221 DATA_ONLY, 0x44,
222 DATA_ONLY, 0x44,
223
224 DATA_ONLY, 0x55,
225 DATA_ONLY, 0x55,
226 DATA_ONLY, 0x66,
227 DATA_ONLY, 0x66,
228 DATA_ONLY, 0x66,
229 DATA_ONLY, 0x66,
230 DATA_ONLY, 0x66,
231 DATA_ONLY, 0x66,
232
233 0xb9, 0x2c,
234 DATA_ONLY, 0x12,
235 DATA_ONLY, 0x0c,
236 DATA_ONLY, 0x0a,
237 DATA_ONLY, 0x10,
238 DATA_ONLY, 0x0e,
239 DATA_ONLY, 0x17,
240 DATA_ONLY, 0x13,
241 DATA_ONLY, 0x1f,
242 DATA_ONLY, 0x1a,
243 DATA_ONLY, 0x2a,
244 DATA_ONLY, 0x24,
245 DATA_ONLY, 0x1f,
246 DATA_ONLY, 0x1b,
247 DATA_ONLY, 0x1a,
248 DATA_ONLY, 0x17,
249
250 DATA_ONLY, 0x2b,
251 DATA_ONLY, 0x26,
252 DATA_ONLY, 0x22,
253 DATA_ONLY, 0x20,
254 DATA_ONLY, 0x3a,
255 DATA_ONLY, 0x34,
256 DATA_ONLY, 0x30,
257 DATA_ONLY, 0x2c,
258 DATA_ONLY, 0x29,
259 DATA_ONLY, 0x26,
260 DATA_ONLY, 0x25,
261 DATA_ONLY, 0x23,
262 DATA_ONLY, 0x21,
263 DATA_ONLY, 0x20,
264 DATA_ONLY, 0x1e,
265 DATA_ONLY, 0x1e,
266
267 0xba, 0x00,
268 DATA_ONLY, 0x00,
269 DATA_ONLY, 0x11,
270 DATA_ONLY, 0x22,
271 DATA_ONLY, 0x33,
272 DATA_ONLY, 0x44,
273 DATA_ONLY, 0x44,
274 DATA_ONLY, 0x44,
275
276 DATA_ONLY, 0x55,
277 DATA_ONLY, 0x55,
278 DATA_ONLY, 0x66,
279 DATA_ONLY, 0x66,
280 DATA_ONLY, 0x66,
281 DATA_ONLY, 0x66,
282 DATA_ONLY, 0x66,
283 DATA_ONLY, 0x66,
284
285 0xc1, 0x4d,
286 DATA_ONLY, 0x96,
287 DATA_ONLY, 0x1d,
288 DATA_ONLY, 0x00,
289 DATA_ONLY, 0x00,
290 DATA_ONLY, 0x01,
291 DATA_ONLY, 0xdf,
292 DATA_ONLY, 0x00,
293 DATA_ONLY, 0x00,
294 DATA_ONLY, 0x03,
295 DATA_ONLY, 0x1f,
296 DATA_ONLY, 0x00,
297 DATA_ONLY, 0x00,
298 DATA_ONLY, 0x00,
299 DATA_ONLY, 0x00,
300 DATA_ONLY, 0x00,
301 DATA_ONLY, 0x00,
302 DATA_ONLY, 0x00,
303 DATA_ONLY, 0x00,
304 DATA_ONLY, 0x03,
305 DATA_ONLY, 0x06,
306 DATA_ONLY, 0x09,
307 DATA_ONLY, 0x0d,
308 DATA_ONLY, 0x0f,
309 DATA_ONLY, 0x12,
310 DATA_ONLY, 0x15,
311 DATA_ONLY, 0x18,
312
313 0xb2, 0x10,
314 DATA_ONLY, 0x10,
315 DATA_ONLY, 0x0b,
316 DATA_ONLY, 0x05,
317
318 ENDDEF, 0x0000
319};
320
Jingoo Han4a959e52013-02-21 16:43:20 -0800321static const unsigned short seq_acl_on[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700322 /* ACL on */
323 0xc0, 0x01,
324
325 ENDDEF, 0x0000
326};
327
Jingoo Han4a959e52013-02-21 16:43:20 -0800328static const unsigned short seq_acl_off[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700329 /* ACL off */
330 0xc0, 0x00,
331
332 ENDDEF, 0x0000
333};
334
Jingoo Han4a959e52013-02-21 16:43:20 -0800335static const unsigned short seq_elvss_on[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700336 /* ELVSS on */
337 0xb1, 0x0b,
338
339 ENDDEF, 0x0000
340};
341
Jingoo Han4a959e52013-02-21 16:43:20 -0800342static const unsigned short seq_elvss_off[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700343 /* ELVSS off */
344 0xb1, 0x0a,
345
346 ENDDEF, 0x0000
347};
348
Jingoo Han4a959e52013-02-21 16:43:20 -0800349static const unsigned short seq_stand_by_off[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700350 0x11, COMMAND_ONLY,
351
352 ENDDEF, 0x0000
353};
354
Jingoo Han4a959e52013-02-21 16:43:20 -0800355static const unsigned short seq_stand_by_on[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700356 0x10, COMMAND_ONLY,
357
358 ENDDEF, 0x0000
359};
360
Jingoo Han4a959e52013-02-21 16:43:20 -0800361static const unsigned short seq_display_on[] = {
InKi Daeee378a52010-05-24 12:21:36 -0700362 0x29, COMMAND_ONLY,
363
364 ENDDEF, 0x0000
365};
366
367
368static int s6e63m0_spi_write_byte(struct s6e63m0 *lcd, int addr, int data)
369{
370 u16 buf[1];
371 struct spi_message msg;
372
373 struct spi_transfer xfer = {
374 .len = 2,
375 .tx_buf = buf,
376 };
377
378 buf[0] = (addr << 8) | data;
379
380 spi_message_init(&msg);
381 spi_message_add_tail(&xfer, &msg);
382
383 return spi_sync(lcd->spi, &msg);
384}
385
386static int s6e63m0_spi_write(struct s6e63m0 *lcd, unsigned char address,
387 unsigned char command)
388{
389 int ret = 0;
390
391 if (address != DATA_ONLY)
392 ret = s6e63m0_spi_write_byte(lcd, 0x0, address);
393 if (command != COMMAND_ONLY)
394 ret = s6e63m0_spi_write_byte(lcd, 0x1, command);
395
396 return ret;
397}
398
399static int s6e63m0_panel_send_sequence(struct s6e63m0 *lcd,
400 const unsigned short *wbuf)
401{
402 int ret = 0, i = 0;
403
404 while ((wbuf[i] & DEFMASK) != ENDDEF) {
405 if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
406 ret = s6e63m0_spi_write(lcd, wbuf[i], wbuf[i+1]);
407 if (ret)
408 break;
Jingoo Han97a4d9d2013-02-21 16:43:21 -0800409 } else {
410 msleep(wbuf[i+1]);
411 }
InKi Daeee378a52010-05-24 12:21:36 -0700412 i += 2;
413 }
414
415 return ret;
416}
417
418static int _s6e63m0_gamma_ctl(struct s6e63m0 *lcd, const unsigned int *gamma)
419{
420 unsigned int i = 0;
421 int ret = 0;
422
423 /* disable gamma table updating. */
424 ret = s6e63m0_spi_write(lcd, 0xfa, 0x00);
425 if (ret) {
426 dev_err(lcd->dev, "failed to disable gamma table updating.\n");
427 goto gamma_err;
428 }
429
430 for (i = 0 ; i < GAMMA_TABLE_COUNT; i++) {
431 ret = s6e63m0_spi_write(lcd, DATA_ONLY, gamma[i]);
432 if (ret) {
433 dev_err(lcd->dev, "failed to set gamma table.\n");
434 goto gamma_err;
435 }
436 }
437
438 /* update gamma table. */
439 ret = s6e63m0_spi_write(lcd, 0xfa, 0x01);
440 if (ret)
441 dev_err(lcd->dev, "failed to update gamma table.\n");
442
443gamma_err:
444 return ret;
445}
446
447static int s6e63m0_gamma_ctl(struct s6e63m0 *lcd, int gamma)
448{
449 int ret = 0;
450
451 ret = _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
452
453 return ret;
454}
455
456
457static int s6e63m0_ldi_init(struct s6e63m0 *lcd)
458{
459 int ret, i;
460 const unsigned short *init_seq[] = {
Jingoo Han4a959e52013-02-21 16:43:20 -0800461 seq_panel_condition_set,
462 seq_display_condition_set,
463 seq_gamma_setting,
464 seq_etc_condition_set,
465 seq_acl_on,
466 seq_elvss_on,
InKi Daeee378a52010-05-24 12:21:36 -0700467 };
468
469 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
470 ret = s6e63m0_panel_send_sequence(lcd, init_seq[i]);
471 if (ret)
472 break;
473 }
474
475 return ret;
476}
477
478static int s6e63m0_ldi_enable(struct s6e63m0 *lcd)
479{
480 int ret = 0, i;
481 const unsigned short *enable_seq[] = {
Jingoo Han4a959e52013-02-21 16:43:20 -0800482 seq_stand_by_off,
483 seq_display_on,
InKi Daeee378a52010-05-24 12:21:36 -0700484 };
485
486 for (i = 0; i < ARRAY_SIZE(enable_seq); i++) {
487 ret = s6e63m0_panel_send_sequence(lcd, enable_seq[i]);
488 if (ret)
489 break;
490 }
491
492 return ret;
493}
494
495static int s6e63m0_ldi_disable(struct s6e63m0 *lcd)
496{
497 int ret;
498
Jingoo Han4a959e52013-02-21 16:43:20 -0800499 ret = s6e63m0_panel_send_sequence(lcd, seq_stand_by_on);
InKi Daeee378a52010-05-24 12:21:36 -0700500
501 return ret;
502}
503
504static int s6e63m0_power_on(struct s6e63m0 *lcd)
505{
506 int ret = 0;
507 struct lcd_platform_data *pd = NULL;
508 struct backlight_device *bd = NULL;
509
510 pd = lcd->lcd_pd;
511 if (!pd) {
512 dev_err(lcd->dev, "platform data is NULL.\n");
513 return -EFAULT;
514 }
515
516 bd = lcd->bd;
517 if (!bd) {
518 dev_err(lcd->dev, "backlight device is NULL.\n");
519 return -EFAULT;
520 }
521
522 if (!pd->power_on) {
523 dev_err(lcd->dev, "power_on is NULL.\n");
524 return -EFAULT;
525 } else {
526 pd->power_on(lcd->ld, 1);
Jingoo Han97a4d9d2013-02-21 16:43:21 -0800527 msleep(pd->power_on_delay);
InKi Daeee378a52010-05-24 12:21:36 -0700528 }
529
530 if (!pd->reset) {
531 dev_err(lcd->dev, "reset is NULL.\n");
532 return -EFAULT;
533 } else {
534 pd->reset(lcd->ld);
Jingoo Han97a4d9d2013-02-21 16:43:21 -0800535 msleep(pd->reset_delay);
InKi Daeee378a52010-05-24 12:21:36 -0700536 }
537
538 ret = s6e63m0_ldi_init(lcd);
539 if (ret) {
540 dev_err(lcd->dev, "failed to initialize ldi.\n");
541 return ret;
542 }
543
544 ret = s6e63m0_ldi_enable(lcd);
545 if (ret) {
546 dev_err(lcd->dev, "failed to enable ldi.\n");
547 return ret;
548 }
549
550 /* set brightness to current value after power on or resume. */
551 ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
552 if (ret) {
553 dev_err(lcd->dev, "lcd gamma setting failed.\n");
554 return ret;
555 }
556
557 return 0;
558}
559
560static int s6e63m0_power_off(struct s6e63m0 *lcd)
561{
562 int ret = 0;
563 struct lcd_platform_data *pd = NULL;
564
565 pd = lcd->lcd_pd;
566 if (!pd) {
567 dev_err(lcd->dev, "platform data is NULL.\n");
568 return -EFAULT;
569 }
570
571 ret = s6e63m0_ldi_disable(lcd);
572 if (ret) {
573 dev_err(lcd->dev, "lcd setting failed.\n");
574 return -EIO;
575 }
576
Jingoo Han97a4d9d2013-02-21 16:43:21 -0800577 msleep(pd->power_off_delay);
InKi Daeee378a52010-05-24 12:21:36 -0700578
579 if (!pd->power_on) {
580 dev_err(lcd->dev, "power_on is NULL.\n");
581 return -EFAULT;
582 } else
583 pd->power_on(lcd->ld, 0);
584
585 return 0;
586}
587
588static int s6e63m0_power(struct s6e63m0 *lcd, int power)
589{
590 int ret = 0;
591
592 if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
593 ret = s6e63m0_power_on(lcd);
594 else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
595 ret = s6e63m0_power_off(lcd);
596
597 if (!ret)
598 lcd->power = power;
599
600 return ret;
601}
602
603static int s6e63m0_set_power(struct lcd_device *ld, int power)
604{
605 struct s6e63m0 *lcd = lcd_get_data(ld);
606
607 if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
608 power != FB_BLANK_NORMAL) {
609 dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
610 return -EINVAL;
611 }
612
613 return s6e63m0_power(lcd, power);
614}
615
616static int s6e63m0_get_power(struct lcd_device *ld)
617{
618 struct s6e63m0 *lcd = lcd_get_data(ld);
619
620 return lcd->power;
621}
622
623static int s6e63m0_get_brightness(struct backlight_device *bd)
624{
625 return bd->props.brightness;
626}
627
628static int s6e63m0_set_brightness(struct backlight_device *bd)
629{
630 int ret = 0, brightness = bd->props.brightness;
631 struct s6e63m0 *lcd = bl_get_data(bd);
632
633 if (brightness < MIN_BRIGHTNESS ||
634 brightness > bd->props.max_brightness) {
635 dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
636 MIN_BRIGHTNESS, MAX_BRIGHTNESS);
637 return -EINVAL;
638 }
639
640 ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
641 if (ret) {
642 dev_err(&bd->dev, "lcd brightness setting failed.\n");
643 return -EIO;
644 }
645
646 return ret;
647}
648
649static struct lcd_ops s6e63m0_lcd_ops = {
650 .set_power = s6e63m0_set_power,
651 .get_power = s6e63m0_get_power,
652};
653
654static const struct backlight_ops s6e63m0_backlight_ops = {
655 .get_brightness = s6e63m0_get_brightness,
656 .update_status = s6e63m0_set_brightness,
657};
658
659static ssize_t s6e63m0_sysfs_show_gamma_mode(struct device *dev,
660 struct device_attribute *attr, char *buf)
661{
662 struct s6e63m0 *lcd = dev_get_drvdata(dev);
663 char temp[10];
664
665 switch (lcd->gamma_mode) {
666 case 0:
667 sprintf(temp, "2.2 mode\n");
668 strcat(buf, temp);
669 break;
670 case 1:
671 sprintf(temp, "1.9 mode\n");
672 strcat(buf, temp);
673 break;
674 case 2:
675 sprintf(temp, "1.7 mode\n");
676 strcat(buf, temp);
677 break;
678 default:
679 dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7)n");
680 break;
681 }
682
683 return strlen(buf);
684}
685
686static ssize_t s6e63m0_sysfs_store_gamma_mode(struct device *dev,
687 struct device_attribute *attr,
688 const char *buf, size_t len)
689{
690 struct s6e63m0 *lcd = dev_get_drvdata(dev);
691 struct backlight_device *bd = NULL;
692 int brightness, rc;
693
Dan Carpentercf2b94d2012-03-15 15:17:12 -0700694 rc = kstrtouint(buf, 0, &lcd->gamma_mode);
InKi Daeee378a52010-05-24 12:21:36 -0700695 if (rc < 0)
696 return rc;
697
698 bd = lcd->bd;
699
700 brightness = bd->props.brightness;
701
702 switch (lcd->gamma_mode) {
703 case 0:
704 _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
705 break;
706 case 1:
707 _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_19_table[brightness]);
708 break;
709 case 2:
710 _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_17_table[brightness]);
711 break;
712 default:
713 dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7\n");
714 _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
715 break;
716 }
717 return len;
718}
719
720static DEVICE_ATTR(gamma_mode, 0644,
721 s6e63m0_sysfs_show_gamma_mode, s6e63m0_sysfs_store_gamma_mode);
722
723static ssize_t s6e63m0_sysfs_show_gamma_table(struct device *dev,
724 struct device_attribute *attr, char *buf)
725{
726 struct s6e63m0 *lcd = dev_get_drvdata(dev);
727 char temp[3];
728
729 sprintf(temp, "%d\n", lcd->gamma_table_count);
730 strcpy(buf, temp);
731
732 return strlen(buf);
733}
Axel Lined3a6782010-11-11 14:05:25 -0800734static DEVICE_ATTR(gamma_table, 0444,
InKi Daeee378a52010-05-24 12:21:36 -0700735 s6e63m0_sysfs_show_gamma_table, NULL);
736
Bill Pemberton1b9e4502012-11-19 13:21:46 -0500737static int s6e63m0_probe(struct spi_device *spi)
InKi Daeee378a52010-05-24 12:21:36 -0700738{
739 int ret = 0;
740 struct s6e63m0 *lcd = NULL;
741 struct lcd_device *ld = NULL;
742 struct backlight_device *bd = NULL;
Axel Linef22f6a2011-07-25 17:12:01 -0700743 struct backlight_properties props;
InKi Daeee378a52010-05-24 12:21:36 -0700744
Jingoo Han541f9362012-05-29 15:07:24 -0700745 lcd = devm_kzalloc(&spi->dev, sizeof(struct s6e63m0), GFP_KERNEL);
InKi Daeee378a52010-05-24 12:21:36 -0700746 if (!lcd)
747 return -ENOMEM;
748
749 /* s6e63m0 lcd panel uses 3-wire 9bits SPI Mode. */
750 spi->bits_per_word = 9;
751
752 ret = spi_setup(spi);
753 if (ret < 0) {
754 dev_err(&spi->dev, "spi setup failed.\n");
Jingoo Han541f9362012-05-29 15:07:24 -0700755 return ret;
InKi Daeee378a52010-05-24 12:21:36 -0700756 }
757
758 lcd->spi = spi;
759 lcd->dev = &spi->dev;
760
Jingoo Han3c48d1f2012-12-17 16:00:55 -0800761 lcd->lcd_pd = spi->dev.platform_data;
InKi Daeee378a52010-05-24 12:21:36 -0700762 if (!lcd->lcd_pd) {
763 dev_err(&spi->dev, "platform data is NULL.\n");
Jingoo Han541f9362012-05-29 15:07:24 -0700764 return -EFAULT;
InKi Daeee378a52010-05-24 12:21:36 -0700765 }
766
767 ld = lcd_device_register("s6e63m0", &spi->dev, lcd, &s6e63m0_lcd_ops);
Jingoo Han541f9362012-05-29 15:07:24 -0700768 if (IS_ERR(ld))
769 return PTR_ERR(ld);
InKi Daeee378a52010-05-24 12:21:36 -0700770
771 lcd->ld = ld;
772
Axel Linef22f6a2011-07-25 17:12:01 -0700773 memset(&props, 0, sizeof(struct backlight_properties));
774 props.type = BACKLIGHT_RAW;
775 props.max_brightness = MAX_BRIGHTNESS;
776
InKi Daeee378a52010-05-24 12:21:36 -0700777 bd = backlight_device_register("s6e63m0bl-bl", &spi->dev, lcd,
Axel Linef22f6a2011-07-25 17:12:01 -0700778 &s6e63m0_backlight_ops, &props);
InKi Daeee378a52010-05-24 12:21:36 -0700779 if (IS_ERR(bd)) {
780 ret = PTR_ERR(bd);
781 goto out_lcd_unregister;
782 }
783
InKi Daeee378a52010-05-24 12:21:36 -0700784 bd->props.brightness = MAX_BRIGHTNESS;
785 lcd->bd = bd;
786
787 /*
788 * it gets gamma table count available so it gets user
789 * know that.
790 */
791 lcd->gamma_table_count =
792 sizeof(gamma_table) / (MAX_GAMMA_LEVEL * sizeof(int));
793
794 ret = device_create_file(&(spi->dev), &dev_attr_gamma_mode);
795 if (ret < 0)
796 dev_err(&(spi->dev), "failed to add sysfs entries\n");
797
798 ret = device_create_file(&(spi->dev), &dev_attr_gamma_table);
799 if (ret < 0)
800 dev_err(&(spi->dev), "failed to add sysfs entries\n");
801
802 /*
803 * if lcd panel was on from bootloader like u-boot then
804 * do not lcd on.
805 */
806 if (!lcd->lcd_pd->lcd_enabled) {
807 /*
808 * if lcd panel was off from bootloader then
809 * current lcd status is powerdown and then
810 * it enables lcd panel.
811 */
812 lcd->power = FB_BLANK_POWERDOWN;
813
814 s6e63m0_power(lcd, FB_BLANK_UNBLANK);
815 } else
816 lcd->power = FB_BLANK_UNBLANK;
817
818 dev_set_drvdata(&spi->dev, lcd);
819
820 dev_info(&spi->dev, "s6e63m0 panel driver has been probed.\n");
821
822 return 0;
823
824out_lcd_unregister:
825 lcd_device_unregister(ld);
InKi Daeee378a52010-05-24 12:21:36 -0700826 return ret;
827}
828
Bill Pemberton7e4b9d02012-11-19 13:26:34 -0500829static int s6e63m0_remove(struct spi_device *spi)
InKi Daeee378a52010-05-24 12:21:36 -0700830{
831 struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev);
832
833 s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
Axel Lind974e002010-11-11 14:05:26 -0800834 device_remove_file(&spi->dev, &dev_attr_gamma_table);
835 device_remove_file(&spi->dev, &dev_attr_gamma_mode);
836 backlight_device_unregister(lcd->bd);
InKi Daeee378a52010-05-24 12:21:36 -0700837 lcd_device_unregister(lcd->ld);
InKi Daeee378a52010-05-24 12:21:36 -0700838
839 return 0;
840}
841
842#if defined(CONFIG_PM)
Jingoo Hana4c8aaa2011-07-25 17:12:00 -0700843static unsigned int before_power;
InKi Daeee378a52010-05-24 12:21:36 -0700844
845static int s6e63m0_suspend(struct spi_device *spi, pm_message_t mesg)
846{
847 int ret = 0;
848 struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev);
849
850 dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
851
852 before_power = lcd->power;
853
854 /*
855 * when lcd panel is suspend, lcd panel becomes off
856 * regardless of status.
857 */
858 ret = s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
859
860 return ret;
861}
862
863static int s6e63m0_resume(struct spi_device *spi)
864{
865 int ret = 0;
866 struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev);
867
868 /*
869 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
870 * (at that time, before_power is FB_BLANK_UNBLANK) then
871 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
872 */
873 if (before_power == FB_BLANK_UNBLANK)
874 lcd->power = FB_BLANK_POWERDOWN;
875
876 dev_dbg(&spi->dev, "before_power = %d\n", before_power);
877
878 ret = s6e63m0_power(lcd, before_power);
879
880 return ret;
881}
882#else
883#define s6e63m0_suspend NULL
884#define s6e63m0_resume NULL
885#endif
886
887/* Power down all displays on reboot, poweroff or halt. */
888static void s6e63m0_shutdown(struct spi_device *spi)
889{
890 struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev);
891
892 s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
893}
894
895static struct spi_driver s6e63m0_driver = {
896 .driver = {
897 .name = "s6e63m0",
InKi Daeee378a52010-05-24 12:21:36 -0700898 .owner = THIS_MODULE,
899 },
900 .probe = s6e63m0_probe,
Bill Pembertond1723fa2012-11-19 13:21:09 -0500901 .remove = s6e63m0_remove,
InKi Daeee378a52010-05-24 12:21:36 -0700902 .shutdown = s6e63m0_shutdown,
903 .suspend = s6e63m0_suspend,
904 .resume = s6e63m0_resume,
905};
906
Axel Lin462dd832012-03-23 15:01:59 -0700907module_spi_driver(s6e63m0_driver);
InKi Daeee378a52010-05-24 12:21:36 -0700908
909MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>");
910MODULE_DESCRIPTION("S6E63M0 LCD Driver");
911MODULE_LICENSE("GPL");
912