summaryrefslogtreecommitdiff
path: root/drivers/video/aty/aty128fb.c
diff options
context:
space:
mode:
authorMichael Hanselmann <linux-kernel@hansmi.ch>2006-06-25 05:47:08 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2006-06-25 10:00:59 -0700
commit5474c120aafe78ca54bf272f7a01107c42da2b21 (patch)
treec1b002a27703ce92c816bfb9844752186e33d403 /drivers/video/aty/aty128fb.c
parent17660bdd5c1f1a165273c1a59cb5b87670a81cc4 (diff)
[PATCH] Rewritten backlight infrastructure for portable Apple computers
This patch contains a total rewrite of the backlight infrastructure for portable Apple computers. Backward compatibility is retained. A sysfs interface allows userland to control the brightness with more steps than before. Userland is allowed to upload a brightness curve for different monitors, similar to Mac OS X. [akpm@osdl.org: add needed exports] Signed-off-by: Michael Hanselmann <linux-kernel@hansmi.ch> Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Richard Purdie <rpurdie@rpsys.net> Cc: "Antonino A. Daplas" <adaplas@pol.net> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/video/aty/aty128fb.c')
-rw-r--r--drivers/video/aty/aty128fb.c322
1 files changed, 225 insertions, 97 deletions
diff --git a/drivers/video/aty/aty128fb.c b/drivers/video/aty/aty128fb.c
index f7bbff4ddc6a..db878fd55fb2 100644
--- a/drivers/video/aty/aty128fb.c
+++ b/drivers/video/aty/aty128fb.c
@@ -64,6 +64,7 @@
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/console.h>
+#include <linux/backlight.h>
#include <asm/io.h>
#ifdef CONFIG_PPC_PMAC
@@ -480,16 +481,6 @@ static struct fb_ops aty128fb_ops = {
.fb_imageblit = cfb_imageblit,
};
-#ifdef CONFIG_PMAC_BACKLIGHT
-static int aty128_set_backlight_enable(int on, int level, void* data);
-static int aty128_set_backlight_level(int level, void* data);
-
-static struct backlight_controller aty128_backlight_controller = {
- aty128_set_backlight_enable,
- aty128_set_backlight_level
-};
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
/*
* Functions to read from/write to the mmio registers
* - endian conversions may possibly be avoided by
@@ -1258,19 +1249,35 @@ static void aty128_set_crt_enable(struct aty128fb_par *par, int on)
static void aty128_set_lcd_enable(struct aty128fb_par *par, int on)
{
u32 reg;
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ struct fb_info *info = pci_get_drvdata(par->pdev);
+#endif
if (on) {
reg = aty_ld_le32(LVDS_GEN_CNTL);
reg |= LVDS_ON | LVDS_EN | LVDS_BLON | LVDS_DIGION;
reg &= ~LVDS_DISPLAY_DIS;
aty_st_le32(LVDS_GEN_CNTL, reg);
-#ifdef CONFIG_PMAC_BACKLIGHT
- aty128_set_backlight_enable(get_backlight_enable(),
- get_backlight_level(), par);
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ mutex_lock(&info->bl_mutex);
+ if (info->bl_dev) {
+ down(&info->bl_dev->sem);
+ info->bl_dev->props->update_status(info->bl_dev);
+ up(&info->bl_dev->sem);
+ }
+ mutex_unlock(&info->bl_mutex);
#endif
} else {
-#ifdef CONFIG_PMAC_BACKLIGHT
- aty128_set_backlight_enable(0, 0, par);
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ mutex_lock(&info->bl_mutex);
+ if (info->bl_dev) {
+ down(&info->bl_dev->sem);
+ info->bl_dev->props->brightness = 0;
+ info->bl_dev->props->power = FB_BLANK_POWERDOWN;
+ info->bl_dev->props->update_status(info->bl_dev);
+ up(&info->bl_dev->sem);
+ }
+ mutex_unlock(&info->bl_mutex);
#endif
reg = aty_ld_le32(LVDS_GEN_CNTL);
reg |= LVDS_DISPLAY_DIS;
@@ -1691,6 +1698,184 @@ static int __init aty128fb_setup(char *options)
}
#endif /* MODULE */
+/* Backlight */
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+#define MAX_LEVEL 0xFF
+
+static struct backlight_properties aty128_bl_data;
+
+static int aty128_bl_get_level_brightness(struct aty128fb_par *par,
+ int level)
+{
+ struct fb_info *info = pci_get_drvdata(par->pdev);
+ int atylevel;
+
+ /* Get and convert the value */
+ mutex_lock(&info->bl_mutex);
+ atylevel = MAX_LEVEL -
+ (info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_LEVEL);
+ mutex_unlock(&info->bl_mutex);
+
+ if (atylevel < 0)
+ atylevel = 0;
+ else if (atylevel > MAX_LEVEL)
+ atylevel = MAX_LEVEL;
+
+ return atylevel;
+}
+
+/* We turn off the LCD completely instead of just dimming the backlight.
+ * This provides greater power saving and the display is useless without
+ * backlight anyway
+ */
+#define BACKLIGHT_LVDS_OFF
+/* That one prevents proper CRT output with LCD off */
+#undef BACKLIGHT_DAC_OFF
+
+static int aty128_bl_update_status(struct backlight_device *bd)
+{
+ struct aty128fb_par *par = class_get_devdata(&bd->class_dev);
+ unsigned int reg = aty_ld_le32(LVDS_GEN_CNTL);
+ int level;
+
+ if (bd->props->power != FB_BLANK_UNBLANK ||
+ bd->props->fb_blank != FB_BLANK_UNBLANK ||
+ !par->lcd_on)
+ level = 0;
+ else
+ level = bd->props->brightness;
+
+ reg |= LVDS_BL_MOD_EN | LVDS_BLON;
+ if (level > 0) {
+ reg |= LVDS_DIGION;
+ if (!(reg & LVDS_ON)) {
+ reg &= ~LVDS_BLON;
+ aty_st_le32(LVDS_GEN_CNTL, reg);
+ aty_ld_le32(LVDS_GEN_CNTL);
+ mdelay(10);
+ reg |= LVDS_BLON;
+ aty_st_le32(LVDS_GEN_CNTL, reg);
+ }
+ reg &= ~LVDS_BL_MOD_LEVEL_MASK;
+ reg |= (aty128_bl_get_level_brightness(par, level) << LVDS_BL_MOD_LEVEL_SHIFT);
+#ifdef BACKLIGHT_LVDS_OFF
+ reg |= LVDS_ON | LVDS_EN;
+ reg &= ~LVDS_DISPLAY_DIS;
+#endif
+ aty_st_le32(LVDS_GEN_CNTL, reg);
+#ifdef BACKLIGHT_DAC_OFF
+ aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) & (~DAC_PDWN));
+#endif
+ } else {
+ reg &= ~LVDS_BL_MOD_LEVEL_MASK;
+ reg |= (aty128_bl_get_level_brightness(par, 0) << LVDS_BL_MOD_LEVEL_SHIFT);
+#ifdef BACKLIGHT_LVDS_OFF
+ reg |= LVDS_DISPLAY_DIS;
+ aty_st_le32(LVDS_GEN_CNTL, reg);
+ aty_ld_le32(LVDS_GEN_CNTL);
+ udelay(10);
+ reg &= ~(LVDS_ON | LVDS_EN | LVDS_BLON | LVDS_DIGION);
+#endif
+ aty_st_le32(LVDS_GEN_CNTL, reg);
+#ifdef BACKLIGHT_DAC_OFF
+ aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) | DAC_PDWN);
+#endif
+ }
+
+ return 0;
+}
+
+static int aty128_bl_get_brightness(struct backlight_device *bd)
+{
+ return bd->props->brightness;
+}
+
+static struct backlight_properties aty128_bl_data = {
+ .owner = THIS_MODULE,
+ .get_brightness = aty128_bl_get_brightness,
+ .update_status = aty128_bl_update_status,
+ .max_brightness = (FB_BACKLIGHT_LEVELS - 1),
+};
+
+static void aty128_bl_init(struct aty128fb_par *par)
+{
+ struct fb_info *info = pci_get_drvdata(par->pdev);
+ struct backlight_device *bd;
+ char name[12];
+
+ /* Could be extended to Rage128Pro LVDS output too */
+ if (par->chip_gen != rage_M3)
+ return;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+ if (!pmac_has_backlight_type("ati"))
+ return;
+#endif
+
+ snprintf(name, sizeof(name), "aty128bl%d", info->node);
+
+ bd = backlight_device_register(name, par, &aty128_bl_data);
+ if (IS_ERR(bd)) {
+ info->bl_dev = NULL;
+ printk("aty128: Backlight registration failed\n");
+ goto error;
+ }
+
+ mutex_lock(&info->bl_mutex);
+ info->bl_dev = bd;
+ fb_bl_default_curve(info, 0,
+ 63 * FB_BACKLIGHT_MAX / MAX_LEVEL,
+ 219 * FB_BACKLIGHT_MAX / MAX_LEVEL);
+ mutex_unlock(&info->bl_mutex);
+
+ up(&bd->sem);
+ bd->props->brightness = aty128_bl_data.max_brightness;
+ bd->props->power = FB_BLANK_UNBLANK;
+ bd->props->update_status(bd);
+ down(&bd->sem);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+ mutex_lock(&pmac_backlight_mutex);
+ if (!pmac_backlight)
+ pmac_backlight = bd;
+ mutex_unlock(&pmac_backlight_mutex);
+#endif
+
+ printk("aty128: Backlight initialized (%s)\n", name);
+
+ return;
+
+error:
+ return;
+}
+
+static void aty128_bl_exit(struct aty128fb_par *par)
+{
+ struct fb_info *info = pci_get_drvdata(par->pdev);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+ mutex_lock(&pmac_backlight_mutex);
+#endif
+
+ mutex_lock(&info->bl_mutex);
+ if (info->bl_dev) {
+#ifdef CONFIG_PMAC_BACKLIGHT
+ if (pmac_backlight == info->bl_dev)
+ pmac_backlight = NULL;
+#endif
+
+ backlight_device_unregister(info->bl_dev);
+ info->bl_dev = NULL;
+
+ printk("aty128: Backlight unloaded\n");
+ }
+ mutex_unlock(&info->bl_mutex);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+ mutex_unlock(&pmac_backlight_mutex);
+#endif
+}
+#endif /* CONFIG_FB_ATY128_BACKLIGHT */
/*
* Initialisation
@@ -1835,17 +2020,15 @@ static int __init aty128_init(struct pci_dev *pdev, const struct pci_device_id *
if (register_framebuffer(info) < 0)
return 0;
-#ifdef CONFIG_PMAC_BACKLIGHT
- /* Could be extended to Rage128Pro LVDS output too */
- if (par->chip_gen == rage_M3)
- register_backlight_controller(&aty128_backlight_controller, par, "ati");
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
par->pm_reg = pci_find_capability(pdev, PCI_CAP_ID_PM);
par->pdev = pdev;
par->asleep = 0;
par->lock_blank = 0;
-
+
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ aty128_bl_init(par);
+#endif
+
printk(KERN_INFO "fb%d: %s frame buffer device on %s\n",
info->node, info->fix.id, video_card);
@@ -1981,6 +2164,10 @@ static void __devexit aty128_remove(struct pci_dev *pdev)
par = info->par;
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ aty128_bl_exit(par);
+#endif
+
unregister_framebuffer(info);
#ifdef CONFIG_MTRR
if (par->mtrr.vram_valid)
@@ -2011,10 +2198,14 @@ static int aty128fb_blank(int blank, struct fb_info *fb)
if (par->lock_blank || par->asleep)
return 0;
-#ifdef CONFIG_PMAC_BACKLIGHT
- if (machine_is(powermac) && blank)
- set_backlight_enable(0);
-#endif /* CONFIG_PMAC_BACKLIGHT */
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ if (machine_is(powermac) && blank) {
+ down(&fb->bl_dev->sem);
+ fb->bl_dev->props->power = FB_BLANK_POWERDOWN;
+ fb->bl_dev->props->update_status(fb->bl_dev);
+ up(&fb->bl_dev->sem);
+ }
+#endif
if (blank & FB_BLANK_VSYNC_SUSPEND)
state |= 2;
@@ -2029,10 +2220,14 @@ static int aty128fb_blank(int blank, struct fb_info *fb)
aty128_set_crt_enable(par, par->crt_on && !blank);
aty128_set_lcd_enable(par, par->lcd_on && !blank);
}
-#ifdef CONFIG_PMAC_BACKLIGHT
- if (machine_is(powermac) && !blank)
- set_backlight_enable(1);
-#endif /* CONFIG_PMAC_BACKLIGHT */
+#ifdef CONFIG_FB_ATY128_BACKLIGHT
+ if (machine_is(powermac) && !blank) {
+ down(&fb->bl_dev->sem);
+ fb->bl_dev->props->power = FB_BLANK_UNBLANK;
+ fb->bl_dev->props->update_status(fb->bl_dev);
+ up(&fb->bl_dev->sem);
+ }
+#endif
return 0;
}
@@ -2138,73 +2333,6 @@ static int aty128fb_ioctl(struct fb_info *info, u_int cmd, u_long arg)
return -EINVAL;
}
-#ifdef CONFIG_PMAC_BACKLIGHT
-static int backlight_conv[] = {
- 0xff, 0xc0, 0xb5, 0xaa, 0x9f, 0x94, 0x89, 0x7e,
- 0x73, 0x68, 0x5d, 0x52, 0x47, 0x3c, 0x31, 0x24
-};
-
-/* We turn off the LCD completely instead of just dimming the backlight.
- * This provides greater power saving and the display is useless without
- * backlight anyway
- */
-#define BACKLIGHT_LVDS_OFF
-/* That one prevents proper CRT output with LCD off */
-#undef BACKLIGHT_DAC_OFF
-
-static int aty128_set_backlight_enable(int on, int level, void *data)
-{
- struct aty128fb_par *par = data;
- unsigned int reg = aty_ld_le32(LVDS_GEN_CNTL);
-
- if (!par->lcd_on)
- on = 0;
- reg |= LVDS_BL_MOD_EN | LVDS_BLON;
- if (on && level > BACKLIGHT_OFF) {
- reg |= LVDS_DIGION;
- if (!(reg & LVDS_ON)) {
- reg &= ~LVDS_BLON;
- aty_st_le32(LVDS_GEN_CNTL, reg);
- (void)aty_ld_le32(LVDS_GEN_CNTL);
- mdelay(10);
- reg |= LVDS_BLON;
- aty_st_le32(LVDS_GEN_CNTL, reg);
- }
- reg &= ~LVDS_BL_MOD_LEVEL_MASK;
- reg |= (backlight_conv[level] << LVDS_BL_MOD_LEVEL_SHIFT);
-#ifdef BACKLIGHT_LVDS_OFF
- reg |= LVDS_ON | LVDS_EN;
- reg &= ~LVDS_DISPLAY_DIS;
-#endif
- aty_st_le32(LVDS_GEN_CNTL, reg);
-#ifdef BACKLIGHT_DAC_OFF
- aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) & (~DAC_PDWN));
-#endif
- } else {
- reg &= ~LVDS_BL_MOD_LEVEL_MASK;
- reg |= (backlight_conv[0] << LVDS_BL_MOD_LEVEL_SHIFT);
-#ifdef BACKLIGHT_LVDS_OFF
- reg |= LVDS_DISPLAY_DIS;
- aty_st_le32(LVDS_GEN_CNTL, reg);
- (void)aty_ld_le32(LVDS_GEN_CNTL);
- udelay(10);
- reg &= ~(LVDS_ON | LVDS_EN | LVDS_BLON | LVDS_DIGION);
-#endif
- aty_st_le32(LVDS_GEN_CNTL, reg);
-#ifdef BACKLIGHT_DAC_OFF
- aty_st_le32(DAC_CNTL, aty_ld_le32(DAC_CNTL) | DAC_PDWN);
-#endif
- }
-
- return 0;
-}
-
-static int aty128_set_backlight_level(int level, void* data)
-{
- return aty128_set_backlight_enable(1, level, data);
-}
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
#if 0
/*
* Accelerated functions