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