diff --git a/src/i830.h b/src/i830.h index 57f0544..17d2fe2 100644 --- a/src/i830.h +++ b/src/i830.h @@ -295,6 +295,57 @@ enum last_3d { LAST_3D_ROTATION }; +/* + * Backlight control has some unfortunate properties: + * - many machines won't give us brightness change notifications + * o brightness hotkeys + * o events like AC plug/unplug (can be controlled via _DOS setting) + * o ambient light sensor triggered changes + * - some machines use the so-called "legacy" backlight interface + * o resulting brightness is a combo of LBB and PWM values + * o LBB sits in config space + * - some machines have ACPI methods for changing brightness + * o one of the few ways the X server and firmware can stay in sync + * - new machines have the IGD OpRegion interface available + * o a reliable way of keeping the firmware and X in sync + * + * So the real problem is on machines where ACPI or OpRegion methods aren't + * available. In that case, problems can occur: + * 1) the BIOS and X will have different ideas of what the brightness is, + * leading to unexpected results when the brightness is increased or + * decreased via hotkey or X protocol + * 2) unless X takes the legacy register into account, machines using it + * may prevent X from raising the brightness above 0 if the firmware + * set LBB to 0 + * Given these problems, we provide the user with a selection of methods, + * so they can choose an ideal one for their platform (assuming our quirk + * code picks the wrong one). + * + * Four different methods are available: + * NATIVE: only ever touch the native backlight control registers + * This method may be susceptible to problem (2) above if the firmware + * modifies the legacy registers. + * LEGACY: only ever touch the legacy backlight control registers + * This method may be susceptible to problem (1) above if the firmware + * also modifies the legacy registers. + * COMBO: try to use both sets + * In this case, the driver will try to modify both sets of registers + * if needed. To avoid problem (2) above it may set the LBB register + * to a non-zero value if the brightness is to be increased. It's still + * susceptible to problem (1), but to a lesser extent than the LEGACY only + * method. + * KERNEL: use kernel methods for controlling the backlight + * This is only available on some platforms, but where present this can + * provide the best user experience. + */ + +enum backlight_control { + NATIVE = 0, + LEGACY, + COMBO, + KERNEL, +}; + typedef struct _I830Rec { unsigned char *MMIOBase; unsigned char *GTTBase; @@ -498,6 +549,8 @@ typedef struct _I830Rec { int ddc2; + enum backlight_control backlight_control_method; + CARD32 saveDSPACNTR; CARD32 saveDSPBCNTR; CARD32 savePIPEACONF; diff --git a/src/i830_lvds.c b/src/i830_lvds.c index 0b6b192..93da468 100644 --- a/src/i830_lvds.c +++ b/src/i830_lvds.c @@ -29,6 +29,13 @@ #include "config.h" #endif +#include +#include +#include +#include +#include +#include + #include "xf86.h" #include "i830.h" #include "i830_bios.h" @@ -44,67 +51,111 @@ struct i830_lvds_priv { /* restore backlight to this value */ int backlight_duty_cycle; + + void (*set_backlight)(xf86OutputPtr output, int level); + int (*get_backlight)(xf86OutputPtr output); + int backlight_max; }; -/** - * Use legacy backlight controls? - * - * \param pI830 device in question - * - * Returns TRUE if legacy backlight should be used, false otherwise. +#define BACKLIGHT_CLASS "/sys/class/backlight" + +/* + * List of available kernel interfaces in priority order */ -static int -i830_lvds_backlight_legacy(I830Ptr pI830) +static char *backlight_interfaces[] = { + "acpi_video1", + "acpi_video0", + "thinkpad_screen", +}; +/* + * Must be long enough for BACKLIGHT_CLASS + '/' + longest in above table + + * '/' + "max_backlight" + */ +#define BACKLIGHT_PATH_LEN 80 + +static int backlight_index; + +static Bool +i830_kernel_backlight_available(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + struct stat buf; + char path[BACKLIGHT_PATH_LEN]; + int i; + + for (i = 0; i < sizeof(backlight_interfaces); i++) { + sprintf(path, "%s/%s", BACKLIGHT_CLASS, backlight_interfaces[i]); + if (!stat(path, &buf)) { + backlight_index = i; + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "found backlight control " + "method %s\n", path); + return 1; + } + } + + return 0; +} + +/* Try to figure out which backlight control method to use */ +static void +i830_set_lvds_backlight_method(xf86OutputPtr output) { + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); CARD32 blc_pwm_ctl, blc_pwm_ctl2; + enum backlight_control method = NATIVE; /* Default to native */ - /* 965GM+ change the location of the legacy control bit */ - if (IS_I965GM(pI830)) { + /* ...but use combo if LBB is in use */ + if (i830_kernel_backlight_available(output)) { + method = KERNEL; + } else if (IS_I965GM(pI830)) { blc_pwm_ctl2 = INREG(BLC_PWM_CTL2); if (blc_pwm_ctl2 & BLM_LEGACY_MODE2) - return TRUE; + method = COMBO; } else { blc_pwm_ctl = INREG(BLC_PWM_CTL); if (blc_pwm_ctl & BLM_LEGACY_MODE) - return TRUE; + method = COMBO; } - return FALSE; + + + pI830->backlight_control_method = method; } -/** - * Sets the backlight level. - * - * \param level backlight level, from 0 to i830_lvds_get_max_backlight(). +/* + * Native methods */ static void -i830_lvds_set_backlight(xf86OutputPtr output, int level) +i830_lvds_set_backlight_native(xf86OutputPtr output, int level) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); CARD32 blc_pwm_ctl; - if (i830_lvds_backlight_legacy(pI830)) -#if XSERVER_LIBPCIACCESS - pci_device_cfg_write_u8 (pI830->PciInfo, 0xfe, LEGACY_BACKLIGHT_BRIGHTNESS); -#else - pciWriteByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS, 0xfe); -#endif - blc_pwm_ctl = INREG(BLC_PWM_CTL); blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK; OUTREG(BLC_PWM_CTL, blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT)); } -/** - * Returns the maximum level of the backlight duty cycle field. - */ -static CARD32 -i830_lvds_get_max_backlight(xf86OutputPtr output) +static int +i830_lvds_get_backlight_native(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; - I830Ptr pI830 = I830PTR(pScrn); - CARD32 pwm_ctl = INREG(BLC_PWM_CTL); - CARD32 val; + I830Ptr pI830 = I830PTR(pScrn); + CARD32 blc_pwm_ctl; + + blc_pwm_ctl = INREG(BLC_PWM_CTL); + blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK; + return blc_pwm_ctl; +} + +static int +i830_lvds_get_backlight_max_native(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); + CARD32 pwm_ctl = INREG(BLC_PWM_CTL); + int val; if (IS_I965GM(pI830)) { val = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK2) >> @@ -113,15 +164,166 @@ i830_lvds_get_max_backlight(xf86OutputPtr output) val = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK) >> BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; } - + + return val; +} + +/* + * Legacy methods + */ +static void +i830_lvds_set_backlight_legacy(xf86OutputPtr output, int level) +{ + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); + +#if XSERVER_LIBPCIACCESS + pci_device_cfg_write_u8(pI830->PciInfo, level, + LEGACY_BACKLIGHT_BRIGHTNESS); +#else + pciWriteByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS, level); +#endif +} + +static int +i830_lvds_get_backlight_legacy(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); + CARD8 lbb; + +#if XSERVER_LIBPCIACCESS + pci_device_cfg_read_u8(pI830->PciInfo, &lbb, LEGACY_BACKLIGHT_BRIGHTNESS); +#else + lbb = pciReadByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS); +#endif + + return lbb; +} + +/* + * Combo methods + */ +static void +i830_lvds_set_backlight_combo(xf86OutputPtr output, int level) +{ + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); + CARD32 blc_pwm_ctl; + CARD8 lbb; + +#if XSERVER_LIBPCIACCESS + pci_device_cfg_read_u8(pI830->PciInfo, &lbb, LEGACY_BACKLIGHT_BRIGHTNESS); +#else + lbb = pciReadByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS); +#endif /* - * In legacy control mode, backlight value is calculated: - * if (LBB[7:0] != 0xff) - * backlight = BLC_PWM_CTL[15:0] * BPC[7:0] - * else - * backlight = BLC_PWM_CTL[15:0] + * If LBB is zero and we're shooting for a non-zero brightness level, + * we have to increase LBB by at least 1. */ - return val; + if (!lbb && level) { +#if XSERVER_LIBPCIACCESS + pci_device_cfg_write_u8(pI830->PciInfo, 1, + LEGACY_BACKLIGHT_BRIGHTNESS); +#else + pciWriteByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS, 1); +#endif + } + + blc_pwm_ctl = INREG(BLC_PWM_CTL); + blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK; + OUTREG(BLC_PWM_CTL, blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT)); +} + +static int +i830_lvds_get_backlight_combo(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); + CARD32 blc_pwm_ctl; + + blc_pwm_ctl = INREG(BLC_PWM_CTL); + blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK; + return blc_pwm_ctl; +} + +/* + * Kernel methods + */ +static void +i830_lvds_set_backlight_kernel(xf86OutputPtr output, int level) +{ + ScrnInfoPtr pScrn = output->scrn; + char path[BACKLIGHT_PATH_LEN], val[5]; + int fd; + + sprintf(val, "%3d\n", level); + + sprintf(path, "%s/%s/brightness", BACKLIGHT_CLASS, + backlight_interfaces[backlight_index]); + fd = open(path, O_RDWR); + if (fd == -1) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight " + "control\n", path); + return; + } + + write(fd, val, 5); + close(fd); +} + +static int +i830_lvds_get_backlight_kernel(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + char path[BACKLIGHT_PATH_LEN], val[5]; + int fd; + + sprintf(path, "%s/%s/brightness", BACKLIGHT_CLASS, + backlight_interfaces[backlight_index]); + fd = open(path, O_RDWR); + if (fd == -1) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight " + "control\n", path); + return 0; + } + + if (read(fd, val, 5) == -1) + goto out_err; + + close(fd); + return atoi(val); + +out_err: + close(fd); + return 0; +} + +static int +i830_lvds_get_backlight_max_kernel(xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + char path[BACKLIGHT_PATH_LEN], val[5]; + int fd; + + sprintf(path, "%s/%s/max_brightness", BACKLIGHT_CLASS, + backlight_interfaces[backlight_index]); + fd = open(path, O_RDWR); + if (fd == -1) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight " + "control\n", path); + return 0; + } + + if (read(fd, val, 5) == -1) + goto out_err; + + close(fd); + return atoi(val); + +out_err: + close(fd); + return 0; } /** @@ -142,9 +344,9 @@ i830SetLVDSPanelPower(xf86OutputPtr output, Bool on) pp_status = INREG(PP_STATUS); } while ((pp_status & PP_ON) == 0); - i830_lvds_set_backlight(output, dev_priv->backlight_duty_cycle); + dev_priv->set_backlight(output, dev_priv->backlight_duty_cycle); } else { - i830_lvds_set_backlight(output, 0); + dev_priv->set_backlight(output, 0); OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON); do { @@ -179,14 +381,13 @@ i830_lvds_save (xf86OutputPtr output) pI830->savePP_CONTROL = INREG(PP_CONTROL); pI830->savePP_CYCLE = INREG(PP_CYCLE); pI830->saveBLC_PWM_CTL = INREG(BLC_PWM_CTL); - dev_priv->backlight_duty_cycle = (pI830->saveBLC_PWM_CTL & - BACKLIGHT_DUTY_CYCLE_MASK); + dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); /* * If the light is off at server startup, just make it full brightness */ if (dev_priv->backlight_duty_cycle == 0) - dev_priv->backlight_duty_cycle = i830_lvds_get_max_backlight(output); + dev_priv->backlight_duty_cycle = dev_priv->backlight_max; } static void @@ -390,31 +591,40 @@ i830_lvds_destroy (xf86OutputPtr output) #ifdef RANDR_12_INTERFACE #define BACKLIGHT_NAME "BACKLIGHT" static Atom backlight_atom; + +/* + * Backlight control lets the user select how the driver should manage + * backlight changes: using the legacy interface, the native interface, + * or not at all. + */ +#define BACKLIGHT_CONTROL_NAME "BACKLIGHT_CONTROL" +static Atom backlight_control_atom; #endif /* RANDR_12_INTERFACE */ static void i830_lvds_create_resources(xf86OutputPtr output) { #ifdef RANDR_12_INTERFACE + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; - ScrnInfoPtr pScrn = output->scrn; - INT32 range[2]; + INT32 backlight_range[2], backlight_control_range[2]; int data, err; /* Set up the backlight property, which takes effect immediately - * and accepts values only within the range. + * and accepts values only within the backlight_range. * * XXX: Currently, RandR doesn't verify that properties set are - * within the range. + * within the backlight_range. */ backlight_atom = MakeAtom(BACKLIGHT_NAME, sizeof(BACKLIGHT_NAME) - 1, TRUE); - range[0] = 0; - range[1] = i830_lvds_get_max_backlight(output); + backlight_range[0] = 0; + backlight_range[1] = dev_priv->backlight_max; err = RRConfigureOutputProperty(output->randr_output, backlight_atom, - FALSE, TRUE, FALSE, 2, range); + FALSE, TRUE, FALSE, 2, backlight_range); if (err != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "RRConfigureOutputProperty error, %d\n", err); @@ -429,6 +639,30 @@ i830_lvds_create_resources(xf86OutputPtr output) "RRChangeOutputProperty error, %d\n", err); } + /* + * Now setup the control selection property + */ + backlight_control_atom = MakeAtom(BACKLIGHT_CONTROL_NAME, + sizeof(BACKLIGHT_CONTROL_NAME) - 1, TRUE); + backlight_control_range[0] = 0; + backlight_control_range[1] = 4; + err = RRConfigureOutputProperty(output->randr_output, + backlight_control_atom, FALSE, TRUE, FALSE, + 2, backlight_control_range); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "RRConfigureOutputProperty error, %d\n", err); + } + + data = pI830->backlight_control_method; + err = RRChangeOutputProperty(output->randr_output, backlight_control_atom, + XA_INTEGER, 32, PropModeReplace, 1, &data, + FALSE, TRUE); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "RRChangeOutputProperty error, %d\n", err); + } + #endif /* RANDR_12_INTERFACE */ } @@ -437,6 +671,8 @@ static Bool i830_lvds_set_property(xf86OutputPtr output, Atom property, RRPropertyValuePtr value) { + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; @@ -450,15 +686,80 @@ i830_lvds_set_property(xf86OutputPtr output, Atom property, } val = *(INT32 *)value->data; - if (val < 0 || val > i830_lvds_get_max_backlight(output)) + if (val < 0 || val > dev_priv->backlight_max) return FALSE; - if (val != dev_priv->backlight_duty_cycle) - { - i830_lvds_set_backlight(output, val); + if (val != dev_priv->backlight_duty_cycle) { + dev_priv->set_backlight(output, val); dev_priv->backlight_duty_cycle = val; } return TRUE; + } else if (property == backlight_control_atom) { + enum backlight_control val; + Atom backlight_atom; + int backlight_range[2], err, data; + + if (value->type != XA_INTEGER || value->format != 32 || + value->size != 1) + return FALSE; + + val = *(enum backlight_control *)value->data; + if (val < 0 || val > KERNEL) + return FALSE; + + pI830->backlight_control_method = val; + switch (pI830->backlight_control_method) { + case NATIVE: + dev_priv->set_backlight = i830_lvds_set_backlight_native; + dev_priv->get_backlight = i830_lvds_get_backlight_native; + dev_priv->backlight_max = + i830_lvds_get_backlight_max_native(output); + break; + case LEGACY: + dev_priv->set_backlight = i830_lvds_set_backlight_legacy; + dev_priv->get_backlight = i830_lvds_get_backlight_legacy; + dev_priv->backlight_max = 0xff; + break; + case COMBO: + dev_priv->set_backlight = i830_lvds_set_backlight_combo; + dev_priv->get_backlight = i830_lvds_get_backlight_combo; + dev_priv->backlight_max = + i830_lvds_get_backlight_max_native(output); + break; + case KERNEL: + dev_priv->set_backlight = i830_lvds_set_backlight_kernel; + dev_priv->get_backlight = i830_lvds_get_backlight_kernel; + dev_priv->backlight_max = 0; + break; + default: + /* really shouldn't get here given the range check above */ + break; + } + + /* + * Update the backlight atom since the range and value may have changed + */ + backlight_atom = MakeAtom(BACKLIGHT_NAME, sizeof(BACKLIGHT_NAME) - 1, + TRUE); + + backlight_range[0] = 0; + backlight_range[1] = dev_priv->backlight_max; + err = RRConfigureOutputProperty(output->randr_output, backlight_atom, + FALSE, TRUE, FALSE, 2, backlight_range); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "RRConfigureOutputProperty error, %d\n", err); + } + /* Set the current value of the backlight property */ + data = dev_priv->get_backlight(output); + err = RRChangeOutputProperty(output->randr_output, backlight_atom, + XA_INTEGER, 32, PropModeReplace, 1, &data, + FALSE, TRUE); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "RRChangeOutputProperty error, %d\n", err); + } + return TRUE; } return TRUE; @@ -628,6 +929,35 @@ i830_lvds_init(ScrnInfoPtr pScrn) } } + i830_set_lvds_backlight_method(output); + + switch (pI830->backlight_control_method) { + case NATIVE: + dev_priv->set_backlight = i830_lvds_set_backlight_native; + dev_priv->get_backlight = i830_lvds_get_backlight_native; + dev_priv->backlight_max = i830_lvds_get_backlight_max_native(output); + break; + case LEGACY: + dev_priv->set_backlight = i830_lvds_set_backlight_legacy; + dev_priv->get_backlight = i830_lvds_get_backlight_legacy; + dev_priv->backlight_max = 0xff; + break; + case COMBO: + dev_priv->set_backlight = i830_lvds_set_backlight_combo; + dev_priv->get_backlight = i830_lvds_get_backlight_combo; + dev_priv->backlight_max = i830_lvds_get_backlight_max_native(output); + break; + case KERNEL: + dev_priv->set_backlight = i830_lvds_set_backlight_kernel; + dev_priv->get_backlight = i830_lvds_get_backlight_kernel; + dev_priv->backlight_max = i830_lvds_get_backlight_max_kernel(output); + break; + break; + default: + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "bad backlight control method\n"); + break; + } + return; disable_exit: