ams369fg06.c revision c7855f15e2bed7a35edecc3ebcaa445ea8a57848
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
37struct 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
46static const unsigned short seq_display_on[] = {
47	0x14, 0x03,
48	ENDDEF, 0x0000
49};
50
51static const unsigned short seq_display_off[] = {
52	0x14, 0x00,
53	ENDDEF, 0x0000
54};
55
56static const unsigned short seq_stand_by_on[] = {
57	0x1D, 0xA1,
58	SLEEPMSEC, 200,
59	ENDDEF, 0x0000
60};
61
62static const unsigned short seq_stand_by_off[] = {
63	0x1D, 0xA0,
64	SLEEPMSEC, 250,
65	ENDDEF, 0x0000
66};
67
68static 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 */
121static 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
127static 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
133static 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
139static 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
145static 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
151struct ams369fg06_gamma {
152	unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
153};
154
155static 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
163static 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
181static 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
194static 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
213static 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
229gamma_err:
230	return ret;
231}
232
233static 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
254static 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
271static 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
288static 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
306static int ams369fg06_power_is_on(int power)
307{
308	return power <= FB_BLANK_NORMAL;
309}
310
311static 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		dev_err(lcd->dev, "power_on is NULL.\n");
322		return -EINVAL;
323	} else {
324		pd->power_on(lcd->ld, 1);
325		msleep(pd->power_on_delay);
326	}
327
328	if (!pd->reset) {
329		dev_err(lcd->dev, "reset is NULL.\n");
330		return -EINVAL;
331	} else {
332		pd->reset(lcd->ld);
333		msleep(pd->reset_delay);
334	}
335
336	ret = ams369fg06_ldi_init(lcd);
337	if (ret) {
338		dev_err(lcd->dev, "failed to initialize ldi.\n");
339		return ret;
340	}
341
342	ret = ams369fg06_ldi_enable(lcd);
343	if (ret) {
344		dev_err(lcd->dev, "failed to enable ldi.\n");
345		return ret;
346	}
347
348	/* set brightness to current value after power on or resume. */
349	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
350	if (ret) {
351		dev_err(lcd->dev, "lcd gamma setting failed.\n");
352		return ret;
353	}
354
355	return 0;
356}
357
358static int ams369fg06_power_off(struct ams369fg06 *lcd)
359{
360	int ret;
361	struct lcd_platform_data *pd;
362
363	pd = lcd->lcd_pd;
364
365	ret = ams369fg06_ldi_disable(lcd);
366	if (ret) {
367		dev_err(lcd->dev, "lcd setting failed.\n");
368		return -EIO;
369	}
370
371	msleep(pd->power_off_delay);
372
373	pd->power_on(lcd->ld, 0);
374
375	return 0;
376}
377
378static int ams369fg06_power(struct ams369fg06 *lcd, int power)
379{
380	int ret = 0;
381
382	if (ams369fg06_power_is_on(power) &&
383		!ams369fg06_power_is_on(lcd->power))
384		ret = ams369fg06_power_on(lcd);
385	else if (!ams369fg06_power_is_on(power) &&
386		ams369fg06_power_is_on(lcd->power))
387		ret = ams369fg06_power_off(lcd);
388
389	if (!ret)
390		lcd->power = power;
391
392	return ret;
393}
394
395static int ams369fg06_get_power(struct lcd_device *ld)
396{
397	struct ams369fg06 *lcd = lcd_get_data(ld);
398
399	return lcd->power;
400}
401
402static int ams369fg06_set_power(struct lcd_device *ld, int power)
403{
404	struct ams369fg06 *lcd = lcd_get_data(ld);
405
406	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
407		power != FB_BLANK_NORMAL) {
408		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
409		return -EINVAL;
410	}
411
412	return ams369fg06_power(lcd, power);
413}
414
415static int ams369fg06_get_brightness(struct backlight_device *bd)
416{
417	return bd->props.brightness;
418}
419
420static int ams369fg06_set_brightness(struct backlight_device *bd)
421{
422	int ret = 0;
423	int brightness = bd->props.brightness;
424	struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev);
425
426	if (brightness < MIN_BRIGHTNESS ||
427		brightness > bd->props.max_brightness) {
428		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
429			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
430		return -EINVAL;
431	}
432
433	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
434	if (ret) {
435		dev_err(&bd->dev, "lcd brightness setting failed.\n");
436		return -EIO;
437	}
438
439	return ret;
440}
441
442static struct lcd_ops ams369fg06_lcd_ops = {
443	.get_power = ams369fg06_get_power,
444	.set_power = ams369fg06_set_power,
445};
446
447static const struct backlight_ops ams369fg06_backlight_ops = {
448	.get_brightness = ams369fg06_get_brightness,
449	.update_status = ams369fg06_set_brightness,
450};
451
452static int ams369fg06_probe(struct spi_device *spi)
453{
454	int ret = 0;
455	struct ams369fg06 *lcd = NULL;
456	struct lcd_device *ld = NULL;
457	struct backlight_device *bd = NULL;
458	struct backlight_properties props;
459
460	lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
461	if (!lcd)
462		return -ENOMEM;
463
464	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
465	spi->bits_per_word = 16;
466
467	ret = spi_setup(spi);
468	if (ret < 0) {
469		dev_err(&spi->dev, "spi setup failed.\n");
470		return ret;
471	}
472
473	lcd->spi = spi;
474	lcd->dev = &spi->dev;
475
476	lcd->lcd_pd = spi->dev.platform_data;
477	if (!lcd->lcd_pd) {
478		dev_err(&spi->dev, "platform data is NULL\n");
479		return -EINVAL;
480	}
481
482	ld = lcd_device_register("ams369fg06", &spi->dev, lcd,
483		&ams369fg06_lcd_ops);
484	if (IS_ERR(ld))
485		return PTR_ERR(ld);
486
487	lcd->ld = ld;
488
489	memset(&props, 0, sizeof(struct backlight_properties));
490	props.type = BACKLIGHT_RAW;
491	props.max_brightness = MAX_BRIGHTNESS;
492
493	bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd,
494		&ams369fg06_backlight_ops, &props);
495	if (IS_ERR(bd)) {
496		ret =  PTR_ERR(bd);
497		goto out_lcd_unregister;
498	}
499
500	bd->props.brightness = DEFAULT_BRIGHTNESS;
501	lcd->bd = bd;
502
503	if (!lcd->lcd_pd->lcd_enabled) {
504		/*
505		 * if lcd panel was off from bootloader then
506		 * current lcd status is powerdown and then
507		 * it enables lcd panel.
508		 */
509		lcd->power = FB_BLANK_POWERDOWN;
510
511		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
512	} else {
513		lcd->power = FB_BLANK_UNBLANK;
514	}
515
516	spi_set_drvdata(spi, lcd);
517
518	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
519
520	return 0;
521
522out_lcd_unregister:
523	lcd_device_unregister(ld);
524	return ret;
525}
526
527static int ams369fg06_remove(struct spi_device *spi)
528{
529	struct ams369fg06 *lcd = spi_get_drvdata(spi);
530
531	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
532	backlight_device_unregister(lcd->bd);
533	lcd_device_unregister(lcd->ld);
534
535	return 0;
536}
537
538#if defined(CONFIG_PM)
539static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg)
540{
541	struct ams369fg06 *lcd = spi_get_drvdata(spi);
542
543	dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power);
544
545	/*
546	 * when lcd panel is suspend, lcd panel becomes off
547	 * regardless of status.
548	 */
549	return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
550}
551
552static int ams369fg06_resume(struct spi_device *spi)
553{
554	struct ams369fg06 *lcd = spi_get_drvdata(spi);
555
556	lcd->power = FB_BLANK_POWERDOWN;
557
558	return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
559}
560#else
561#define ams369fg06_suspend	NULL
562#define ams369fg06_resume	NULL
563#endif
564
565static void ams369fg06_shutdown(struct spi_device *spi)
566{
567	struct ams369fg06 *lcd = spi_get_drvdata(spi);
568
569	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
570}
571
572static struct spi_driver ams369fg06_driver = {
573	.driver = {
574		.name	= "ams369fg06",
575		.owner	= THIS_MODULE,
576	},
577	.probe		= ams369fg06_probe,
578	.remove		= ams369fg06_remove,
579	.shutdown	= ams369fg06_shutdown,
580	.suspend	= ams369fg06_suspend,
581	.resume		= ams369fg06_resume,
582};
583
584module_spi_driver(ams369fg06_driver);
585
586MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
587MODULE_DESCRIPTION("ams369fg06 LCD Driver");
588MODULE_LICENSE("GPL");
589