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