1/*
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
14#include <linux/backlight.h>
15#include <linux/delay.h>
16#include <linux/fb.h>
17#include <linux/gpio.h>
18#include <linux/interrupt.h>
19#include <linux/irq.h>
20#include <linux/kernel.h>
21#include <linux/lcd.h>
22#include <linux/module.h>
23#include <linux/spi/spi.h>
24#include <linux/wait.h>
25
26#include "s6e63m0_gamma.h"
27
28#define SLEEPMSEC		0x1000
29#define ENDDEF			0x2000
30#define	DEFMASK			0xFF00
31#define COMMAND_ONLY		0xFE
32#define DATA_ONLY		0xFF
33
34#define MIN_BRIGHTNESS		0
35#define MAX_BRIGHTNESS		10
36
37struct s6e63m0 {
38	struct device			*dev;
39	struct spi_device		*spi;
40	unsigned int			power;
41	unsigned int			current_brightness;
42	unsigned int			gamma_mode;
43	unsigned int			gamma_table_count;
44	struct lcd_device		*ld;
45	struct backlight_device		*bd;
46	struct lcd_platform_data	*lcd_pd;
47};
48
49static const unsigned short seq_panel_condition_set[] = {
50	0xF8, 0x01,
51	DATA_ONLY, 0x27,
52	DATA_ONLY, 0x27,
53	DATA_ONLY, 0x07,
54	DATA_ONLY, 0x07,
55	DATA_ONLY, 0x54,
56	DATA_ONLY, 0x9f,
57	DATA_ONLY, 0x63,
58	DATA_ONLY, 0x86,
59	DATA_ONLY, 0x1a,
60	DATA_ONLY, 0x33,
61	DATA_ONLY, 0x0d,
62	DATA_ONLY, 0x00,
63	DATA_ONLY, 0x00,
64
65	ENDDEF, 0x0000
66};
67
68static const unsigned short seq_display_condition_set[] = {
69	0xf2, 0x02,
70	DATA_ONLY, 0x03,
71	DATA_ONLY, 0x1c,
72	DATA_ONLY, 0x10,
73	DATA_ONLY, 0x10,
74
75	0xf7, 0x03,
76	DATA_ONLY, 0x00,
77	DATA_ONLY, 0x00,
78
79	ENDDEF, 0x0000
80};
81
82static const unsigned short seq_gamma_setting[] = {
83	0xfa, 0x00,
84	DATA_ONLY, 0x18,
85	DATA_ONLY, 0x08,
86	DATA_ONLY, 0x24,
87	DATA_ONLY, 0x64,
88	DATA_ONLY, 0x56,
89	DATA_ONLY, 0x33,
90	DATA_ONLY, 0xb6,
91	DATA_ONLY, 0xba,
92	DATA_ONLY, 0xa8,
93	DATA_ONLY, 0xac,
94	DATA_ONLY, 0xb1,
95	DATA_ONLY, 0x9d,
96	DATA_ONLY, 0xc1,
97	DATA_ONLY, 0xc1,
98	DATA_ONLY, 0xb7,
99	DATA_ONLY, 0x00,
100	DATA_ONLY, 0x9c,
101	DATA_ONLY, 0x00,
102	DATA_ONLY, 0x9f,
103	DATA_ONLY, 0x00,
104	DATA_ONLY, 0xd6,
105
106	0xfa, 0x01,
107
108	ENDDEF, 0x0000
109};
110
111static const unsigned short seq_etc_condition_set[] = {
112	0xf6, 0x00,
113	DATA_ONLY, 0x8c,
114	DATA_ONLY, 0x07,
115
116	0xb3, 0xc,
117
118	0xb5, 0x2c,
119	DATA_ONLY, 0x12,
120	DATA_ONLY, 0x0c,
121	DATA_ONLY, 0x0a,
122	DATA_ONLY, 0x10,
123	DATA_ONLY, 0x0e,
124	DATA_ONLY, 0x17,
125	DATA_ONLY, 0x13,
126	DATA_ONLY, 0x1f,
127	DATA_ONLY, 0x1a,
128	DATA_ONLY, 0x2a,
129	DATA_ONLY, 0x24,
130	DATA_ONLY, 0x1f,
131	DATA_ONLY, 0x1b,
132	DATA_ONLY, 0x1a,
133	DATA_ONLY, 0x17,
134
135	DATA_ONLY, 0x2b,
136	DATA_ONLY, 0x26,
137	DATA_ONLY, 0x22,
138	DATA_ONLY, 0x20,
139	DATA_ONLY, 0x3a,
140	DATA_ONLY, 0x34,
141	DATA_ONLY, 0x30,
142	DATA_ONLY, 0x2c,
143	DATA_ONLY, 0x29,
144	DATA_ONLY, 0x26,
145	DATA_ONLY, 0x25,
146	DATA_ONLY, 0x23,
147	DATA_ONLY, 0x21,
148	DATA_ONLY, 0x20,
149	DATA_ONLY, 0x1e,
150	DATA_ONLY, 0x1e,
151
152	0xb6, 0x00,
153	DATA_ONLY, 0x00,
154	DATA_ONLY, 0x11,
155	DATA_ONLY, 0x22,
156	DATA_ONLY, 0x33,
157	DATA_ONLY, 0x44,
158	DATA_ONLY, 0x44,
159	DATA_ONLY, 0x44,
160
161	DATA_ONLY, 0x55,
162	DATA_ONLY, 0x55,
163	DATA_ONLY, 0x66,
164	DATA_ONLY, 0x66,
165	DATA_ONLY, 0x66,
166	DATA_ONLY, 0x66,
167	DATA_ONLY, 0x66,
168	DATA_ONLY, 0x66,
169
170	0xb7, 0x2c,
171	DATA_ONLY, 0x12,
172	DATA_ONLY, 0x0c,
173	DATA_ONLY, 0x0a,
174	DATA_ONLY, 0x10,
175	DATA_ONLY, 0x0e,
176	DATA_ONLY, 0x17,
177	DATA_ONLY, 0x13,
178	DATA_ONLY, 0x1f,
179	DATA_ONLY, 0x1a,
180	DATA_ONLY, 0x2a,
181	DATA_ONLY, 0x24,
182	DATA_ONLY, 0x1f,
183	DATA_ONLY, 0x1b,
184	DATA_ONLY, 0x1a,
185	DATA_ONLY, 0x17,
186
187	DATA_ONLY, 0x2b,
188	DATA_ONLY, 0x26,
189	DATA_ONLY, 0x22,
190	DATA_ONLY, 0x20,
191	DATA_ONLY, 0x3a,
192	DATA_ONLY, 0x34,
193	DATA_ONLY, 0x30,
194	DATA_ONLY, 0x2c,
195	DATA_ONLY, 0x29,
196	DATA_ONLY, 0x26,
197	DATA_ONLY, 0x25,
198	DATA_ONLY, 0x23,
199	DATA_ONLY, 0x21,
200	DATA_ONLY, 0x20,
201	DATA_ONLY, 0x1e,
202	DATA_ONLY, 0x1e,
203
204	0xb8, 0x00,
205	DATA_ONLY, 0x00,
206	DATA_ONLY, 0x11,
207	DATA_ONLY, 0x22,
208	DATA_ONLY, 0x33,
209	DATA_ONLY, 0x44,
210	DATA_ONLY, 0x44,
211	DATA_ONLY, 0x44,
212
213	DATA_ONLY, 0x55,
214	DATA_ONLY, 0x55,
215	DATA_ONLY, 0x66,
216	DATA_ONLY, 0x66,
217	DATA_ONLY, 0x66,
218	DATA_ONLY, 0x66,
219	DATA_ONLY, 0x66,
220	DATA_ONLY, 0x66,
221
222	0xb9, 0x2c,
223	DATA_ONLY, 0x12,
224	DATA_ONLY, 0x0c,
225	DATA_ONLY, 0x0a,
226	DATA_ONLY, 0x10,
227	DATA_ONLY, 0x0e,
228	DATA_ONLY, 0x17,
229	DATA_ONLY, 0x13,
230	DATA_ONLY, 0x1f,
231	DATA_ONLY, 0x1a,
232	DATA_ONLY, 0x2a,
233	DATA_ONLY, 0x24,
234	DATA_ONLY, 0x1f,
235	DATA_ONLY, 0x1b,
236	DATA_ONLY, 0x1a,
237	DATA_ONLY, 0x17,
238
239	DATA_ONLY, 0x2b,
240	DATA_ONLY, 0x26,
241	DATA_ONLY, 0x22,
242	DATA_ONLY, 0x20,
243	DATA_ONLY, 0x3a,
244	DATA_ONLY, 0x34,
245	DATA_ONLY, 0x30,
246	DATA_ONLY, 0x2c,
247	DATA_ONLY, 0x29,
248	DATA_ONLY, 0x26,
249	DATA_ONLY, 0x25,
250	DATA_ONLY, 0x23,
251	DATA_ONLY, 0x21,
252	DATA_ONLY, 0x20,
253	DATA_ONLY, 0x1e,
254	DATA_ONLY, 0x1e,
255
256	0xba, 0x00,
257	DATA_ONLY, 0x00,
258	DATA_ONLY, 0x11,
259	DATA_ONLY, 0x22,
260	DATA_ONLY, 0x33,
261	DATA_ONLY, 0x44,
262	DATA_ONLY, 0x44,
263	DATA_ONLY, 0x44,
264
265	DATA_ONLY, 0x55,
266	DATA_ONLY, 0x55,
267	DATA_ONLY, 0x66,
268	DATA_ONLY, 0x66,
269	DATA_ONLY, 0x66,
270	DATA_ONLY, 0x66,
271	DATA_ONLY, 0x66,
272	DATA_ONLY, 0x66,
273
274	0xc1, 0x4d,
275	DATA_ONLY, 0x96,
276	DATA_ONLY, 0x1d,
277	DATA_ONLY, 0x00,
278	DATA_ONLY, 0x00,
279	DATA_ONLY, 0x01,
280	DATA_ONLY, 0xdf,
281	DATA_ONLY, 0x00,
282	DATA_ONLY, 0x00,
283	DATA_ONLY, 0x03,
284	DATA_ONLY, 0x1f,
285	DATA_ONLY, 0x00,
286	DATA_ONLY, 0x00,
287	DATA_ONLY, 0x00,
288	DATA_ONLY, 0x00,
289	DATA_ONLY, 0x00,
290	DATA_ONLY, 0x00,
291	DATA_ONLY, 0x00,
292	DATA_ONLY, 0x00,
293	DATA_ONLY, 0x03,
294	DATA_ONLY, 0x06,
295	DATA_ONLY, 0x09,
296	DATA_ONLY, 0x0d,
297	DATA_ONLY, 0x0f,
298	DATA_ONLY, 0x12,
299	DATA_ONLY, 0x15,
300	DATA_ONLY, 0x18,
301
302	0xb2, 0x10,
303	DATA_ONLY, 0x10,
304	DATA_ONLY, 0x0b,
305	DATA_ONLY, 0x05,
306
307	ENDDEF, 0x0000
308};
309
310static const unsigned short seq_acl_on[] = {
311	/* ACL on */
312	0xc0, 0x01,
313
314	ENDDEF, 0x0000
315};
316
317static const unsigned short seq_acl_off[] = {
318	/* ACL off */
319	0xc0, 0x00,
320
321	ENDDEF, 0x0000
322};
323
324static const unsigned short seq_elvss_on[] = {
325	/* ELVSS on */
326	0xb1, 0x0b,
327
328	ENDDEF, 0x0000
329};
330
331static const unsigned short seq_elvss_off[] = {
332	/* ELVSS off */
333	0xb1, 0x0a,
334
335	ENDDEF, 0x0000
336};
337
338static const unsigned short seq_stand_by_off[] = {
339	0x11, COMMAND_ONLY,
340
341	ENDDEF, 0x0000
342};
343
344static const unsigned short seq_stand_by_on[] = {
345	0x10, COMMAND_ONLY,
346
347	ENDDEF, 0x0000
348};
349
350static const unsigned short seq_display_on[] = {
351	0x29, COMMAND_ONLY,
352
353	ENDDEF, 0x0000
354};
355
356
357static int s6e63m0_spi_write_byte(struct s6e63m0 *lcd, int addr, int data)
358{
359	u16 buf[1];
360	struct spi_message msg;
361
362	struct spi_transfer xfer = {
363		.len		= 2,
364		.tx_buf		= buf,
365	};
366
367	buf[0] = (addr << 8) | data;
368
369	spi_message_init(&msg);
370	spi_message_add_tail(&xfer, &msg);
371
372	return spi_sync(lcd->spi, &msg);
373}
374
375static int s6e63m0_spi_write(struct s6e63m0 *lcd, unsigned char address,
376	unsigned char command)
377{
378	int ret = 0;
379
380	if (address != DATA_ONLY)
381		ret = s6e63m0_spi_write_byte(lcd, 0x0, address);
382	if (command != COMMAND_ONLY)
383		ret = s6e63m0_spi_write_byte(lcd, 0x1, command);
384
385	return ret;
386}
387
388static int s6e63m0_panel_send_sequence(struct s6e63m0 *lcd,
389	const unsigned short *wbuf)
390{
391	int ret = 0, i = 0;
392
393	while ((wbuf[i] & DEFMASK) != ENDDEF) {
394		if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
395			ret = s6e63m0_spi_write(lcd, wbuf[i], wbuf[i+1]);
396			if (ret)
397				break;
398		} else {
399			msleep(wbuf[i+1]);
400		}
401		i += 2;
402	}
403
404	return ret;
405}
406
407static int _s6e63m0_gamma_ctl(struct s6e63m0 *lcd, const unsigned int *gamma)
408{
409	unsigned int i = 0;
410	int ret = 0;
411
412	/* disable gamma table updating. */
413	ret = s6e63m0_spi_write(lcd, 0xfa, 0x00);
414	if (ret) {
415		dev_err(lcd->dev, "failed to disable gamma table updating.\n");
416		goto gamma_err;
417	}
418
419	for (i = 0 ; i < GAMMA_TABLE_COUNT; i++) {
420		ret = s6e63m0_spi_write(lcd, DATA_ONLY, gamma[i]);
421		if (ret) {
422			dev_err(lcd->dev, "failed to set gamma table.\n");
423			goto gamma_err;
424		}
425	}
426
427	/* update gamma table. */
428	ret = s6e63m0_spi_write(lcd, 0xfa, 0x01);
429	if (ret)
430		dev_err(lcd->dev, "failed to update gamma table.\n");
431
432gamma_err:
433	return ret;
434}
435
436static int s6e63m0_gamma_ctl(struct s6e63m0 *lcd, int gamma)
437{
438	int ret = 0;
439
440	ret = _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
441
442	return ret;
443}
444
445
446static int s6e63m0_ldi_init(struct s6e63m0 *lcd)
447{
448	int ret, i;
449	const unsigned short *init_seq[] = {
450		seq_panel_condition_set,
451		seq_display_condition_set,
452		seq_gamma_setting,
453		seq_etc_condition_set,
454		seq_acl_on,
455		seq_elvss_on,
456	};
457
458	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
459		ret = s6e63m0_panel_send_sequence(lcd, init_seq[i]);
460		if (ret)
461			break;
462	}
463
464	return ret;
465}
466
467static int s6e63m0_ldi_enable(struct s6e63m0 *lcd)
468{
469	int ret = 0, i;
470	const unsigned short *enable_seq[] = {
471		seq_stand_by_off,
472		seq_display_on,
473	};
474
475	for (i = 0; i < ARRAY_SIZE(enable_seq); i++) {
476		ret = s6e63m0_panel_send_sequence(lcd, enable_seq[i]);
477		if (ret)
478			break;
479	}
480
481	return ret;
482}
483
484static int s6e63m0_ldi_disable(struct s6e63m0 *lcd)
485{
486	int ret;
487
488	ret = s6e63m0_panel_send_sequence(lcd, seq_stand_by_on);
489
490	return ret;
491}
492
493static int s6e63m0_power_is_on(int power)
494{
495	return power <= FB_BLANK_NORMAL;
496}
497
498static int s6e63m0_power_on(struct s6e63m0 *lcd)
499{
500	int ret = 0;
501	struct lcd_platform_data *pd;
502	struct backlight_device *bd;
503
504	pd = lcd->lcd_pd;
505	bd = lcd->bd;
506
507	if (!pd->power_on) {
508		dev_err(lcd->dev, "power_on is NULL.\n");
509		return -EINVAL;
510	}
511
512	pd->power_on(lcd->ld, 1);
513	msleep(pd->power_on_delay);
514
515	if (!pd->reset) {
516		dev_err(lcd->dev, "reset is NULL.\n");
517		return -EINVAL;
518	}
519
520	pd->reset(lcd->ld);
521	msleep(pd->reset_delay);
522
523	ret = s6e63m0_ldi_init(lcd);
524	if (ret) {
525		dev_err(lcd->dev, "failed to initialize ldi.\n");
526		return ret;
527	}
528
529	ret = s6e63m0_ldi_enable(lcd);
530	if (ret) {
531		dev_err(lcd->dev, "failed to enable ldi.\n");
532		return ret;
533	}
534
535	/* set brightness to current value after power on or resume. */
536	ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
537	if (ret) {
538		dev_err(lcd->dev, "lcd gamma setting failed.\n");
539		return ret;
540	}
541
542	return 0;
543}
544
545static int s6e63m0_power_off(struct s6e63m0 *lcd)
546{
547	int ret;
548	struct lcd_platform_data *pd;
549
550	pd = lcd->lcd_pd;
551
552	ret = s6e63m0_ldi_disable(lcd);
553	if (ret) {
554		dev_err(lcd->dev, "lcd setting failed.\n");
555		return -EIO;
556	}
557
558	msleep(pd->power_off_delay);
559
560	pd->power_on(lcd->ld, 0);
561
562	return 0;
563}
564
565static int s6e63m0_power(struct s6e63m0 *lcd, int power)
566{
567	int ret = 0;
568
569	if (s6e63m0_power_is_on(power) && !s6e63m0_power_is_on(lcd->power))
570		ret = s6e63m0_power_on(lcd);
571	else if (!s6e63m0_power_is_on(power) && s6e63m0_power_is_on(lcd->power))
572		ret = s6e63m0_power_off(lcd);
573
574	if (!ret)
575		lcd->power = power;
576
577	return ret;
578}
579
580static int s6e63m0_set_power(struct lcd_device *ld, int power)
581{
582	struct s6e63m0 *lcd = lcd_get_data(ld);
583
584	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
585		power != FB_BLANK_NORMAL) {
586		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
587		return -EINVAL;
588	}
589
590	return s6e63m0_power(lcd, power);
591}
592
593static int s6e63m0_get_power(struct lcd_device *ld)
594{
595	struct s6e63m0 *lcd = lcd_get_data(ld);
596
597	return lcd->power;
598}
599
600static int s6e63m0_set_brightness(struct backlight_device *bd)
601{
602	int ret = 0, brightness = bd->props.brightness;
603	struct s6e63m0 *lcd = bl_get_data(bd);
604
605	if (brightness < MIN_BRIGHTNESS ||
606		brightness > bd->props.max_brightness) {
607		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
608			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
609		return -EINVAL;
610	}
611
612	ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
613	if (ret) {
614		dev_err(&bd->dev, "lcd brightness setting failed.\n");
615		return -EIO;
616	}
617
618	return ret;
619}
620
621static struct lcd_ops s6e63m0_lcd_ops = {
622	.set_power = s6e63m0_set_power,
623	.get_power = s6e63m0_get_power,
624};
625
626static const struct backlight_ops s6e63m0_backlight_ops  = {
627	.update_status = s6e63m0_set_brightness,
628};
629
630static ssize_t s6e63m0_sysfs_show_gamma_mode(struct device *dev,
631				      struct device_attribute *attr, char *buf)
632{
633	struct s6e63m0 *lcd = dev_get_drvdata(dev);
634	char temp[10];
635
636	switch (lcd->gamma_mode) {
637	case 0:
638		sprintf(temp, "2.2 mode\n");
639		strcat(buf, temp);
640		break;
641	case 1:
642		sprintf(temp, "1.9 mode\n");
643		strcat(buf, temp);
644		break;
645	case 2:
646		sprintf(temp, "1.7 mode\n");
647		strcat(buf, temp);
648		break;
649	default:
650		dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7)n");
651		break;
652	}
653
654	return strlen(buf);
655}
656
657static ssize_t s6e63m0_sysfs_store_gamma_mode(struct device *dev,
658				       struct device_attribute *attr,
659				       const char *buf, size_t len)
660{
661	struct s6e63m0 *lcd = dev_get_drvdata(dev);
662	struct backlight_device *bd = NULL;
663	int brightness, rc;
664
665	rc = kstrtouint(buf, 0, &lcd->gamma_mode);
666	if (rc < 0)
667		return rc;
668
669	bd = lcd->bd;
670
671	brightness = bd->props.brightness;
672
673	switch (lcd->gamma_mode) {
674	case 0:
675		_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
676		break;
677	case 1:
678		_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_19_table[brightness]);
679		break;
680	case 2:
681		_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_17_table[brightness]);
682		break;
683	default:
684		dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7\n");
685		_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
686		break;
687	}
688	return len;
689}
690
691static DEVICE_ATTR(gamma_mode, 0644,
692		s6e63m0_sysfs_show_gamma_mode, s6e63m0_sysfs_store_gamma_mode);
693
694static ssize_t s6e63m0_sysfs_show_gamma_table(struct device *dev,
695				      struct device_attribute *attr, char *buf)
696{
697	struct s6e63m0 *lcd = dev_get_drvdata(dev);
698	char temp[3];
699
700	sprintf(temp, "%u\n", lcd->gamma_table_count);
701	strcpy(buf, temp);
702
703	return strlen(buf);
704}
705static DEVICE_ATTR(gamma_table, 0444,
706		s6e63m0_sysfs_show_gamma_table, NULL);
707
708static int s6e63m0_probe(struct spi_device *spi)
709{
710	int ret = 0;
711	struct s6e63m0 *lcd = NULL;
712	struct lcd_device *ld = NULL;
713	struct backlight_device *bd = NULL;
714	struct backlight_properties props;
715
716	lcd = devm_kzalloc(&spi->dev, sizeof(struct s6e63m0), GFP_KERNEL);
717	if (!lcd)
718		return -ENOMEM;
719
720	/* s6e63m0 lcd panel uses 3-wire 9bits SPI Mode. */
721	spi->bits_per_word = 9;
722
723	ret = spi_setup(spi);
724	if (ret < 0) {
725		dev_err(&spi->dev, "spi setup failed.\n");
726		return ret;
727	}
728
729	lcd->spi = spi;
730	lcd->dev = &spi->dev;
731
732	lcd->lcd_pd = dev_get_platdata(&spi->dev);
733	if (!lcd->lcd_pd) {
734		dev_err(&spi->dev, "platform data is NULL.\n");
735		return -EINVAL;
736	}
737
738	ld = devm_lcd_device_register(&spi->dev, "s6e63m0", &spi->dev, lcd,
739				&s6e63m0_lcd_ops);
740	if (IS_ERR(ld))
741		return PTR_ERR(ld);
742
743	lcd->ld = ld;
744
745	memset(&props, 0, sizeof(struct backlight_properties));
746	props.type = BACKLIGHT_RAW;
747	props.max_brightness = MAX_BRIGHTNESS;
748
749	bd = devm_backlight_device_register(&spi->dev, "s6e63m0bl-bl",
750					&spi->dev, lcd, &s6e63m0_backlight_ops,
751					&props);
752	if (IS_ERR(bd))
753		return PTR_ERR(bd);
754
755	bd->props.brightness = MAX_BRIGHTNESS;
756	lcd->bd = bd;
757
758	/*
759	 * it gets gamma table count available so it gets user
760	 * know that.
761	 */
762	lcd->gamma_table_count =
763	    sizeof(gamma_table) / (MAX_GAMMA_LEVEL * sizeof(int *));
764
765	ret = device_create_file(&(spi->dev), &dev_attr_gamma_mode);
766	if (ret < 0)
767		dev_err(&(spi->dev), "failed to add sysfs entries\n");
768
769	ret = device_create_file(&(spi->dev), &dev_attr_gamma_table);
770	if (ret < 0)
771		dev_err(&(spi->dev), "failed to add sysfs entries\n");
772
773	/*
774	 * if lcd panel was on from bootloader like u-boot then
775	 * do not lcd on.
776	 */
777	if (!lcd->lcd_pd->lcd_enabled) {
778		/*
779		 * if lcd panel was off from bootloader then
780		 * current lcd status is powerdown and then
781		 * it enables lcd panel.
782		 */
783		lcd->power = FB_BLANK_POWERDOWN;
784
785		s6e63m0_power(lcd, FB_BLANK_UNBLANK);
786	} else {
787		lcd->power = FB_BLANK_UNBLANK;
788	}
789
790	spi_set_drvdata(spi, lcd);
791
792	dev_info(&spi->dev, "s6e63m0 panel driver has been probed.\n");
793
794	return 0;
795}
796
797static int s6e63m0_remove(struct spi_device *spi)
798{
799	struct s6e63m0 *lcd = spi_get_drvdata(spi);
800
801	s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
802	device_remove_file(&spi->dev, &dev_attr_gamma_table);
803	device_remove_file(&spi->dev, &dev_attr_gamma_mode);
804
805	return 0;
806}
807
808#ifdef CONFIG_PM_SLEEP
809static int s6e63m0_suspend(struct device *dev)
810{
811	struct s6e63m0 *lcd = dev_get_drvdata(dev);
812
813	dev_dbg(dev, "lcd->power = %d\n", lcd->power);
814
815	/*
816	 * when lcd panel is suspend, lcd panel becomes off
817	 * regardless of status.
818	 */
819	return s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
820}
821
822static int s6e63m0_resume(struct device *dev)
823{
824	struct s6e63m0 *lcd = dev_get_drvdata(dev);
825
826	lcd->power = FB_BLANK_POWERDOWN;
827
828	return s6e63m0_power(lcd, FB_BLANK_UNBLANK);
829}
830#endif
831
832static SIMPLE_DEV_PM_OPS(s6e63m0_pm_ops, s6e63m0_suspend, s6e63m0_resume);
833
834/* Power down all displays on reboot, poweroff or halt. */
835static void s6e63m0_shutdown(struct spi_device *spi)
836{
837	struct s6e63m0 *lcd = spi_get_drvdata(spi);
838
839	s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
840}
841
842static struct spi_driver s6e63m0_driver = {
843	.driver = {
844		.name	= "s6e63m0",
845		.owner	= THIS_MODULE,
846		.pm	= &s6e63m0_pm_ops,
847	},
848	.probe		= s6e63m0_probe,
849	.remove		= s6e63m0_remove,
850	.shutdown	= s6e63m0_shutdown,
851};
852
853module_spi_driver(s6e63m0_driver);
854
855MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>");
856MODULE_DESCRIPTION("S6E63M0 LCD Driver");
857MODULE_LICENSE("GPL");
858
859