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
ams369fg06_spi_write_byte(struct ams369fg06 * lcd,int addr,int data)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
ams369fg06_spi_write(struct ams369fg06 * lcd,unsigned char address,unsigned char command)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
ams369fg06_panel_send_sequence(struct ams369fg06 * lcd,const unsigned short * wbuf)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
_ams369fg06_gamma_ctl(struct ams369fg06 * lcd,const unsigned int * gamma)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
ams369fg06_gamma_ctl(struct ams369fg06 * lcd,int brightness)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
ams369fg06_ldi_init(struct ams369fg06 * lcd)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
ams369fg06_ldi_enable(struct ams369fg06 * lcd)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
ams369fg06_ldi_disable(struct ams369fg06 * lcd)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
ams369fg06_power_is_on(int power)300 static int ams369fg06_power_is_on(int power)
301 {
302 return power <= BACKLIGHT_POWER_REDUCED;
303 }
304
ams369fg06_power_on(struct ams369fg06 * lcd)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
ams369fg06_power_off(struct ams369fg06 * lcd)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
ams369fg06_power(struct ams369fg06 * lcd,int power)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
ams369fg06_get_power(struct lcd_device * ld)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
ams369fg06_set_power(struct lcd_device * ld,int power)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
ams369fg06_set_brightness(struct backlight_device * bd)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
ams369fg06_probe(struct spi_device * spi)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
ams369fg06_remove(struct spi_device * spi)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
ams369fg06_suspend(struct device * dev)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
ams369fg06_resume(struct device * dev)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
ams369fg06_shutdown(struct spi_device * spi)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