2 * ld9040 AMOLED LCD panel driver.
4 * Copyright (c) 2011 Samsung Electronics
5 * Author: Donghwa Lee <dh09.lee@samsung.com>
6 * Derived from drivers/video/backlight/s6e63m0.c
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.
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.
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.
23 #include <linux/wait.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>
33 #include <linux/module.h>
34 #include <linux/regulator/consumer.h>
36 #include "ld9040_gamma.h"
38 #define SLEEPMSEC 0x1000
40 #define DEFMASK 0xFF00
41 #define COMMAND_ONLY 0xFE
42 #define DATA_ONLY 0xFF
44 #define MIN_BRIGHTNESS 0
45 #define MAX_BRIGHTNESS 24
46 #define power_is_on(pwr) ((pwr) <= FB_BLANK_NORMAL)
50 struct spi_device
*spi
;
52 unsigned int current_brightness
;
54 struct lcd_device
*ld
;
55 struct backlight_device
*bd
;
56 struct lcd_platform_data
*lcd_pd
;
62 static struct regulator_bulk_data supplies
[] = {
63 { .supply
= "vdd3", },
67 static void ld9040_regulator_enable(struct ld9040
*lcd
)
70 struct lcd_platform_data
*pd
= NULL
;
73 mutex_lock(&lcd
->lock
);
75 ret
= regulator_bulk_enable(ARRAY_SIZE(supplies
), supplies
);
81 mdelay(pd
->power_on_delay
);
83 mutex_unlock(&lcd
->lock
);
86 static void ld9040_regulator_disable(struct ld9040
*lcd
)
90 mutex_lock(&lcd
->lock
);
92 ret
= regulator_bulk_disable(ARRAY_SIZE(supplies
), supplies
);
99 mutex_unlock(&lcd
->lock
);
102 static const unsigned short seq_swreset
[] = {
107 static const unsigned short seq_user_setting
[] = {
114 static const unsigned short seq_elvss_on
[] = {
122 static const unsigned short seq_gtcon
[] = {
130 static const unsigned short seq_panel_condition
[] = {
158 static const unsigned short seq_gamma_set1
[] = {
184 static const unsigned short seq_gamma_ctrl
[] = {
191 static const unsigned short seq_gamma_start
[] = {
197 static const unsigned short seq_apon
[] = {
207 static const unsigned short seq_display_ctrl
[] = {
217 static const unsigned short seq_manual_pwr
[] = {
222 static const unsigned short seq_pwr_ctrl
[] = {
234 static const unsigned short seq_sleep_out
[] = {
239 static const unsigned short seq_sleep_in
[] = {
244 static const unsigned short seq_display_on
[] = {
249 static const unsigned short seq_display_off
[] = {
254 static const unsigned short seq_vci1_1st_en
[] = {
264 static const unsigned short seq_vl1_en
[] = {
274 static const unsigned short seq_vl2_en
[] = {
284 static const unsigned short seq_vci1_2nd_en
[] = {
294 static const unsigned short seq_vl3_en
[] = {
304 static const unsigned short seq_vreg1_amp_en
[] = {
314 static const unsigned short seq_vgh_amp_en
[] = {
324 static const unsigned short seq_vgl_amp_en
[] = {
334 static const unsigned short seq_vmos_amp_en
[] = {
344 static const unsigned short seq_vint_amp_en
[] = {
348 /* DATA_ONLY, 0x71, VMOS/VBL/VBH not used */
352 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
356 static const unsigned short seq_vbh_amp_en
[] = {
366 static const unsigned short seq_vbl_amp_en
[] = {
376 static const unsigned short seq_gam_amp_en
[] = {
380 /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
384 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
388 static const unsigned short seq_sd_amp_en
[] = {
392 /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
396 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
400 static const unsigned short seq_gls_en
[] = {
404 /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
408 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
412 static const unsigned short seq_els_en
[] = {
416 /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
420 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
424 static const unsigned short seq_el_on
[] = {
428 /* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
432 /* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
436 static int ld9040_spi_write_byte(struct ld9040
*lcd
, int addr
, int data
)
439 struct spi_message msg
;
441 struct spi_transfer xfer
= {
446 buf
[0] = (addr
<< 8) | data
;
448 spi_message_init(&msg
);
449 spi_message_add_tail(&xfer
, &msg
);
451 return spi_sync(lcd
->spi
, &msg
);
454 static int ld9040_spi_write(struct ld9040
*lcd
, unsigned char address
,
455 unsigned char command
)
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
);
467 static int ld9040_panel_send_sequence(struct ld9040
*lcd
,
468 const unsigned short *wbuf
)
472 while ((wbuf
[i
] & DEFMASK
) != ENDDEF
) {
473 if ((wbuf
[i
] & DEFMASK
) != SLEEPMSEC
) {
474 ret
= ld9040_spi_write(lcd
, wbuf
[i
], wbuf
[i
+1]);
478 udelay(wbuf
[i
+1]*1000);
485 static int _ld9040_gamma_ctl(struct ld9040
*lcd
, const unsigned int *gamma
)
490 /* start gamma table updating. */
491 ret
= ld9040_panel_send_sequence(lcd
, seq_gamma_start
);
493 dev_err(lcd
->dev
, "failed to disable gamma table updating.\n");
497 for (i
= 0 ; i
< GAMMA_TABLE_COUNT
; i
++) {
498 ret
= ld9040_spi_write(lcd
, DATA_ONLY
, gamma
[i
]);
500 dev_err(lcd
->dev
, "failed to set gamma table.\n");
505 /* update gamma table. */
506 ret
= ld9040_panel_send_sequence(lcd
, seq_gamma_ctrl
);
508 dev_err(lcd
->dev
, "failed to update gamma table.\n");
514 static int ld9040_gamma_ctl(struct ld9040
*lcd
, int gamma
)
518 ret
= _ld9040_gamma_ctl(lcd
, gamma_table
.gamma_22_table
[gamma
]);
524 static int ld9040_ldi_init(struct ld9040
*lcd
)
527 static const unsigned short *init_seq
[] = {
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 */
550 static int ld9040_ldi_enable(struct ld9040
*lcd
)
554 ret
= ld9040_panel_send_sequence(lcd
, seq_display_on
);
559 static int ld9040_ldi_disable(struct ld9040
*lcd
)
563 ret
= ld9040_panel_send_sequence(lcd
, seq_display_off
);
564 ret
= ld9040_panel_send_sequence(lcd
, seq_sleep_in
);
569 static int ld9040_power_on(struct ld9040
*lcd
)
572 struct lcd_platform_data
*pd
= NULL
;
575 dev_err(lcd
->dev
, "platform data is NULL.\n");
580 ld9040_regulator_enable(lcd
);
583 dev_err(lcd
->dev
, "reset is NULL.\n");
587 mdelay(pd
->reset_delay
);
590 ret
= ld9040_ldi_init(lcd
);
592 dev_err(lcd
->dev
, "failed to initialize ldi.\n");
596 ret
= ld9040_ldi_enable(lcd
);
598 dev_err(lcd
->dev
, "failed to enable ldi.\n");
605 static int ld9040_power_off(struct ld9040
*lcd
)
608 struct lcd_platform_data
*pd
= NULL
;
612 dev_err(lcd
->dev
, "platform data is NULL.\n");
616 ret
= ld9040_ldi_disable(lcd
);
618 dev_err(lcd
->dev
, "lcd setting failed.\n");
622 mdelay(pd
->power_off_delay
);
625 ld9040_regulator_disable(lcd
);
630 static int ld9040_power(struct ld9040
*lcd
, int power
)
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
);
645 static int ld9040_set_power(struct lcd_device
*ld
, int power
)
647 struct ld9040
*lcd
= lcd_get_data(ld
);
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");
655 return ld9040_power(lcd
, power
);
658 static int ld9040_get_power(struct lcd_device
*ld
)
660 struct ld9040
*lcd
= lcd_get_data(ld
);
665 static int ld9040_get_brightness(struct backlight_device
*bd
)
667 return bd
->props
.brightness
;
670 static int ld9040_set_brightness(struct backlight_device
*bd
)
672 int ret
= 0, brightness
= bd
->props
.brightness
;
673 struct ld9040
*lcd
= bl_get_data(bd
);
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
);
682 ret
= ld9040_gamma_ctl(lcd
, bd
->props
.brightness
);
684 dev_err(&bd
->dev
, "lcd brightness setting failed.\n");
691 static struct lcd_ops ld9040_lcd_ops
= {
692 .set_power
= ld9040_set_power
,
693 .get_power
= ld9040_get_power
,
696 static const struct backlight_ops ld9040_backlight_ops
= {
697 .get_brightness
= ld9040_get_brightness
,
698 .update_status
= ld9040_set_brightness
,
702 static int ld9040_probe(struct spi_device
*spi
)
705 struct ld9040
*lcd
= NULL
;
706 struct lcd_device
*ld
= NULL
;
707 struct backlight_device
*bd
= NULL
;
708 struct backlight_properties props
;
710 lcd
= kzalloc(sizeof(struct ld9040
), GFP_KERNEL
);
714 /* ld9040 lcd panel uses 3-wire 9bits SPI Mode. */
715 spi
->bits_per_word
= 9;
717 ret
= spi_setup(spi
);
719 dev_err(&spi
->dev
, "spi setup failed.\n");
724 lcd
->dev
= &spi
->dev
;
726 lcd
->lcd_pd
= spi
->dev
.platform_data
;
728 dev_err(&spi
->dev
, "platform data is NULL.\n");
732 mutex_init(&lcd
->lock
);
734 ret
= regulator_bulk_get(lcd
->dev
, ARRAY_SIZE(supplies
), supplies
);
736 dev_err(lcd
->dev
, "Failed to get regulators: %d\n", ret
);
740 ld
= lcd_device_register("ld9040", &spi
->dev
, lcd
, &ld9040_lcd_ops
);
748 memset(&props
, 0, sizeof(struct backlight_properties
));
749 props
.type
= BACKLIGHT_RAW
;
750 props
.max_brightness
= MAX_BRIGHTNESS
;
752 bd
= backlight_device_register("ld9040-bl", &spi
->dev
,
753 lcd
, &ld9040_backlight_ops
, &props
);
756 goto out_unregister_lcd
;
759 bd
->props
.brightness
= MAX_BRIGHTNESS
;
763 * if lcd panel was on from bootloader like u-boot then
766 if (!lcd
->lcd_pd
->lcd_enabled
) {
768 * if lcd panel was off from bootloader then
769 * current lcd status is powerdown and then
770 * it enables lcd panel.
772 lcd
->power
= FB_BLANK_POWERDOWN
;
774 ld9040_power(lcd
, FB_BLANK_UNBLANK
);
776 lcd
->power
= FB_BLANK_UNBLANK
;
778 dev_set_drvdata(&spi
->dev
, lcd
);
780 dev_info(&spi
->dev
, "ld9040 panel driver has been probed.\n");
784 lcd_device_unregister(lcd
->ld
);
786 regulator_bulk_free(ARRAY_SIZE(supplies
), supplies
);
792 static int __devexit
ld9040_remove(struct spi_device
*spi
)
794 struct ld9040
*lcd
= dev_get_drvdata(&spi
->dev
);
796 ld9040_power(lcd
, FB_BLANK_POWERDOWN
);
797 backlight_device_unregister(lcd
->bd
);
798 lcd_device_unregister(lcd
->ld
);
799 regulator_bulk_free(ARRAY_SIZE(supplies
), supplies
);
805 #if defined(CONFIG_PM)
806 static int ld9040_suspend(struct spi_device
*spi
, pm_message_t mesg
)
809 struct ld9040
*lcd
= dev_get_drvdata(&spi
->dev
);
811 dev_dbg(&spi
->dev
, "lcd->power = %d\n", lcd
->power
);
814 * when lcd panel is suspend, lcd panel becomes off
815 * regardless of status.
817 ret
= ld9040_power(lcd
, FB_BLANK_POWERDOWN
);
822 static int ld9040_resume(struct spi_device
*spi
)
825 struct ld9040
*lcd
= dev_get_drvdata(&spi
->dev
);
827 lcd
->power
= FB_BLANK_POWERDOWN
;
829 ret
= ld9040_power(lcd
, FB_BLANK_UNBLANK
);
834 #define ld9040_suspend NULL
835 #define ld9040_resume NULL
838 /* Power down all displays on reboot, poweroff or halt. */
839 static void ld9040_shutdown(struct spi_device
*spi
)
841 struct ld9040
*lcd
= dev_get_drvdata(&spi
->dev
);
843 ld9040_power(lcd
, FB_BLANK_POWERDOWN
);
846 static struct spi_driver ld9040_driver
= {
849 .bus
= &spi_bus_type
,
850 .owner
= THIS_MODULE
,
852 .probe
= ld9040_probe
,
853 .remove
= __devexit_p(ld9040_remove
),
854 .shutdown
= ld9040_shutdown
,
855 .suspend
= ld9040_suspend
,
856 .resume
= ld9040_resume
,
859 static int __init
ld9040_init(void)
861 return spi_register_driver(&ld9040_driver
);
864 static void __exit
ld9040_exit(void)
866 spi_unregister_driver(&ld9040_driver
);
869 module_init(ld9040_init
);
870 module_exit(ld9040_exit
);
872 MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
873 MODULE_DESCRIPTION("ld9040 LCD Driver");
874 MODULE_LICENSE("GPL");