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 			mdelay(wbuf[i+1]);
215 		i += 2;
216 	}
217 
218 	return ret;
219 }
220 
221 static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
222 	const unsigned int *gamma)
223 {
224 	unsigned int i = 0;
225 	int ret = 0;
226 
227 	for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
228 		ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
229 		ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
230 		ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
231 		if (ret) {
232 			dev_err(lcd->dev, "failed to set gamma table.\n");
233 			goto gamma_err;
234 		}
235 	}
236 
237 gamma_err:
238 	return ret;
239 }
240 
241 static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
242 {
243 	int ret = 0;
244 	int gamma = 0;
245 
246 	if ((brightness >= 0) && (brightness <= 50))
247 		gamma = 0;
248 	else if ((brightness > 50) && (brightness <= 100))
249 		gamma = 1;
250 	else if ((brightness > 100) && (brightness <= 150))
251 		gamma = 2;
252 	else if ((brightness > 150) && (brightness <= 200))
253 		gamma = 3;
254 	else if ((brightness > 200) && (brightness <= 255))
255 		gamma = 4;
256 
257 	ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
258 
259 	return ret;
260 }
261 
262 static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
263 {
264 	int ret, i;
265 	static const unsigned short *init_seq[] = {
266 		seq_setting,
267 		seq_stand_by_off,
268 	};
269 
270 	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
271 		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
272 		if (ret)
273 			break;
274 	}
275 
276 	return ret;
277 }
278 
279 static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
280 {
281 	int ret, i;
282 	static const unsigned short *init_seq[] = {
283 		seq_stand_by_off,
284 		seq_display_on,
285 	};
286 
287 	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
288 		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
289 		if (ret)
290 			break;
291 	}
292 
293 	return ret;
294 }
295 
296 static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
297 {
298 	int ret, i;
299 
300 	static const unsigned short *init_seq[] = {
301 		seq_display_off,
302 		seq_stand_by_on,
303 	};
304 
305 	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
306 		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
307 		if (ret)
308 			break;
309 	}
310 
311 	return ret;
312 }
313 
314 static int ams369fg06_power_is_on(int power)
315 {
316 	return ((power) <= FB_BLANK_NORMAL);
317 }
318 
319 static int ams369fg06_power_on(struct ams369fg06 *lcd)
320 {
321 	int ret = 0;
322 	struct lcd_platform_data *pd = NULL;
323 	struct backlight_device *bd = NULL;
324 
325 	pd = lcd->lcd_pd;
326 	if (!pd) {
327 		dev_err(lcd->dev, "platform data is NULL.\n");
328 		return -EFAULT;
329 	}
330 
331 	bd = lcd->bd;
332 	if (!bd) {
333 		dev_err(lcd->dev, "backlight device is NULL.\n");
334 		return -EFAULT;
335 	}
336 
337 	if (!pd->power_on) {
338 		dev_err(lcd->dev, "power_on is NULL.\n");
339 		return -EFAULT;
340 	} else {
341 		pd->power_on(lcd->ld, 1);
342 		mdelay(pd->power_on_delay);
343 	}
344 
345 	if (!pd->reset) {
346 		dev_err(lcd->dev, "reset is NULL.\n");
347 		return -EFAULT;
348 	} else {
349 		pd->reset(lcd->ld);
350 		mdelay(pd->reset_delay);
351 	}
352 
353 	ret = ams369fg06_ldi_init(lcd);
354 	if (ret) {
355 		dev_err(lcd->dev, "failed to initialize ldi.\n");
356 		return ret;
357 	}
358 
359 	ret = ams369fg06_ldi_enable(lcd);
360 	if (ret) {
361 		dev_err(lcd->dev, "failed to enable ldi.\n");
362 		return ret;
363 	}
364 
365 	/* set brightness to current value after power on or resume. */
366 	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
367 	if (ret) {
368 		dev_err(lcd->dev, "lcd gamma setting failed.\n");
369 		return ret;
370 	}
371 
372 	return 0;
373 }
374 
375 static int ams369fg06_power_off(struct ams369fg06 *lcd)
376 {
377 	int ret = 0;
378 	struct lcd_platform_data *pd = NULL;
379 
380 	pd = lcd->lcd_pd;
381 	if (!pd) {
382 		dev_err(lcd->dev, "platform data is NULL\n");
383 		return -EFAULT;
384 	}
385 
386 	ret = ams369fg06_ldi_disable(lcd);
387 	if (ret) {
388 		dev_err(lcd->dev, "lcd setting failed.\n");
389 		return -EIO;
390 	}
391 
392 	mdelay(pd->power_off_delay);
393 
394 	if (!pd->power_on) {
395 		dev_err(lcd->dev, "power_on is NULL.\n");
396 		return -EFAULT;
397 	} else
398 		pd->power_on(lcd->ld, 0);
399 
400 	return 0;
401 }
402 
403 static int ams369fg06_power(struct ams369fg06 *lcd, int power)
404 {
405 	int ret = 0;
406 
407 	if (ams369fg06_power_is_on(power) &&
408 		!ams369fg06_power_is_on(lcd->power))
409 		ret = ams369fg06_power_on(lcd);
410 	else if (!ams369fg06_power_is_on(power) &&
411 		ams369fg06_power_is_on(lcd->power))
412 		ret = ams369fg06_power_off(lcd);
413 
414 	if (!ret)
415 		lcd->power = power;
416 
417 	return ret;
418 }
419 
420 static int ams369fg06_get_power(struct lcd_device *ld)
421 {
422 	struct ams369fg06 *lcd = lcd_get_data(ld);
423 
424 	return lcd->power;
425 }
426 
427 static int ams369fg06_set_power(struct lcd_device *ld, int power)
428 {
429 	struct ams369fg06 *lcd = lcd_get_data(ld);
430 
431 	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
432 		power != FB_BLANK_NORMAL) {
433 		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
434 		return -EINVAL;
435 	}
436 
437 	return ams369fg06_power(lcd, power);
438 }
439 
440 static int ams369fg06_get_brightness(struct backlight_device *bd)
441 {
442 	return bd->props.brightness;
443 }
444 
445 static int ams369fg06_set_brightness(struct backlight_device *bd)
446 {
447 	int ret = 0;
448 	int brightness = bd->props.brightness;
449 	struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
450 
451 	if (brightness < MIN_BRIGHTNESS ||
452 		brightness > bd->props.max_brightness) {
453 		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
454 			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
455 		return -EINVAL;
456 	}
457 
458 	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
459 	if (ret) {
460 		dev_err(&bd->dev, "lcd brightness setting failed.\n");
461 		return -EIO;
462 	}
463 
464 	return ret;
465 }
466 
467 static struct lcd_ops ams369fg06_lcd_ops = {
468 	.get_power = ams369fg06_get_power,
469 	.set_power = ams369fg06_set_power,
470 };
471 
472 static const struct backlight_ops ams369fg06_backlight_ops = {
473 	.get_brightness = ams369fg06_get_brightness,
474 	.update_status = ams369fg06_set_brightness,
475 };
476 
477 static int __devinit ams369fg06_probe(struct spi_device *spi)
478 {
479 	int ret = 0;
480 	struct ams369fg06 *lcd = NULL;
481 	struct lcd_device *ld = NULL;
482 	struct backlight_device *bd = NULL;
483 	struct backlight_properties props;
484 
485 	lcd = kzalloc(sizeof(struct ams369fg06), GFP_KERNEL);
486 	if (!lcd)
487 		return -ENOMEM;
488 
489 	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
490 	spi->bits_per_word = 16;
491 
492 	ret = spi_setup(spi);
493 	if (ret < 0) {
494 		dev_err(&spi->dev, "spi setup failed.\n");
495 		goto out_free_lcd;
496 	}
497 
498 	lcd->spi = spi;
499 	lcd->dev = &spi->dev;
500 
501 	lcd->lcd_pd = spi->dev.platform_data;
502 	if (!lcd->lcd_pd) {
503 		dev_err(&spi->dev, "platform data is NULL\n");
504 		goto out_free_lcd;
505 	}
506 
507 	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
508 		&ams369fg06_lcd_ops);
509 	if (IS_ERR(ld)) {
510 		ret = PTR_ERR(ld);
511 		goto out_free_lcd;
512 	}
513 
514 	lcd->ld = ld;
515 
516 	memset(&props, 0, sizeof(struct backlight_properties));
517 	props.type = BACKLIGHT_RAW;
518 	props.max_brightness = MAX_BRIGHTNESS;
519 
520 	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
521 		&ams369fg06_backlight_ops, &props);
522 	if (IS_ERR(bd)) {
523 		ret =  PTR_ERR(bd);
524 		goto out_lcd_unregister;
525 	}
526 
527 	bd->props.brightness = DEFAULT_BRIGHTNESS;
528 	lcd->bd = bd;
529 
530 	if (!lcd->lcd_pd->lcd_enabled) {
531 		/*
532 		 * if lcd panel was off from bootloader then
533 		 * current lcd status is powerdown and then
534 		 * it enables lcd panel.
535 		 */
536 		lcd->power = FB_BLANK_POWERDOWN;
537 
538 		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
539 	} else
540 		lcd->power = FB_BLANK_UNBLANK;
541 
542 	dev_set_drvdata(&spi->dev, lcd);
543 
544 	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
545 
546 	return 0;
547 
548 out_lcd_unregister:
549 	lcd_device_unregister(ld);
550 out_free_lcd:
551 	kfree(lcd);
552 	return ret;
553 }
554 
555 static int __devexit ams369fg06_remove(struct spi_device *spi)
556 {
557 	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
558 
559 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
560 	backlight_device_unregister(lcd->bd);
561 	lcd_device_unregister(lcd->ld);
562 	kfree(lcd);
563 
564 	return 0;
565 }
566 
567 #if defined(CONFIG_PM)
568 static unsigned int before_power;
569 
570 static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
571 {
572 	int ret = 0;
573 	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
574 
575 	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
576 
577 	before_power = lcd->power;
578 
579 	/*
580 	 * when lcd panel is suspend, lcd panel becomes off
581 	 * regardless of status.
582 	 */
583 	ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
584 
585 	return ret;
586 }
587 
588 static int ams369fg06_resume(struct spi_device *spi)
589 {
590 	int ret = 0;
591 	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
592 
593 	/*
594 	 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
595 	 * (at that time, before_power is FB_BLANK_UNBLANK) then
596 	 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
597 	 */
598 	if (before_power == FB_BLANK_UNBLANK)
599 		lcd->power = FB_BLANK_POWERDOWN;
600 
601 	dev_dbg(&spi->dev, "before_power = %d\n", before_power);
602 
603 	ret = ams369fg06_power(lcd, before_power);
604 
605 	return ret;
606 }
607 #else
608 #define ams369fg06_suspend	NULL
609 #define ams369fg06_resume	NULL
610 #endif
611 
612 static void ams369fg06_shutdown(struct spi_device *spi)
613 {
614 	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
615 
616 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
617 }
618 
619 static struct spi_driver ams369fg06_driver = {
620 	.driver = {
621 		.name	= "ams369fg06",
622 		.bus	= &spi_bus_type,
623 		.owner	= THIS_MODULE,
624 	},
625 	.probe		= ams369fg06_probe,
626 	.remove		= __devexit_p(ams369fg06_remove),
627 	.shutdown	= ams369fg06_shutdown,
628 	.suspend	= ams369fg06_suspend,
629 	.resume		= ams369fg06_resume,
630 };
631 
632 module_spi_driver(ams369fg06_driver);
633 
634 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
635 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
636 MODULE_LICENSE("GPL");
637