WIP FPC-III support
[linux/fpc-iii.git] / drivers / video / backlight / ams369fg06.c
blob8a4361e95a1143be5658ca7a515e9868243821a6
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * ams369fg06 AMOLED LCD panel driver.
5 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
6 * Author: Jingoo Han <jg1.han@samsung.com>
8 * Derived from drivers/video/s6e63m0.c
9 */
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>
19 #define SLEEPMSEC 0x1000
20 #define ENDDEF 0x2000
21 #define DEFMASK 0xFF00
22 #define COMMAND_ONLY 0xFE
23 #define DATA_ONLY 0xFF
25 #define MAX_GAMMA_LEVEL 5
26 #define GAMMA_TABLE_COUNT 21
28 #define MIN_BRIGHTNESS 0
29 #define MAX_BRIGHTNESS 255
30 #define DEFAULT_BRIGHTNESS 150
32 struct 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;
41 static const unsigned short seq_display_on[] = {
42 0x14, 0x03,
43 ENDDEF, 0x0000
46 static const unsigned short seq_display_off[] = {
47 0x14, 0x00,
48 ENDDEF, 0x0000
51 static const unsigned short seq_stand_by_on[] = {
52 0x1D, 0xA1,
53 SLEEPMSEC, 200,
54 ENDDEF, 0x0000
57 static const unsigned short seq_stand_by_off[] = {
58 0x1D, 0xA0,
59 SLEEPMSEC, 250,
60 ENDDEF, 0x0000
63 static 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,
73 0xef, 0xd0,
74 DATA_ONLY, 0xe8,
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,
99 0x17, 0x22,
100 0x18, 0x33,
101 0x19, 0x03,
102 0x1a, 0x01,
103 0x22, 0xa4,
104 0x23, 0x00,
105 0x26, 0xa0,
107 0x1d, 0xa0,
108 SLEEPMSEC, 300,
110 0x14, 0x03,
112 ENDDEF, 0x0000
115 /* gamma value: 2.2 */
116 static 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,
122 static 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,
128 static 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,
134 static 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,
140 static 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,
146 struct ams369fg06_gamma {
147 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
150 static 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,
158 static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
160 u16 buf[1];
161 struct spi_message msg;
163 struct spi_transfer xfer = {
164 .len = 2,
165 .tx_buf = buf,
168 buf[0] = (addr << 8) | data;
170 spi_message_init(&msg);
171 spi_message_add_tail(&xfer, &msg);
173 return spi_sync(lcd->spi, &msg);
176 static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
177 unsigned char command)
179 int ret = 0;
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);
186 return ret;
189 static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
190 const unsigned short *wbuf)
192 int ret = 0, i = 0;
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]);
202 i += 2;
205 return ret;
208 static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
209 const unsigned int *gamma)
211 unsigned int i = 0;
212 int ret = 0;
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;
224 gamma_err:
225 return ret;
228 static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
230 int ret = 0;
231 int gamma = 0;
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;
244 ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
246 return ret;
249 static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
251 int ret, i;
252 static const unsigned short *init_seq[] = {
253 seq_setting,
254 seq_stand_by_off,
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;
263 return ret;
266 static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
268 int ret, i;
269 static const unsigned short *init_seq[] = {
270 seq_stand_by_off,
271 seq_display_on,
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;
280 return ret;
283 static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
285 int ret, i;
287 static const unsigned short *init_seq[] = {
288 seq_display_off,
289 seq_stand_by_on,
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;
298 return ret;
301 static int ams369fg06_power_is_on(int power)
303 return power <= FB_BLANK_NORMAL;
306 static int ams369fg06_power_on(struct ams369fg06 *lcd)
308 int ret = 0;
309 struct lcd_platform_data *pd;
310 struct backlight_device *bd;
312 pd = lcd->lcd_pd;
313 bd = lcd->bd;
315 if (pd->power_on) {
316 pd->power_on(lcd->ld, 1);
317 msleep(pd->power_on_delay);
320 if (!pd->reset) {
321 dev_err(lcd->dev, "reset is NULL.\n");
322 return -EINVAL;
325 pd->reset(lcd->ld);
326 msleep(pd->reset_delay);
328 ret = ams369fg06_ldi_init(lcd);
329 if (ret) {
330 dev_err(lcd->dev, "failed to initialize ldi.\n");
331 return ret;
334 ret = ams369fg06_ldi_enable(lcd);
335 if (ret) {
336 dev_err(lcd->dev, "failed to enable ldi.\n");
337 return ret;
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;
347 return 0;
350 static int ams369fg06_power_off(struct ams369fg06 *lcd)
352 int ret;
353 struct lcd_platform_data *pd;
355 pd = lcd->lcd_pd;
357 ret = ams369fg06_ldi_disable(lcd);
358 if (ret) {
359 dev_err(lcd->dev, "lcd setting failed.\n");
360 return -EIO;
363 msleep(pd->power_off_delay);
365 if (pd->power_on)
366 pd->power_on(lcd->ld, 0);
368 return 0;
371 static int ams369fg06_power(struct ams369fg06 *lcd, int power)
373 int ret = 0;
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);
382 if (!ret)
383 lcd->power = power;
385 return ret;
388 static int ams369fg06_get_power(struct lcd_device *ld)
390 struct ams369fg06 *lcd = lcd_get_data(ld);
392 return lcd->power;
395 static int ams369fg06_set_power(struct lcd_device *ld, int power)
397 struct ams369fg06 *lcd = lcd_get_data(ld);
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;
405 return ams369fg06_power(lcd, power);
408 static int ams369fg06_set_brightness(struct backlight_device *bd)
410 int ret = 0;
411 int brightness = bd->props.brightness;
412 struct ams369fg06 *lcd = bl_get_data(bd);
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;
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;
427 return ret;
430 static struct lcd_ops ams369fg06_lcd_ops = {
431 .get_power = ams369fg06_get_power,
432 .set_power = ams369fg06_set_power,
435 static const struct backlight_ops ams369fg06_backlight_ops = {
436 .update_status = ams369fg06_set_brightness,
439 static int ams369fg06_probe(struct spi_device *spi)
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;
447 lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
448 if (!lcd)
449 return -ENOMEM;
451 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
452 spi->bits_per_word = 16;
454 ret = spi_setup(spi);
455 if (ret < 0) {
456 dev_err(&spi->dev, "spi setup failed.\n");
457 return ret;
460 lcd->spi = spi;
461 lcd->dev = &spi->dev;
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;
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);
474 lcd->ld = ld;
476 memset(&props, 0, sizeof(struct backlight_properties));
477 props.type = BACKLIGHT_RAW;
478 props.max_brightness = MAX_BRIGHTNESS;
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);
486 bd->props.brightness = DEFAULT_BRIGHTNESS;
487 lcd->bd = bd;
489 if (!lcd->lcd_pd->lcd_enabled) {
491 * if lcd panel was off from bootloader then
492 * current lcd status is powerdown and then
493 * it enables lcd panel.
495 lcd->power = FB_BLANK_POWERDOWN;
497 ams369fg06_power(lcd, FB_BLANK_UNBLANK);
498 } else {
499 lcd->power = FB_BLANK_UNBLANK;
502 spi_set_drvdata(spi, lcd);
504 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
506 return 0;
509 static int ams369fg06_remove(struct spi_device *spi)
511 struct ams369fg06 *lcd = spi_get_drvdata(spi);
513 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
514 return 0;
517 #ifdef CONFIG_PM_SLEEP
518 static int ams369fg06_suspend(struct device *dev)
520 struct ams369fg06 *lcd = dev_get_drvdata(dev);
522 dev_dbg(dev, "lcd->power = %d\n", lcd->power);
525 * when lcd panel is suspend, lcd panel becomes off
526 * regardless of status.
528 return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
531 static int ams369fg06_resume(struct device *dev)
533 struct ams369fg06 *lcd = dev_get_drvdata(dev);
535 lcd->power = FB_BLANK_POWERDOWN;
537 return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
539 #endif
541 static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
542 ams369fg06_resume);
544 static void ams369fg06_shutdown(struct spi_device *spi)
546 struct ams369fg06 *lcd = spi_get_drvdata(spi);
548 ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
551 static struct spi_driver ams369fg06_driver = {
552 .driver = {
553 .name = "ams369fg06",
554 .pm = &ams369fg06_pm_ops,
556 .probe = ams369fg06_probe,
557 .remove = ams369fg06_remove,
558 .shutdown = ams369fg06_shutdown,
561 module_spi_driver(ams369fg06_driver);
563 MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
564 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
565 MODULE_LICENSE("GPL");