/* * Copyright © 2006 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Authors: * Eric Anholt * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "xf86.h" #include "i830.h" #include "i830_bios.h" #include "i830_display.h" #include "X11/Xatom.h" /* * Three panel fitting modes: * CENTER - center image on screen, don't scale * FULL_ASPECT - scale image to fit screen, but preserve aspect ratio * FULL - scale image to fit screen without regard to aspect ratio */ enum pfit_mode { CENTER = 0, FULL_ASPECT, FULL, }; struct i830_lvds_priv { /* The panel is in DPMS off */ Bool dpmsoff; /* restore backlight to this value */ int backlight_duty_cycle; void (*set_backlight)(xf86OutputPtr output, int level); int (*get_backlight)(xf86OutputPtr output); int backlight_max; enum pfit_mode fitting_mode; uint32_t pfit_control; uint32_t pfit_pgm_ratios; }; #define BACKLIGHT_CLASS "/sys/class/backlight" /* * List of available kernel interfaces in priority order */ static char *backlight_interfaces[] = { "asus-laptop", "eeepc", "thinkpad_screen", "acpi_video1", "acpi_video0", "fujitsu-laptop", NULL, }; /* * Must be long enough for BACKLIGHT_CLASS + '/' + longest in above table + * '/' + "max_backlight" */ #define BACKLIGHT_PATH_LEN 80 /* Enough for 8 digits of backlight + '\n' + '\0' */ #define BACKLIGHT_VALUE_LEN 10 static int backlight_index; enum lid_status { LID_UNKNOWN = -1, LID_OPEN, LID_CLOSE, }; #define ACPI_BUTTON "/proc/acpi/button/" #define ACPI_LID "/proc/acpi/button/lid/" 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; backlight_interfaces[i] != NULL; 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); uint32_t blc_pwm_ctl, blc_pwm_ctl2; enum backlight_control method = BCM_NATIVE; /* Default to native */ if (i830_kernel_backlight_available(output)) { method = BCM_KERNEL; } else if (IS_I965GM(pI830) || IS_GM45(pI830)) { blc_pwm_ctl2 = INREG(BLC_PWM_CTL2); if (blc_pwm_ctl2 & BLM_LEGACY_MODE2) method = BCM_COMBO; } else { blc_pwm_ctl = INREG(BLC_PWM_CTL); if (blc_pwm_ctl & BLM_LEGACY_MODE) method = BCM_COMBO; } pI830->backlight_control_method = method; } /* * Native methods */ static void i830_lvds_set_backlight_native(xf86OutputPtr output, int level) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); uint32_t blc_pwm_ctl; 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_native(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); uint32_t 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); uint32_t pwm_ctl = INREG(BLC_PWM_CTL); int val; if (IS_I965GM(pI830) || IS_GM45(pI830)) { val = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK2) >> BACKLIGHT_MODULATION_FREQ_SHIFT2); } else { 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); uint8_t 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); uint32_t blc_pwm_ctl; uint8_t lbb; #if XSERVER_LIBPCIACCESS pci_device_cfg_read_u8(pI830->PciInfo, &lbb, LEGACY_BACKLIGHT_BRIGHTNESS); #else lbb = pciReadByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS); #endif /* * If LBB is zero and we're shooting for a non-zero brightness level, * we have to increase LBB by at least 1. */ 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 } /* * Don't set the lowest bit in combo configs since it can act as a flag for * max brightness. */ level <<= 1; 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); uint32_t blc_pwm_ctl; blc_pwm_ctl = INREG(BLC_PWM_CTL); blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK; /* Since we don't use the low bit when using combo, the value is halved */ return blc_pwm_ctl >> 1; } static int i830_lvds_get_backlight_max_combo(xf86OutputPtr output) { /* Since we don't set the low bit when using combo, the range is halved */ return i830_lvds_get_backlight_max_native(output) >> 1; } /* * Kernel methods */ static void i830_lvds_set_backlight_kernel(xf86OutputPtr output, int level) { ScrnInfoPtr pScrn = output->scrn; char path[BACKLIGHT_PATH_LEN], val[BACKLIGHT_VALUE_LEN]; int fd, len, ret; len = snprintf(val, BACKLIGHT_VALUE_LEN, "%d\n", level); if (len > BACKLIGHT_VALUE_LEN) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "backlight value too large: %d\n", level); return; } 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: %s\n", path, strerror(errno)); return; } ret = write(fd, val, len); if (ret == -1) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "write to %s for backlight " "control failed: %s\n", path, strerror(errno)); } close(fd); } static int i830_lvds_get_backlight_kernel(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; char path[BACKLIGHT_PATH_LEN], val[BACKLIGHT_VALUE_LEN]; int fd; sprintf(path, "%s/%s/actual_brightness", BACKLIGHT_CLASS, backlight_interfaces[backlight_index]); fd = open(path, O_RDONLY); if (fd == -1) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight " "control: %s\n", path, strerror(errno)); return 0; } memset(val, 0, sizeof(val)); if (read(fd, val, BACKLIGHT_VALUE_LEN) == -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[BACKLIGHT_VALUE_LEN]; int fd, max = 0; sprintf(path, "%s/%s/max_brightness", BACKLIGHT_CLASS, backlight_interfaces[backlight_index]); fd = open(path, O_RDONLY); if (fd == -1) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight " "control: %s\n", path, strerror(errno)); return 0; } if (read(fd, val, BACKLIGHT_VALUE_LEN) == -1) goto out_err; close(fd); max = atoi(val); return max; out_err: close(fd); return 0; } /** * Get lid state from ACPI button driver */ static int i830_lvds_acpi_lid_open(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); int fd; DIR *button_dir; DIR *lid_dir; struct dirent *lid_dent; char *state_name; char state[64]; enum lid_status ret = LID_UNKNOWN; if (pI830->quirk_flag & QUIRK_BROKEN_ACPI_LID) goto out; button_dir = opendir(ACPI_BUTTON); /* If acpi button driver is not loaded, bypass ACPI check method */ if (button_dir == NULL) goto out; closedir(button_dir); lid_dir = opendir(ACPI_LID); /* no acpi lid object found */ if (lid_dir == NULL) goto out; while (1) { lid_dent = readdir(lid_dir); if (lid_dent == NULL) { /* no LID object */ closedir(lid_dir); goto out; } if (strcmp(lid_dent->d_name, ".") && strcmp(lid_dent->d_name, "..")) { break; } } state_name = malloc(strlen(ACPI_LID) + strlen(lid_dent->d_name) + 7); memset(state_name, 0, sizeof(state_name)); strcat(state_name, ACPI_LID); strcat(state_name, lid_dent->d_name); strcat(state_name, "/state"); closedir(lid_dir); if ((fd = open(state_name, O_RDONLY)) == -1) { free(state_name); goto out; } free(state_name); if (read(fd, state, 64) == -1) { close(fd); goto out; } close(fd); if (strstr(state, "open")) ret = LID_OPEN; else if (strstr(state, "closed")) ret = LID_CLOSE; else /* "unsupported" */ ret = LID_UNKNOWN; out: if (pI830->debug_modes && (ret != LID_UNKNOWN)) xf86DrvMsg(pScrn->scrnIndex, X_INFO, "LID switch detect %s with ACPI button\n", ret ? "closed" : "open"); return ret; } /** * Get LID switch close state from SWF */ static Bool i830_lvds_swf_lid_close(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); uint32_t swf14 = INREG(SWF14); Bool ret; if (swf14 & SWF14_LID_SWITCH_EN) ret = TRUE; else ret = FALSE; if (pI830->debug_modes) xf86DrvMsg(pScrn->scrnIndex, X_INFO, "LID switch detect %s with SWF14 0x%8x\n", ret ? "closed" : "open", swf14); return ret; } /** * Sets the power state for the panel. */ static void i830SetLVDSPanelPower(xf86OutputPtr output, Bool on) { I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); uint32_t pp_status; if (on) { /* if we're going from on->on, be aware to current level. */ if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); /* * If we're going from off->on we may need to turn on the backlight. * We should use the saved value whenever possible, but on some * machines 0 is a valid backlight value (due to an external backlight * controller for example), so on them, when turning LVDS back on, * they'll always re-maximize the brightness. */ if (!(INREG(PP_CONTROL) & POWER_TARGET_ON) && dev_priv->backlight_duty_cycle == 0 && pI830->backlight_control_method < BCM_KERNEL) dev_priv->backlight_duty_cycle = dev_priv->backlight_max; OUTREG(PP_CONTROL, INREG(PP_CONTROL) | POWER_TARGET_ON); do { pp_status = INREG(PP_STATUS); } while ((pp_status & PP_ON) == 0); dev_priv->set_backlight(output, dev_priv->backlight_duty_cycle); dev_priv->dpmsoff = FALSE; } else { /* * Only save the current backlight value if we're going from * on to off. */ if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); dev_priv->set_backlight(output, 0); OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON); do { pp_status = INREG(PP_STATUS); } while (pp_status & PP_ON); dev_priv->dpmsoff = TRUE; } } static void i830_lvds_dpms (xf86OutputPtr output, int mode) { if (mode == DPMSModeOn) i830SetLVDSPanelPower(output, TRUE); else i830SetLVDSPanelPower(output, FALSE); /* XXX: We never power down the LVDS pairs. */ } static void i830_lvds_save (xf86OutputPtr output) { I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); if (IS_I965GM(pI830) || IS_GM45(pI830)) pI830->saveBLC_PWM_CTL2 = INREG(BLC_PWM_CTL2); pI830->savePP_ON = INREG(PP_ON_DELAYS); pI830->savePP_OFF = INREG(PP_OFF_DELAYS); pI830->savePP_CONTROL = INREG(PP_CONTROL); pI830->savePP_DIVISOR = INREG(PP_DIVISOR); pI830->saveBLC_PWM_CTL = INREG(BLC_PWM_CTL); if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); } static void i830_lvds_restore(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); if (IS_I965GM(pI830) || IS_GM45(pI830)) OUTREG(BLC_PWM_CTL2, pI830->saveBLC_PWM_CTL2); OUTREG(BLC_PWM_CTL, pI830->saveBLC_PWM_CTL); OUTREG(PP_ON_DELAYS, pI830->savePP_ON); OUTREG(PP_OFF_DELAYS, pI830->savePP_OFF); OUTREG(PP_DIVISOR, pI830->savePP_DIVISOR); OUTREG(PP_CONTROL, pI830->savePP_CONTROL); if (pI830->savePP_CONTROL & POWER_TARGET_ON) i830SetLVDSPanelPower(output, TRUE); else i830SetLVDSPanelPower(output, FALSE); } static int i830_lvds_mode_valid(xf86OutputPtr output, DisplayModePtr pMode) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); DisplayModePtr pFixedMode = pI830->lvds_fixed_mode; if (pFixedMode) { if (pMode->HDisplay > pFixedMode->HDisplay) return MODE_PANEL; if (pMode->VDisplay > pFixedMode->VDisplay) return MODE_PANEL; } return MODE_OK; } static Bool i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode) { I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; ScrnInfoPtr pScrn = output->scrn; xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn); I830CrtcPrivatePtr intel_crtc = output->crtc->driver_private; I830Ptr pI830 = I830PTR(pScrn); uint32_t pfit_control = 0, pfit_pgm_ratios = 0; float panel_ratio, desired_ratio, vert_scale, horiz_scale; float horiz_ratio, vert_ratio; int left_border = 0, right_border = 0, top_border = 0, bottom_border = 0; int i; Bool border = 0; for (i = 0; i < xf86_config->num_output; i++) { xf86OutputPtr other_output = xf86_config->output[i]; if (other_output != output && other_output->crtc == output->crtc) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Can't enable LVDS and another output on the same " "pipe\n"); return FALSE; } } if (intel_crtc->pipe == 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Can't support LVDS on pipe A\n"); return FALSE; } /* If we don't have a panel mode there's not much we can do */ if (pI830->lvds_fixed_mode == NULL) return TRUE; /* If we have timings from the BIOS for the panel, put them in * to the adjusted mode. The CRTC will be set up for this mode, * with the panel scaling set up to source from the H/VDisplay * of the original mode. */ adjusted_mode->HDisplay = pI830->lvds_fixed_mode->HDisplay; adjusted_mode->HSyncStart = pI830->lvds_fixed_mode->HSyncStart; adjusted_mode->HSyncEnd = pI830->lvds_fixed_mode->HSyncEnd; adjusted_mode->HTotal = pI830->lvds_fixed_mode->HTotal; adjusted_mode->VDisplay = pI830->lvds_fixed_mode->VDisplay; adjusted_mode->VSyncStart = pI830->lvds_fixed_mode->VSyncStart; adjusted_mode->VSyncEnd = pI830->lvds_fixed_mode->VSyncEnd; adjusted_mode->VTotal = pI830->lvds_fixed_mode->VTotal; adjusted_mode->Clock = pI830->lvds_fixed_mode->Clock; xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V); /* Make sure pre-965s set dither correctly */ if (!IS_I965G(pI830) && pI830->lvds_dither) pfit_control |= PANEL_8TO6_DITHER_ENABLE; /* Native modes don't need fitting */ if (adjusted_mode->HDisplay == mode->HDisplay && adjusted_mode->VDisplay == mode->VDisplay) { pfit_pgm_ratios = 0; border = 0; goto out; } /* 965+ wants fuzzy fitting */ if (IS_I965G(pI830)) pfit_control |= (intel_crtc->pipe << PFIT_PIPE_SHIFT) | PFIT_FILTER_FUZZY; /* * Deal with panel fitting options. Figure out how to stretch the image * based on its aspect ratio & the current panel fitting mode. */ panel_ratio = (float)adjusted_mode->HDisplay / (float)adjusted_mode->VDisplay; desired_ratio = (float)mode->HDisplay / (float)mode->VDisplay; /* * Enable automatic panel scaling for non-native modes so that they fill * the screen. Should be enabled before the pipe is enabled, according to * register description and PRM. */ /* Change the value here to see the borders for debugging */ OUTREG(BCLRPAT_A, 0); OUTREG(BCLRPAT_B, 0); switch (dev_priv->fitting_mode) { case CENTER: /* * For centered modes, we have to calculate border widths & heights and * modify the values programmed into the CRTC. Also need to make sure * LVDS borders are enabled (see i830_display.c). */ left_border = (pI830->lvds_fixed_mode->HDisplay - mode->HDisplay) / 2; right_border = left_border; if (mode->HDisplay & 1) right_border++; top_border = (pI830->lvds_fixed_mode->VDisplay - mode->VDisplay) / 2; bottom_border = top_border; if (mode->VDisplay & 1) bottom_border++; /* Set active & border values */ adjusted_mode->CrtcHDisplay = mode->HDisplay; adjusted_mode->CrtcHBlankStart = mode->HDisplay + right_border - 1; adjusted_mode->CrtcHBlankEnd = adjusted_mode->CrtcHTotal - left_border - 1; adjusted_mode->CrtcHSyncStart = adjusted_mode->CrtcHBlankStart; adjusted_mode->CrtcHSyncEnd = adjusted_mode->CrtcHBlankEnd; adjusted_mode->CrtcVDisplay = mode->VDisplay; adjusted_mode->CrtcVBlankStart = mode->VDisplay + bottom_border - 1; adjusted_mode->CrtcVBlankEnd = adjusted_mode->CrtcVTotal - top_border - 1; adjusted_mode->CrtcVSyncStart = adjusted_mode->CrtcVBlankStart; adjusted_mode->CrtcVSyncEnd = adjusted_mode->CrtcVBlankEnd; border = 1; break; case FULL_ASPECT: /* Scale but preserve aspect ratio */ pfit_control |= PFIT_ENABLE; if (IS_I965G(pI830)) { /* * 965+ is easy, it does everything in hw */ if (panel_ratio > desired_ratio) pfit_control |= PFIT_SCALING_PILLAR; else if (panel_ratio < desired_ratio) pfit_control |= PFIT_SCALING_LETTER; else pfit_control |= PFIT_SCALING_AUTO; } else { /* * For earlier chips we have to calculate the scaling ratio * by hand and program it into the PFIT_PGM_RATIOS reg. */ uint32_t horiz_bits, vert_bits, bits = 12; horiz_ratio = ((float)mode->HDisplay) / ((float)adjusted_mode->HDisplay); vert_ratio = ((float)mode->VDisplay) / ((float)adjusted_mode->VDisplay); horiz_scale = ((float)adjusted_mode->HDisplay) / ((float)mode->HDisplay); vert_scale = ((float)adjusted_mode->VDisplay) / ((float)mode->VDisplay); /* Retain aspect ratio */ if (panel_ratio > desired_ratio) { /* Pillar */ unsigned long scaled_width = (float)mode->HDisplay * vert_scale; horiz_ratio = vert_ratio; pfit_control |= VERT_AUTO_SCALE | VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; /* Pillar will have left/right borders */ left_border = (pI830->lvds_fixed_mode->HDisplay - scaled_width) / 2; right_border = left_border; if (mode->HDisplay & 1) /* odd resolutions */ right_border++; adjusted_mode->CrtcHDisplay = scaled_width; adjusted_mode->CrtcHBlankStart = scaled_width + right_border - 1; adjusted_mode->CrtcHBlankEnd = adjusted_mode->CrtcHTotal - left_border - 1; adjusted_mode->CrtcHSyncStart = adjusted_mode->CrtcHBlankStart; adjusted_mode->CrtcHSyncEnd = adjusted_mode->CrtcHBlankEnd; border = 1; } else if (panel_ratio < desired_ratio) { /* Letter */ unsigned long scaled_height = (float)mode->VDisplay * horiz_scale; vert_ratio = horiz_ratio; pfit_control |= HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; /* Letterbox will have top/bottom borders */ top_border = (pI830->lvds_fixed_mode->VDisplay - scaled_height) / 2; bottom_border = top_border; if (mode->VDisplay & 1) bottom_border++; adjusted_mode->CrtcVDisplay = scaled_height; adjusted_mode->CrtcVBlankStart = scaled_height + bottom_border - 1; adjusted_mode->CrtcVBlankEnd = adjusted_mode->CrtcVTotal - top_border - 1; adjusted_mode->CrtcVSyncStart = adjusted_mode->CrtcVBlankStart; adjusted_mode->CrtcVSyncEnd = adjusted_mode->CrtcVBlankEnd; border = 1; } else { /* Aspects match, let hw scale both directions */ pfit_control |= VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; } horiz_bits = 0.5 + (1 << bits) * horiz_ratio; vert_bits = 0.5 + (1 << bits) * vert_ratio; pfit_pgm_ratios = (((vert_bits << PFIT_VERT_SCALE_SHIFT) & PFIT_VERT_SCALE_MASK) | ((horiz_bits << PFIT_HORIZ_SCALE_SHIFT) & PFIT_HORIZ_SCALE_MASK)); } break; case FULL: /* * Full scaling, even if it changes the aspect ratio. Fortunately * this is all done for us in hw. */ pfit_control |= PFIT_ENABLE; if (IS_I965G(pI830)) pfit_control |= PFIT_SCALING_AUTO; else pfit_control |= VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; break; default: /* shouldn't happen */ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "error: bad fitting mode\n"); break; } out: dev_priv->pfit_control = pfit_control; dev_priv->pfit_pgm_ratios = pfit_pgm_ratios; if (border) intel_output->lvds_bits |= LVDS_BORDER_ENABLE; else intel_output->lvds_bits &= ~LVDS_BORDER_ENABLE; /* XXX: It would be nice to support lower refresh rates on the * panels to reduce power consumption, and perhaps match the * user's requested refresh rate. */ return TRUE; } static void i830_lvds_prepare(xf86OutputPtr output) { i830_lvds_dpms(output, DPMSModeOff); } static void i830_lvds_mode_set(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode) { I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); /* * PFIT must be enabled/disabled while LVDS is on but pipes are still off */ OUTREG(PFIT_PGM_RATIOS, dev_priv->pfit_pgm_ratios); OUTREG(PFIT_CONTROL, dev_priv->pfit_control); } /** * Detect the LVDS connection. */ static xf86OutputStatus i830_lvds_detect(xf86OutputPtr output) { enum lid_status lid; lid = i830_lvds_acpi_lid_open(output); if (lid == LID_OPEN) return XF86OutputStatusConnected; else if (lid == LID_CLOSE) return XF86OutputStatusDisconnected; if (i830_lvds_swf_lid_close(output)) return XF86OutputStatusDisconnected; return XF86OutputStatusConnected; } /** * Return the list of DDC modes if available, or the BIOS fixed mode otherwise. */ static DisplayModePtr i830_lvds_get_modes(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; xf86MonPtr edid_mon; DisplayModePtr modes; edid_mon = xf86OutputGetEDID (output, intel_output->pDDCBus); xf86OutputSetEDID (output, edid_mon); modes = xf86OutputGetEDIDModes (output); if (modes != NULL) return modes; if (!output->MonInfo) { edid_mon = xcalloc (1, sizeof (xf86Monitor)); if (edid_mon) { /* Set wide sync ranges so we get all modes * handed to valid_mode for checking */ edid_mon->det_mon[0].type = DS_RANGES; edid_mon->det_mon[0].section.ranges.min_v = 0; edid_mon->det_mon[0].section.ranges.max_v = 200; edid_mon->det_mon[0].section.ranges.min_h = 0; edid_mon->det_mon[0].section.ranges.max_h = 200; output->MonInfo = edid_mon; } } if (pI830->lvds_fixed_mode != NULL) return xf86DuplicateMode(pI830->lvds_fixed_mode); return NULL; } static void i830_lvds_destroy (xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; xf86DeleteMode (&pI830->lvds_fixed_mode, pI830->lvds_fixed_mode); if (intel_output) xfree (intel_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" #define NUM_BACKLIGHT_CONTROL_METHODS 4 static char *backlight_control_names[] = { "native", "legacy", "combination", "kernel", }; static Atom backlight_control_atom; static Atom backlight_control_name_atoms[NUM_BACKLIGHT_CONTROL_METHODS]; #define PANEL_FITTING_NAME "PANEL_FITTING" #define NUM_PANEL_FITTING_TYPES 3 static char *panel_fitting_names[] = { "center", "full_aspect", "full", }; static Atom panel_fitting_atom; static Atom panel_fitting_name_atoms[NUM_PANEL_FITTING_TYPES]; static int i830_backlight_control_lookup(char *name) { int i; for (i = 0; i < NUM_BACKLIGHT_CONTROL_METHODS; i++) if (!strcmp(name, backlight_control_names[i])) return i; return -1; } static Bool i830_lvds_set_backlight_control(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; switch (pI830->backlight_control_method) { case BCM_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 BCM_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 BCM_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_combo(output); break; case BCM_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; default: /* * Should be impossible to get here unless the caller set a bogus * backlight_control_method */ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "error: bad backlight control " "method\n"); break; } return Success; } static int i830_panel_fitting_lookup(char *name) { int i; for (i = 0; i < NUM_PANEL_FITTING_TYPES; i++) if (!strcmp(name, panel_fitting_names[i])) return i; return -1; } #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; INT32 backlight_range[2]; int data, err, i; /* Set up the backlight property, which takes effect immediately * and accepts values only within the backlight_range. * * XXX: Currently, RandR doesn't verify that properties set are * within the backlight_range. */ 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->backlight_duty_cycle; 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); } /* * Now setup the control selection property */ backlight_control_atom = MakeAtom(BACKLIGHT_CONTROL_NAME, sizeof(BACKLIGHT_CONTROL_NAME) - 1, TRUE); for (i = 0; i < NUM_BACKLIGHT_CONTROL_METHODS; i++) { backlight_control_name_atoms[i] = MakeAtom(backlight_control_names[i], strlen(backlight_control_names[i]), TRUE); } err = RRConfigureOutputProperty(output->randr_output, backlight_control_atom, TRUE, FALSE, FALSE, NUM_BACKLIGHT_CONTROL_METHODS, (INT32 *)backlight_control_name_atoms); if (err != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "RRConfigureOutputProperty error, %d\n", err); } err = RRChangeOutputProperty(output->randr_output, backlight_control_atom, XA_ATOM, 32, PropModeReplace, 1, &backlight_control_name_atoms[pI830->backlight_control_method], FALSE, TRUE); if (err != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to set backlight control, %d\n", err); } /* * Panel fitting control */ /* Disable panel fitting setting on untested pre-915 chips */ if (!IS_I9XX(pI830) && !(pI830->quirk_flag & QUIRK_PFIT_SAFE)) return; panel_fitting_atom = MakeAtom(PANEL_FITTING_NAME, sizeof(PANEL_FITTING_NAME) - 1, TRUE); for (i = 0; i < NUM_PANEL_FITTING_TYPES; i++) { panel_fitting_name_atoms[i] = MakeAtom(panel_fitting_names[i], strlen(panel_fitting_names[i]), TRUE); } err = RRConfigureOutputProperty(output->randr_output, panel_fitting_atom, TRUE, FALSE, FALSE, NUM_PANEL_FITTING_TYPES, (INT32 *)panel_fitting_name_atoms); if (err != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "RRConfigureOutputProperty error, %d\n", err); } err = RRChangeOutputProperty(output->randr_output, panel_fitting_atom, XA_ATOM, 32, PropModeReplace, 1, &panel_fitting_name_atoms[dev_priv->fitting_mode], FALSE, TRUE); if (err != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to set panel fitting mode, %d\n", err); } #endif /* RANDR_12_INTERFACE */ } #ifdef RANDR_12_INTERFACE 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; if (property == backlight_atom) { INT32 val; if (value->type != XA_INTEGER || value->format != 32 || value->size != 1) { return FALSE; } val = *(INT32 *)value->data; if (val < 0 || val > dev_priv->backlight_max) return FALSE; 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) { INT32 backlight_range[2]; Atom atom; char *name; int ret, data; if (value->type != XA_ATOM || value->format != 32 || value->size != 1) return FALSE; memcpy(&atom, value->data, 4); name = NameForAtom(atom); ret = i830_backlight_control_lookup(name); if (ret < 0) return FALSE; pI830->backlight_control_method = ret; i830_lvds_set_backlight_control(output); /* * Update the backlight atom since the range and value may have changed */ backlight_range[0] = 0; backlight_range[1] = dev_priv->backlight_max; ret = RRConfigureOutputProperty(output->randr_output, backlight_atom, FALSE, TRUE, FALSE, 2, backlight_range); if (ret != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "RRConfigureOutputProperty error, %d\n", ret); } /* Set the current value of the backlight property */ if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) data = dev_priv->get_backlight(output); else data = dev_priv->backlight_duty_cycle; ret = RRChangeOutputProperty(output->randr_output, backlight_atom, XA_INTEGER, 32, PropModeReplace, 1, &data, FALSE, TRUE); if (ret != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "RRChangeOutputProperty error, %d\n", ret); } return TRUE; } else if (property == panel_fitting_atom) { Atom atom; char *name; int ret; if (value->type != XA_ATOM || value->format != 32 || value->size != 1) return FALSE; memcpy(&atom, value->data, 4); name = NameForAtom(atom); ret = i830_panel_fitting_lookup(name); if (ret < 0) return FALSE; if (dev_priv->fitting_mode == ret) return TRUE; dev_priv->fitting_mode = ret; if (output->crtc) { xf86CrtcPtr crtc = output->crtc; if (crtc->enabled) { if (!xf86CrtcSetMode(crtc, &crtc->desiredMode, crtc->desiredRotation, crtc->desiredX, crtc->desiredY)) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Failed to set mode after panel fitting change!\n"); return FALSE; } } } return TRUE; } return TRUE; } #endif /* RANDR_12_INTERFACE */ #ifdef RANDR_13_INTERFACE static Bool i830_lvds_get_property(xf86OutputPtr output, Atom property) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; int ret; /* * Only need to update properties that might change out from under * us. The others will be cached by the randr core code. */ if (property == backlight_atom) { int val; if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) { val = dev_priv->get_backlight(output); dev_priv->backlight_duty_cycle = val; } else val = dev_priv->backlight_duty_cycle; ret = RRChangeOutputProperty(output->randr_output, backlight_atom, XA_INTEGER, 32, PropModeReplace, 1, &val, FALSE, TRUE); if (ret != Success) return FALSE; } return TRUE; } #endif /* RANDR_13_INTERFACE */ #ifdef RANDR_GET_CRTC_INTERFACE static xf86CrtcPtr i830_lvds_get_crtc(xf86OutputPtr output) { ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); int pipe = !!(INREG(LVDS) & LVDS_PIPEB_SELECT); return i830_pipe_to_crtc(pScrn, pipe); } #endif static const xf86OutputFuncsRec i830_lvds_output_funcs = { .create_resources = i830_lvds_create_resources, .dpms = i830_lvds_dpms, .save = i830_lvds_save, .restore = i830_lvds_restore, .mode_valid = i830_lvds_mode_valid, .mode_fixup = i830_lvds_mode_fixup, .prepare = i830_lvds_prepare, .mode_set = i830_lvds_mode_set, .commit = i830_output_commit, .detect = i830_lvds_detect, .get_modes = i830_lvds_get_modes, #ifdef RANDR_12_INTERFACE .set_property = i830_lvds_set_property, #endif #ifdef RANDR_13_INTERFACE .get_property = i830_lvds_get_property, #endif .destroy = i830_lvds_destroy, #ifdef RANDR_GET_CRTC_INTERFACE .get_crtc = i830_lvds_get_crtc, #endif }; void i830_lvds_init(ScrnInfoPtr pScrn) { I830Ptr pI830 = I830PTR(pScrn); xf86OutputPtr output; I830OutputPrivatePtr intel_output; DisplayModePtr modes, scan; DisplayModePtr lvds_ddc_mode = NULL; struct i830_lvds_priv *dev_priv; if (pI830->quirk_flag & QUIRK_IGNORE_LVDS) return; output = xf86OutputCreate (pScrn, &i830_lvds_output_funcs, "LVDS"); if (!output) return; intel_output = xnfcalloc (sizeof (I830OutputPrivateRec) + sizeof (struct i830_lvds_priv), 1); if (!intel_output) { xf86OutputDestroy (output); return; } intel_output->type = I830_OUTPUT_LVDS; intel_output->pipe_mask = (1 << 1); intel_output->clone_mask = (1 << I830_OUTPUT_LVDS); output->driver_private = intel_output; output->subpixel_order = SubPixelHorizontalRGB; output->interlaceAllowed = FALSE; output->doubleScanAllowed = FALSE; dev_priv = (struct i830_lvds_priv *) (intel_output + 1); intel_output->dev_priv = dev_priv; /* * Mode detection algorithms for LFP: * 1) if EDID present, use it, done * 2) if VBT present, use it, done * 3) if current mode is programmed, use it, done * 4) check for Mac mini & other quirks * 4) fail, assume no LFP */ /* Set up the LVDS DDC channel. Most panels won't support it, but it can * be useful if available. */ I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOC, "LVDSDDC_C"); if (pI830->skip_panel_detect) { xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Skipping any attempt to determine panel fixed mode.\n"); goto found_mode; } xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Attempting to determine panel fixed mode.\n"); /* Attempt to get the fixed panel mode from DDC. Assume that the preferred * mode is the right one. */ modes = i830_ddc_get_modes(output); for (scan = modes; scan != NULL; scan = scan->next) { if (scan->type & M_T_PREFERRED) break; } if (scan != NULL) { /* Pull our chosen mode out and make it the fixed mode */ if (modes == scan) modes = modes->next; if (scan->prev != NULL) scan->prev = scan->next; if (scan->next != NULL) scan->next = scan->prev; lvds_ddc_mode = scan; } /* Delete the mode list */ while (modes != NULL) xf86DeleteMode(&modes, modes); if (lvds_ddc_mode) { pI830->lvds_fixed_mode = lvds_ddc_mode; goto found_mode; } /* Get the LVDS fixed mode out of the BIOS. We should support LVDS with * the BIOS being unavailable or broken, but lack the configuration options * for now. */ if (pI830->lvds_fixed_mode) goto found_mode; /* If we *still* don't have a mode, try checking if the panel is already * turned on. If so, assume that whatever is currently programmed is the * correct mode. */ if (!pI830->lvds_fixed_mode) { uint32_t lvds = INREG(LVDS); int pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0; xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn); xf86CrtcPtr crtc = xf86_config->crtc[pipe]; if (lvds & LVDS_PORT_EN) { pI830->lvds_fixed_mode = i830_crtc_mode_get(pScrn, crtc); if (pI830->lvds_fixed_mode != NULL) { pI830->lvds_fixed_mode->type |= M_T_PREFERRED; goto found_mode; } } } if (!pI830->lvds_fixed_mode) goto disable_exit; found_mode: /* Blacklist machines with BIOSes that list an LVDS panel without actually * having one. */ if (pI830->quirk_flag & QUIRK_IGNORE_MACMINI_LVDS) { /* It's a Mac Mini or Macbook Pro. * * Apple hardware is out to get us. The macbook pro has a real * LVDS panel, but the mac mini does not, and they have the same * device IDs. We'll distinguish by panel size, on the assumption * that Apple isn't about to make any machines with an 800x600 * display. */ if (pI830->lvds_fixed_mode != NULL && pI830->lvds_fixed_mode->HDisplay == 800 && pI830->lvds_fixed_mode->VDisplay == 600) { xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Suspected Mac Mini, ignoring the LVDS\n"); goto disable_exit; } } i830_set_lvds_backlight_method(output); switch (pI830->backlight_control_method) { case BCM_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 BCM_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 BCM_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_combo(output); break; case BCM_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; default: xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "bad backlight control method\n"); break; } dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); /* * Avoid munging the aspect ratio by default. */ dev_priv->fitting_mode = FULL_ASPECT; return; disable_exit: xf86DestroyI2CBusRec(intel_output->pDDCBus, TRUE, TRUE); xf86OutputDestroy(output); }