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