diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-02-06 04:39:26 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-06 13:41:16 -0500 |
commit | a9a84c37d1ee50db8f3752b117caf2b48dcd4f8a (patch) | |
tree | adac878234cbe372624c5d33e7d70322972392d7 | |
parent | b1230ee50a9903a987feaad767fb71e2fd173894 (diff) |
atmel_lcdfb: backlight control
On the sam9 EK boards, the LCD backlight is hooked up to a PWM output from
the LCD controller. It's controlled by "contrast" registers though.
This patch lets boards declare that they have that kind of backlight
control. The driver can then export this control, letting screenblank and
other operations actually take effect ... reducing the typically
substantial power drain from the backlight.
Note that it's not fully cooked
- doesn't force backlight off during system suspend
- the "power" and "blank" events may not be done right
This should be easily added in the future.
[nicolas.ferre@atmel.com: remove unneeded inline and rename functions]
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Cc: Andrew Victor <linux@maxim.org.za>
Cc: Russell King <rmk@arm.linux.org.uk>
Cc: "Antonino A. Daplas" <adaplas@pol.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | arch/arm/mach-at91/board-sam9261ek.c | 1 | ||||
-rw-r--r-- | arch/arm/mach-at91/board-sam9263ek.c | 1 | ||||
-rw-r--r-- | drivers/video/atmel_lcdfb.c | 115 | ||||
-rw-r--r-- | drivers/video/backlight/Kconfig | 13 | ||||
-rw-r--r-- | include/video/atmel_lcdc.h | 11 |
5 files changed, 135 insertions, 6 deletions
diff --git a/arch/arm/mach-at91/board-sam9261ek.c b/arch/arm/mach-at91/board-sam9261ek.c index aa29ea58ca09..0ce38dfa6ebe 100644 --- a/arch/arm/mach-at91/board-sam9261ek.c +++ b/arch/arm/mach-at91/board-sam9261ek.c | |||
@@ -383,6 +383,7 @@ static void at91_lcdc_tft_power_control(int on) | |||
383 | } | 383 | } |
384 | 384 | ||
385 | static struct atmel_lcdfb_info __initdata ek_lcdc_data = { | 385 | static struct atmel_lcdfb_info __initdata ek_lcdc_data = { |
386 | .lcdcon_is_backlight = true, | ||
386 | .default_bpp = 16, | 387 | .default_bpp = 16, |
387 | .default_dmacon = ATMEL_LCDC_DMAEN, | 388 | .default_dmacon = ATMEL_LCDC_DMAEN, |
388 | .default_lcdcon2 = AT91SAM9261_DEFAULT_TFT_LCDCON2, | 389 | .default_lcdcon2 = AT91SAM9261_DEFAULT_TFT_LCDCON2, |
diff --git a/arch/arm/mach-at91/board-sam9263ek.c b/arch/arm/mach-at91/board-sam9263ek.c index f09347a86e71..38313abef657 100644 --- a/arch/arm/mach-at91/board-sam9263ek.c +++ b/arch/arm/mach-at91/board-sam9263ek.c | |||
@@ -253,6 +253,7 @@ static void at91_lcdc_power_control(int on) | |||
253 | 253 | ||
254 | /* Driver datas */ | 254 | /* Driver datas */ |
255 | static struct atmel_lcdfb_info __initdata ek_lcdc_data = { | 255 | static struct atmel_lcdfb_info __initdata ek_lcdc_data = { |
256 | .lcdcon_is_backlight = true, | ||
256 | .default_bpp = 16, | 257 | .default_bpp = 16, |
257 | .default_dmacon = ATMEL_LCDC_DMAEN, | 258 | .default_dmacon = ATMEL_LCDC_DMAEN, |
258 | .default_lcdcon2 = AT91SAM9263_DEFAULT_LCDCON2, | 259 | .default_lcdcon2 = AT91SAM9263_DEFAULT_LCDCON2, |
diff --git a/drivers/video/atmel_lcdfb.c b/drivers/video/atmel_lcdfb.c index 5d22ea532e42..fc65c02306dd 100644 --- a/drivers/video/atmel_lcdfb.c +++ b/drivers/video/atmel_lcdfb.c | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <linux/fb.h> | 16 | #include <linux/fb.h> |
17 | #include <linux/init.h> | 17 | #include <linux/init.h> |
18 | #include <linux/delay.h> | 18 | #include <linux/delay.h> |
19 | #include <linux/backlight.h> | ||
19 | 20 | ||
20 | #include <asm/arch/board.h> | 21 | #include <asm/arch/board.h> |
21 | #include <asm/arch/cpu.h> | 22 | #include <asm/arch/cpu.h> |
@@ -69,6 +70,107 @@ static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo, | |||
69 | } | 70 | } |
70 | #endif | 71 | #endif |
71 | 72 | ||
73 | static const u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 | ||
74 | | ATMEL_LCDC_POL_POSITIVE | ||
75 | | ATMEL_LCDC_ENA_PWMENABLE; | ||
76 | |||
77 | #ifdef CONFIG_BACKLIGHT_ATMEL_LCDC | ||
78 | |||
79 | /* some bl->props field just changed */ | ||
80 | static int atmel_bl_update_status(struct backlight_device *bl) | ||
81 | { | ||
82 | struct atmel_lcdfb_info *sinfo = bl_get_data(bl); | ||
83 | int power = sinfo->bl_power; | ||
84 | int brightness = bl->props.brightness; | ||
85 | |||
86 | /* REVISIT there may be a meaningful difference between | ||
87 | * fb_blank and power ... there seem to be some cases | ||
88 | * this doesn't handle correctly. | ||
89 | */ | ||
90 | if (bl->props.fb_blank != sinfo->bl_power) | ||
91 | power = bl->props.fb_blank; | ||
92 | else if (bl->props.power != sinfo->bl_power) | ||
93 | power = bl->props.power; | ||
94 | |||
95 | if (brightness < 0 && power == FB_BLANK_UNBLANK) | ||
96 | brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); | ||
97 | else if (power != FB_BLANK_UNBLANK) | ||
98 | brightness = 0; | ||
99 | |||
100 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); | ||
101 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, | ||
102 | brightness ? contrast_ctr : 0); | ||
103 | |||
104 | bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; | ||
105 | |||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | static int atmel_bl_get_brightness(struct backlight_device *bl) | ||
110 | { | ||
111 | struct atmel_lcdfb_info *sinfo = bl_get_data(bl); | ||
112 | |||
113 | return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); | ||
114 | } | ||
115 | |||
116 | static struct backlight_ops atmel_lcdc_bl_ops = { | ||
117 | .update_status = atmel_bl_update_status, | ||
118 | .get_brightness = atmel_bl_get_brightness, | ||
119 | }; | ||
120 | |||
121 | static void init_backlight(struct atmel_lcdfb_info *sinfo) | ||
122 | { | ||
123 | struct backlight_device *bl; | ||
124 | |||
125 | sinfo->bl_power = FB_BLANK_UNBLANK; | ||
126 | |||
127 | if (sinfo->backlight) | ||
128 | return; | ||
129 | |||
130 | bl = backlight_device_register("backlight", &sinfo->pdev->dev, | ||
131 | sinfo, &atmel_lcdc_bl_ops); | ||
132 | if (IS_ERR(sinfo->backlight)) { | ||
133 | dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", | ||
134 | PTR_ERR(bl)); | ||
135 | return; | ||
136 | } | ||
137 | sinfo->backlight = bl; | ||
138 | |||
139 | bl->props.power = FB_BLANK_UNBLANK; | ||
140 | bl->props.fb_blank = FB_BLANK_UNBLANK; | ||
141 | bl->props.max_brightness = 0xff; | ||
142 | bl->props.brightness = atmel_bl_get_brightness(bl); | ||
143 | } | ||
144 | |||
145 | static void exit_backlight(struct atmel_lcdfb_info *sinfo) | ||
146 | { | ||
147 | if (sinfo->backlight) | ||
148 | backlight_device_unregister(sinfo->backlight); | ||
149 | } | ||
150 | |||
151 | #else | ||
152 | |||
153 | static void init_backlight(struct atmel_lcdfb_info *sinfo) | ||
154 | { | ||
155 | dev_warn(&sinfo->pdev->dev, "backlight control is not available\n"); | ||
156 | } | ||
157 | |||
158 | static void exit_backlight(struct atmel_lcdfb_info *sinfo) | ||
159 | { | ||
160 | } | ||
161 | |||
162 | #endif | ||
163 | |||
164 | static void init_contrast(struct atmel_lcdfb_info *sinfo) | ||
165 | { | ||
166 | /* have some default contrast/backlight settings */ | ||
167 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); | ||
168 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); | ||
169 | |||
170 | if (sinfo->lcdcon_is_backlight) | ||
171 | init_backlight(sinfo); | ||
172 | } | ||
173 | |||
72 | 174 | ||
73 | static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { | 175 | static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { |
74 | .type = FB_TYPE_PACKED_PIXELS, | 176 | .type = FB_TYPE_PACKED_PIXELS, |
@@ -390,10 +492,6 @@ static int atmel_lcdfb_set_par(struct fb_info *info) | |||
390 | /* Disable all interrupts */ | 492 | /* Disable all interrupts */ |
391 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); | 493 | lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); |
392 | 494 | ||
393 | /* Set contrast */ | ||
394 | value = ATMEL_LCDC_PS_DIV8 | ATMEL_LCDC_POL_POSITIVE | ATMEL_LCDC_ENA_PWMENABLE; | ||
395 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, value); | ||
396 | lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); | ||
397 | /* ...wait for DMA engine to become idle... */ | 495 | /* ...wait for DMA engine to become idle... */ |
398 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) | 496 | while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) |
399 | msleep(10); | 497 | msleep(10); |
@@ -597,6 +695,7 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) | |||
597 | sinfo->default_monspecs = pdata_sinfo->default_monspecs; | 695 | sinfo->default_monspecs = pdata_sinfo->default_monspecs; |
598 | sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; | 696 | sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; |
599 | sinfo->guard_time = pdata_sinfo->guard_time; | 697 | sinfo->guard_time = pdata_sinfo->guard_time; |
698 | sinfo->lcdcon_is_backlight = pdata_sinfo->lcdcon_is_backlight; | ||
600 | } else { | 699 | } else { |
601 | dev_err(dev, "cannot get default configuration\n"); | 700 | dev_err(dev, "cannot get default configuration\n"); |
602 | goto free_info; | 701 | goto free_info; |
@@ -690,6 +789,9 @@ static int __init atmel_lcdfb_probe(struct platform_device *pdev) | |||
690 | goto release_mem; | 789 | goto release_mem; |
691 | } | 790 | } |
692 | 791 | ||
792 | /* Initialize PWM for contrast or backlight ("off") */ | ||
793 | init_contrast(sinfo); | ||
794 | |||
693 | /* interrupt */ | 795 | /* interrupt */ |
694 | ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); | 796 | ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); |
695 | if (ret) { | 797 | if (ret) { |
@@ -741,6 +843,7 @@ free_cmap: | |||
741 | unregister_irqs: | 843 | unregister_irqs: |
742 | free_irq(sinfo->irq_base, info); | 844 | free_irq(sinfo->irq_base, info); |
743 | unmap_mmio: | 845 | unmap_mmio: |
846 | exit_backlight(sinfo); | ||
744 | iounmap(sinfo->mmio); | 847 | iounmap(sinfo->mmio); |
745 | release_mem: | 848 | release_mem: |
746 | release_mem_region(info->fix.mmio_start, info->fix.mmio_len); | 849 | release_mem_region(info->fix.mmio_start, info->fix.mmio_len); |
@@ -775,6 +878,7 @@ static int __exit atmel_lcdfb_remove(struct platform_device *pdev) | |||
775 | if (!sinfo) | 878 | if (!sinfo) |
776 | return 0; | 879 | return 0; |
777 | 880 | ||
881 | exit_backlight(sinfo); | ||
778 | if (sinfo->atmel_lcdfb_power_control) | 882 | if (sinfo->atmel_lcdfb_power_control) |
779 | sinfo->atmel_lcdfb_power_control(0); | 883 | sinfo->atmel_lcdfb_power_control(0); |
780 | unregister_framebuffer(info); | 884 | unregister_framebuffer(info); |
@@ -801,6 +905,9 @@ static int __exit atmel_lcdfb_remove(struct platform_device *pdev) | |||
801 | 905 | ||
802 | static struct platform_driver atmel_lcdfb_driver = { | 906 | static struct platform_driver atmel_lcdfb_driver = { |
803 | .remove = __exit_p(atmel_lcdfb_remove), | 907 | .remove = __exit_p(atmel_lcdfb_remove), |
908 | |||
909 | // FIXME need suspend, resume | ||
910 | |||
804 | .driver = { | 911 | .driver = { |
805 | .name = "atmel_lcdfb", | 912 | .name = "atmel_lcdfb", |
806 | .owner = THIS_MODULE, | 913 | .owner = THIS_MODULE, |
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 9609a6c676be..924e2551044a 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig | |||
@@ -50,6 +50,19 @@ config BACKLIGHT_CLASS_DEVICE | |||
50 | To have support for your specific LCD panel you will have to | 50 | To have support for your specific LCD panel you will have to |
51 | select the proper drivers which depend on this option. | 51 | select the proper drivers which depend on this option. |
52 | 52 | ||
53 | config BACKLIGHT_ATMEL_LCDC | ||
54 | bool "Atmel LCDC Contrast-as-Backlight control" | ||
55 | depends on BACKLIGHT_CLASS_DEVICE && FB_ATMEL | ||
56 | default y if MACH_SAM9261EK || MACH_SAM9263EK | ||
57 | help | ||
58 | This provides a backlight control internal to the Atmel LCDC | ||
59 | driver. If the LCD "contrast control" on your board is wired | ||
60 | so it controls the backlight brightness, select this option to | ||
61 | export this as a PWM-based backlight control. | ||
62 | |||
63 | If in doubt, it's safe to enable this option; it doesn't kick | ||
64 | in unless the board's description says it's wired that way. | ||
65 | |||
53 | config BACKLIGHT_CORGI | 66 | config BACKLIGHT_CORGI |
54 | tristate "Generic (aka Sharp Corgi) Backlight Driver" | 67 | tristate "Generic (aka Sharp Corgi) Backlight Driver" |
55 | depends on BACKLIGHT_CLASS_DEVICE | 68 | depends on BACKLIGHT_CLASS_DEVICE |
diff --git a/include/video/atmel_lcdc.h b/include/video/atmel_lcdc.h index 76095e70935b..336c20db87f8 100644 --- a/include/video/atmel_lcdc.h +++ b/include/video/atmel_lcdc.h | |||
@@ -22,7 +22,7 @@ | |||
22 | #ifndef __ATMEL_LCDC_H__ | 22 | #ifndef __ATMEL_LCDC_H__ |
23 | #define __ATMEL_LCDC_H__ | 23 | #define __ATMEL_LCDC_H__ |
24 | 24 | ||
25 | /* LCD Controller info data structure */ | 25 | /* LCD Controller info data structure, stored in device platform_data */ |
26 | struct atmel_lcdfb_info { | 26 | struct atmel_lcdfb_info { |
27 | spinlock_t lock; | 27 | spinlock_t lock; |
28 | struct fb_info *info; | 28 | struct fb_info *info; |
@@ -33,7 +33,14 @@ struct atmel_lcdfb_info { | |||
33 | struct platform_device *pdev; | 33 | struct platform_device *pdev; |
34 | struct clk *bus_clk; | 34 | struct clk *bus_clk; |
35 | struct clk *lcdc_clk; | 35 | struct clk *lcdc_clk; |
36 | unsigned int default_bpp; | 36 | |
37 | #ifdef CONFIG_BACKLIGHT_ATMEL_LCDC | ||
38 | struct backlight_device *backlight; | ||
39 | u8 bl_power; | ||
40 | #endif | ||
41 | bool lcdcon_is_backlight; | ||
42 | |||
43 | u8 default_bpp; | ||
37 | unsigned int default_lcdcon2; | 44 | unsigned int default_lcdcon2; |
38 | unsigned int default_dmacon; | 45 | unsigned int default_dmacon; |
39 | void (*atmel_lcdfb_power_control)(int on); | 46 | void (*atmel_lcdfb_power_control)(int on); |