]> Git Repo - linux.git/blob - drivers/video/backlight/ams369fg06.c
Linux 6.14-rc3
[linux.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  <[email protected]>
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
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
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
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
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
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
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
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
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
300 static int ams369fg06_power_is_on(int power)
301 {
302         return power <= BACKLIGHT_POWER_REDUCED;
303 }
304
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
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
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
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
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
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
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
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
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
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
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 <[email protected]>");
562 MODULE_DESCRIPTION("ams369fg06 LCD Driver");
563 MODULE_LICENSE("GPL");
This page took 0.065638 seconds and 4 git commands to generate.