Subject: Backlight support for radeonfb
To: None <port-macppc@netbsd.org>
From: Johan =?iso-8859-1?Q?Wall=E9n?= <johan.wallen+lists@tkk.fi>
List: port-macppc
Date: 12/17/2006 17:49:43
Hello,

the following patch adds rudimentary backlight support to radeonfb(4).
Some of the Radeon chips found on PowerBooks have, according to the
Linux radeon_backlight driver, the backlight levels negated (higher
levels give less backlight).  You should be able to work around this
using `options RADEONFB_BACKLIGHT_NEGATED', but I have not tested it.
On Radeon Mobility 9700 `wsconsctl -d -w backlight=xxx' seems to
work as expected.

-- Johan

Index: radeonfb.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/radeonfb.c,v
retrieving revision 1.8
diff -u -r1.8 radeonfb.c
--- radeonfb.c	13 Dec 2006 00:19:01 -0000	1.8
+++ radeonfb.c	17 Dec 2006 15:25:00 -0000
@@ -163,6 +163,10 @@
 static void radeonfb_putchar(void *, int, int, unsigned, long);
 static int radeonfb_allocattr(void *, int, int, int, long *);
 
+static int radeonfb_get_backlight(struct radeonfb_display *);
+static int radeonfb_set_backlight(struct radeonfb_display *, int);
+static void radeonfb_lvds_callout(void *);
+
 static struct videomode *radeonfb_best_refresh(struct videomode *,
     struct videomode *);
 static void radeonfb_pickres(struct radeonfb_display *, uint16_t *,
@@ -394,6 +398,8 @@
 	{ RADEON_R420,	{{-1, 0xb01cb}}},
 };
 
+#define RADEONFB_BACKLIGHT_MAX    255  /* Maximum backlight level. */
+
 
 CFATTACH_DECL(radeonfb, sizeof (struct radeonfb_softc),
     radeonfb_match, radeonfb_attach, NULL, NULL);
@@ -866,6 +872,11 @@
 
 		config_found(&sc->sc_dev, &aa, wsemuldisplaydevprint);
 		radeonfb_blank(dp, 0);
+		
+		/* Initialise delayed lvds operations for backlight. */
+		callout_init(&dp->rd_bl_lvds_co);
+		callout_setfunc(&dp->rd_bl_lvds_co,
+				radeonfb_lvds_callout, dp);
 	}
 
 	return;
@@ -888,6 +899,7 @@
 	struct vcons_data	*vd;
 	struct radeonfb_display	*dp;
 	struct radeonfb_softc	*sc;
+	struct wsdisplay_param  *param;
 
 	vd = (struct vcons_data *)v;
 	dp = (struct radeonfb_display *)vd->cookie;
@@ -991,6 +1003,22 @@
 #else
 		return ENODEV;
 #endif
+	case WSDISPLAYIO_GETPARAM:
+		param = (struct wsdisplay_param *)d;
+		if (param->param == WSDISPLAYIO_PARAM_BACKLIGHT) {
+			param->min = 0;
+			param->max = RADEONFB_BACKLIGHT_MAX;
+			param->curval = radeonfb_get_backlight(dp);
+			return 0;
+		}
+		return EPASSTHROUGH;
+
+	case WSDISPLAYIO_SETPARAM:
+		param = (struct wsdisplay_param *)d;
+		if (param->param == WSDISPLAYIO_PARAM_BACKLIGHT) {
+			return radeonfb_set_backlight(dp, param->curval);
+		}
+		return EPASSTHROUGH;
 
 	default:
 		return EPASSTHROUGH;
@@ -3302,3 +3330,114 @@
 		*y = 480;
 	}
 }
+
+
+/* Get the current backlight level for the display.  */
+
+static int 
+radeonfb_get_backlight(struct radeonfb_display *dp)
+{
+	int s;
+	uint32_t level;
+
+	s = spltty();
+
+	level = radeonfb_get32(dp->rd_softc, RADEON_LVDS_GEN_CNTL);
+	level &= RADEON_LVDS_BL_MOD_LEV_MASK;
+	level >>= RADEON_LVDS_BL_MOD_LEV_SHIFT;
+
+	/* 
+	 * On some chips, we should negate the backlight level. 
+	 * XXX Find out on which chips. 
+	 */
+#ifdef RADEONFB_BACKLIGHT_NEGATED
+	level = RADEONFB_BACKLIGHT_MAX - level;
+#endif /* RADEONFB_BACKLIGHT_NEGATED */
+
+	splx(s);
+
+	return level;
+}	
+
+/* Set the backlight to the given level for the display.  */
+
+static int 
+radeonfb_set_backlight(struct radeonfb_display *dp, int level)
+{
+	struct radeonfb_softc *sc;
+	int rlevel, s;
+	uint32_t lvds;
+
+	s = spltty();
+	
+	if (level < 0)
+		level = 0;
+	else if (level >= RADEONFB_BACKLIGHT_MAX)
+		level = RADEONFB_BACKLIGHT_MAX;
+
+	sc = dp->rd_softc;
+
+	/* On some chips, we should negate the backlight level. */
+#ifdef RADEONFB_BACKLIGHT_NEGATED
+	rlevel = RADEONFB_BACKLIGHT_MAX - level;
+#else
+	rlevel = level;
+#endif /* RADEONFB_BACKLIGHT_NEGATED */
+
+	callout_stop(&dp->rd_bl_lvds_co);
+	radeonfb_engine_idle(sc);
+
+	/* 
+	 * Turn off the display if the backlight is set to 0, since the
+	 * display is useless without backlight anyway. 
+	 */
+	if (level == 0)
+		radeonfb_blank(dp, 1);
+	else if (radeonfb_get_backlight(dp) == 0)
+		radeonfb_blank(dp, 0);
+	
+	lvds = radeonfb_get32(sc, RADEON_LVDS_GEN_CNTL);
+	lvds &= ~RADEON_LVDS_DISPLAY_DIS;
+	if (!(lvds & RADEON_LVDS_BLON) || !(lvds & RADEON_LVDS_ON)) {
+		lvds |= dp->rd_bl_lvds_val & RADEON_LVDS_DIGON;
+		lvds |= RADEON_LVDS_BLON | RADEON_LVDS_EN;
+		radeonfb_put32(sc, RADEON_LVDS_GEN_CNTL, lvds);
+		lvds &= ~RADEON_LVDS_BL_MOD_LEV_MASK;
+		lvds |= rlevel << RADEON_LVDS_BL_MOD_LEV_SHIFT;
+		lvds |= RADEON_LVDS_ON;
+		lvds |= dp->rd_bl_lvds_val & RADEON_LVDS_BL_MOD_EN;
+	} else {
+		lvds &= ~RADEON_LVDS_BL_MOD_LEV_MASK;
+		lvds |= rlevel << RADEON_LVDS_BL_MOD_LEV_SHIFT;
+		radeonfb_put32(sc, RADEON_LVDS_GEN_CNTL, lvds);
+	}
+	
+	dp->rd_bl_lvds_val &= ~RADEON_LVDS_STATE_MASK;
+	dp->rd_bl_lvds_val |= lvds & RADEON_LVDS_STATE_MASK;
+	/* XXX What is the correct delay? */
+	callout_schedule(&dp->rd_bl_lvds_co, 200 * hz); 
+
+	splx(s);
+
+	return 0;
+}
+
+/* 
+ * Callout function for delayed operations on the LVDS_GEN_CNTL register. 
+ * Set the delayed bits in the register, and clear the stored delayed
+ * value.
+ */
+
+static void radeonfb_lvds_callout(void *arg)
+{
+	struct radeonfb_display *dp = arg;
+	int s;
+
+	s = splhigh();
+
+	radeonfb_mask32(dp->rd_softc, RADEON_LVDS_GEN_CNTL, ~0, 
+			dp->rd_bl_lvds_val);
+	dp->rd_bl_lvds_val = 0;
+
+	splx(s);
+}
Index: radeonfbreg.h
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/radeonfbreg.h,v
retrieving revision 1.2
diff -u -r1.2 radeonfbreg.h
--- radeonfbreg.h	29 Aug 2006 17:09:33 -0000	1.2
+++ radeonfbreg.h	17 Dec 2006 15:25:03 -0000
@@ -816,6 +816,12 @@
 #       define RADEON_LVDS_DIGON            (1   << 18)
 #       define RADEON_LVDS_BLON             (1   << 19)
 #       define RADEON_LVDS_SEL_CRTC2        (1   << 23)
+#       define RADEON_LVDS_BL_MOD_LEV_MASK  0x0000ff00
+#       define RADEON_LVDS_BL_MOD_LEV_SHIFT 8
+#       define RADEON_LVDS_BL_MOD_EN        (1 << 16)
+#       define RADEON_LVDS_STATE_MASK					      \
+        (RADEON_LVDS_ON | RADEON_LVDS_DISPLAY_DIS |			      \
+         RADEON_LVDS_BL_MOD_LEV_MASK | RADEON_LVDS_BLON)
 #define RADEON_LVDS_PLL_CNTL                0x02d4
 #       define RADEON_HSYNC_DELAY_SHIFT     28
 #       define RADEON_HSYNC_DELAY_MASK      (0xf << 28)
Index: radeonfbvar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/radeonfbvar.h,v
retrieving revision 1.2
diff -u -r1.2 radeonfbvar.h
--- radeonfbvar.h	29 Aug 2006 17:09:33 -0000	1.2
+++ radeonfbvar.h	17 Dec 2006 15:25:03 -0000
@@ -46,6 +46,7 @@
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/device.h>
+#include <sys/callout.h>
 #include <dev/pci/pcivar.h>
 #include <dev/wscons/wsdisplayvar.h>
 #include <dev/wscons/wsconsio.h>
@@ -172,6 +173,9 @@
 	int			rd_bg;		/* background */
 	int			rd_console;
 
+	struct callout          rd_bl_lvds_co;  /* delayed lvds operation */
+	uint32_t                rd_bl_lvds_val; /* value of delayed lvds */
+
 	int			rd_wsmode;
 
 	int			rd_ncrtcs;