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