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