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