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/lcd.h>
15#include <linux/module.h>
16#include <linux/spi/spi.h>
17#include <linux/wait.h>
18
19#define SLEEPMSEC 0x1000
20#define ENDDEF 0x2000
21#define DEFMASK 0xFF00
22#define COMMAND_ONLY 0xFE
23#define DATA_ONLY 0xFF
24
25#define MAX_GAMMA_LEVEL 5
26#define GAMMA_TABLE_COUNT 21
27
28#define MIN_BRIGHTNESS 0
29#define MAX_BRIGHTNESS 255
30#define DEFAULT_BRIGHTNESS 150
31
32struct 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;
39};
40
41static const unsigned short seq_display_on[] = {
42 0x14, 0x03,
43 ENDDEF, 0x0000
44};
45
46static const unsigned short seq_display_off[] = {
47 0x14, 0x00,
48 ENDDEF, 0x0000
49};
50
51static const unsigned short seq_stand_by_on[] = {
52 0x1D, 0xA1,
53 SLEEPMSEC, 200,
54 ENDDEF, 0x0000
55};
56
57static const unsigned short seq_stand_by_off[] = {
58 0x1D, 0xA0,
59 SLEEPMSEC, 250,
60 ENDDEF, 0x0000
61};
62
63static 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,
72
73 0xef, 0xd0,
74 DATA_ONLY, 0xe8,
75
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,
98
99 0x17, 0x22,
100 0x18, 0x33,
101 0x19, 0x03,
102 0x1a, 0x01,
103 0x22, 0xa4,
104 0x23, 0x00,
105 0x26, 0xa0,
106
107 0x1d, 0xa0,
108 SLEEPMSEC, 300,
109
110 0x14, 0x03,
111
112 ENDDEF, 0x0000
113};
114
115/* gamma value: 2.2 */
116static 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,
120};
121
122static 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,
126};
127
128static 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,
132};
133
134static 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,
138};
139
140static 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,
144};
145
146struct ams369fg06_gamma {
147 unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
148};
149
150static 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,
156};
157
158static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
159{
160 u16 buf[1];
161 struct spi_message msg;
162
163 struct spi_transfer xfer = {
164 .len = 2,
165 .tx_buf = buf,
166 };
167
168 buf[0] = (addr << 8) | data;
169
170 spi_message_init(m: &msg);
171 spi_message_add_tail(t: &xfer, m: &msg);
172
173 return spi_sync(spi: lcd->spi, message: &msg);
174}
175
176static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
177 unsigned char command)
178{
179 int ret = 0;
180
181 if (address != DATA_ONLY)
182 ret = ams369fg06_spi_write_byte(lcd, addr: 0x70, data: address);
183 if (command != COMMAND_ONLY)
184 ret = ams369fg06_spi_write_byte(lcd, addr: 0x72, data: command);
185
186 return ret;
187}
188
189static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
190 const unsigned short *wbuf)
191{
192 int ret = 0, i = 0;
193
194 while ((wbuf[i] & DEFMASK) != ENDDEF) {
195 if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
196 ret = ams369fg06_spi_write(lcd, address: wbuf[i], command: wbuf[i+1]);
197 if (ret)
198 break;
199 } else {
200 msleep(msecs: wbuf[i+1]);
201 }
202 i += 2;
203 }
204
205 return ret;
206}
207
208static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
209 const unsigned int *gamma)
210{
211 unsigned int i = 0;
212 int ret = 0;
213
214 for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
215 ret = ams369fg06_spi_write(lcd, address: 0x40 + i, command: gamma[i]);
216 ret = ams369fg06_spi_write(lcd, address: 0x50 + i, command: gamma[i+7*1]);
217 ret = ams369fg06_spi_write(lcd, address: 0x60 + i, command: gamma[i+7*2]);
218 if (ret) {
219 dev_err(lcd->dev, "failed to set gamma table.\n");
220 goto gamma_err;
221 }
222 }
223
224gamma_err:
225 return ret;
226}
227
228static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
229{
230 int ret = 0;
231 int gamma = 0;
232
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;
243
244 ret = _ams369fg06_gamma_ctl(lcd, gamma: gamma_table.gamma_22_table[gamma]);
245
246 return ret;
247}
248
249static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
250{
251 int ret, i;
252 static const unsigned short *init_seq[] = {
253 seq_setting,
254 seq_stand_by_off,
255 };
256
257 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
258 ret = ams369fg06_panel_send_sequence(lcd, wbuf: init_seq[i]);
259 if (ret)
260 break;
261 }
262
263 return ret;
264}
265
266static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
267{
268 int ret, i;
269 static const unsigned short *init_seq[] = {
270 seq_stand_by_off,
271 seq_display_on,
272 };
273
274 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
275 ret = ams369fg06_panel_send_sequence(lcd, wbuf: init_seq[i]);
276 if (ret)
277 break;
278 }
279
280 return ret;
281}
282
283static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
284{
285 int ret, i;
286
287 static const unsigned short *init_seq[] = {
288 seq_display_off,
289 seq_stand_by_on,
290 };
291
292 for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
293 ret = ams369fg06_panel_send_sequence(lcd, wbuf: init_seq[i]);
294 if (ret)
295 break;
296 }
297
298 return ret;
299}
300
301static int ams369fg06_power_is_on(int power)
302{
303 return power <= FB_BLANK_NORMAL;
304}
305
306static int ams369fg06_power_on(struct ams369fg06 *lcd)
307{
308 int ret = 0;
309 struct lcd_platform_data *pd;
310 struct backlight_device *bd;
311
312 pd = lcd->lcd_pd;
313 bd = lcd->bd;
314
315 if (pd->power_on) {
316 pd->power_on(lcd->ld, 1);
317 msleep(msecs: pd->power_on_delay);
318 }
319
320 if (!pd->reset) {
321 dev_err(lcd->dev, "reset is NULL.\n");
322 return -EINVAL;
323 }
324
325 pd->reset(lcd->ld);
326 msleep(msecs: pd->reset_delay);
327
328 ret = ams369fg06_ldi_init(lcd);
329 if (ret) {
330 dev_err(lcd->dev, "failed to initialize ldi.\n");
331 return ret;
332 }
333
334 ret = ams369fg06_ldi_enable(lcd);
335 if (ret) {
336 dev_err(lcd->dev, "failed to enable ldi.\n");
337 return ret;
338 }
339
340 /* set brightness to current value after power on or resume. */
341 ret = ams369fg06_gamma_ctl(lcd, brightness: bd->props.brightness);
342 if (ret) {
343 dev_err(lcd->dev, "lcd gamma setting failed.\n");
344 return ret;
345 }
346
347 return 0;
348}
349
350static int ams369fg06_power_off(struct ams369fg06 *lcd)
351{
352 int ret;
353 struct lcd_platform_data *pd;
354
355 pd = lcd->lcd_pd;
356
357 ret = ams369fg06_ldi_disable(lcd);
358 if (ret) {
359 dev_err(lcd->dev, "lcd setting failed.\n");
360 return -EIO;
361 }
362
363 msleep(msecs: pd->power_off_delay);
364
365 if (pd->power_on)
366 pd->power_on(lcd->ld, 0);
367
368 return 0;
369}
370
371static int ams369fg06_power(struct ams369fg06 *lcd, int power)
372{
373 int ret = 0;
374
375 if (ams369fg06_power_is_on(power) &&
376 !ams369fg06_power_is_on(power: lcd->power))
377 ret = ams369fg06_power_on(lcd);
378 else if (!ams369fg06_power_is_on(power) &&
379 ams369fg06_power_is_on(power: lcd->power))
380 ret = ams369fg06_power_off(lcd);
381
382 if (!ret)
383 lcd->power = power;
384
385 return ret;
386}
387
388static int ams369fg06_get_power(struct lcd_device *ld)
389{
390 struct ams369fg06 *lcd = lcd_get_data(ld_dev: ld);
391
392 return lcd->power;
393}
394
395static int ams369fg06_set_power(struct lcd_device *ld, int power)
396{
397 struct ams369fg06 *lcd = lcd_get_data(ld_dev: ld);
398
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;
403 }
404
405 return ams369fg06_power(lcd, power);
406}
407
408static int ams369fg06_set_brightness(struct backlight_device *bd)
409{
410 int ret = 0;
411 int brightness = bd->props.brightness;
412 struct ams369fg06 *lcd = bl_get_data(bl_dev: bd);
413
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;
419 }
420
421 ret = ams369fg06_gamma_ctl(lcd, brightness: bd->props.brightness);
422 if (ret) {
423 dev_err(&bd->dev, "lcd brightness setting failed.\n");
424 return -EIO;
425 }
426
427 return ret;
428}
429
430static struct lcd_ops ams369fg06_lcd_ops = {
431 .get_power = ams369fg06_get_power,
432 .set_power = ams369fg06_set_power,
433};
434
435static const struct backlight_ops ams369fg06_backlight_ops = {
436 .update_status = ams369fg06_set_brightness,
437};
438
439static int ams369fg06_probe(struct spi_device *spi)
440{
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;
446
447 lcd = devm_kzalloc(dev: &spi->dev, size: sizeof(struct ams369fg06), GFP_KERNEL);
448 if (!lcd)
449 return -ENOMEM;
450
451 /* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
452 spi->bits_per_word = 16;
453
454 ret = spi_setup(spi);
455 if (ret < 0) {
456 dev_err(&spi->dev, "spi setup failed.\n");
457 return ret;
458 }
459
460 lcd->spi = spi;
461 lcd->dev = &spi->dev;
462
463 lcd->lcd_pd = dev_get_platdata(dev: &spi->dev);
464 if (!lcd->lcd_pd) {
465 dev_err(&spi->dev, "platform data is NULL\n");
466 return -EINVAL;
467 }
468
469 ld = devm_lcd_device_register(dev: &spi->dev, name: "ams369fg06", parent: &spi->dev, devdata: lcd,
470 ops: &ams369fg06_lcd_ops);
471 if (IS_ERR(ptr: ld))
472 return PTR_ERR(ptr: ld);
473
474 lcd->ld = ld;
475
476 memset(&props, 0, sizeof(struct backlight_properties));
477 props.type = BACKLIGHT_RAW;
478 props.max_brightness = MAX_BRIGHTNESS;
479
480 bd = devm_backlight_device_register(dev: &spi->dev, name: "ams369fg06-bl",
481 parent: &spi->dev, devdata: lcd,
482 ops: &ams369fg06_backlight_ops, props: &props);
483 if (IS_ERR(ptr: bd))
484 return PTR_ERR(ptr: bd);
485
486 bd->props.brightness = DEFAULT_BRIGHTNESS;
487 lcd->bd = bd;
488
489 if (!lcd->lcd_pd->lcd_enabled) {
490 /*
491 * if lcd panel was off from bootloader then
492 * current lcd status is powerdown and then
493 * it enables lcd panel.
494 */
495 lcd->power = FB_BLANK_POWERDOWN;
496
497 ams369fg06_power(lcd, power: FB_BLANK_UNBLANK);
498 } else {
499 lcd->power = FB_BLANK_UNBLANK;
500 }
501
502 spi_set_drvdata(spi, data: lcd);
503
504 dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
505
506 return 0;
507}
508
509static void ams369fg06_remove(struct spi_device *spi)
510{
511 struct ams369fg06 *lcd = spi_get_drvdata(spi);
512
513 ams369fg06_power(lcd, power: FB_BLANK_POWERDOWN);
514}
515
516#ifdef CONFIG_PM_SLEEP
517static int ams369fg06_suspend(struct device *dev)
518{
519 struct ams369fg06 *lcd = dev_get_drvdata(dev);
520
521 dev_dbg(dev, "lcd->power = %d\n", lcd->power);
522
523 /*
524 * when lcd panel is suspend, lcd panel becomes off
525 * regardless of status.
526 */
527 return ams369fg06_power(lcd, power: FB_BLANK_POWERDOWN);
528}
529
530static int ams369fg06_resume(struct device *dev)
531{
532 struct ams369fg06 *lcd = dev_get_drvdata(dev);
533
534 lcd->power = FB_BLANK_POWERDOWN;
535
536 return ams369fg06_power(lcd, power: FB_BLANK_UNBLANK);
537}
538#endif
539
540static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
541 ams369fg06_resume);
542
543static void ams369fg06_shutdown(struct spi_device *spi)
544{
545 struct ams369fg06 *lcd = spi_get_drvdata(spi);
546
547 ams369fg06_power(lcd, power: FB_BLANK_POWERDOWN);
548}
549
550static struct spi_driver ams369fg06_driver = {
551 .driver = {
552 .name = "ams369fg06",
553 .pm = &ams369fg06_pm_ops,
554 },
555 .probe = ams369fg06_probe,
556 .remove = ams369fg06_remove,
557 .shutdown = ams369fg06_shutdown,
558};
559
560module_spi_driver(ams369fg06_driver);
561
562MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
563MODULE_DESCRIPTION("ams369fg06 LCD Driver");
564MODULE_LICENSE("GPL");
565

source code of linux/drivers/video/backlight/ams369fg06.c