[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>
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 5a2840a..168ede7 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -86,6 +86,11 @@
 	 combination with certain motherboards and monitors are known to
 	 suffer from this problem.
 
+config FB_BACKLIGHT
+       bool
+       depends on FB
+       default n
+
 config FB_MODE_HELPERS
         bool "Enable Video Mode Handling Helpers"
         depends on FB
@@ -717,6 +722,16 @@
 	  independently validate video mode parameters, you should say Y
 	  here.
 
+config FB_NVIDIA_BACKLIGHT
+	bool "Support for backlight control"
+	depends on FB_NVIDIA && PPC_PMAC
+	select FB_BACKLIGHT
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	default y
+	help
+	  Say Y here if you want to control the backlight of your display.
+
 config FB_RIVA
 	tristate "nVidia Riva support"
 	depends on FB && PCI
@@ -755,6 +770,16 @@
 	  of debugging informations to provide to the maintainer when
 	  something goes wrong.
 
+config FB_RIVA_BACKLIGHT
+	bool "Support for backlight control"
+	depends on FB_RIVA && PPC_PMAC
+	select FB_BACKLIGHT
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	default y
+	help
+	  Say Y here if you want to control the backlight of your display.
+
 config FB_I810
 	tristate "Intel 810/815 support (EXPERIMENTAL)"
 	depends on FB && EXPERIMENTAL && PCI && X86_32
@@ -993,6 +1018,7 @@
 
 	  There is a product page at
 	  http://apps.ati.com/ATIcompare/
+
 config FB_RADEON_I2C
 	bool "DDC/I2C for ATI Radeon support"
 	depends on FB_RADEON
@@ -1000,6 +1026,16 @@
 	help
 	  Say Y here if you want DDC/I2C support for your Radeon board. 
 
+config FB_RADEON_BACKLIGHT
+	bool "Support for backlight control"
+	depends on FB_RADEON && PPC_PMAC
+	select FB_BACKLIGHT
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	default y
+	help
+	  Say Y here if you want to control the backlight of your display.
+
 config FB_RADEON_DEBUG
 	bool "Lots of debug output from Radeon driver"
 	depends on FB_RADEON
@@ -1024,6 +1060,16 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called aty128fb.
 
+config FB_ATY128_BACKLIGHT
+	bool "Support for backlight control"
+	depends on FB_ATY128 && PPC_PMAC
+	select FB_BACKLIGHT
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	default y
+	help
+	  Say Y here if you want to control the backlight of your display.
+
 config FB_ATY
 	tristate "ATI Mach64 display support" if PCI || ATARI
 	depends on FB && !SPARC32
@@ -1066,6 +1112,16 @@
 	  is at
 	  <http://support.ati.com/products/pc/mach64/graphics_xpression.html>.
 
+config FB_ATY_BACKLIGHT
+	bool "Support for backlight control"
+	depends on FB_ATY && PPC_PMAC
+	select FB_BACKLIGHT
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
+	default y
+	help
+	  Say Y here if you want to control the backlight of your display.
+
 config FB_S3TRIO
 	bool "S3 Trio display support"
 	depends on (FB = y) && PPC && BROKEN
diff --git a/drivers/video/aty/Makefile b/drivers/video/aty/Makefile
index 1852139..a6cc0e9 100644
--- a/drivers/video/aty/Makefile
+++ b/drivers/video/aty/Makefile
@@ -10,5 +10,6 @@
 
 radeonfb-y			:= radeon_base.o radeon_pm.o radeon_monitor.o radeon_accel.o
 radeonfb-$(CONFIG_FB_RADEON_I2C)	+= radeon_i2c.o
+radeonfb-$(CONFIG_FB_RADEON_BACKLIGHT)	+= radeon_backlight.o
 radeonfb-objs			:= $(radeonfb-y)
 
diff --git a/drivers/video/aty/aty128fb.c b/drivers/video/aty/aty128fb.c
index f7bbff4..db878fd 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 @@
 	.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_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 @@
 }
 #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 @@
 	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 @@
 
 	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 @@
 	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 @@
 		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 @@
 	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
diff --git a/drivers/video/aty/atyfb.h b/drivers/video/aty/atyfb.h
index e9b7a64..43d2cb5 100644
--- a/drivers/video/aty/atyfb.h
+++ b/drivers/video/aty/atyfb.h
@@ -151,6 +151,7 @@
 	int lock_blank;
 	unsigned long res_start;
 	unsigned long res_size;
+	struct pci_dev *pdev;
 #ifdef __sparc__
 	struct pci_mmap_map *mmap_map;
 	u8 mmaped;
diff --git a/drivers/video/aty/atyfb_base.c b/drivers/video/aty/atyfb_base.c
index c054bb2..c5185f7 100644
--- a/drivers/video/aty/atyfb_base.c
+++ b/drivers/video/aty/atyfb_base.c
@@ -66,6 +66,7 @@
 #include <linux/interrupt.h>
 #include <linux/spinlock.h>
 #include <linux/wait.h>
+#include <linux/backlight.h>
 
 #include <asm/io.h>
 #include <asm/uaccess.h>
@@ -2115,45 +2116,142 @@
 
 #endif /*  defined(CONFIG_PM) && defined(CONFIG_PCI) */
 
-#ifdef CONFIG_PMAC_BACKLIGHT
+/* Backlight */
+#ifdef CONFIG_FB_ATY_BACKLIGHT
+#define MAX_LEVEL 0xFF
 
-    /*
-     *   LCD backlight control
-     */
+static struct backlight_properties aty_bl_data;
 
-static int backlight_conv[] = {
-	0x00, 0x3f, 0x4c, 0x59, 0x66, 0x73, 0x80, 0x8d,
-	0x9a, 0xa7, 0xb4, 0xc1, 0xcf, 0xdc, 0xe9, 0xff
-};
-
-static int aty_set_backlight_enable(int on, int level, void *data)
+static int aty_bl_get_level_brightness(struct atyfb_par *par, int level)
 {
-	struct fb_info *info = (struct fb_info *) data;
-	struct atyfb_par *par = (struct atyfb_par *) info->par;
+	struct fb_info *info = pci_get_drvdata(par->pdev);
+	int atylevel;
+
+	/* Get and convert the value */
+	mutex_lock(&info->bl_mutex);
+	atylevel = 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;
+}
+
+static int aty_bl_update_status(struct backlight_device *bd)
+{
+	struct atyfb_par *par = class_get_devdata(&bd->class_dev);
 	unsigned int reg = aty_ld_lcd(LCD_MISC_CNTL, par);
+	int level;
+
+	if (bd->props->power != FB_BLANK_UNBLANK ||
+	    bd->props->fb_blank != FB_BLANK_UNBLANK)
+		level = 0;
+	else
+		level = bd->props->brightness;
 
 	reg |= (BLMOD_EN | BIASMOD_EN);
-	if (on && level > BACKLIGHT_OFF) {
+	if (level > 0) {
 		reg &= ~BIAS_MOD_LEVEL_MASK;
-		reg |= (backlight_conv[level] << BIAS_MOD_LEVEL_SHIFT);
+		reg |= (aty_bl_get_level_brightness(par, level) << BIAS_MOD_LEVEL_SHIFT);
 	} else {
 		reg &= ~BIAS_MOD_LEVEL_MASK;
-		reg |= (backlight_conv[0] << BIAS_MOD_LEVEL_SHIFT);
+		reg |= (aty_bl_get_level_brightness(par, 0) << BIAS_MOD_LEVEL_SHIFT);
 	}
 	aty_st_lcd(LCD_MISC_CNTL, reg, par);
+
 	return 0;
 }
 
-static int aty_set_backlight_level(int level, void *data)
+static int aty_bl_get_brightness(struct backlight_device *bd)
 {
-	return aty_set_backlight_enable(1, level, data);
+	return bd->props->brightness;
 }
 
-static struct backlight_controller aty_backlight_controller = {
-	aty_set_backlight_enable,
-	aty_set_backlight_level
+static struct backlight_properties aty_bl_data = {
+	.owner	  = THIS_MODULE,
+	.get_brightness = aty_bl_get_brightness,
+	.update_status	= aty_bl_update_status,
+	.max_brightness = (FB_BACKLIGHT_LEVELS - 1),
 };
-#endif /* CONFIG_PMAC_BACKLIGHT */
+
+static void aty_bl_init(struct atyfb_par *par)
+{
+	struct fb_info *info = pci_get_drvdata(par->pdev);
+	struct backlight_device *bd;
+	char name[12];
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	if (!pmac_has_backlight_type("ati"))
+		return;
+#endif
+
+	snprintf(name, sizeof(name), "atybl%d", info->node);
+
+	bd = backlight_device_register(name, par, &aty_bl_data);
+	if (IS_ERR(bd)) {
+		info->bl_dev = NULL;
+		printk("aty: Backlight registration failed\n");
+		goto error;
+	}
+
+	mutex_lock(&info->bl_mutex);
+	info->bl_dev = bd;
+	fb_bl_default_curve(info, 0,
+		0x3F * FB_BACKLIGHT_MAX / MAX_LEVEL,
+		0xFF * FB_BACKLIGHT_MAX / MAX_LEVEL);
+	mutex_unlock(&info->bl_mutex);
+
+	up(&bd->sem);
+	bd->props->brightness = aty_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("aty: Backlight initialized (%s)\n", name);
+
+	return;
+
+error:
+	return;
+}
+
+static void aty_bl_exit(struct atyfb_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);
+
+		printk("aty: Backlight unloaded\n");
+	}
+	mutex_unlock(&info->bl_mutex);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	mutex_unlock(&pmac_backlight_mutex);
+#endif
+}
+
+#endif /* CONFIG_FB_ATY_BACKLIGHT */
 
 static void __init aty_calc_mem_refresh(struct atyfb_par *par, int xclk)
 {
@@ -2513,9 +2611,13 @@
 		/* these bits let the 101 powerbook wake up from sleep -- paulus */
 		aty_st_lcd(POWER_MANAGEMENT, aty_ld_lcd(POWER_MANAGEMENT, par)
 			   | (USE_F32KHZ | TRISTATE_MEM_EN), par);
-	} else if (M64_HAS(MOBIL_BUS))
-		register_backlight_controller(&aty_backlight_controller, info, "ati");
-#endif /* CONFIG_PMAC_BACKLIGHT */
+	} else
+#endif
+	if (M64_HAS(MOBIL_BUS)) {
+#ifdef CONFIG_FB_ATY_BACKLIGHT
+		aty_bl_init (par);
+#endif
+	}
 
 	memset(&var, 0, sizeof(var));
 #ifdef CONFIG_PPC
@@ -2674,8 +2776,16 @@
 		return 0;
 
 #ifdef CONFIG_PMAC_BACKLIGHT
-	if (machine_is(powermac) && blank > FB_BLANK_NORMAL)
-		set_backlight_enable(0);
+	if (machine_is(powermac) && blank > FB_BLANK_NORMAL) {
+		mutex_lock(&info->bl_mutex);
+		if (info->bl_dev) {
+			down(&info->bl_dev->sem);
+			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);
+	}
 #elif defined(CONFIG_FB_ATY_GENERIC_LCD)
 	if (par->lcd_table && blank > FB_BLANK_NORMAL &&
 	    (aty_ld_lcd(LCD_GEN_CNTL, par) & LCD_ON)) {
@@ -2706,8 +2816,16 @@
 	aty_st_le32(CRTC_GEN_CNTL, gen_cntl, par);
 
 #ifdef CONFIG_PMAC_BACKLIGHT
-	if (machine_is(powermac) && blank <= FB_BLANK_NORMAL)
-		set_backlight_enable(1);
+	if (machine_is(powermac) && blank <= FB_BLANK_NORMAL) {
+		mutex_lock(&info->bl_mutex);
+		if (info->bl_dev) {
+			down(&info->bl_dev->sem);
+			info->bl_dev->props->power = FB_BLANK_UNBLANK;
+			info->bl_dev->props->update_status(info->bl_dev);
+			up(&info->bl_dev->sem);
+		}
+		mutex_unlock(&info->bl_mutex);
+	}
 #elif defined(CONFIG_FB_ATY_GENERIC_LCD)
 	if (par->lcd_table && blank <= FB_BLANK_NORMAL &&
 	    (aty_ld_lcd(LCD_GEN_CNTL, par) & LCD_ON)) {
@@ -3440,6 +3558,7 @@
 	par->res_start = res_start;
 	par->res_size = res_size;
 	par->irq = pdev->irq;
+	par->pdev = pdev;
 
 	/* Setup "info" structure */
 #ifdef __sparc__
@@ -3571,6 +3690,11 @@
 	aty_set_crtc(par, &saved_crtc);
 	par->pll_ops->set_pll(info, &saved_pll);
 
+#ifdef CONFIG_FB_ATY_BACKLIGHT
+	if (M64_HAS(MOBIL_BUS))
+		aty_bl_exit(par);
+#endif
+
 	unregister_framebuffer(info);
 
 #ifdef CONFIG_MTRR
diff --git a/drivers/video/aty/radeon_backlight.c b/drivers/video/aty/radeon_backlight.c
new file mode 100644
index 0000000..7de66b8
--- /dev/null
+++ b/drivers/video/aty/radeon_backlight.c
@@ -0,0 +1,247 @@
+/*
+ * Backlight code for ATI Radeon based graphic cards
+ *
+ * Copyright (c) 2000 Ani Joshi <ajoshi@kernel.crashing.org>
+ * Copyright (c) 2003 Benjamin Herrenschmidt <benh@kernel.crashing.org>
+ * Copyright (c) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "radeonfb.h"
+#include <linux/backlight.h>
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+#include <asm/backlight.h>
+#endif
+
+#define MAX_RADEON_LEVEL 0xFF
+
+static struct backlight_properties radeon_bl_data;
+
+struct radeon_bl_privdata {
+	struct radeonfb_info *rinfo;
+	uint8_t negative;
+};
+
+static int radeon_bl_get_level_brightness(struct radeon_bl_privdata *pdata,
+		int level)
+{
+	struct fb_info *info = pdata->rinfo->info;
+	int rlevel;
+
+	mutex_lock(&info->bl_mutex);
+
+	/* Get and convert the value */
+	rlevel = pdata->rinfo->info->bl_curve[level] *
+		 FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL;
+
+	mutex_unlock(&info->bl_mutex);
+
+	if (pdata->negative)
+		rlevel = MAX_RADEON_LEVEL - rlevel;
+
+	if (rlevel < 0)
+		rlevel = 0;
+	else if (rlevel > MAX_RADEON_LEVEL)
+		rlevel = MAX_RADEON_LEVEL;
+
+	return rlevel;
+}
+
+static int radeon_bl_update_status(struct backlight_device *bd)
+{
+	struct radeon_bl_privdata *pdata = class_get_devdata(&bd->class_dev);
+	struct radeonfb_info *rinfo = pdata->rinfo;
+	u32 lvds_gen_cntl, tmpPixclksCntl;
+	int level;
+
+	if (rinfo->mon1_type != MT_LCD)
+		return 0;
+
+	/* We turn off the LCD completely instead of just dimming the
+	 * backlight. This provides some greater power saving and the display
+	 * is useless without backlight anyway.
+	 */
+        if (bd->props->power != FB_BLANK_UNBLANK ||
+	    bd->props->fb_blank != FB_BLANK_UNBLANK)
+		level = 0;
+	else
+		level = bd->props->brightness;
+
+	del_timer_sync(&rinfo->lvds_timer);
+	radeon_engine_idle();
+
+	lvds_gen_cntl = INREG(LVDS_GEN_CNTL);
+	if (level > 0) {
+		lvds_gen_cntl &= ~LVDS_DISPLAY_DIS;
+		if (!(lvds_gen_cntl & LVDS_BLON) || !(lvds_gen_cntl & LVDS_ON)) {
+			lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_DIGON);
+			lvds_gen_cntl |= LVDS_BLON | LVDS_EN;
+			OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
+			lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
+			lvds_gen_cntl |=
+				(radeon_bl_get_level_brightness(pdata, level) <<
+				 LVDS_BL_MOD_LEVEL_SHIFT);
+			lvds_gen_cntl |= LVDS_ON;
+			lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_BL_MOD_EN);
+			rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
+			mod_timer(&rinfo->lvds_timer,
+				  jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
+		} else {
+			lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
+			lvds_gen_cntl |=
+				(radeon_bl_get_level_brightness(pdata, level) <<
+				 LVDS_BL_MOD_LEVEL_SHIFT);
+			OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
+		}
+		rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
+		rinfo->init_state.lvds_gen_cntl |= rinfo->pending_lvds_gen_cntl
+			& LVDS_STATE_MASK;
+	} else {
+		/* Asic bug, when turning off LVDS_ON, we have to make sure
+		   RADEON_PIXCLK_LVDS_ALWAYS_ON bit is off
+		*/
+		tmpPixclksCntl = INPLL(PIXCLKS_CNTL);
+		if (rinfo->is_mobility || rinfo->is_IGP)
+			OUTPLLP(PIXCLKS_CNTL, 0, ~PIXCLK_LVDS_ALWAYS_ONb);
+		lvds_gen_cntl &= ~(LVDS_BL_MOD_LEVEL_MASK | LVDS_BL_MOD_EN);
+		lvds_gen_cntl |= (radeon_bl_get_level_brightness(pdata, 0) <<
+				  LVDS_BL_MOD_LEVEL_SHIFT);
+		lvds_gen_cntl |= LVDS_DISPLAY_DIS;
+		OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
+		udelay(100);
+		lvds_gen_cntl &= ~(LVDS_ON | LVDS_EN);
+		OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
+		lvds_gen_cntl &= ~(LVDS_DIGON);
+		rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
+		mod_timer(&rinfo->lvds_timer,
+			  jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
+		if (rinfo->is_mobility || rinfo->is_IGP)
+			OUTPLL(PIXCLKS_CNTL, tmpPixclksCntl);
+	}
+	rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
+	rinfo->init_state.lvds_gen_cntl |= (lvds_gen_cntl & LVDS_STATE_MASK);
+
+	return 0;
+}
+
+static int radeon_bl_get_brightness(struct backlight_device *bd)
+{
+	return bd->props->brightness;
+}
+
+static struct backlight_properties radeon_bl_data = {
+	.owner		= THIS_MODULE,
+	.get_brightness = radeon_bl_get_brightness,
+	.update_status	= radeon_bl_update_status,
+	.max_brightness = (FB_BACKLIGHT_LEVELS - 1),
+};
+
+void radeonfb_bl_init(struct radeonfb_info *rinfo)
+{
+	struct backlight_device *bd;
+	struct radeon_bl_privdata *pdata;
+	char name[12];
+
+	if (rinfo->mon1_type != MT_LCD)
+		return;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	if (!pmac_has_backlight_type("ati") &&
+	    !pmac_has_backlight_type("mnca"))
+		return;
+#endif
+
+	pdata = kmalloc(sizeof(struct radeon_bl_privdata), GFP_KERNEL);
+	if (!pdata) {
+		printk("radeonfb: Memory allocation failed\n");
+		goto error;
+	}
+
+	snprintf(name, sizeof(name), "radeonbl%d", rinfo->info->node);
+
+	bd = backlight_device_register(name, pdata, &radeon_bl_data);
+	if (IS_ERR(bd)) {
+		rinfo->info->bl_dev = NULL;
+		printk("radeonfb: Backlight registration failed\n");
+		goto error;
+	}
+
+	pdata->rinfo = rinfo;
+
+	/* Pardon me for that hack... maybe some day we can figure out in what
+	 * direction backlight should work on a given panel?
+	 */
+	pdata->negative =
+		(rinfo->family != CHIP_FAMILY_RV200 &&
+		 rinfo->family != CHIP_FAMILY_RV250 &&
+		 rinfo->family != CHIP_FAMILY_RV280 &&
+		 rinfo->family != CHIP_FAMILY_RV350);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	pdata->negative = pdata->negative ||
+		machine_is_compatible("PowerBook4,3") ||
+		machine_is_compatible("PowerBook6,3") ||
+		machine_is_compatible("PowerBook6,5");
+#endif
+
+	mutex_lock(&rinfo->info->bl_mutex);
+	rinfo->info->bl_dev = bd;
+	fb_bl_default_curve(rinfo->info, 0,
+		 63 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL,
+		217 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL);
+	mutex_unlock(&rinfo->info->bl_mutex);
+
+	up(&bd->sem);
+	bd->props->brightness = radeon_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("radeonfb: Backlight initialized (%s)\n", name);
+
+	return;
+
+error:
+	kfree(pdata);
+	return;
+}
+
+void radeonfb_bl_exit(struct radeonfb_info *rinfo)
+{
+#ifdef CONFIG_PMAC_BACKLIGHT
+	mutex_lock(&pmac_backlight_mutex);
+#endif
+
+	mutex_lock(&rinfo->info->bl_mutex);
+	if (rinfo->info->bl_dev) {
+		struct radeon_bl_privdata *pdata;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+		if (pmac_backlight == rinfo->info->bl_dev)
+			pmac_backlight = NULL;
+#endif
+
+		pdata = class_get_devdata(&rinfo->info->bl_dev->class_dev);
+		backlight_device_unregister(rinfo->info->bl_dev);
+		kfree(pdata);
+		rinfo->info->bl_dev = NULL;
+
+		printk("radeonfb: Backlight unloaded\n");
+	}
+	mutex_unlock(&rinfo->info->bl_mutex);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	mutex_unlock(&pmac_backlight_mutex);
+#endif
+}
diff --git a/drivers/video/aty/radeon_base.c b/drivers/video/aty/radeon_base.c
index 387a18a..c5ecbb0 100644
--- a/drivers/video/aty/radeon_base.c
+++ b/drivers/video/aty/radeon_base.c
@@ -78,10 +78,6 @@
 #include <asm/pci-bridge.h>
 #include "../macmodes.h"
 
-#ifdef CONFIG_PMAC_BACKLIGHT
-#include <asm/backlight.h>
-#endif
-
 #ifdef CONFIG_BOOTX_TEXT
 #include <asm/btext.h>
 #endif
@@ -277,20 +273,6 @@
  * prototypes
  */
 
-
-#ifdef CONFIG_PPC_OF
-
-#ifdef CONFIG_PMAC_BACKLIGHT
-static int radeon_set_backlight_enable(int on, int level, void *data);
-static int radeon_set_backlight_level(int level, void *data);
-static struct backlight_controller radeon_backlight_controller = {
-	radeon_set_backlight_enable,
-	radeon_set_backlight_level
-};
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
-#endif /* CONFIG_PPC_OF */
-
 static void radeon_unmap_ROM(struct radeonfb_info *rinfo, struct pci_dev *dev)
 {
 	if (!rinfo->bios_seg)
@@ -1913,116 +1895,6 @@
         return 0;
 }
 
-
-#ifdef CONFIG_PMAC_BACKLIGHT
-
-/* TODO: Dbl check these tables, we don't go up to full ON backlight
- * in these, possibly because we noticed MacOS doesn't, but I'd prefer
- * having some more official numbers from ATI
- */
-static int backlight_conv_m6[] = {
-	0xff, 0xc0, 0xb5, 0xaa, 0x9f, 0x94, 0x89, 0x7e,
-	0x73, 0x68, 0x5d, 0x52, 0x47, 0x3c, 0x31, 0x24
-};
-static int backlight_conv_m7[] = {
-	0x00, 0x3f, 0x4a, 0x55, 0x60, 0x6b, 0x76, 0x81,
-	0x8c, 0x97, 0xa2, 0xad, 0xb8, 0xc3, 0xce, 0xd9
-};
-
-#define BACKLIGHT_LVDS_OFF
-#undef BACKLIGHT_DAC_OFF
-
-/* We turn off the LCD completely instead of just dimming the backlight.
- * This provides some greater power saving and the display is useless
- * without backlight anyway.
- */
-static int radeon_set_backlight_enable(int on, int level, void *data)
-{
-	struct radeonfb_info *rinfo = (struct radeonfb_info *)data;
-	u32 lvds_gen_cntl, tmpPixclksCntl;
-	int* conv_table;
-
-	if (rinfo->mon1_type != MT_LCD)
-		return 0;
-
-	/* Pardon me for that hack... maybe some day we can figure
-	 * out in what direction backlight should work on a given
-	 * panel ?
-	 */
-	if ((rinfo->family == CHIP_FAMILY_RV200 ||
-	     rinfo->family == CHIP_FAMILY_RV250 ||
-	     rinfo->family == CHIP_FAMILY_RV280 ||
-	     rinfo->family == CHIP_FAMILY_RV350) &&
-	    !machine_is_compatible("PowerBook4,3") &&
-	    !machine_is_compatible("PowerBook6,3") &&
-	    !machine_is_compatible("PowerBook6,5"))
-		conv_table = backlight_conv_m7;
-	else
-		conv_table = backlight_conv_m6;
-
-	del_timer_sync(&rinfo->lvds_timer);
-	radeon_engine_idle();
-
-	lvds_gen_cntl = INREG(LVDS_GEN_CNTL);
-	if (on && (level > BACKLIGHT_OFF)) {
-		lvds_gen_cntl &= ~LVDS_DISPLAY_DIS;
-		if (!(lvds_gen_cntl & LVDS_BLON) || !(lvds_gen_cntl & LVDS_ON)) {
-			lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_DIGON);
-			lvds_gen_cntl |= LVDS_BLON | LVDS_EN;
-			OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
-			lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
-			lvds_gen_cntl |= (conv_table[level] <<
-					  LVDS_BL_MOD_LEVEL_SHIFT);
-			lvds_gen_cntl |= LVDS_ON;
-			lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_BL_MOD_EN);
-			rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
-			mod_timer(&rinfo->lvds_timer,
-				  jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
-		} else {
-			lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
-			lvds_gen_cntl |= (conv_table[level] <<
-					  LVDS_BL_MOD_LEVEL_SHIFT);
-			OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
-		}
-		rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
-		rinfo->init_state.lvds_gen_cntl |= rinfo->pending_lvds_gen_cntl
-			& LVDS_STATE_MASK;
-	} else {
-		/* Asic bug, when turning off LVDS_ON, we have to make sure
-		   RADEON_PIXCLK_LVDS_ALWAYS_ON bit is off
-		*/
-		tmpPixclksCntl = INPLL(PIXCLKS_CNTL);
-		if (rinfo->is_mobility || rinfo->is_IGP)
-			OUTPLLP(PIXCLKS_CNTL, 0, ~PIXCLK_LVDS_ALWAYS_ONb);
-		lvds_gen_cntl &= ~(LVDS_BL_MOD_LEVEL_MASK | LVDS_BL_MOD_EN);
-		lvds_gen_cntl |= (conv_table[0] <<
-				  LVDS_BL_MOD_LEVEL_SHIFT);
-		lvds_gen_cntl |= LVDS_DISPLAY_DIS;
-		OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
-		udelay(100);
-		lvds_gen_cntl &= ~(LVDS_ON | LVDS_EN);
-		OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
-		lvds_gen_cntl &= ~(LVDS_DIGON);
-		rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
-		mod_timer(&rinfo->lvds_timer,
-			  jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
-		if (rinfo->is_mobility || rinfo->is_IGP)
-			OUTPLL(PIXCLKS_CNTL, tmpPixclksCntl);
-	}
-	rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
-	rinfo->init_state.lvds_gen_cntl |= (lvds_gen_cntl & LVDS_STATE_MASK);
-
-	return 0;
-}
-
-
-static int radeon_set_backlight_level(int level, void *data)
-{
-	return radeon_set_backlight_enable(1, level, data);
-}
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
-
 /*
  * This reconfigure the card's internal memory map. In theory, we'd like
  * to setup the card's memory at the same address as it's PCI bus address,
@@ -2477,14 +2349,7 @@
 						 MTRR_TYPE_WRCOMB, 1);
 #endif
 
-#ifdef CONFIG_PMAC_BACKLIGHT
-	if (rinfo->mon1_type == MT_LCD) {
-		register_backlight_controller(&radeon_backlight_controller,
-					      rinfo, "ati");
-		register_backlight_controller(&radeon_backlight_controller,
-					      rinfo, "mnca");
-	}
-#endif
+	radeonfb_bl_init(rinfo);
 
 	printk ("radeonfb (%s): %s\n", pci_name(rinfo->pdev), rinfo->name);
 
@@ -2528,7 +2393,8 @@
  
         if (!rinfo)
                 return;
- 
+
+	radeonfb_bl_exit(rinfo);
 	radeonfb_pm_exit(rinfo);
 
 	if (rinfo->mon1_EDID)
diff --git a/drivers/video/aty/radeonfb.h b/drivers/video/aty/radeonfb.h
index 217e00a..1645943 100644
--- a/drivers/video/aty/radeonfb.h
+++ b/drivers/video/aty/radeonfb.h
@@ -625,4 +625,13 @@
 extern void radeon_write_mode (struct radeonfb_info *rinfo, struct radeon_regs *mode,
 			       int reg_only);
 
+/* Backlight functions */
+#ifdef CONFIG_FB_RADEON_BACKLIGHT
+extern void radeonfb_bl_init(struct radeonfb_info *rinfo);
+extern void radeonfb_bl_exit(struct radeonfb_info *rinfo);
+#else
+static inline void radeonfb_bl_init(struct radeonfb_info *rinfo) {}
+static inline void radeonfb_bl_exit(struct radeonfb_info *rinfo) {}
+#endif
+
 #endif /* __RADEONFB_H__ */
diff --git a/drivers/video/chipsfb.c b/drivers/video/chipsfb.c
index 72ff6bf..d76bbfac 100644
--- a/drivers/video/chipsfb.c
+++ b/drivers/video/chipsfb.c
@@ -148,9 +148,24 @@
 static int chipsfb_blank(int blank, struct fb_info *info)
 {
 #ifdef CONFIG_PMAC_BACKLIGHT
-	// used to disable backlight only for blank > 1, but it seems
-	// useful at blank = 1 too (saves battery, extends backlight life)
-	set_backlight_enable(!blank);
+	mutex_lock(&pmac_backlight_mutex);
+
+	if (pmac_backlight) {
+		down(&pmac_backlight->sem);
+
+		/* used to disable backlight only for blank > 1, but it seems
+		 * useful at blank = 1 too (saves battery, extends backlight
+		 * life)
+	 	 */
+		if (blank)
+			pmac_backlight->props->power = FB_BLANK_POWERDOWN;
+		else
+			pmac_backlight->props->power = FB_BLANK_UNBLANK;
+		pmac_backlight->props->update_status(pmac_backlight);
+		up(&pmac_backlight->sem);
+	}
+
+	mutex_unlock(&pmac_backlight_mutex);
 #endif /* CONFIG_PMAC_BACKLIGHT */
 
 	return 1;	/* get fb_blank to set the colormap to all black */
@@ -401,7 +416,14 @@
 
 #ifdef CONFIG_PMAC_BACKLIGHT
 	/* turn on the backlight */
-	set_backlight_enable(1);
+	mutex_lock(&pmac_backlight_mutex);
+	if (pmac_backlight) {
+		down(&pmac_backlight->sem);
+		pmac_backlight->props->power = FB_BLANK_UNBLANK;
+		pmac_backlight->props->update_status(pmac_backlight);
+		up(&pmac_backlight->sem);
+	}
+	mutex_unlock(&pmac_backlight_mutex);
 #endif /* CONFIG_PMAC_BACKLIGHT */
 
 #ifdef CONFIG_PPC
diff --git a/drivers/video/fbsysfs.c b/drivers/video/fbsysfs.c
index 34e0739..3ceb8c1 100644
--- a/drivers/video/fbsysfs.c
+++ b/drivers/video/fbsysfs.c
@@ -18,6 +18,7 @@
 #include <linux/kernel.h>
 #include <linux/fb.h>
 #include <linux/console.h>
+#include <linux/module.h>
 
 /**
  * framebuffer_alloc - creates a new frame buffer info structure
@@ -55,6 +56,10 @@
 
 	info->device = dev;
 
+#ifdef CONFIG_FB_BACKLIGHT
+	mutex_init(&info->bl_mutex);
+#endif
+
 	return info;
 #undef PADDING
 #undef BYTES_PER_LONG
@@ -414,6 +419,65 @@
 	return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->state);
 }
 
+#ifdef CONFIG_FB_BACKLIGHT
+static ssize_t store_bl_curve(struct class_device *class_device,
+		const char *buf, size_t count)
+{
+	struct fb_info *fb_info = class_get_devdata(class_device);
+	u8 tmp_curve[FB_BACKLIGHT_LEVELS];
+	unsigned int i;
+
+	if (count != (FB_BACKLIGHT_LEVELS / 8 * 24))
+		return -EINVAL;
+
+	for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i)
+		if (sscanf(&buf[i * 24],
+			"%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n",
+			&tmp_curve[i * 8 + 0],
+			&tmp_curve[i * 8 + 1],
+			&tmp_curve[i * 8 + 2],
+			&tmp_curve[i * 8 + 3],
+			&tmp_curve[i * 8 + 4],
+			&tmp_curve[i * 8 + 5],
+			&tmp_curve[i * 8 + 6],
+			&tmp_curve[i * 8 + 7]) != 8)
+			return -EINVAL;
+
+	/* If there has been an error in the input data, we won't
+	 * reach this loop.
+	 */
+	mutex_lock(&fb_info->bl_mutex);
+	for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i)
+		fb_info->bl_curve[i] = tmp_curve[i];
+	mutex_unlock(&fb_info->bl_mutex);
+
+	return count;
+}
+
+static ssize_t show_bl_curve(struct class_device *class_device, char *buf)
+{
+	struct fb_info *fb_info = class_get_devdata(class_device);
+	ssize_t len = 0;
+	unsigned int i;
+
+	mutex_lock(&fb_info->bl_mutex);
+	for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8)
+		len += snprintf(&buf[len], PAGE_SIZE,
+				"%02x %02x %02x %02x %02x %02x %02x %02x\n",
+				fb_info->bl_curve[i + 0],
+				fb_info->bl_curve[i + 1],
+				fb_info->bl_curve[i + 2],
+				fb_info->bl_curve[i + 3],
+				fb_info->bl_curve[i + 4],
+				fb_info->bl_curve[i + 5],
+				fb_info->bl_curve[i + 6],
+				fb_info->bl_curve[i + 7]);
+	mutex_unlock(&fb_info->bl_mutex);
+
+	return len;
+}
+#endif
+
 /* When cmap is added back in it should be a binary attribute
  * not a text one. Consideration should also be given to converting
  * fbdev to use configfs instead of sysfs */
@@ -432,6 +496,9 @@
 	__ATTR(con_rotate, S_IRUGO|S_IWUSR, show_con_rotate, store_con_rotate),
 	__ATTR(con_rotate_all, S_IWUSR, NULL, store_con_rotate_all),
 	__ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
+#ifdef CONFIG_FB_BACKLIGHT
+	__ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
+#endif
 };
 
 int fb_init_class_device(struct fb_info *fb_info)
@@ -454,4 +521,25 @@
 					 &class_device_attrs[i]);
 }
 
+#ifdef CONFIG_FB_BACKLIGHT
+/* This function generates a linear backlight curve
+ *
+ *     0: off
+ *   1-7: min
+ * 8-127: linear from min to max
+ */
+void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max)
+{
+	unsigned int i, flat, count, range = (max - min);
 
+	fb_info->bl_curve[0] = off;
+
+	for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
+		fb_info->bl_curve[flat] = min;
+
+	count = FB_BACKLIGHT_LEVELS * 15 / 16;
+	for (i = 0; i < count; ++i)
+		fb_info->bl_curve[flat + i] = min + (range * (i + 1) / count);
+}
+EXPORT_SYMBOL_GPL(fb_bl_default_curve);
+#endif
diff --git a/drivers/video/nvidia/Makefile b/drivers/video/nvidia/Makefile
index 690d37e..ca47432 100644
--- a/drivers/video/nvidia/Makefile
+++ b/drivers/video/nvidia/Makefile
@@ -7,6 +7,7 @@
 nvidiafb-y                       := nvidia.o nv_hw.o nv_setup.o \
 			            nv_accel.o
 nvidiafb-$(CONFIG_FB_NVIDIA_I2C) += nv_i2c.o
+nvidiafb-$(CONFIG_FB_NVIDIA_BACKLIGHT)  += nv_backlight.o
 nvidiafb-$(CONFIG_PPC_OF)	 += nv_of.o
 
-nvidiafb-objs                    := $(nvidiafb-y)
\ No newline at end of file
+nvidiafb-objs                    := $(nvidiafb-y)
diff --git a/drivers/video/nvidia/nv_backlight.c b/drivers/video/nvidia/nv_backlight.c
new file mode 100644
index 0000000..1c1c10c
--- /dev/null
+++ b/drivers/video/nvidia/nv_backlight.c
@@ -0,0 +1,175 @@
+/*
+ * Backlight code for nVidia based graphic cards
+ *
+ * Copyright 2004 Antonino Daplas <adaplas@pol.net>
+ * Copyright (c) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/pci.h>
+#include "nv_local.h"
+#include "nv_type.h"
+#include "nv_proto.h"
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+#include <asm/backlight.h>
+#include <asm/machdep.h>
+#endif
+
+/* We do not have any information about which values are allowed, thus
+ * we used safe values.
+ */
+#define MIN_LEVEL 0x158
+#define MAX_LEVEL 0x534
+
+static struct backlight_properties nvidia_bl_data;
+
+static int nvidia_bl_get_level_brightness(struct nvidia_par *par,
+		int level)
+{
+	struct fb_info *info = pci_get_drvdata(par->pci_dev);
+	int nlevel;
+
+	/* Get and convert the value */
+	mutex_lock(&info->bl_mutex);
+	nlevel = info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_LEVEL;
+	mutex_unlock(&info->bl_mutex);
+
+	if (nlevel < 0)
+		nlevel = 0;
+	else if (nlevel < MIN_LEVEL)
+		nlevel = MIN_LEVEL;
+	else if (nlevel > MAX_LEVEL)
+		nlevel = MAX_LEVEL;
+
+	return nlevel;
+}
+
+static int nvidia_bl_update_status(struct backlight_device *bd)
+{
+	struct nvidia_par *par = class_get_devdata(&bd->class_dev);
+	u32 tmp_pcrt, tmp_pmc, fpcontrol;
+	int level;
+
+	if (!par->FlatPanel)
+		return 0;
+
+	if (bd->props->power != FB_BLANK_UNBLANK ||
+	    bd->props->fb_blank != FB_BLANK_UNBLANK)
+		level = 0;
+	else
+		level = bd->props->brightness;
+
+	tmp_pmc = NV_RD32(par->PMC, 0x10F0) & 0x0000FFFF;
+	tmp_pcrt = NV_RD32(par->PCRTC0, 0x081C) & 0xFFFFFFFC;
+	fpcontrol = NV_RD32(par->PRAMDAC, 0x0848) & 0xCFFFFFCC;
+
+	if (level > 0) {
+		tmp_pcrt |= 0x1;
+		tmp_pmc |= (1 << 31); /* backlight bit */
+		tmp_pmc |= nvidia_bl_get_level_brightness(par, level) << 16;
+		fpcontrol |= par->fpSyncs;
+	} else
+		fpcontrol |= 0x20000022;
+
+	NV_WR32(par->PCRTC0, 0x081C, tmp_pcrt);
+	NV_WR32(par->PMC, 0x10F0, tmp_pmc);
+	NV_WR32(par->PRAMDAC, 0x848, fpcontrol);
+
+	return 0;
+}
+
+static int nvidia_bl_get_brightness(struct backlight_device *bd)
+{
+	return bd->props->brightness;
+}
+
+static struct backlight_properties nvidia_bl_data = {
+	.owner		= THIS_MODULE,
+	.get_brightness = nvidia_bl_get_brightness,
+	.update_status	= nvidia_bl_update_status,
+	.max_brightness = (FB_BACKLIGHT_LEVELS - 1),
+};
+
+void nvidia_bl_init(struct nvidia_par *par)
+{
+	struct fb_info *info = pci_get_drvdata(par->pci_dev);
+	struct backlight_device *bd;
+	char name[12];
+
+	if (!par->FlatPanel)
+		return;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	if (!machine_is(powermac) ||
+	    !pmac_has_backlight_type("mnca"))
+		return;
+#endif
+
+	snprintf(name, sizeof(name), "nvidiabl%d", info->node);
+
+	bd = backlight_device_register(name, par, &nvidia_bl_data);
+	if (IS_ERR(bd)) {
+		info->bl_dev = NULL;
+		printk("nvidia: Backlight registration failed\n");
+		goto error;
+	}
+
+	mutex_lock(&info->bl_mutex);
+	info->bl_dev = bd;
+	fb_bl_default_curve(info, 0,
+		0x158 * FB_BACKLIGHT_MAX / MAX_LEVEL,
+		0x534 * FB_BACKLIGHT_MAX / MAX_LEVEL);
+	mutex_unlock(&info->bl_mutex);
+
+	up(&bd->sem);
+	bd->props->brightness = nvidia_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("nvidia: Backlight initialized (%s)\n", name);
+
+	return;
+
+error:
+	return;
+}
+
+void nvidia_bl_exit(struct nvidia_par *par)
+{
+	struct fb_info *info = pci_get_drvdata(par->pci_dev);
+
+#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);
+
+		printk("nvidia: Backlight unloaded\n");
+	}
+	mutex_unlock(&info->bl_mutex);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	mutex_unlock(&pmac_backlight_mutex);
+#endif
+}
diff --git a/drivers/video/nvidia/nv_proto.h b/drivers/video/nvidia/nv_proto.h
index b149a69..6fba656 100644
--- a/drivers/video/nvidia/nv_proto.h
+++ b/drivers/video/nvidia/nv_proto.h
@@ -63,4 +63,14 @@
 			       const struct fb_image *image);
 extern int nvidiafb_sync(struct fb_info *info);
 extern u8 byte_rev[256];
+
+/* in nv_backlight.h */
+#ifdef CONFIG_FB_NVIDIA_BACKLIGHT
+extern void nvidia_bl_init(struct nvidia_par *par);
+extern void nvidia_bl_exit(struct nvidia_par *par);
+#else
+static inline void nvidia_bl_init(struct nvidia_par *par) {}
+static inline void nvidia_bl_exit(struct nvidia_par *par) {}
+#endif
+
 #endif				/* __NV_PROTO_H__ */
diff --git a/drivers/video/nvidia/nvidia.c b/drivers/video/nvidia/nvidia.c
index 093ab99..03a7c1e 100644
--- a/drivers/video/nvidia/nvidia.c
+++ b/drivers/video/nvidia/nvidia.c
@@ -22,6 +22,7 @@
 #include <linux/init.h>
 #include <linux/pci.h>
 #include <linux/console.h>
+#include <linux/backlight.h>
 #ifdef CONFIG_MTRR
 #include <asm/mtrr.h>
 #endif
@@ -29,10 +30,6 @@
 #include <asm/prom.h>
 #include <asm/pci-bridge.h>
 #endif
-#ifdef CONFIG_PMAC_BACKLIGHT
-#include <asm/machdep.h>
-#include <asm/backlight.h>
-#endif
 
 #include "nv_local.h"
 #include "nv_type.h"
@@ -470,75 +467,6 @@
 	.vmode = FB_VMODE_NONINTERLACED
 };
 
-/*
- * Backlight control
- */
-#ifdef CONFIG_PMAC_BACKLIGHT
-
-static int nvidia_backlight_levels[] = {
-	0x158,
-	0x192,
-	0x1c6,
-	0x200,
-	0x234,
-	0x268,
-	0x2a2,
-	0x2d6,
-	0x310,
-	0x344,
-	0x378,
-	0x3b2,
-	0x3e6,
-	0x41a,
-	0x454,
-	0x534,
-};
-
-/* ------------------------------------------------------------------------- *
- *
- * Backlight operations
- *
- * ------------------------------------------------------------------------- */
-
-static int nvidia_set_backlight_enable(int on, int level, void *data)
-{
-	struct nvidia_par *par = data;
-	u32 tmp_pcrt, tmp_pmc, fpcontrol;
-
-	tmp_pmc = NV_RD32(par->PMC, 0x10F0) & 0x0000FFFF;
-	tmp_pcrt = NV_RD32(par->PCRTC0, 0x081C) & 0xFFFFFFFC;
-	fpcontrol = NV_RD32(par->PRAMDAC, 0x0848) & 0xCFFFFFCC;
-
-	if (on && (level > BACKLIGHT_OFF)) {
-		tmp_pcrt |= 0x1;
-		tmp_pmc |= (1 << 31);	// backlight bit
-		tmp_pmc |= nvidia_backlight_levels[level - 1] << 16;
-	}
-
-	if (on)
-		fpcontrol |= par->fpSyncs;
-	else
-		fpcontrol |= 0x20000022;
-
-	NV_WR32(par->PCRTC0, 0x081C, tmp_pcrt);
-	NV_WR32(par->PMC, 0x10F0, tmp_pmc);
-	NV_WR32(par->PRAMDAC, 0x848, fpcontrol);
-
-	return 0;
-}
-
-static int nvidia_set_backlight_level(int level, void *data)
-{
-	return nvidia_set_backlight_enable(1, level, data);
-}
-
-static struct backlight_controller nvidia_backlight_controller = {
-	nvidia_set_backlight_enable,
-	nvidia_set_backlight_level
-};
-
-#endif				/* CONFIG_PMAC_BACKLIGHT */
-
 static void nvidiafb_load_cursor_image(struct nvidia_par *par, u8 * data8,
 				       u16 bg, u16 fg, u32 w, u32 h)
 {
@@ -1355,10 +1283,15 @@
 	NVWriteSeq(par, 0x01, tmp);
 	NVWriteCrtc(par, 0x1a, vesa);
 
-#ifdef CONFIG_PMAC_BACKLIGHT
-	if (par->FlatPanel && machine_is(powermac)) {
-		set_backlight_enable(!blank);
+#ifdef CONFIG_FB_NVIDIA_BACKLIGHT
+	mutex_lock(&info->bl_mutex);
+	if (info->bl_dev) {
+		down(&info->bl_dev->sem);
+		info->bl_dev->props->power = blank;
+		info->bl_dev->props->update_status(info->bl_dev);
+		up(&info->bl_dev->sem);
 	}
+	mutex_unlock(&info->bl_mutex);
 #endif
 
 	NVTRACE_LEAVE();
@@ -1741,11 +1674,9 @@
 	       "PCI nVidia %s framebuffer (%dMB @ 0x%lX)\n",
 	       info->fix.id,
 	       par->FbMapSize / (1024 * 1024), info->fix.smem_start);
-#ifdef CONFIG_PMAC_BACKLIGHT
-	if (par->FlatPanel && machine_is(powermac))
-		register_backlight_controller(&nvidia_backlight_controller,
-					      par, "mnca");
-#endif
+
+	nvidia_bl_init(par);
+
 	NVTRACE_LEAVE();
 	return 0;
 
@@ -1775,6 +1706,8 @@
 
 	NVTRACE_ENTER();
 
+	nvidia_bl_exit(par);
+
 	unregister_framebuffer(info);
 #ifdef CONFIG_MTRR
 	if (par->mtrr.vram_valid)
diff --git a/drivers/video/riva/fbdev.c b/drivers/video/riva/fbdev.c
index 3e9308f..d4384ab 100644
--- a/drivers/video/riva/fbdev.c
+++ b/drivers/video/riva/fbdev.c
@@ -41,6 +41,7 @@
 #include <linux/fb.h>
 #include <linux/init.h>
 #include <linux/pci.h>
+#include <linux/backlight.h>
 #ifdef CONFIG_MTRR
 #include <asm/mtrr.h>
 #endif
@@ -272,34 +273,154 @@
 /*
  * Backlight control
  */
+#ifdef CONFIG_FB_RIVA_BACKLIGHT
+/* We do not have any information about which values are allowed, thus
+ * we used safe values.
+ */
+#define MIN_LEVEL 0x158
+#define MAX_LEVEL 0x534
+
+static struct backlight_properties riva_bl_data;
+
+static int riva_bl_get_level_brightness(struct riva_par *par,
+		int level)
+{
+	struct fb_info *info = pci_get_drvdata(par->pdev);
+	int nlevel;
+
+	/* Get and convert the value */
+	mutex_lock(&info->bl_mutex);
+	nlevel = info->bl_curve[level] * FB_BACKLIGHT_MAX / MAX_LEVEL;
+	mutex_unlock(&info->bl_mutex);
+
+	if (nlevel < 0)
+		nlevel = 0;
+	else if (nlevel < MIN_LEVEL)
+		nlevel = MIN_LEVEL;
+	else if (nlevel > MAX_LEVEL)
+		nlevel = MAX_LEVEL;
+
+	return nlevel;
+}
+
+static int riva_bl_update_status(struct backlight_device *bd)
+{
+	struct riva_par *par = class_get_devdata(&bd->class_dev);
+	U032 tmp_pcrt, tmp_pmc;
+	int level;
+
+	if (bd->props->power != FB_BLANK_UNBLANK ||
+	    bd->props->fb_blank != FB_BLANK_UNBLANK)
+		level = 0;
+	else
+		level = bd->props->brightness;
+
+	tmp_pmc = par->riva.PMC[0x10F0/4] & 0x0000FFFF;
+	tmp_pcrt = par->riva.PCRTC0[0x081C/4] & 0xFFFFFFFC;
+	if(level > 0) {
+		tmp_pcrt |= 0x1;
+		tmp_pmc |= (1 << 31); /* backlight bit */
+		tmp_pmc |= riva_bl_get_level_brightness(par, level) << 16; /* level */
+	}
+	par->riva.PCRTC0[0x081C/4] = tmp_pcrt;
+	par->riva.PMC[0x10F0/4] = tmp_pmc;
+
+	return 0;
+}
+
+static int riva_bl_get_brightness(struct backlight_device *bd)
+{
+	return bd->props->brightness;
+}
+
+static struct backlight_properties riva_bl_data = {
+	.owner    = THIS_MODULE,
+	.get_brightness = riva_bl_get_brightness,
+	.update_status	= riva_bl_update_status,
+	.max_brightness = (FB_BACKLIGHT_LEVELS - 1),
+};
+
+static void riva_bl_init(struct riva_par *par)
+{
+	struct fb_info *info = pci_get_drvdata(par->pdev);
+	struct backlight_device *bd;
+	char name[12];
+
+	if (!par->FlatPanel)
+		return;
+
 #ifdef CONFIG_PMAC_BACKLIGHT
+	if (!machine_is(powermac) ||
+	    !pmac_has_backlight_type("mnca"))
+		return;
+#endif
 
-static int riva_backlight_levels[] = {
-    0x158,
-    0x192,
-    0x1c6,
-    0x200,
-    0x234,
-    0x268,
-    0x2a2,
-    0x2d6,
-    0x310,
-    0x344,
-    0x378,
-    0x3b2,
-    0x3e6,
-    0x41a,
-    0x454,
-    0x534,
-};
+	snprintf(name, sizeof(name), "rivabl%d", info->node);
 
-static int riva_set_backlight_enable(int on, int level, void *data);
-static int riva_set_backlight_level(int level, void *data);
-static struct backlight_controller riva_backlight_controller = {
-	riva_set_backlight_enable,
-	riva_set_backlight_level
-};
-#endif /* CONFIG_PMAC_BACKLIGHT */
+	bd = backlight_device_register(name, par, &riva_bl_data);
+	if (IS_ERR(bd)) {
+		info->bl_dev = NULL;
+		printk("riva: Backlight registration failed\n");
+		goto error;
+	}
+
+	mutex_lock(&info->bl_mutex);
+	info->bl_dev = bd;
+	fb_bl_default_curve(info, 0,
+		0x158 * FB_BACKLIGHT_MAX / MAX_LEVEL,
+		0x534 * FB_BACKLIGHT_MAX / MAX_LEVEL);
+	mutex_unlock(&info->bl_mutex);
+
+	up(&bd->sem);
+	bd->props->brightness = riva_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("riva: Backlight initialized (%s)\n", name);
+
+	return;
+
+error:
+	return;
+}
+
+static void riva_bl_exit(struct riva_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);
+
+		printk("riva: Backlight unloaded\n");
+	}
+	mutex_unlock(&info->bl_mutex);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	mutex_unlock(&pmac_backlight_mutex);
+#endif
+}
+#else
+static inline void riva_bl_init(struct riva_par *par) {}
+static inline void riva_bl_exit(struct riva_par *par) {}
+#endif /* CONFIG_FB_RIVA_BACKLIGHT */
 
 /* ------------------------------------------------------------------------- *
  *
@@ -973,36 +1094,6 @@
 
 /* ------------------------------------------------------------------------- *
  *
- * Backlight operations
- *
- * ------------------------------------------------------------------------- */
-
-#ifdef CONFIG_PMAC_BACKLIGHT
-static int riva_set_backlight_enable(int on, int level, void *data)
-{
-	struct riva_par *par = data;
-	U032 tmp_pcrt, tmp_pmc;
-
-	tmp_pmc = par->riva.PMC[0x10F0/4] & 0x0000FFFF;
-	tmp_pcrt = par->riva.PCRTC0[0x081C/4] & 0xFFFFFFFC;
-	if(on && (level > BACKLIGHT_OFF)) {
-		tmp_pcrt |= 0x1;
-		tmp_pmc |= (1 << 31); // backlight bit
-		tmp_pmc |= riva_backlight_levels[level-1] << 16; // level
-	}
-	par->riva.PCRTC0[0x081C/4] = tmp_pcrt;
-	par->riva.PMC[0x10F0/4] = tmp_pmc;
-	return 0;
-}
-
-static int riva_set_backlight_level(int level, void *data)
-{
-	return riva_set_backlight_enable(1, level, data);
-}
-#endif /* CONFIG_PMAC_BACKLIGHT */
-
-/* ------------------------------------------------------------------------- *
- *
  * framebuffer operations
  *
  * ------------------------------------------------------------------------- */
@@ -1247,10 +1338,15 @@
 	SEQout(par, 0x01, tmp);
 	CRTCout(par, 0x1a, vesa);
 
-#ifdef CONFIG_PMAC_BACKLIGHT
-	if ( par->FlatPanel && machine_is(powermac)) {
-		set_backlight_enable(!blank);
+#ifdef CONFIG_FB_RIVA_BACKLIGHT
+	mutex_lock(&info->bl_mutex);
+	if (info->bl_dev) {
+		down(&info->bl_dev->sem);
+		info->bl_dev->props->power = blank;
+		info->bl_dev->props->update_status(info->bl_dev);
+		up(&info->bl_dev->sem);
 	}
+	mutex_unlock(&info->bl_mutex);
 #endif
 
 	NVTRACE_LEAVE();
@@ -2037,11 +2133,9 @@
 		RIVAFB_VERSION,
 		info->fix.smem_len / (1024 * 1024),
 		info->fix.smem_start);
-#ifdef CONFIG_PMAC_BACKLIGHT
-	if (default_par->FlatPanel && machine_is(powermac))
-		register_backlight_controller(&riva_backlight_controller,
-					      default_par, "mnca");
-#endif
+
+	riva_bl_init(info->par);
+
 	NVTRACE_LEAVE();
 	return 0;
 
@@ -2074,6 +2168,8 @@
 	
 	NVTRACE_ENTER();
 
+	riva_bl_exit(par);
+
 #ifdef CONFIG_FB_RIVA_I2C
 	riva_delete_i2c_busses(par);
 	kfree(par->EDID);