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 	} else {
329 		pd->reset(lcd->ld);
330 		msleep(pd->reset_delay);
331 	}
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_get_brightness(struct backlight_device *bd)
414 {
415 	return bd->props.brightness;
416 }
417 
418 static int ams369fg06_set_brightness(struct backlight_device *bd)
419 {
420 	int ret = 0;
421 	int brightness = bd->props.brightness;
422 	struct ams369fg06 *lcd = bl_get_data(bd);
423 
424 	if (brightness < MIN_BRIGHTNESS ||
425 		brightness > bd->props.max_brightness) {
426 		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
427 			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
428 		return -EINVAL;
429 	}
430 
431 	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
432 	if (ret) {
433 		dev_err(&bd->dev, "lcd brightness setting failed.\n");
434 		return -EIO;
435 	}
436 
437 	return ret;
438 }
439 
440 static struct lcd_ops ams369fg06_lcd_ops = {
441 	.get_power = ams369fg06_get_power,
442 	.set_power = ams369fg06_set_power,
443 };
444 
445 static const struct backlight_ops ams369fg06_backlight_ops = {
446 	.get_brightness = ams369fg06_get_brightness,
447 	.update_status = ams369fg06_set_brightness,
448 };
449 
450 static int ams369fg06_probe(struct spi_device *spi)
451 {
452 	int ret = 0;
453 	struct ams369fg06 *lcd = NULL;
454 	struct lcd_device *ld = NULL;
455 	struct backlight_device *bd = NULL;
456 	struct backlight_properties props;
457 
458 	lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
459 	if (!lcd)
460 		return -ENOMEM;
461 
462 	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
463 	spi->bits_per_word = 16;
464 
465 	ret = spi_setup(spi);
466 	if (ret < 0) {
467 		dev_err(&spi->dev, "spi setup failed.\n");
468 		return ret;
469 	}
470 
471 	lcd->spi = spi;
472 	lcd->dev = &spi->dev;
473 
474 	lcd->lcd_pd = dev_get_platdata(&spi->dev);
475 	if (!lcd->lcd_pd) {
476 		dev_err(&spi->dev, "platform data is NULL\n");
477 		return -EINVAL;
478 	}
479 
480 	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
481 		&ams369fg06_lcd_ops);
482 	if (IS_ERR(ld))
483 		return PTR_ERR(ld);
484 
485 	lcd->ld = ld;
486 
487 	memset(&props, 0, sizeof(struct backlight_properties));
488 	props.type = BACKLIGHT_RAW;
489 	props.max_brightness = MAX_BRIGHTNESS;
490 
491 	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
492 		&ams369fg06_backlight_ops, &props);
493 	if (IS_ERR(bd)) {
494 		ret =  PTR_ERR(bd);
495 		goto out_lcd_unregister;
496 	}
497 
498 	bd->props.brightness = DEFAULT_BRIGHTNESS;
499 	lcd->bd = bd;
500 
501 	if (!lcd->lcd_pd->lcd_enabled) {
502 		/*
503 		 * if lcd panel was off from bootloader then
504 		 * current lcd status is powerdown and then
505 		 * it enables lcd panel.
506 		 */
507 		lcd->power = FB_BLANK_POWERDOWN;
508 
509 		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
510 	} else {
511 		lcd->power = FB_BLANK_UNBLANK;
512 	}
513 
514 	spi_set_drvdata(spi, lcd);
515 
516 	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
517 
518 	return 0;
519 
520 out_lcd_unregister:
521 	lcd_device_unregister(ld);
522 	return ret;
523 }
524 
525 static int ams369fg06_remove(struct spi_device *spi)
526 {
527 	struct ams369fg06 *lcd = spi_get_drvdata(spi);
528 
529 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
530 	backlight_device_unregister(lcd->bd);
531 	lcd_device_unregister(lcd->ld);
532 
533 	return 0;
534 }
535 
536 #ifdef CONFIG_PM_SLEEP
537 static int ams369fg06_suspend(struct device *dev)
538 {
539 	struct ams369fg06 *lcd = dev_get_drvdata(dev);
540 
541 	dev_dbg(dev, "lcd->power = %d\n", lcd->power);
542 
543 	/*
544 	 * when lcd panel is suspend, lcd panel becomes off
545 	 * regardless of status.
546 	 */
547 	return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
548 }
549 
550 static int ams369fg06_resume(struct device *dev)
551 {
552 	struct ams369fg06 *lcd = dev_get_drvdata(dev);
553 
554 	lcd->power = FB_BLANK_POWERDOWN;
555 
556 	return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
557 }
558 #endif
559 
560 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
561 			ams369fg06_resume);
562 
563 static void ams369fg06_shutdown(struct spi_device *spi)
564 {
565 	struct ams369fg06 *lcd = spi_get_drvdata(spi);
566 
567 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
568 }
569 
570 static struct spi_driver ams369fg06_driver = {
571 	.driver = {
572 		.name	= "ams369fg06",
573 		.owner	= THIS_MODULE,
574 		.pm	= &ams369fg06_pm_ops,
575 	},
576 	.probe		= ams369fg06_probe,
577 	.remove		= ams369fg06_remove,
578 	.shutdown	= ams369fg06_shutdown,
579 };
580 
581 module_spi_driver(ams369fg06_driver);
582 
583 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
584 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
585 MODULE_LICENSE("GPL");
586