ams369fg06.c revision a5d8e2e73c7f1dcee485d55225628317d8d441c0
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		pd->power_on(lcd->ld, 1);
322		msleep(pd->power_on_delay);
323	}
324
325	if (!pd->reset) {
326		dev_err(lcd->dev, "reset is NULL.\n");
327		return -EINVAL;
328	} else {
329		pd->reset(lcd->ld);
330		msleep(pd->reset_delay);
331	}
332
333	ret = ams369fg06_ldi_init(lcd);
334	if (ret) {
335		dev_err(lcd->dev, "failed to initialize ldi.\n");
336		return ret;
337	}
338
339	ret = ams369fg06_ldi_enable(lcd);
340	if (ret) {
341		dev_err(lcd->dev, "failed to enable ldi.\n");
342		return ret;
343	}
344
345	/* set brightness to current value after power on or resume. */
346	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
347	if (ret) {
348		dev_err(lcd->dev, "lcd gamma setting failed.\n");
349		return ret;
350	}
351
352	return 0;
353}
354
355static int ams369fg06_power_off(struct ams369fg06 *lcd)
356{
357	int ret;
358	struct lcd_platform_data *pd;
359
360	pd = lcd->lcd_pd;
361
362	ret = ams369fg06_ldi_disable(lcd);
363	if (ret) {
364		dev_err(lcd->dev, "lcd setting failed.\n");
365		return -EIO;
366	}
367
368	msleep(pd->power_off_delay);
369
370	if (pd->power_on)
371		pd->power_on(lcd->ld, 0);
372
373	return 0;
374}
375
376static int ams369fg06_power(struct ams369fg06 *lcd, int power)
377{
378	int ret = 0;
379
380	if (ams369fg06_power_is_on(power) &&
381		!ams369fg06_power_is_on(lcd->power))
382		ret = ams369fg06_power_on(lcd);
383	else if (!ams369fg06_power_is_on(power) &&
384		ams369fg06_power_is_on(lcd->power))
385		ret = ams369fg06_power_off(lcd);
386
387	if (!ret)
388		lcd->power = power;
389
390	return ret;
391}
392
393static int ams369fg06_get_power(struct lcd_device *ld)
394{
395	struct ams369fg06 *lcd = lcd_get_data(ld);
396
397	return lcd->power;
398}
399
400static int ams369fg06_set_power(struct lcd_device *ld, int power)
401{
402	struct ams369fg06 *lcd = lcd_get_data(ld);
403
404	if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
405		power != FB_BLANK_NORMAL) {
406		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
407		return -EINVAL;
408	}
409
410	return ams369fg06_power(lcd, power);
411}
412
413static int ams369fg06_set_brightness(struct backlight_device *bd)
414{
415	int ret = 0;
416	int brightness = bd->props.brightness;
417	struct ams369fg06 *lcd = bl_get_data(bd);
418
419	if (brightness < MIN_BRIGHTNESS ||
420		brightness > bd->props.max_brightness) {
421		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
422			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
423		return -EINVAL;
424	}
425
426	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
427	if (ret) {
428		dev_err(&bd->dev, "lcd brightness setting failed.\n");
429		return -EIO;
430	}
431
432	return ret;
433}
434
435static struct lcd_ops ams369fg06_lcd_ops = {
436	.get_power = ams369fg06_get_power,
437	.set_power = ams369fg06_set_power,
438};
439
440static const struct backlight_ops ams369fg06_backlight_ops = {
441	.update_status = ams369fg06_set_brightness,
442};
443
444static int ams369fg06_probe(struct spi_device *spi)
445{
446	int ret = 0;
447	struct ams369fg06 *lcd = NULL;
448	struct lcd_device *ld = NULL;
449	struct backlight_device *bd = NULL;
450	struct backlight_properties props;
451
452	lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
453	if (!lcd)
454		return -ENOMEM;
455
456	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
457	spi->bits_per_word = 16;
458
459	ret = spi_setup(spi);
460	if (ret < 0) {
461		dev_err(&spi->dev, "spi setup failed.\n");
462		return ret;
463	}
464
465	lcd->spi = spi;
466	lcd->dev = &spi->dev;
467
468	lcd->lcd_pd = dev_get_platdata(&spi->dev);
469	if (!lcd->lcd_pd) {
470		dev_err(&spi->dev, "platform data is NULL\n");
471		return -EINVAL;
472	}
473
474	ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd,
475					&ams369fg06_lcd_ops);
476	if (IS_ERR(ld))
477		return PTR_ERR(ld);
478
479	lcd->ld = ld;
480
481	memset(&props, 0, sizeof(struct backlight_properties));
482	props.type = BACKLIGHT_RAW;
483	props.max_brightness = MAX_BRIGHTNESS;
484
485	bd = devm_backlight_device_register(&spi->dev, "ams369fg06-bl",
486					&spi->dev, lcd,
487					&ams369fg06_backlight_ops, &props);
488	if (IS_ERR(bd))
489		return PTR_ERR(bd);
490
491	bd->props.brightness = DEFAULT_BRIGHTNESS;
492	lcd->bd = bd;
493
494	if (!lcd->lcd_pd->lcd_enabled) {
495		/*
496		 * if lcd panel was off from bootloader then
497		 * current lcd status is powerdown and then
498		 * it enables lcd panel.
499		 */
500		lcd->power = FB_BLANK_POWERDOWN;
501
502		ams369fg06_power(lcd, FB_BLANK_UNBLANK);
503	} else {
504		lcd->power = FB_BLANK_UNBLANK;
505	}
506
507	spi_set_drvdata(spi, lcd);
508
509	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
510
511	return 0;
512}
513
514static int ams369fg06_remove(struct spi_device *spi)
515{
516	struct ams369fg06 *lcd = spi_get_drvdata(spi);
517
518	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
519	return 0;
520}
521
522#ifdef CONFIG_PM_SLEEP
523static int ams369fg06_suspend(struct device *dev)
524{
525	struct ams369fg06 *lcd = dev_get_drvdata(dev);
526
527	dev_dbg(dev, "lcd->power = %d\n", lcd->power);
528
529	/*
530	 * when lcd panel is suspend, lcd panel becomes off
531	 * regardless of status.
532	 */
533	return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
534}
535
536static int ams369fg06_resume(struct device *dev)
537{
538	struct ams369fg06 *lcd = dev_get_drvdata(dev);
539
540	lcd->power = FB_BLANK_POWERDOWN;
541
542	return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
543}
544#endif
545
546static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
547			ams369fg06_resume);
548
549static void ams369fg06_shutdown(struct spi_device *spi)
550{
551	struct ams369fg06 *lcd = spi_get_drvdata(spi);
552
553	ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
554}
555
556static struct spi_driver ams369fg06_driver = {
557	.driver = {
558		.name	= "ams369fg06",
559		.owner	= THIS_MODULE,
560		.pm	= &ams369fg06_pm_ops,
561	},
562	.probe		= ams369fg06_probe,
563	.remove		= ams369fg06_remove,
564	.shutdown	= ams369fg06_shutdown,
565};
566
567module_spi_driver(ams369fg06_driver);
568
569MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
570MODULE_DESCRIPTION("ams369fg06 LCD Driver");
571MODULE_LICENSE("GPL");
572