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
32struct 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
41static const unsigned short seq_display_on[] = {
42	0x14, 0x03,
43	ENDDEF, 0x0000
44};
45
46static const unsigned short seq_display_off[] = {
47	0x14, 0x00,
48	ENDDEF, 0x0000
49};
50
51static const unsigned short seq_stand_by_on[] = {
52	0x1D, 0xA1,
53	SLEEPMSEC, 200,
54	ENDDEF, 0x0000
55};
56
57static const unsigned short seq_stand_by_off[] = {
58	0x1D, 0xA0,
59	SLEEPMSEC, 250,
60	ENDDEF, 0x0000
61};
62
63static 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 */
116static 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
122static 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
128static 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
134static 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
140static 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
146struct ams369fg06_gamma {
147	unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
148};
149
150static 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
158static 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
176static 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
189static 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
208static 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
224gamma_err:
225	return ret;
226}
227
228static 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
249static 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
266static 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
283static 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
301static int ams369fg06_power_is_on(int power)
302{
303	return power <= FB_BLANK_NORMAL;
304}
305
306static 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
350static 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
371static 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
388static int ams369fg06_get_power(struct lcd_device *ld)
389{
390	struct ams369fg06 *lcd = lcd_get_data(ld);
391
392	return lcd->power;
393}
394
395static 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
408static 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
430static struct lcd_ops ams369fg06_lcd_ops = {
431	.get_power = ams369fg06_get_power,
432	.set_power = ams369fg06_set_power,
433};
434
435static const struct backlight_ops ams369fg06_backlight_ops = {
436	.update_status = ams369fg06_set_brightness,
437};
438
439static 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
509static 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
517static 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
530static 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
540static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
541			ams369fg06_resume);
542
543static 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
550static 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
560module_spi_driver(ams369fg06_driver);
561
562MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
563MODULE_DESCRIPTION("ams369fg06 LCD Driver");
564MODULE_LICENSE("GPL");
565