ams369fg06.c revision c9023492b49b2a9506ae2f054167da02c71ef2b6
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			msleep(wbuf[i+1]);
215		}
216		i += 2;
217	}
218
219	return ret;
220}
221
222static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
223	const unsigned int *gamma)
224{
225	unsigned int i = 0;
226	int ret = 0;
227
228	for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
229		ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
230		ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
231		ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
232		if (ret) {
233			dev_err(lcd->dev, "failed to set gamma table.\n");
234			goto gamma_err;
235		}
236	}
237
238gamma_err:
239	return ret;
240}
241
242static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
243{
244	int ret = 0;
245	int gamma = 0;
246
247	if ((brightness >= 0) && (brightness <= 50))
248		gamma = 0;
249	else if ((brightness > 50) && (brightness <= 100))
250		gamma = 1;
251	else if ((brightness > 100) && (brightness <= 150))
252		gamma = 2;
253	else if ((brightness > 150) && (brightness <= 200))
254		gamma = 3;
255	else if ((brightness > 200) && (brightness <= 255))
256		gamma = 4;
257
258	ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
259
260	return ret;
261}
262
263static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
264{
265	int ret, i;
266	static const unsigned short *init_seq[] = {
267		seq_setting,
268		seq_stand_by_off,
269	};
270
271	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
272		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
273		if (ret)
274			break;
275	}
276
277	return ret;
278}
279
280static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
281{
282	int ret, i;
283	static const unsigned short *init_seq[] = {
284		seq_stand_by_off,
285		seq_display_on,
286	};
287
288	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
289		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
290		if (ret)
291			break;
292	}
293
294	return ret;
295}
296
297static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
298{
299	int ret, i;
300
301	static const unsigned short *init_seq[] = {
302		seq_display_off,
303		seq_stand_by_on,
304	};
305
306	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
307		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
308		if (ret)
309			break;
310	}
311
312	return ret;
313}
314
315static int ams369fg06_power_is_on(int power)
316{
317	return power <= FB_BLANK_NORMAL;
318}
319
320static int ams369fg06_power_on(struct ams369fg06 *lcd)
321{
322	int ret = 0;
323	struct lcd_platform_data *pd;
324	struct backlight_device *bd;
325
326	pd = lcd->lcd_pd;
327	bd = lcd->bd;
328
329	if (!pd->power_on) {
330		dev_err(lcd->dev, "power_on is NULL.\n");
331		return -EINVAL;
332	} else {
333		pd->power_on(lcd->ld, 1);
334		msleep(pd->power_on_delay);
335	}
336
337	if (!pd->reset) {
338		dev_err(lcd->dev, "reset is NULL.\n");
339		return -EINVAL;
340	} else {
341		pd->reset(lcd->ld);
342		msleep(pd->reset_delay);
343	}
344
345	ret = ams369fg06_ldi_init(lcd);
346	if (ret) {
347		dev_err(lcd->dev, "failed to initialize ldi.\n");
348		return ret;
349	}
350
351	ret = ams369fg06_ldi_enable(lcd);
352	if (ret) {
353		dev_err(lcd->dev, "failed to enable ldi.\n");
354		return ret;
355	}
356
357	/* set brightness to current value after power on or resume. */
358	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
359	if (ret) {
360		dev_err(lcd->dev, "lcd gamma setting failed.\n");
361		return ret;
362	}
363
364	return 0;
365}
366
367static int ams369fg06_power_off(struct ams369fg06 *lcd)
368{
369	int ret;
370	struct lcd_platform_data *pd;
371
372	pd = lcd->lcd_pd;
373
374	ret = ams369fg06_ldi_disable(lcd);
375	if (ret) {
376		dev_err(lcd->dev, "lcd setting failed.\n");
377		return -EIO;
378	}
379
380	msleep(pd->power_off_delay);
381
382	pd->power_on(lcd->ld, 0);
383
384	return 0;
385}
386
387static int ams369fg06_power(struct ams369fg06 *lcd, int power)
388{
389	int ret = 0;
390
391	if (ams369fg06_power_is_on(power) &&
392		!ams369fg06_power_is_on(lcd->power))
393		ret = ams369fg06_power_on(lcd);
394	else if (!ams369fg06_power_is_on(power) &&
395		ams369fg06_power_is_on(lcd->power))
396		ret = ams369fg06_power_off(lcd);
397
398	if (!ret)
399		lcd->power = power;
400
401	return ret;
402}
403
404static int ams369fg06_get_power(struct lcd_device *ld)
405{
406	struct ams369fg06 *lcd = lcd_get_data(ld);
407
408	return lcd->power;
409}
410
411static int ams369fg06_set_power(struct lcd_device *ld, int power)
412{
413	struct ams369fg06 *lcd = lcd_get_data(ld);
414
415	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
416		power != FB_BLANK_NORMAL) {
417		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
418		return -EINVAL;
419	}
420
421	return ams369fg06_power(lcd, power);
422}
423
424static int ams369fg06_get_brightness(struct backlight_device *bd)
425{
426	return bd->props.brightness;
427}
428
429static int ams369fg06_set_brightness(struct backlight_device *bd)
430{
431	int ret = 0;
432	int brightness = bd->props.brightness;
433	struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
434
435	if (brightness < MIN_BRIGHTNESS ||
436		brightness > bd->props.max_brightness) {
437		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
438			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
439		return -EINVAL;
440	}
441
442	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
443	if (ret) {
444		dev_err(&bd->dev, "lcd brightness setting failed.\n");
445		return -EIO;
446	}
447
448	return ret;
449}
450
451static struct lcd_ops ams369fg06_lcd_ops = {
452	.get_power = ams369fg06_get_power,
453	.set_power = ams369fg06_set_power,
454};
455
456static const struct backlight_ops ams369fg06_backlight_ops = {
457	.get_brightness = ams369fg06_get_brightness,
458	.update_status = ams369fg06_set_brightness,
459};
460
461static int ams369fg06_probe(struct spi_device *spi)
462{
463	int ret = 0;
464	struct ams369fg06 *lcd = NULL;
465	struct lcd_device *ld = NULL;
466	struct backlight_device *bd = NULL;
467	struct backlight_properties props;
468
469	lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
470	if (!lcd)
471		return -ENOMEM;
472
473	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
474	spi->bits_per_word = 16;
475
476	ret = spi_setup(spi);
477	if (ret < 0) {
478		dev_err(&spi->dev, "spi setup failed.\n");
479		return ret;
480	}
481
482	lcd->spi = spi;
483	lcd->dev = &spi->dev;
484
485	lcd->lcd_pd = spi->dev.platform_data;
486	if (!lcd->lcd_pd) {
487		dev_err(&spi->dev, "platform data is NULL\n");
488		return -EINVAL;
489	}
490
491	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
492		&ams369fg06_lcd_ops);
493	if (IS_ERR(ld))
494		return PTR_ERR(ld);
495
496	lcd->ld = ld;
497
498	memset(&props, 0, sizeof(struct backlight_properties));
499	props.type = BACKLIGHT_RAW;
500	props.max_brightness = MAX_BRIGHTNESS;
501
502	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
503		&ams369fg06_backlight_ops, &props);
504	if (IS_ERR(bd)) {
505		ret =  PTR_ERR(bd);
506		goto out_lcd_unregister;
507	}
508
509	bd->props.brightness = DEFAULT_BRIGHTNESS;
510	lcd->bd = bd;
511
512	if (!lcd->lcd_pd->lcd_enabled) {
513		/*
514		 * if lcd panel was off from bootloader then
515		 * current lcd status is powerdown and then
516		 * it enables lcd panel.
517		 */
518		lcd->power = FB_BLANK_POWERDOWN;
519
520		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
521	} else {
522		lcd->power = FB_BLANK_UNBLANK;
523	}
524
525	dev_set_drvdata(&spi->dev, lcd);
526
527	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
528
529	return 0;
530
531out_lcd_unregister:
532	lcd_device_unregister(ld);
533	return ret;
534}
535
536static int ams369fg06_remove(struct spi_device *spi)
537{
538	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
539
540	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
541	backlight_device_unregister(lcd->bd);
542	lcd_device_unregister(lcd->ld);
543
544	return 0;
545}
546
547#if defined(CONFIG_PM)
548static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
549{
550	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
551
552	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
553
554	/*
555	 * when lcd panel is suspend, lcd panel becomes off
556	 * regardless of status.
557	 */
558	return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
559}
560
561static int ams369fg06_resume(struct spi_device *spi)
562{
563	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
564
565	lcd->power = FB_BLANK_POWERDOWN;
566
567	return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
568}
569#else
570#define ams369fg06_suspend	NULL
571#define ams369fg06_resume	NULL
572#endif
573
574static void ams369fg06_shutdown(struct spi_device *spi)
575{
576	struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev);
577
578	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
579}
580
581static struct spi_driver ams369fg06_driver = {
582	.driver = {
583		.name	= "ams369fg06",
584		.owner	= THIS_MODULE,
585	},
586	.probe		= ams369fg06_probe,
587	.remove		= ams369fg06_remove,
588	.shutdown	= ams369fg06_shutdown,
589	.suspend	= ams369fg06_suspend,
590	.resume		= ams369fg06_resume,
591};
592
593module_spi_driver(ams369fg06_driver);
594
595MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
596MODULE_DESCRIPTION("ams369fg06 LCD Driver");
597MODULE_LICENSE("GPL");
598