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