ams369fg06.c revision 80629efcae09c5d80a9fdeea5226cd81b4fec7f3
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 = devm_kzalloc(&spi->dev, 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		return ret;
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		return -EFAULT;
505	}
506
507	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
508		&ams369fg06_lcd_ops);
509	if (IS_ERR(ld))
510		return PTR_ERR(ld);
511
512	lcd->ld = ld;
513
514	memset(&props, 0, sizeof(struct backlight_properties));
515	props.type = BACKLIGHT_RAW;
516	props.max_brightness = MAX_BRIGHTNESS;
517
518	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
519		&ams369fg06_backlight_ops, &props);
520	if (IS_ERR(bd)) {
521		ret =  PTR_ERR(bd);
522		goto out_lcd_unregister;
523	}
524
525	bd->props.brightness = DEFAULT_BRIGHTNESS;
526	lcd->bd = bd;
527
528	if (!lcd->lcd_pd->lcd_enabled) {
529		/*
530		 * if lcd panel was off from bootloader then
531		 * current lcd status is powerdown and then
532		 * it enables lcd panel.
533		 */
534		lcd->power = FB_BLANK_POWERDOWN;
535
536		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
537	} else
538		lcd->power = FB_BLANK_UNBLANK;
539
540	dev_set_drvdata(&spi->dev, lcd);
541
542	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
543
544	return 0;
545
546out_lcd_unregister:
547	lcd_device_unregister(ld);
548	return ret;
549}
550
551static int __devexit ams369fg06_remove(struct spi_device *spi)
552{
553	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
554
555	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
556	backlight_device_unregister(lcd->bd);
557	lcd_device_unregister(lcd->ld);
558
559	return 0;
560}
561
562#if defined(CONFIG_PM)
563static unsigned int before_power;
564
565static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
566{
567	int ret = 0;
568	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
569
570	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
571
572	before_power = lcd->power;
573
574	/*
575	 * when lcd panel is suspend, lcd panel becomes off
576	 * regardless of status.
577	 */
578	ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
579
580	return ret;
581}
582
583static int ams369fg06_resume(struct spi_device *spi)
584{
585	int ret = 0;
586	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
587
588	/*
589	 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
590	 * (at that time, before_power is FB_BLANK_UNBLANK) then
591	 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
592	 */
593	if (before_power == FB_BLANK_UNBLANK)
594		lcd->power = FB_BLANK_POWERDOWN;
595
596	dev_dbg(&spi->dev, "before_power = %d\n", before_power);
597
598	ret = ams369fg06_power(lcd, before_power);
599
600	return ret;
601}
602#else
603#define ams369fg06_suspend	NULL
604#define ams369fg06_resume	NULL
605#endif
606
607static void ams369fg06_shutdown(struct spi_device *spi)
608{
609	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
610
611	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
612}
613
614static struct spi_driver ams369fg06_driver = {
615	.driver = {
616		.name	= "ams369fg06",
617		.owner	= THIS_MODULE,
618	},
619	.probe		= ams369fg06_probe,
620	.remove		= __devexit_p(ams369fg06_remove),
621	.shutdown	= ams369fg06_shutdown,
622	.suspend	= ams369fg06_suspend,
623	.resume		= ams369fg06_resume,
624};
625
626module_spi_driver(ams369fg06_driver);
627
628MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
629MODULE_DESCRIPTION("ams369fg06 LCD Driver");
630MODULE_LICENSE("GPL");
631