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