ams369fg06.c revision 462dd83833065a6b3add3f102f4fe69efa1422e9
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
46struct 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
55static const unsigned short seq_display_on[] = {
56	0x14, 0x03,
57	ENDDEF, 0x0000
58};
59
60static const unsigned short seq_display_off[] = {
61	0x14, 0x00,
62	ENDDEF, 0x0000
63};
64
65static const unsigned short seq_stand_by_on[] = {
66	0x1D, 0xA1,
67	SLEEPMSEC, 200,
68	ENDDEF, 0x0000
69};
70
71static const unsigned short seq_stand_by_off[] = {
72	0x1D, 0xA0,
73	SLEEPMSEC, 250,
74	ENDDEF, 0x0000
75};
76
77static 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 */
130static 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
136static 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
142static 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
148static 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
154static 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
160struct ams369fg06_gamma {
161	unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
162};
163
164static 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
172static 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
190static 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
203static 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
221static 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
237gamma_err:
238	return ret;
239}
240
241static 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
262static 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
279static 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
296static 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
314static int ams369fg06_power_is_on(int power)
315{
316	return ((power) <= FB_BLANK_NORMAL);
317}
318
319static 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
375static 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
403static 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
420static int ams369fg06_get_power(struct lcd_device *ld)
421{
422	struct ams369fg06 *lcd = lcd_get_data(ld);
423
424	return lcd->power;
425}
426
427static 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
440static int ams369fg06_get_brightness(struct backlight_device *bd)
441{
442	return bd->props.brightness;
443}
444
445static 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
467static struct lcd_ops ams369fg06_lcd_ops = {
468	.get_power = ams369fg06_get_power,
469	.set_power = ams369fg06_set_power,
470};
471
472static const struct backlight_ops ams369fg06_backlight_ops = {
473	.get_brightness = ams369fg06_get_brightness,
474	.update_status = ams369fg06_set_brightness,
475};
476
477static 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
548out_lcd_unregister:
549	lcd_device_unregister(ld);
550out_free_lcd:
551	kfree(lcd);
552	return ret;
553}
554
555static 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)
568static unsigned int before_power;
569
570static 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
588static 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
612static 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
619static 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
632module_spi_driver(ams369fg06_driver);
633
634MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
635MODULE_DESCRIPTION("ams369fg06 LCD Driver");
636MODULE_LICENSE("GPL");
637