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