ams369fg06.c revision ef22f6a70c9186c8e25f757b0e8f7374b37f69bf
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/fb.h>
26#include <linux/delay.h>
27#include <linux/gpio.h>
28#include <linux/spi/spi.h>
29#include <linux/lcd.h>
30#include <linux/backlight.h>
31
32#define SLEEPMSEC		0x1000
33#define ENDDEF			0x2000
34#define	DEFMASK			0xFF00
35#define COMMAND_ONLY		0xFE
36#define DATA_ONLY		0xFF
37
38#define MAX_GAMMA_LEVEL		5
39#define GAMMA_TABLE_COUNT	21
40
41#define MIN_BRIGHTNESS		0
42#define MAX_BRIGHTNESS		255
43#define DEFAULT_BRIGHTNESS	150
44
45struct ams369fg06 {
46	struct device			*dev;
47	struct spi_device		*spi;
48	unsigned int			power;
49	struct lcd_device		*ld;
50	struct backlight_device		*bd;
51	struct lcd_platform_data	*lcd_pd;
52};
53
54static const unsigned short seq_display_on[] = {
55	0x14, 0x03,
56	ENDDEF, 0x0000
57};
58
59static const unsigned short seq_display_off[] = {
60	0x14, 0x00,
61	ENDDEF, 0x0000
62};
63
64static const unsigned short seq_stand_by_on[] = {
65	0x1D, 0xA1,
66	SLEEPMSEC, 200,
67	ENDDEF, 0x0000
68};
69
70static const unsigned short seq_stand_by_off[] = {
71	0x1D, 0xA0,
72	SLEEPMSEC, 250,
73	ENDDEF, 0x0000
74};
75
76static const unsigned short seq_setting[] = {
77	0x31, 0x08,
78	0x32, 0x14,
79	0x30, 0x02,
80	0x27, 0x01,
81	0x12, 0x08,
82	0x13, 0x08,
83	0x15, 0x00,
84	0x16, 0x00,
85
86	0xef, 0xd0,
87	DATA_ONLY, 0xe8,
88
89	0x39, 0x44,
90	0x40, 0x00,
91	0x41, 0x3f,
92	0x42, 0x2a,
93	0x43, 0x27,
94	0x44, 0x27,
95	0x45, 0x1f,
96	0x46, 0x44,
97	0x50, 0x00,
98	0x51, 0x00,
99	0x52, 0x17,
100	0x53, 0x24,
101	0x54, 0x26,
102	0x55, 0x1f,
103	0x56, 0x43,
104	0x60, 0x00,
105	0x61, 0x3f,
106	0x62, 0x2a,
107	0x63, 0x25,
108	0x64, 0x24,
109	0x65, 0x1b,
110	0x66, 0x5c,
111
112	0x17, 0x22,
113	0x18, 0x33,
114	0x19, 0x03,
115	0x1a, 0x01,
116	0x22, 0xa4,
117	0x23, 0x00,
118	0x26, 0xa0,
119
120	0x1d, 0xa0,
121	SLEEPMSEC, 300,
122
123	0x14, 0x03,
124
125	ENDDEF, 0x0000
126};
127
128/* gamma value: 2.2 */
129static const unsigned int ams369fg06_22_250[] = {
130	0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44,
131	0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43,
132	0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c,
133};
134
135static const unsigned int ams369fg06_22_200[] = {
136	0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e,
137	0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d,
138	0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53,
139};
140
141static const unsigned int ams369fg06_22_150[] = {
142	0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37,
143	0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36,
144	0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a,
145};
146
147static const unsigned int ams369fg06_22_100[] = {
148	0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f,
149	0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e,
150	0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f,
151};
152
153static const unsigned int ams369fg06_22_50[] = {
154	0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24,
155	0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23,
156	0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31,
157};
158
159struct ams369fg06_gamma {
160	unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
161};
162
163static struct ams369fg06_gamma gamma_table = {
164	.gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50,
165	.gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100,
166	.gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150,
167	.gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200,
168	.gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250,
169};
170
171static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
172{
173	u16 buf[1];
174	struct spi_message msg;
175
176	struct spi_transfer xfer = {
177		.len		= 2,
178		.tx_buf		= buf,
179	};
180
181	buf[0] = (addr << 8) | data;
182
183	spi_message_init(&msg);
184	spi_message_add_tail(&xfer, &msg);
185
186	return spi_sync(lcd->spi, &msg);
187}
188
189static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
190	unsigned char command)
191{
192	int ret = 0;
193
194	if (address != DATA_ONLY)
195		ret = ams369fg06_spi_write_byte(lcd, 0x70, address);
196	if (command != COMMAND_ONLY)
197		ret = ams369fg06_spi_write_byte(lcd, 0x72, command);
198
199	return ret;
200}
201
202static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
203	const unsigned short *wbuf)
204{
205	int ret = 0, i = 0;
206
207	while ((wbuf[i] & DEFMASK) != ENDDEF) {
208		if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
209			ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]);
210			if (ret)
211				break;
212		} else
213			mdelay(wbuf[i+1]);
214		i += 2;
215	}
216
217	return ret;
218}
219
220static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
221	const unsigned int *gamma)
222{
223	unsigned int i = 0;
224	int ret = 0;
225
226	for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
227		ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
228		ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
229		ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
230		if (ret) {
231			dev_err(lcd->dev, "failed to set gamma table.\n");
232			goto gamma_err;
233		}
234	}
235
236gamma_err:
237	return ret;
238}
239
240static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
241{
242	int ret = 0;
243	int gamma = 0;
244
245	if ((brightness >= 0) && (brightness <= 50))
246		gamma = 0;
247	else if ((brightness > 50) && (brightness <= 100))
248		gamma = 1;
249	else if ((brightness > 100) && (brightness <= 150))
250		gamma = 2;
251	else if ((brightness > 150) && (brightness <= 200))
252		gamma = 3;
253	else if ((brightness > 200) && (brightness <= 255))
254		gamma = 4;
255
256	ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
257
258	return ret;
259}
260
261static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
262{
263	int ret, i;
264	static const unsigned short *init_seq[] = {
265		seq_setting,
266		seq_stand_by_off,
267	};
268
269	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
270		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
271		if (ret)
272			break;
273	}
274
275	return ret;
276}
277
278static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
279{
280	int ret, i;
281	static const unsigned short *init_seq[] = {
282		seq_stand_by_off,
283		seq_display_on,
284	};
285
286	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
287		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
288		if (ret)
289			break;
290	}
291
292	return ret;
293}
294
295static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
296{
297	int ret, i;
298
299	static const unsigned short *init_seq[] = {
300		seq_display_off,
301		seq_stand_by_on,
302	};
303
304	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
305		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
306		if (ret)
307			break;
308	}
309
310	return ret;
311}
312
313static int ams369fg06_power_is_on(int power)
314{
315	return ((power) <= FB_BLANK_NORMAL);
316}
317
318static int ams369fg06_power_on(struct ams369fg06 *lcd)
319{
320	int ret = 0;
321	struct lcd_platform_data *pd = NULL;
322	struct backlight_device *bd = NULL;
323
324	pd = lcd->lcd_pd;
325	if (!pd) {
326		dev_err(lcd->dev, "platform data is NULL.\n");
327		return -EFAULT;
328	}
329
330	bd = lcd->bd;
331	if (!bd) {
332		dev_err(lcd->dev, "backlight device is NULL.\n");
333		return -EFAULT;
334	}
335
336	if (!pd->power_on) {
337		dev_err(lcd->dev, "power_on is NULL.\n");
338		return -EFAULT;
339	} else {
340		pd->power_on(lcd->ld, 1);
341		mdelay(pd->power_on_delay);
342	}
343
344	if (!pd->reset) {
345		dev_err(lcd->dev, "reset is NULL.\n");
346		return -EFAULT;
347	} else {
348		pd->reset(lcd->ld);
349		mdelay(pd->reset_delay);
350	}
351
352	ret = ams369fg06_ldi_init(lcd);
353	if (ret) {
354		dev_err(lcd->dev, "failed to initialize ldi.\n");
355		return ret;
356	}
357
358	ret = ams369fg06_ldi_enable(lcd);
359	if (ret) {
360		dev_err(lcd->dev, "failed to enable ldi.\n");
361		return ret;
362	}
363
364	/* set brightness to current value after power on or resume. */
365	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
366	if (ret) {
367		dev_err(lcd->dev, "lcd gamma setting failed.\n");
368		return ret;
369	}
370
371	return 0;
372}
373
374static int ams369fg06_power_off(struct ams369fg06 *lcd)
375{
376	int ret = 0;
377	struct lcd_platform_data *pd = NULL;
378
379	pd = lcd->lcd_pd;
380	if (!pd) {
381		dev_err(lcd->dev, "platform data is NULL\n");
382		return -EFAULT;
383	}
384
385	ret = ams369fg06_ldi_disable(lcd);
386	if (ret) {
387		dev_err(lcd->dev, "lcd setting failed.\n");
388		return -EIO;
389	}
390
391	mdelay(pd->power_off_delay);
392
393	if (!pd->power_on) {
394		dev_err(lcd->dev, "power_on is NULL.\n");
395		return -EFAULT;
396	} else
397		pd->power_on(lcd->ld, 0);
398
399	return 0;
400}
401
402static int ams369fg06_power(struct ams369fg06 *lcd, int power)
403{
404	int ret = 0;
405
406	if (ams369fg06_power_is_on(power) &&
407		!ams369fg06_power_is_on(lcd->power))
408		ret = ams369fg06_power_on(lcd);
409	else if (!ams369fg06_power_is_on(power) &&
410		ams369fg06_power_is_on(lcd->power))
411		ret = ams369fg06_power_off(lcd);
412
413	if (!ret)
414		lcd->power = power;
415
416	return ret;
417}
418
419static int ams369fg06_get_power(struct lcd_device *ld)
420{
421	struct ams369fg06 *lcd = lcd_get_data(ld);
422
423	return lcd->power;
424}
425
426static int ams369fg06_set_power(struct lcd_device *ld, int power)
427{
428	struct ams369fg06 *lcd = lcd_get_data(ld);
429
430	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
431		power != FB_BLANK_NORMAL) {
432		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
433		return -EINVAL;
434	}
435
436	return ams369fg06_power(lcd, power);
437}
438
439static int ams369fg06_get_brightness(struct backlight_device *bd)
440{
441	return bd->props.brightness;
442}
443
444static int ams369fg06_set_brightness(struct backlight_device *bd)
445{
446	int ret = 0;
447	int brightness = bd->props.brightness;
448	struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
449
450	if (brightness < MIN_BRIGHTNESS ||
451		brightness > bd->props.max_brightness) {
452		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
453			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
454		return -EINVAL;
455	}
456
457	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
458	if (ret) {
459		dev_err(&bd->dev, "lcd brightness setting failed.\n");
460		return -EIO;
461	}
462
463	return ret;
464}
465
466static struct lcd_ops ams369fg06_lcd_ops = {
467	.get_power = ams369fg06_get_power,
468	.set_power = ams369fg06_set_power,
469};
470
471static const struct backlight_ops ams369fg06_backlight_ops = {
472	.get_brightness = ams369fg06_get_brightness,
473	.update_status = ams369fg06_set_brightness,
474};
475
476static int __devinit ams369fg06_probe(struct spi_device *spi)
477{
478	int ret = 0;
479	struct ams369fg06 *lcd = NULL;
480	struct lcd_device *ld = NULL;
481	struct backlight_device *bd = NULL;
482	struct backlight_properties props;
483
484	lcd = kzalloc(sizeof(struct ams369fg06), GFP_KERNEL);
485	if (!lcd)
486		return -ENOMEM;
487
488	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
489	spi->bits_per_word = 16;
490
491	ret = spi_setup(spi);
492	if (ret < 0) {
493		dev_err(&spi->dev, "spi setup failed.\n");
494		goto out_free_lcd;
495	}
496
497	lcd->spi = spi;
498	lcd->dev = &spi->dev;
499
500	lcd->lcd_pd = spi->dev.platform_data;
501	if (!lcd->lcd_pd) {
502		dev_err(&spi->dev, "platform data is NULL\n");
503		goto out_free_lcd;
504	}
505
506	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
507		&ams369fg06_lcd_ops);
508	if (IS_ERR(ld)) {
509		ret = PTR_ERR(ld);
510		goto out_free_lcd;
511	}
512
513	lcd->ld = ld;
514
515	memset(&props, 0, sizeof(struct backlight_properties));
516	props.type = BACKLIGHT_RAW;
517	props.max_brightness = MAX_BRIGHTNESS;
518
519	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
520		&ams369fg06_backlight_ops, &props);
521	if (IS_ERR(bd)) {
522		ret =  PTR_ERR(bd);
523		goto out_lcd_unregister;
524	}
525
526	bd->props.brightness = DEFAULT_BRIGHTNESS;
527	lcd->bd = bd;
528
529	if (!lcd->lcd_pd->lcd_enabled) {
530		/*
531		 * if lcd panel was off from bootloader then
532		 * current lcd status is powerdown and then
533		 * it enables lcd panel.
534		 */
535		lcd->power = FB_BLANK_POWERDOWN;
536
537		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
538	} else
539		lcd->power = FB_BLANK_UNBLANK;
540
541	dev_set_drvdata(&spi->dev, lcd);
542
543	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
544
545	return 0;
546
547out_lcd_unregister:
548	lcd_device_unregister(ld);
549out_free_lcd:
550	kfree(lcd);
551	return ret;
552}
553
554static int __devexit ams369fg06_remove(struct spi_device *spi)
555{
556	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
557
558	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
559	backlight_device_unregister(lcd->bd);
560	lcd_device_unregister(lcd->ld);
561	kfree(lcd);
562
563	return 0;
564}
565
566#if defined(CONFIG_PM)
567static unsigned int before_power;
568
569static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
570{
571	int ret = 0;
572	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
573
574	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
575
576	before_power = lcd->power;
577
578	/*
579	 * when lcd panel is suspend, lcd panel becomes off
580	 * regardless of status.
581	 */
582	ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
583
584	return ret;
585}
586
587static int ams369fg06_resume(struct spi_device *spi)
588{
589	int ret = 0;
590	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
591
592	/*
593	 * after suspended, if lcd panel status is FB_BLANK_UNBLANK
594	 * (at that time, before_power is FB_BLANK_UNBLANK) then
595	 * it changes that status to FB_BLANK_POWERDOWN to get lcd on.
596	 */
597	if (before_power == FB_BLANK_UNBLANK)
598		lcd->power = FB_BLANK_POWERDOWN;
599
600	dev_dbg(&spi->dev, "before_power = %d\n", before_power);
601
602	ret = ams369fg06_power(lcd, before_power);
603
604	return ret;
605}
606#else
607#define ams369fg06_suspend	NULL
608#define ams369fg06_resume	NULL
609#endif
610
611static void ams369fg06_shutdown(struct spi_device *spi)
612{
613	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
614
615	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
616}
617
618static struct spi_driver ams369fg06_driver = {
619	.driver = {
620		.name	= "ams369fg06",
621		.bus	= &spi_bus_type,
622		.owner	= THIS_MODULE,
623	},
624	.probe		= ams369fg06_probe,
625	.remove		= __devexit_p(ams369fg06_remove),
626	.shutdown	= ams369fg06_shutdown,
627	.suspend	= ams369fg06_suspend,
628	.resume		= ams369fg06_resume,
629};
630
631static int __init ams369fg06_init(void)
632{
633	return spi_register_driver(&ams369fg06_driver);
634}
635
636static void __exit ams369fg06_exit(void)
637{
638	spi_unregister_driver(&ams369fg06_driver);
639}
640
641module_init(ams369fg06_init);
642module_exit(ams369fg06_exit);
643
644MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
645MODULE_DESCRIPTION("ams369fg06 LCD Driver");
646MODULE_LICENSE("GPL");
647