Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

David Brownell and committed by
Linus Torvalds
a9a84c37 b1230ee5

+135 -6
+1
arch/arm/mach-at91/board-sam9261ek.c
··· 383 383 } 384 384 385 385 static struct atmel_lcdfb_info __initdata ek_lcdc_data = { 386 + .lcdcon_is_backlight = true, 386 387 .default_bpp = 16, 387 388 .default_dmacon = ATMEL_LCDC_DMAEN, 388 389 .default_lcdcon2 = AT91SAM9261_DEFAULT_TFT_LCDCON2,
+1
arch/arm/mach-at91/board-sam9263ek.c
··· 253 253 254 254 /* Driver datas */ 255 255 static struct atmel_lcdfb_info __initdata ek_lcdc_data = { 256 + .lcdcon_is_backlight = true, 256 257 .default_bpp = 16, 257 258 .default_dmacon = ATMEL_LCDC_DMAEN, 258 259 .default_lcdcon2 = AT91SAM9263_DEFAULT_LCDCON2,
+111 -4
drivers/video/atmel_lcdfb.c
··· 16 16 #include <linux/fb.h> 17 17 #include <linux/init.h> 18 18 #include <linux/delay.h> 19 + #include <linux/backlight.h> 19 20 20 21 #include <asm/arch/board.h> 21 22 #include <asm/arch/cpu.h> ··· 69 68 | ATMEL_LCDC_DMAUPDT); 70 69 } 71 70 #endif 71 + 72 + static const u32 contrast_ctr = ATMEL_LCDC_PS_DIV8 73 + | ATMEL_LCDC_POL_POSITIVE 74 + | ATMEL_LCDC_ENA_PWMENABLE; 75 + 76 + #ifdef CONFIG_BACKLIGHT_ATMEL_LCDC 77 + 78 + /* some bl->props field just changed */ 79 + static int atmel_bl_update_status(struct backlight_device *bl) 80 + { 81 + struct atmel_lcdfb_info *sinfo = bl_get_data(bl); 82 + int power = sinfo->bl_power; 83 + int brightness = bl->props.brightness; 84 + 85 + /* REVISIT there may be a meaningful difference between 86 + * fb_blank and power ... there seem to be some cases 87 + * this doesn't handle correctly. 88 + */ 89 + if (bl->props.fb_blank != sinfo->bl_power) 90 + power = bl->props.fb_blank; 91 + else if (bl->props.power != sinfo->bl_power) 92 + power = bl->props.power; 93 + 94 + if (brightness < 0 && power == FB_BLANK_UNBLANK) 95 + brightness = lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); 96 + else if (power != FB_BLANK_UNBLANK) 97 + brightness = 0; 98 + 99 + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, brightness); 100 + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, 101 + brightness ? contrast_ctr : 0); 102 + 103 + bl->props.fb_blank = bl->props.power = sinfo->bl_power = power; 104 + 105 + return 0; 106 + } 107 + 108 + static int atmel_bl_get_brightness(struct backlight_device *bl) 109 + { 110 + struct atmel_lcdfb_info *sinfo = bl_get_data(bl); 111 + 112 + return lcdc_readl(sinfo, ATMEL_LCDC_CONTRAST_VAL); 113 + } 114 + 115 + static struct backlight_ops atmel_lcdc_bl_ops = { 116 + .update_status = atmel_bl_update_status, 117 + .get_brightness = atmel_bl_get_brightness, 118 + }; 119 + 120 + static void init_backlight(struct atmel_lcdfb_info *sinfo) 121 + { 122 + struct backlight_device *bl; 123 + 124 + sinfo->bl_power = FB_BLANK_UNBLANK; 125 + 126 + if (sinfo->backlight) 127 + return; 128 + 129 + bl = backlight_device_register("backlight", &sinfo->pdev->dev, 130 + sinfo, &atmel_lcdc_bl_ops); 131 + if (IS_ERR(sinfo->backlight)) { 132 + dev_err(&sinfo->pdev->dev, "error %ld on backlight register\n", 133 + PTR_ERR(bl)); 134 + return; 135 + } 136 + sinfo->backlight = bl; 137 + 138 + bl->props.power = FB_BLANK_UNBLANK; 139 + bl->props.fb_blank = FB_BLANK_UNBLANK; 140 + bl->props.max_brightness = 0xff; 141 + bl->props.brightness = atmel_bl_get_brightness(bl); 142 + } 143 + 144 + static void exit_backlight(struct atmel_lcdfb_info *sinfo) 145 + { 146 + if (sinfo->backlight) 147 + backlight_device_unregister(sinfo->backlight); 148 + } 149 + 150 + #else 151 + 152 + static void init_backlight(struct atmel_lcdfb_info *sinfo) 153 + { 154 + dev_warn(&sinfo->pdev->dev, "backlight control is not available\n"); 155 + } 156 + 157 + static void exit_backlight(struct atmel_lcdfb_info *sinfo) 158 + { 159 + } 160 + 161 + #endif 162 + 163 + static void init_contrast(struct atmel_lcdfb_info *sinfo) 164 + { 165 + /* have some default contrast/backlight settings */ 166 + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, contrast_ctr); 167 + lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); 168 + 169 + if (sinfo->lcdcon_is_backlight) 170 + init_backlight(sinfo); 171 + } 72 172 73 173 74 174 static struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = { ··· 492 390 /* Disable all interrupts */ 493 391 lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL); 494 392 495 - /* Set contrast */ 496 - value = ATMEL_LCDC_PS_DIV8 | ATMEL_LCDC_POL_POSITIVE | ATMEL_LCDC_ENA_PWMENABLE; 497 - lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, value); 498 - lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT); 499 393 /* ...wait for DMA engine to become idle... */ 500 394 while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY) 501 395 msleep(10); ··· 695 597 sinfo->default_monspecs = pdata_sinfo->default_monspecs; 696 598 sinfo->atmel_lcdfb_power_control = pdata_sinfo->atmel_lcdfb_power_control; 697 599 sinfo->guard_time = pdata_sinfo->guard_time; 600 + sinfo->lcdcon_is_backlight = pdata_sinfo->lcdcon_is_backlight; 698 601 } else { 699 602 dev_err(dev, "cannot get default configuration\n"); 700 603 goto free_info; ··· 789 690 goto release_mem; 790 691 } 791 692 693 + /* Initialize PWM for contrast or backlight ("off") */ 694 + init_contrast(sinfo); 695 + 792 696 /* interrupt */ 793 697 ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info); 794 698 if (ret) { ··· 843 741 unregister_irqs: 844 742 free_irq(sinfo->irq_base, info); 845 743 unmap_mmio: 744 + exit_backlight(sinfo); 846 745 iounmap(sinfo->mmio); 847 746 release_mem: 848 747 release_mem_region(info->fix.mmio_start, info->fix.mmio_len); ··· 878 775 if (!sinfo) 879 776 return 0; 880 777 778 + exit_backlight(sinfo); 881 779 if (sinfo->atmel_lcdfb_power_control) 882 780 sinfo->atmel_lcdfb_power_control(0); 883 781 unregister_framebuffer(info); ··· 905 801 906 802 static struct platform_driver atmel_lcdfb_driver = { 907 803 .remove = __exit_p(atmel_lcdfb_remove), 804 + 805 + // FIXME need suspend, resume 806 + 908 807 .driver = { 909 808 .name = "atmel_lcdfb", 910 809 .owner = THIS_MODULE,
+13
drivers/video/backlight/Kconfig
··· 50 50 To have support for your specific LCD panel you will have to 51 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 66 config BACKLIGHT_CORGI 54 67 tristate "Generic (aka Sharp Corgi) Backlight Driver" 55 68 depends on BACKLIGHT_CLASS_DEVICE
+9 -2
include/video/atmel_lcdc.h
··· 22 22 #ifndef __ATMEL_LCDC_H__ 23 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 26 struct atmel_lcdfb_info { 27 27 spinlock_t lock; 28 28 struct fb_info *info; ··· 33 33 struct platform_device *pdev; 34 34 struct clk *bus_clk; 35 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 44 unsigned int default_lcdcon2; 38 45 unsigned int default_dmacon; 39 46 void (*atmel_lcdfb_power_control)(int on);