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 = devm_lcd_device_register(&spi->dev, "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 = devm_backlight_device_register(&spi->dev, "ams369fg06-bl",
492 					&spi->dev, lcd,
493 					&ams369fg06_backlight_ops, &props);
494 	if (IS_ERR(bd))
495 		return PTR_ERR(bd);
496 
497 	bd->props.brightness = DEFAULT_BRIGHTNESS;
498 	lcd->bd = bd;
499 
500 	if (!lcd->lcd_pd->lcd_enabled) {
501 		/*
502 		 * if lcd panel was off from bootloader then
503 		 * current lcd status is powerdown and then
504 		 * it enables lcd panel.
505 		 */
506 		lcd->power = FB_BLANK_POWERDOWN;
507 
508 		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
509 	} else {
510 		lcd->power = FB_BLANK_UNBLANK;
511 	}
512 
513 	spi_set_drvdata(spi, lcd);
514 
515 	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
516 
517 	return 0;
518 }
519 
520 static int ams369fg06_remove(struct spi_device *spi)
521 {
522 	struct ams369fg06 *lcd = spi_get_drvdata(spi);
523 
524 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
525 	return 0;
526 }
527 
528 #ifdef CONFIG_PM_SLEEP
529 static int ams369fg06_suspend(struct device *dev)
530 {
531 	struct ams369fg06 *lcd = dev_get_drvdata(dev);
532 
533 	dev_dbg(dev, "lcd->power = %d\n", lcd->power);
534 
535 	/*
536 	 * when lcd panel is suspend, lcd panel becomes off
537 	 * regardless of status.
538 	 */
539 	return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
540 }
541 
542 static int ams369fg06_resume(struct device *dev)
543 {
544 	struct ams369fg06 *lcd = dev_get_drvdata(dev);
545 
546 	lcd->power = FB_BLANK_POWERDOWN;
547 
548 	return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
549 }
550 #endif
551 
552 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
553 			ams369fg06_resume);
554 
555 static void ams369fg06_shutdown(struct spi_device *spi)
556 {
557 	struct ams369fg06 *lcd = spi_get_drvdata(spi);
558 
559 	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
560 }
561 
562 static struct spi_driver ams369fg06_driver = {
563 	.driver = {
564 		.name	= "ams369fg06",
565 		.owner	= THIS_MODULE,
566 		.pm	= &ams369fg06_pm_ops,
567 	},
568 	.probe		= ams369fg06_probe,
569 	.remove		= ams369fg06_remove,
570 	.shutdown	= ams369fg06_shutdown,
571 };
572 
573 module_spi_driver(ams369fg06_driver);
574 
575 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
576 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
577 MODULE_LICENSE("GPL");
578