Commit | Line | Data |
---|---|---|
1baf0eb3 DL |
1 | /* |
2 | * ld9040 AMOLED LCD panel driver. | |
3 | * | |
4 | * Copyright (c) 2011 Samsung Electronics | |
5 | * Author: Donghwa Lee <dh09.lee@samsung.com> | |
6 | * Derived from drivers/video/backlight/s6e63m0.c | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2 of the License, or (at your | |
11 | * option) any later version. | |
1baf0eb3 DL |
12 | */ |
13 | ||
a03e7cd3 | 14 | #include <linux/backlight.h> |
1baf0eb3 | 15 | #include <linux/delay.h> |
a03e7cd3 | 16 | #include <linux/fb.h> |
1baf0eb3 | 17 | #include <linux/gpio.h> |
1baf0eb3 | 18 | #include <linux/interrupt.h> |
a03e7cd3 | 19 | #include <linux/irq.h> |
1baf0eb3 DL |
20 | #include <linux/kernel.h> |
21 | #include <linux/lcd.h> | |
355b200b | 22 | #include <linux/module.h> |
b148a272 | 23 | #include <linux/regulator/consumer.h> |
a03e7cd3 JH |
24 | #include <linux/spi/spi.h> |
25 | #include <linux/wait.h> | |
1baf0eb3 DL |
26 | |
27 | #include "ld9040_gamma.h" | |
28 | ||
29 | #define SLEEPMSEC 0x1000 | |
30 | #define ENDDEF 0x2000 | |
31 | #define DEFMASK 0xFF00 | |
32 | #define COMMAND_ONLY 0xFE | |
33 | #define DATA_ONLY 0xFF | |
34 | ||
35 | #define MIN_BRIGHTNESS 0 | |
36 | #define MAX_BRIGHTNESS 24 | |
1baf0eb3 DL |
37 | |
38 | struct ld9040 { | |
39 | struct device *dev; | |
40 | struct spi_device *spi; | |
41 | unsigned int power; | |
42 | unsigned int current_brightness; | |
43 | ||
44 | struct lcd_device *ld; | |
45 | struct backlight_device *bd; | |
46 | struct lcd_platform_data *lcd_pd; | |
b148a272 DL |
47 | |
48 | struct mutex lock; | |
49 | bool enabled; | |
50 | }; | |
51 | ||
52 | static struct regulator_bulk_data supplies[] = { | |
53 | { .supply = "vdd3", }, | |
54 | { .supply = "vci", }, | |
1baf0eb3 DL |
55 | }; |
56 | ||
b148a272 DL |
57 | static void ld9040_regulator_enable(struct ld9040 *lcd) |
58 | { | |
59 | int ret = 0; | |
60 | struct lcd_platform_data *pd = NULL; | |
61 | ||
62 | pd = lcd->lcd_pd; | |
63 | mutex_lock(&lcd->lock); | |
64 | if (!lcd->enabled) { | |
65 | ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); | |
66 | if (ret) | |
67 | goto out; | |
68 | ||
69 | lcd->enabled = true; | |
70 | } | |
d2fff290 | 71 | msleep(pd->power_on_delay); |
b148a272 DL |
72 | out: |
73 | mutex_unlock(&lcd->lock); | |
74 | } | |
75 | ||
76 | static void ld9040_regulator_disable(struct ld9040 *lcd) | |
77 | { | |
78 | int ret = 0; | |
79 | ||
80 | mutex_lock(&lcd->lock); | |
81 | if (lcd->enabled) { | |
82 | ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); | |
83 | if (ret) | |
84 | goto out; | |
85 | ||
86 | lcd->enabled = false; | |
87 | } | |
88 | out: | |
89 | mutex_unlock(&lcd->lock); | |
90 | } | |
91 | ||
1baf0eb3 DL |
92 | static const unsigned short seq_swreset[] = { |
93 | 0x01, COMMAND_ONLY, | |
94 | ENDDEF, 0x00 | |
95 | }; | |
96 | ||
97 | static const unsigned short seq_user_setting[] = { | |
98 | 0xF0, 0x5A, | |
99 | ||
100 | DATA_ONLY, 0x5A, | |
101 | ENDDEF, 0x00 | |
102 | }; | |
103 | ||
104 | static const unsigned short seq_elvss_on[] = { | |
105 | 0xB1, 0x0D, | |
106 | ||
107 | DATA_ONLY, 0x00, | |
108 | DATA_ONLY, 0x16, | |
109 | ENDDEF, 0x00 | |
110 | }; | |
111 | ||
112 | static const unsigned short seq_gtcon[] = { | |
113 | 0xF7, 0x09, | |
114 | ||
115 | DATA_ONLY, 0x00, | |
116 | DATA_ONLY, 0x00, | |
117 | ENDDEF, 0x00 | |
118 | }; | |
119 | ||
120 | static const unsigned short seq_panel_condition[] = { | |
121 | 0xF8, 0x05, | |
122 | ||
123 | DATA_ONLY, 0x65, | |
124 | DATA_ONLY, 0x96, | |
125 | DATA_ONLY, 0x71, | |
126 | DATA_ONLY, 0x7D, | |
127 | DATA_ONLY, 0x19, | |
128 | DATA_ONLY, 0x3B, | |
129 | DATA_ONLY, 0x0D, | |
130 | DATA_ONLY, 0x19, | |
131 | DATA_ONLY, 0x7E, | |
132 | DATA_ONLY, 0x0D, | |
133 | DATA_ONLY, 0xE2, | |
134 | DATA_ONLY, 0x00, | |
135 | DATA_ONLY, 0x00, | |
136 | DATA_ONLY, 0x7E, | |
137 | DATA_ONLY, 0x7D, | |
138 | DATA_ONLY, 0x07, | |
139 | DATA_ONLY, 0x07, | |
140 | DATA_ONLY, 0x20, | |
141 | DATA_ONLY, 0x20, | |
142 | DATA_ONLY, 0x20, | |
143 | DATA_ONLY, 0x02, | |
144 | DATA_ONLY, 0x02, | |
145 | ENDDEF, 0x00 | |
146 | }; | |
147 | ||
148 | static const unsigned short seq_gamma_set1[] = { | |
149 | 0xF9, 0x00, | |
150 | ||
151 | DATA_ONLY, 0xA7, | |
152 | DATA_ONLY, 0xB4, | |
153 | DATA_ONLY, 0xAE, | |
154 | DATA_ONLY, 0xBF, | |
155 | DATA_ONLY, 0x00, | |
156 | DATA_ONLY, 0x91, | |
157 | DATA_ONLY, 0x00, | |
158 | DATA_ONLY, 0xB2, | |
159 | DATA_ONLY, 0xB4, | |
160 | DATA_ONLY, 0xAA, | |
161 | DATA_ONLY, 0xBB, | |
162 | DATA_ONLY, 0x00, | |
163 | DATA_ONLY, 0xAC, | |
164 | DATA_ONLY, 0x00, | |
165 | DATA_ONLY, 0xB3, | |
166 | DATA_ONLY, 0xB1, | |
167 | DATA_ONLY, 0xAA, | |
168 | DATA_ONLY, 0xBC, | |
169 | DATA_ONLY, 0x00, | |
170 | DATA_ONLY, 0xB3, | |
171 | ENDDEF, 0x00 | |
172 | }; | |
173 | ||
174 | static const unsigned short seq_gamma_ctrl[] = { | |
175 | 0xFB, 0x02, | |
176 | ||
177 | DATA_ONLY, 0x5A, | |
178 | ENDDEF, 0x00 | |
179 | }; | |
180 | ||
181 | static const unsigned short seq_gamma_start[] = { | |
182 | 0xF9, COMMAND_ONLY, | |
183 | ||
184 | ENDDEF, 0x00 | |
185 | }; | |
186 | ||
187 | static const unsigned short seq_apon[] = { | |
188 | 0xF3, 0x00, | |
189 | ||
190 | DATA_ONLY, 0x00, | |
191 | DATA_ONLY, 0x00, | |
192 | DATA_ONLY, 0x0A, | |
193 | DATA_ONLY, 0x02, | |
194 | ENDDEF, 0x00 | |
195 | }; | |
196 | ||
197 | static const unsigned short seq_display_ctrl[] = { | |
198 | 0xF2, 0x02, | |
199 | ||
200 | DATA_ONLY, 0x08, | |
201 | DATA_ONLY, 0x08, | |
202 | DATA_ONLY, 0x10, | |
203 | DATA_ONLY, 0x10, | |
204 | ENDDEF, 0x00 | |
205 | }; | |
206 | ||
207 | static const unsigned short seq_manual_pwr[] = { | |
208 | 0xB0, 0x04, | |
209 | ENDDEF, 0x00 | |
210 | }; | |
211 | ||
212 | static const unsigned short seq_pwr_ctrl[] = { | |
213 | 0xF4, 0x0A, | |
214 | ||
215 | DATA_ONLY, 0x87, | |
216 | DATA_ONLY, 0x25, | |
217 | DATA_ONLY, 0x6A, | |
218 | DATA_ONLY, 0x44, | |
219 | DATA_ONLY, 0x02, | |
220 | DATA_ONLY, 0x88, | |
221 | ENDDEF, 0x00 | |
222 | }; | |
223 | ||
224 | static const unsigned short seq_sleep_out[] = { | |
225 | 0x11, COMMAND_ONLY, | |
226 | ENDDEF, 0x00 | |
227 | }; | |
228 | ||
229 | static const unsigned short seq_sleep_in[] = { | |
230 | 0x10, COMMAND_ONLY, | |
231 | ENDDEF, 0x00 | |
232 | }; | |
233 | ||
234 | static const unsigned short seq_display_on[] = { | |
235 | 0x29, COMMAND_ONLY, | |
236 | ENDDEF, 0x00 | |
237 | }; | |
238 | ||
239 | static const unsigned short seq_display_off[] = { | |
240 | 0x28, COMMAND_ONLY, | |
241 | ENDDEF, 0x00 | |
242 | }; | |
243 | ||
244 | static const unsigned short seq_vci1_1st_en[] = { | |
245 | 0xF3, 0x10, | |
246 | ||
247 | DATA_ONLY, 0x00, | |
248 | DATA_ONLY, 0x00, | |
249 | DATA_ONLY, 0x00, | |
250 | DATA_ONLY, 0x02, | |
251 | ENDDEF, 0x00 | |
252 | }; | |
253 | ||
254 | static const unsigned short seq_vl1_en[] = { | |
255 | 0xF3, 0x11, | |
256 | ||
257 | DATA_ONLY, 0x00, | |
258 | DATA_ONLY, 0x00, | |
259 | DATA_ONLY, 0x00, | |
260 | DATA_ONLY, 0x02, | |
261 | ENDDEF, 0x00 | |
262 | }; | |
263 | ||
264 | static const unsigned short seq_vl2_en[] = { | |
265 | 0xF3, 0x13, | |
266 | ||
267 | DATA_ONLY, 0x00, | |
268 | DATA_ONLY, 0x00, | |
269 | DATA_ONLY, 0x00, | |
270 | DATA_ONLY, 0x02, | |
271 | ENDDEF, 0x00 | |
272 | }; | |
273 | ||
274 | static const unsigned short seq_vci1_2nd_en[] = { | |
275 | 0xF3, 0x33, | |
276 | ||
277 | DATA_ONLY, 0x00, | |
278 | DATA_ONLY, 0x00, | |
279 | DATA_ONLY, 0x00, | |
280 | DATA_ONLY, 0x02, | |
281 | ENDDEF, 0x00 | |
282 | }; | |
283 | ||
284 | static const unsigned short seq_vl3_en[] = { | |
285 | 0xF3, 0x37, | |
286 | ||
287 | DATA_ONLY, 0x00, | |
288 | DATA_ONLY, 0x00, | |
289 | DATA_ONLY, 0x00, | |
290 | DATA_ONLY, 0x02, | |
291 | ENDDEF, 0x00 | |
292 | }; | |
293 | ||
294 | static const unsigned short seq_vreg1_amp_en[] = { | |
295 | 0xF3, 0x37, | |
296 | ||
297 | DATA_ONLY, 0x01, | |
298 | DATA_ONLY, 0x00, | |
299 | DATA_ONLY, 0x00, | |
300 | DATA_ONLY, 0x02, | |
301 | ENDDEF, 0x00 | |
302 | }; | |
303 | ||
304 | static const unsigned short seq_vgh_amp_en[] = { | |
305 | 0xF3, 0x37, | |
306 | ||
307 | DATA_ONLY, 0x11, | |
308 | DATA_ONLY, 0x00, | |
309 | DATA_ONLY, 0x00, | |
310 | DATA_ONLY, 0x02, | |
311 | ENDDEF, 0x00 | |
312 | }; | |
313 | ||
314 | static const unsigned short seq_vgl_amp_en[] = { | |
315 | 0xF3, 0x37, | |
316 | ||
317 | DATA_ONLY, 0x31, | |
318 | DATA_ONLY, 0x00, | |
319 | DATA_ONLY, 0x00, | |
320 | DATA_ONLY, 0x02, | |
321 | ENDDEF, 0x00 | |
322 | }; | |
323 | ||
324 | static const unsigned short seq_vmos_amp_en[] = { | |
325 | 0xF3, 0x37, | |
326 | ||
327 | DATA_ONLY, 0xB1, | |
328 | DATA_ONLY, 0x00, | |
329 | DATA_ONLY, 0x00, | |
330 | DATA_ONLY, 0x03, | |
331 | ENDDEF, 0x00 | |
332 | }; | |
333 | ||
334 | static const unsigned short seq_vint_amp_en[] = { | |
335 | 0xF3, 0x37, | |
336 | ||
337 | DATA_ONLY, 0xF1, | |
338 | /* DATA_ONLY, 0x71, VMOS/VBL/VBH not used */ | |
339 | DATA_ONLY, 0x00, | |
340 | DATA_ONLY, 0x00, | |
341 | DATA_ONLY, 0x03, | |
342 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
343 | ENDDEF, 0x00 | |
344 | }; | |
345 | ||
346 | static const unsigned short seq_vbh_amp_en[] = { | |
347 | 0xF3, 0x37, | |
348 | ||
349 | DATA_ONLY, 0xF9, | |
350 | DATA_ONLY, 0x00, | |
351 | DATA_ONLY, 0x00, | |
352 | DATA_ONLY, 0x03, | |
353 | ENDDEF, 0x00 | |
354 | }; | |
355 | ||
356 | static const unsigned short seq_vbl_amp_en[] = { | |
357 | 0xF3, 0x37, | |
358 | ||
359 | DATA_ONLY, 0xFD, | |
360 | DATA_ONLY, 0x00, | |
361 | DATA_ONLY, 0x00, | |
362 | DATA_ONLY, 0x03, | |
363 | ENDDEF, 0x00 | |
364 | }; | |
365 | ||
366 | static const unsigned short seq_gam_amp_en[] = { | |
367 | 0xF3, 0x37, | |
368 | ||
369 | DATA_ONLY, 0xFF, | |
370 | /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */ | |
371 | DATA_ONLY, 0x00, | |
372 | DATA_ONLY, 0x00, | |
373 | DATA_ONLY, 0x03, | |
374 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
375 | ENDDEF, 0x00 | |
376 | }; | |
377 | ||
378 | static const unsigned short seq_sd_amp_en[] = { | |
379 | 0xF3, 0x37, | |
380 | ||
381 | DATA_ONLY, 0xFF, | |
382 | /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */ | |
383 | DATA_ONLY, 0x80, | |
384 | DATA_ONLY, 0x00, | |
385 | DATA_ONLY, 0x03, | |
386 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
387 | ENDDEF, 0x00 | |
388 | }; | |
389 | ||
390 | static const unsigned short seq_gls_en[] = { | |
391 | 0xF3, 0x37, | |
392 | ||
393 | DATA_ONLY, 0xFF, | |
394 | /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */ | |
395 | DATA_ONLY, 0x81, | |
396 | DATA_ONLY, 0x00, | |
397 | DATA_ONLY, 0x03, | |
398 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
399 | ENDDEF, 0x00 | |
400 | }; | |
401 | ||
402 | static const unsigned short seq_els_en[] = { | |
403 | 0xF3, 0x37, | |
404 | ||
405 | DATA_ONLY, 0xFF, | |
406 | /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */ | |
407 | DATA_ONLY, 0x83, | |
408 | DATA_ONLY, 0x00, | |
409 | DATA_ONLY, 0x03, | |
410 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
411 | ENDDEF, 0x00 | |
412 | }; | |
413 | ||
414 | static const unsigned short seq_el_on[] = { | |
415 | 0xF3, 0x37, | |
416 | ||
417 | DATA_ONLY, 0xFF, | |
418 | /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */ | |
419 | DATA_ONLY, 0x87, | |
420 | DATA_ONLY, 0x00, | |
421 | DATA_ONLY, 0x03, | |
422 | /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */ | |
423 | ENDDEF, 0x00 | |
424 | }; | |
425 | ||
426 | static int ld9040_spi_write_byte(struct ld9040 *lcd, int addr, int data) | |
427 | { | |
428 | u16 buf[1]; | |
429 | struct spi_message msg; | |
430 | ||
431 | struct spi_transfer xfer = { | |
432 | .len = 2, | |
433 | .tx_buf = buf, | |
434 | }; | |
435 | ||
436 | buf[0] = (addr << 8) | data; | |
437 | ||
438 | spi_message_init(&msg); | |
439 | spi_message_add_tail(&xfer, &msg); | |
440 | ||
441 | return spi_sync(lcd->spi, &msg); | |
442 | } | |
443 | ||
444 | static int ld9040_spi_write(struct ld9040 *lcd, unsigned char address, | |
445 | unsigned char command) | |
446 | { | |
447 | int ret = 0; | |
448 | ||
449 | if (address != DATA_ONLY) | |
450 | ret = ld9040_spi_write_byte(lcd, 0x0, address); | |
451 | if (command != COMMAND_ONLY) | |
452 | ret = ld9040_spi_write_byte(lcd, 0x1, command); | |
453 | ||
454 | return ret; | |
455 | } | |
456 | ||
457 | static int ld9040_panel_send_sequence(struct ld9040 *lcd, | |
458 | const unsigned short *wbuf) | |
459 | { | |
460 | int ret = 0, i = 0; | |
461 | ||
462 | while ((wbuf[i] & DEFMASK) != ENDDEF) { | |
463 | if ((wbuf[i] & DEFMASK) != SLEEPMSEC) { | |
464 | ret = ld9040_spi_write(lcd, wbuf[i], wbuf[i+1]); | |
465 | if (ret) | |
466 | break; | |
d2fff290 JH |
467 | } else { |
468 | msleep(wbuf[i+1]); | |
469 | } | |
1baf0eb3 DL |
470 | i += 2; |
471 | } | |
472 | ||
473 | return ret; | |
474 | } | |
475 | ||
476 | static int _ld9040_gamma_ctl(struct ld9040 *lcd, const unsigned int *gamma) | |
477 | { | |
478 | unsigned int i = 0; | |
479 | int ret = 0; | |
480 | ||
481 | /* start gamma table updating. */ | |
482 | ret = ld9040_panel_send_sequence(lcd, seq_gamma_start); | |
483 | if (ret) { | |
484 | dev_err(lcd->dev, "failed to disable gamma table updating.\n"); | |
485 | goto gamma_err; | |
486 | } | |
487 | ||
488 | for (i = 0 ; i < GAMMA_TABLE_COUNT; i++) { | |
489 | ret = ld9040_spi_write(lcd, DATA_ONLY, gamma[i]); | |
490 | if (ret) { | |
491 | dev_err(lcd->dev, "failed to set gamma table.\n"); | |
492 | goto gamma_err; | |
493 | } | |
494 | } | |
495 | ||
496 | /* update gamma table. */ | |
497 | ret = ld9040_panel_send_sequence(lcd, seq_gamma_ctrl); | |
498 | if (ret) | |
499 | dev_err(lcd->dev, "failed to update gamma table.\n"); | |
500 | ||
501 | gamma_err: | |
502 | return ret; | |
503 | } | |
504 | ||
505 | static int ld9040_gamma_ctl(struct ld9040 *lcd, int gamma) | |
506 | { | |
2ca8b90a | 507 | return _ld9040_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]); |
1baf0eb3 DL |
508 | } |
509 | ||
1baf0eb3 DL |
510 | static int ld9040_ldi_init(struct ld9040 *lcd) |
511 | { | |
512 | int ret, i; | |
513 | static const unsigned short *init_seq[] = { | |
514 | seq_user_setting, | |
515 | seq_panel_condition, | |
516 | seq_display_ctrl, | |
517 | seq_manual_pwr, | |
518 | seq_elvss_on, | |
519 | seq_gtcon, | |
520 | seq_gamma_set1, | |
521 | seq_gamma_ctrl, | |
522 | seq_sleep_out, | |
523 | }; | |
524 | ||
525 | for (i = 0; i < ARRAY_SIZE(init_seq); i++) { | |
526 | ret = ld9040_panel_send_sequence(lcd, init_seq[i]); | |
527 | /* workaround: minimum delay time for transferring CMD */ | |
d2fff290 | 528 | usleep_range(300, 310); |
1baf0eb3 DL |
529 | if (ret) |
530 | break; | |
531 | } | |
532 | ||
533 | return ret; | |
534 | } | |
535 | ||
536 | static int ld9040_ldi_enable(struct ld9040 *lcd) | |
537 | { | |
2ca8b90a | 538 | return ld9040_panel_send_sequence(lcd, seq_display_on); |
1baf0eb3 DL |
539 | } |
540 | ||
541 | static int ld9040_ldi_disable(struct ld9040 *lcd) | |
542 | { | |
543 | int ret; | |
544 | ||
545 | ret = ld9040_panel_send_sequence(lcd, seq_display_off); | |
546 | ret = ld9040_panel_send_sequence(lcd, seq_sleep_in); | |
547 | ||
548 | return ret; | |
549 | } | |
550 | ||
e2ffe856 JH |
551 | static int ld9040_power_is_on(int power) |
552 | { | |
553 | return power <= FB_BLANK_NORMAL; | |
554 | } | |
555 | ||
1baf0eb3 DL |
556 | static int ld9040_power_on(struct ld9040 *lcd) |
557 | { | |
558 | int ret = 0; | |
e2ffe856 JH |
559 | struct lcd_platform_data *pd; |
560 | ||
1baf0eb3 | 561 | pd = lcd->lcd_pd; |
1baf0eb3 | 562 | |
b148a272 DL |
563 | /* lcd power on */ |
564 | ld9040_regulator_enable(lcd); | |
1baf0eb3 DL |
565 | |
566 | if (!pd->reset) { | |
567 | dev_err(lcd->dev, "reset is NULL.\n"); | |
30f085ca | 568 | return -EINVAL; |
1baf0eb3 DL |
569 | } |
570 | ||
e09bceac JH |
571 | pd->reset(lcd->ld); |
572 | msleep(pd->reset_delay); | |
573 | ||
1baf0eb3 DL |
574 | ret = ld9040_ldi_init(lcd); |
575 | if (ret) { | |
576 | dev_err(lcd->dev, "failed to initialize ldi.\n"); | |
577 | return ret; | |
578 | } | |
579 | ||
580 | ret = ld9040_ldi_enable(lcd); | |
581 | if (ret) { | |
582 | dev_err(lcd->dev, "failed to enable ldi.\n"); | |
583 | return ret; | |
584 | } | |
585 | ||
586 | return 0; | |
587 | } | |
588 | ||
589 | static int ld9040_power_off(struct ld9040 *lcd) | |
590 | { | |
e2ffe856 JH |
591 | int ret; |
592 | struct lcd_platform_data *pd; | |
1baf0eb3 DL |
593 | |
594 | pd = lcd->lcd_pd; | |
1baf0eb3 DL |
595 | |
596 | ret = ld9040_ldi_disable(lcd); | |
597 | if (ret) { | |
598 | dev_err(lcd->dev, "lcd setting failed.\n"); | |
599 | return -EIO; | |
600 | } | |
601 | ||
d2fff290 | 602 | msleep(pd->power_off_delay); |
1baf0eb3 | 603 | |
b148a272 DL |
604 | /* lcd power off */ |
605 | ld9040_regulator_disable(lcd); | |
1baf0eb3 DL |
606 | |
607 | return 0; | |
608 | } | |
609 | ||
610 | static int ld9040_power(struct ld9040 *lcd, int power) | |
611 | { | |
612 | int ret = 0; | |
613 | ||
e2ffe856 | 614 | if (ld9040_power_is_on(power) && !ld9040_power_is_on(lcd->power)) |
1baf0eb3 | 615 | ret = ld9040_power_on(lcd); |
e2ffe856 | 616 | else if (!ld9040_power_is_on(power) && ld9040_power_is_on(lcd->power)) |
1baf0eb3 DL |
617 | ret = ld9040_power_off(lcd); |
618 | ||
619 | if (!ret) | |
620 | lcd->power = power; | |
621 | ||
622 | return ret; | |
623 | } | |
624 | ||
625 | static int ld9040_set_power(struct lcd_device *ld, int power) | |
626 | { | |
627 | struct ld9040 *lcd = lcd_get_data(ld); | |
628 | ||
629 | if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && | |
630 | power != FB_BLANK_NORMAL) { | |
631 | dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); | |
632 | return -EINVAL; | |
633 | } | |
634 | ||
635 | return ld9040_power(lcd, power); | |
636 | } | |
637 | ||
638 | static int ld9040_get_power(struct lcd_device *ld) | |
639 | { | |
640 | struct ld9040 *lcd = lcd_get_data(ld); | |
641 | ||
642 | return lcd->power; | |
643 | } | |
644 | ||
1baf0eb3 DL |
645 | static int ld9040_set_brightness(struct backlight_device *bd) |
646 | { | |
647 | int ret = 0, brightness = bd->props.brightness; | |
648 | struct ld9040 *lcd = bl_get_data(bd); | |
649 | ||
650 | if (brightness < MIN_BRIGHTNESS || | |
651 | brightness > bd->props.max_brightness) { | |
652 | dev_err(&bd->dev, "lcd brightness should be %d to %d.\n", | |
653 | MIN_BRIGHTNESS, MAX_BRIGHTNESS); | |
654 | return -EINVAL; | |
655 | } | |
656 | ||
657 | ret = ld9040_gamma_ctl(lcd, bd->props.brightness); | |
658 | if (ret) { | |
659 | dev_err(&bd->dev, "lcd brightness setting failed.\n"); | |
660 | return -EIO; | |
661 | } | |
662 | ||
663 | return ret; | |
664 | } | |
665 | ||
666 | static struct lcd_ops ld9040_lcd_ops = { | |
667 | .set_power = ld9040_set_power, | |
668 | .get_power = ld9040_get_power, | |
669 | }; | |
670 | ||
671 | static const struct backlight_ops ld9040_backlight_ops = { | |
1baf0eb3 DL |
672 | .update_status = ld9040_set_brightness, |
673 | }; | |
674 | ||
1baf0eb3 DL |
675 | static int ld9040_probe(struct spi_device *spi) |
676 | { | |
677 | int ret = 0; | |
678 | struct ld9040 *lcd = NULL; | |
679 | struct lcd_device *ld = NULL; | |
680 | struct backlight_device *bd = NULL; | |
ef22f6a7 | 681 | struct backlight_properties props; |
1baf0eb3 | 682 | |
86f6be4f | 683 | lcd = devm_kzalloc(&spi->dev, sizeof(struct ld9040), GFP_KERNEL); |
1baf0eb3 DL |
684 | if (!lcd) |
685 | return -ENOMEM; | |
686 | ||
687 | /* ld9040 lcd panel uses 3-wire 9bits SPI Mode. */ | |
688 | spi->bits_per_word = 9; | |
689 | ||
690 | ret = spi_setup(spi); | |
691 | if (ret < 0) { | |
692 | dev_err(&spi->dev, "spi setup failed.\n"); | |
86f6be4f | 693 | return ret; |
1baf0eb3 DL |
694 | } |
695 | ||
696 | lcd->spi = spi; | |
697 | lcd->dev = &spi->dev; | |
698 | ||
c512794c | 699 | lcd->lcd_pd = dev_get_platdata(&spi->dev); |
1baf0eb3 DL |
700 | if (!lcd->lcd_pd) { |
701 | dev_err(&spi->dev, "platform data is NULL.\n"); | |
30f085ca | 702 | return -EINVAL; |
1baf0eb3 DL |
703 | } |
704 | ||
b148a272 DL |
705 | mutex_init(&lcd->lock); |
706 | ||
a5f82211 | 707 | ret = devm_regulator_bulk_get(lcd->dev, ARRAY_SIZE(supplies), supplies); |
b148a272 DL |
708 | if (ret) { |
709 | dev_err(lcd->dev, "Failed to get regulators: %d\n", ret); | |
86f6be4f | 710 | return ret; |
b148a272 DL |
711 | } |
712 | ||
7a78e1b2 JH |
713 | ld = devm_lcd_device_register(&spi->dev, "ld9040", &spi->dev, lcd, |
714 | &ld9040_lcd_ops); | |
a5f82211 SK |
715 | if (IS_ERR(ld)) |
716 | return PTR_ERR(ld); | |
1baf0eb3 DL |
717 | |
718 | lcd->ld = ld; | |
719 | ||
ef22f6a7 AL |
720 | memset(&props, 0, sizeof(struct backlight_properties)); |
721 | props.type = BACKLIGHT_RAW; | |
722 | props.max_brightness = MAX_BRIGHTNESS; | |
723 | ||
7a78e1b2 JH |
724 | bd = devm_backlight_device_register(&spi->dev, "ld9040-bl", &spi->dev, |
725 | lcd, &ld9040_backlight_ops, &props); | |
726 | if (IS_ERR(bd)) | |
727 | return PTR_ERR(bd); | |
1baf0eb3 | 728 | |
1baf0eb3 DL |
729 | bd->props.brightness = MAX_BRIGHTNESS; |
730 | lcd->bd = bd; | |
731 | ||
732 | /* | |
733 | * if lcd panel was on from bootloader like u-boot then | |
734 | * do not lcd on. | |
735 | */ | |
736 | if (!lcd->lcd_pd->lcd_enabled) { | |
737 | /* | |
738 | * if lcd panel was off from bootloader then | |
739 | * current lcd status is powerdown and then | |
740 | * it enables lcd panel. | |
741 | */ | |
742 | lcd->power = FB_BLANK_POWERDOWN; | |
743 | ||
744 | ld9040_power(lcd, FB_BLANK_UNBLANK); | |
e2ffe856 | 745 | } else { |
1baf0eb3 | 746 | lcd->power = FB_BLANK_UNBLANK; |
e2ffe856 | 747 | } |
1baf0eb3 | 748 | |
8ec47063 | 749 | spi_set_drvdata(spi, lcd); |
1baf0eb3 DL |
750 | |
751 | dev_info(&spi->dev, "ld9040 panel driver has been probed.\n"); | |
752 | return 0; | |
1baf0eb3 DL |
753 | } |
754 | ||
7e4b9d0b | 755 | static int ld9040_remove(struct spi_device *spi) |
1baf0eb3 | 756 | { |
8ec47063 | 757 | struct ld9040 *lcd = spi_get_drvdata(spi); |
1baf0eb3 DL |
758 | |
759 | ld9040_power(lcd, FB_BLANK_POWERDOWN); | |
1baf0eb3 DL |
760 | return 0; |
761 | } | |
762 | ||
eba3bfb4 JH |
763 | #ifdef CONFIG_PM_SLEEP |
764 | static int ld9040_suspend(struct device *dev) | |
1baf0eb3 | 765 | { |
eba3bfb4 | 766 | struct ld9040 *lcd = dev_get_drvdata(dev); |
1baf0eb3 | 767 | |
eba3bfb4 | 768 | dev_dbg(dev, "lcd->power = %d\n", lcd->power); |
1baf0eb3 DL |
769 | |
770 | /* | |
771 | * when lcd panel is suspend, lcd panel becomes off | |
772 | * regardless of status. | |
773 | */ | |
2ca8b90a | 774 | return ld9040_power(lcd, FB_BLANK_POWERDOWN); |
1baf0eb3 DL |
775 | } |
776 | ||
eba3bfb4 | 777 | static int ld9040_resume(struct device *dev) |
1baf0eb3 | 778 | { |
eba3bfb4 | 779 | struct ld9040 *lcd = dev_get_drvdata(dev); |
1baf0eb3 DL |
780 | |
781 | lcd->power = FB_BLANK_POWERDOWN; | |
782 | ||
2ca8b90a | 783 | return ld9040_power(lcd, FB_BLANK_UNBLANK); |
1baf0eb3 | 784 | } |
1baf0eb3 DL |
785 | #endif |
786 | ||
eba3bfb4 JH |
787 | static SIMPLE_DEV_PM_OPS(ld9040_pm_ops, ld9040_suspend, ld9040_resume); |
788 | ||
1baf0eb3 DL |
789 | /* Power down all displays on reboot, poweroff or halt. */ |
790 | static void ld9040_shutdown(struct spi_device *spi) | |
791 | { | |
8ec47063 | 792 | struct ld9040 *lcd = spi_get_drvdata(spi); |
1baf0eb3 DL |
793 | |
794 | ld9040_power(lcd, FB_BLANK_POWERDOWN); | |
795 | } | |
796 | ||
797 | static struct spi_driver ld9040_driver = { | |
798 | .driver = { | |
799 | .name = "ld9040", | |
eba3bfb4 | 800 | .pm = &ld9040_pm_ops, |
1baf0eb3 DL |
801 | }, |
802 | .probe = ld9040_probe, | |
d1723fa2 | 803 | .remove = ld9040_remove, |
1baf0eb3 | 804 | .shutdown = ld9040_shutdown, |
1baf0eb3 DL |
805 | }; |
806 | ||
462dd838 | 807 | module_spi_driver(ld9040_driver); |
1baf0eb3 DL |
808 | |
809 | MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); | |
810 | MODULE_DESCRIPTION("ld9040 LCD Driver"); | |
811 | MODULE_LICENSE("GPL"); |