]> git.proxmox.com Git - mirror_ubuntu-kernels.git/blob - drivers/video/backlight/ams369fg06.c
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
[mirror_ubuntu-kernels.git] / drivers / video / backlight / ams369fg06.c
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/gpio.h>
15 #include <linux/lcd.h>
16 #include <linux/module.h>
17 #include <linux/spi/spi.h>
18 #include <linux/wait.h>
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
33 struct 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
42 static const unsigned short seq_display_on[] = {
43 0x14, 0x03,
44 ENDDEF, 0x0000
45 };
46
47 static const unsigned short seq_display_off[] = {
48 0x14, 0x00,
49 ENDDEF, 0x0000
50 };
51
52 static const unsigned short seq_stand_by_on[] = {
53 0x1D, 0xA1,
54 SLEEPMSEC, 200,
55 ENDDEF, 0x0000
56 };
57
58 static const unsigned short seq_stand_by_off[] = {
59 0x1D, 0xA0,
60 SLEEPMSEC, 250,
61 ENDDEF, 0x0000
62 };
63
64 static 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 */
117 static 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
123 static 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
129 static 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
135 static 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
141 static 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
147 struct ams369fg06_gamma {
148 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
149 };
150
151 static 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
159 static 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
177 static 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
190 static 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;
200 } else {
201 msleep(wbuf[i+1]);
202 }
203 i += 2;
204 }
205
206 return ret;
207 }
208
209 static 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
225 gamma_err:
226 return ret;
227 }
228
229 static 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
250 static 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
267 static 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
284 static 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
302 static int ams369fg06_power_is_on(int power)
303 {
304 return power <= FB_BLANK_NORMAL;
305 }
306
307 static int ams369fg06_power_on(struct ams369fg06 *lcd)
308 {
309 int ret = 0;
310 struct lcd_platform_data *pd;
311 struct backlight_device *bd;
312
313 pd = lcd->lcd_pd;
314 bd = lcd->bd;
315
316 if (pd->power_on) {
317 pd->power_on(lcd->ld, 1);
318 msleep(pd->power_on_delay);
319 }
320
321 if (!pd->reset) {
322 dev_err(lcd->dev, "reset is NULL.\n");
323 return -EINVAL;
324 }
325
326 pd->reset(lcd->ld);
327 msleep(pd->reset_delay);
328
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
351 static int ams369fg06_power_off(struct ams369fg06 *lcd)
352 {
353 int ret;
354 struct lcd_platform_data *pd;
355
356 pd = lcd->lcd_pd;
357
358 ret = ams369fg06_ldi_disable(lcd);
359 if (ret) {
360 dev_err(lcd->dev, "lcd setting failed.\n");
361 return -EIO;
362 }
363
364 msleep(pd->power_off_delay);
365
366 if (pd->power_on)
367 pd->power_on(lcd->ld, 0);
368
369 return 0;
370 }
371
372 static 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
389 static int ams369fg06_get_power(struct lcd_device *ld)
390 {
391 struct ams369fg06 *lcd = lcd_get_data(ld);
392
393 return lcd->power;
394 }
395
396 static 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
409 static int ams369fg06_set_brightness(struct backlight_device *bd)
410 {
411 int ret = 0;
412 int brightness = bd->props.brightness;
413 struct ams369fg06 *lcd = bl_get_data(bd);
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
431 static struct lcd_ops ams369fg06_lcd_ops = {
432 .get_power = ams369fg06_get_power,
433 .set_power = ams369fg06_set_power,
434 };
435
436 static const struct backlight_ops ams369fg06_backlight_ops = {
437 .update_status = ams369fg06_set_brightness,
438 };
439
440 static int ams369fg06_probe(struct spi_device *spi)
441 {
442 int ret = 0;
443 struct ams369fg06 *lcd = NULL;
444 struct lcd_device *ld = NULL;
445 struct backlight_device *bd = NULL;
446 struct backlight_properties props;
447
448 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
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");
458 return ret;
459 }
460
461 lcd->spi = spi;
462 lcd->dev = &spi->dev;
463
464 lcd->lcd_pd = dev_get_platdata(&spi->dev);
465 if (!lcd->lcd_pd) {
466 dev_err(&spi->dev, "platform data is NULL\n");
467 return -EINVAL;
468 }
469
470 ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd,
471 &ams369fg06_lcd_ops);
472 if (IS_ERR(ld))
473 return PTR_ERR(ld);
474
475 lcd->ld = ld;
476
477 memset(&props, 0, sizeof(struct backlight_properties));
478 props.type = BACKLIGHT_RAW;
479 props.max_brightness = MAX_BRIGHTNESS;
480
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);
486
487 bd->props.brightness = DEFAULT_BRIGHTNESS;
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);
499 } else {
500 lcd->power = FB_BLANK_UNBLANK;
501 }
502
503 spi_set_drvdata(spi, lcd);
504
505 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
506
507 return 0;
508 }
509
510 static int ams369fg06_remove(struct spi_device *spi)
511 {
512 struct ams369fg06 *lcd = spi_get_drvdata(spi);
513
514 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
515 return 0;
516 }
517
518 #ifdef CONFIG_PM_SLEEP
519 static int ams369fg06_suspend(struct device *dev)
520 {
521 struct ams369fg06 *lcd = dev_get_drvdata(dev);
522
523 dev_dbg(dev, "lcd->power = %d\n", lcd->power);
524
525 /*
526 * when lcd panel is suspend, lcd panel becomes off
527 * regardless of status.
528 */
529 return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
530 }
531
532 static int ams369fg06_resume(struct device *dev)
533 {
534 struct ams369fg06 *lcd = dev_get_drvdata(dev);
535
536 lcd->power = FB_BLANK_POWERDOWN;
537
538 return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
539 }
540 #endif
541
542 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
543 ams369fg06_resume);
544
545 static void ams369fg06_shutdown(struct spi_device *spi)
546 {
547 struct ams369fg06 *lcd = spi_get_drvdata(spi);
548
549 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
550 }
551
552 static struct spi_driver ams369fg06_driver = {
553 .driver = {
554 .name = "ams369fg06",
555 .pm = &ams369fg06_pm_ops,
556 },
557 .probe = ams369fg06_probe,
558 .remove = ams369fg06_remove,
559 .shutdown = ams369fg06_shutdown,
560 };
561
562 module_spi_driver(ams369fg06_driver);
563
564 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
565 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
566 MODULE_LICENSE("GPL");