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