aboutsummaryrefslogtreecommitdiff
path: root/linux-core/intel_lvds.c
diff options
context:
space:
mode:
authorJesse Barnes <jbarnes@hobbes.virtuousgeek.org>2007-04-09 20:46:38 -0700
committerJesse Barnes <jbarnes@hobbes.virtuousgeek.org>2007-04-09 20:46:38 -0700
commitc446bf50e3ae730f272c6842f4ad04d523bd40c3 (patch)
treed24a77798540deac95d337c75a8f1536d774ccae /linux-core/intel_lvds.c
parent7e2b1a6cf55579c6f8b1fd56a97e9f41e34b88fc (diff)
Slam in most of X.Org's i830_lvds (not quite done yet so removed from Makefile.kernel too).
Diffstat (limited to 'linux-core/intel_lvds.c')
-rw-r--r--linux-core/intel_lvds.c423
1 files changed, 383 insertions, 40 deletions
diff --git a/linux-core/intel_lvds.c b/linux-core/intel_lvds.c
index a444f145..7527dfce 100644
--- a/linux-core/intel_lvds.c
+++ b/linux-core/intel_lvds.c
@@ -1,5 +1,6 @@
/*
* Copyright © 2006-2007 Intel Corporation
+ * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -22,10 +23,8 @@
*
* Authors:
* Eric Anholt <eric@anholt.net>
- */
-/*
- * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
- * Jesse Barnes <jesse.barnes@intel.com>
+ * Dave Airlie <airlied@linux.ie>
+ * Jesse Barnes <jesse.barnes@intel.com>
*/
#include <linux/i2c.h>
@@ -39,23 +38,22 @@
/**
* Sets the backlight level.
*
- * \param level backlight level, from 0 to i830_lvds_get_max_backlight().
+ * \param level backlight level, from 0 to intel_lvds_get_max_backlight().
*/
-static void lvds_set_backlight(drm_device_t *dev, u32 level)
+static void intel_lvds_set_backlight(struct drm_device *dev, int level)
{
drm_i915_private_t *dev_priv = dev->dev_private;
- unsigned long blc_pwm_ctl;
+ u32 blc_pwm_ctl;
- level &= BACKLIGHT_DUTY_CYCLE_MASK;
blc_pwm_ctl = I915_READ(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK;
- I915_WRITE(BLC_PWM_CTL, blc_pwm_ctl |
- (level << BACKLIGHT_DUTY_CYCLE_SHIFT));
+ I915_WRITE(BLC_PWM_CTL, (blc_pwm_ctl |
+ (level << BACKLIGHT_DUTY_CYCLE_SHIFT)));
}
/**
* Returns the maximum level of the backlight duty cycle field.
*/
-static u32 lvds_get_max_backlight(drm_device_t *dev)
+static u32 intel_lvds_get_max_backlight(struct drm_device *dev)
{
drm_i915_private_t *dev_priv = dev->dev_private;
@@ -63,30 +61,386 @@ static u32 lvds_get_max_backlight(drm_device_t *dev)
BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
}
-int lvds_backlight(DRM_IOCTL_ARGS)
+/**
+ * Sets the power state for the panel.
+ */
+static void intel_lvds_set_power(struct drm_device *dev, bool on)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ u32 pp_status;
+
+ if (on) {
+ I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) |
+ POWER_TARGET_ON);
+ do {
+ pp_status = I915_READ(PP_STATUS);
+ } while ((pp_status & PP_ON) == 0);
+
+ intel_lvds_set_backlight(dev, dev_priv->backlight_duty_cycle);
+ } else {
+ intel_lvds_set_backlight(dev, 0);
+
+ I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) &
+ ~POWER_TARGET_ON);
+ do {
+ pp_status = I915_READ(PP_STATUS);
+ } while (pp_status & PP_ON);
+ }
+}
+
+static void intel_lvds_dpms(struct drm_output *output, int mode)
+{
+ struct drm_device *dev = output->dev;
+
+ if (mode == DPMSModeOn)
+ intel_lvds_set_power(dev, true);
+ else
+ intel_lvds_set_power(dev, false);
+
+ /* XXX: We never power down the LVDS pairs. */
+}
+
+static void intel_lvds_save(struct drm_output *output)
+{
+ struct drm_device *dev = output->dev;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+
+ dev_priv->savePP_ON = I915_READ(LVDSPP_ON);
+ dev_priv->savePP_OFF = I915_READ(LVDSPP_OFF);
+ dev_priv->savePP_CONTROL = I915_READ(PP_CONTROL);
+ dev_priv->savePP_CYCLE = I915_READ(PP_CYCLE);
+ dev_priv->saveBLC_PWM_CTL = I915_READ(BLC_PWM_CTL);
+ dev_priv->backlight_duty_cycle = (dev_priv->saveBLC_PWM_CTL &
+ BACKLIGHT_DUTY_CYCLE_MASK);
+
+ /*
+ * 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 =
+ intel_lvds_get_max_backlight(dev);
+}
+
+static void intel_lvds_restore(struct drm_output *output)
+{
+ struct drm_device *dev = output->dev;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+
+ I915_WRITE(BLC_PWM_CTL, dev_priv->saveBLC_PWM_CTL);
+ I915_WRITE(LVDSPP_ON, dev_priv->savePP_ON);
+ I915_WRITE(LVDSPP_OFF, dev_priv->savePP_OFF);
+ I915_WRITE(PP_CYCLE, dev_priv->savePP_CYCLE);
+ I915_WRITE(PP_CONTROL, dev_priv->savePP_CONTROL);
+ if (dev_priv->savePP_CONTROL & POWER_TARGET_ON)
+ intel_lvds_set_power(dev, true);
+ else
+ intel_lvds_set_power(dev, false);
+}
+
+static int intel_lvds_mode_valid(struct drm_output *output,
+ struct drm_display_mode *mode)
+{
+ struct drm_device *dev = output->dev;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct drm_display_mode *fixed_mode = dev_priv->panel_fixed_mode;
+
+ if (fixed_mode) {
+ if (mode->hdisplay > fixed_mode->hdisplay)
+ return MODE_PANEL;
+ if (mode->vdisplay > fixed_mode->vdisplay)
+ return MODE_PANEL;
+ }
+
+ return MODE_OK;
+}
+
+static bool intel_lvds_mode_fixup(struct drm_output *output,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = output->dev;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct intel_crtc *intel_crtc = output->crtc->driver_private;
+ struct drm_output *tmp_output;
+
+ spin_lock(&dev->crtc_config.config_lock);
+ list_for_each_entry(tmp_output, &dev->crtc_config.output_list, head) {
+ if (tmp_output != output && tmp_output->crtc == output->crtc) {
+ printk(KERN_ERR "Can't enable LVDS and another "
+ "output on the same pipe\n");
+ return false;
+ }
+ }
+ spin_lock(&dev->crtc_config.config_lock);
+
+ if (intel_crtc->pipe == 0) {
+ printk(KERN_ERR "Can't support LVDS on pipe A\n");
+ return false;
+ }
+
+ /*
+ * 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.
+ */
+ if (dev_priv->panel_fixed_mode != NULL) {
+ adjusted_mode->hdisplay = dev_priv->panel_fixed_mode->hdisplay;
+ adjusted_mode->hsync_start =
+ dev_priv->panel_fixed_mode->hsync_start;
+ adjusted_mode->hsync_end =
+ dev_priv->panel_fixed_mode->hsync_end;
+ adjusted_mode->htotal = dev_priv->panel_fixed_mode->htotal;
+ adjusted_mode->vdisplay = dev_priv->panel_fixed_mode->vdisplay;
+ adjusted_mode->vsync_start =
+ dev_priv->panel_fixed_mode->vsync_start;
+ adjusted_mode->vsync_end =
+ dev_priv->panel_fixed_mode->vsync_end;
+ adjusted_mode->vtotal = dev_priv->panel_fixed_mode->vtotal;
+ adjusted_mode->clock = dev_priv->panel_fixed_mode->clock;
+// xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V);
+ }
+
+ /*
+ * 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 intel_lvds_mode_set(struct drm_output *output,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = output->dev;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct intel_crtc *intel_crtc = output->crtc->driver_private;
+ u32 pfit_control;
+
+ /*
+ * The LVDS pin pair will already have been turned on in the
+ * intel_crtc_mode_set since it has a large impact on the DPLL
+ * settings.
+ */
+
+ /*
+ * Enable automatic panel scaling so that non-native modes fill the
+ * screen. Should be enabled before the pipe is enabled, according to
+ * register description and PRM.
+ */
+ pfit_control = (PFIT_ENABLE | VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
+ VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR);
+
+ if (!IS_I965G(dev)) {
+ if (dev_priv->panel_wants_dither)
+ pfit_control |= PANEL_8TO6_DITHER_ENABLE;
+ }
+ else
+ pfit_control |= intel_crtc->pipe << PFIT_PIPE_SHIFT;
+
+ I915_WRITE(PFIT_CONTROL, pfit_control);
+}
+
+/**
+ * Detect the LVDS connection.
+ *
+ * This always returns OUTPUT_STATUS_CONNECTED. This output should only have
+ * been set up if the LVDS was actually connected anyway.
+ */
+static enum drm_output_status intel_lvds_detect(struct drm_output *output)
+{
+ return output_status_connected;
+}
+
+/**
+ * Return the list of DDC modes if available, or the BIOS fixed mode otherwise.
+ */
+static int intel_lvds_get_modes(struct drm_output *output)
{
- DRM_DEVICE;
- unsigned long dvoa_enabled, dvob_enabled, dvoc_enabled, lvds_enabled;
+ struct intel_output *intel_output = output->driver_private;
+ struct drm_device *dev = output->dev;
drm_i915_private_t *dev_priv = dev->dev_private;
+ struct drm_monitor_info *edid_mon;
+ int ret = 0;
+
+ intel_output->ddc_bus = intel_i2c_create(dev, GPIOC, "LVDSDDC_C");
+ if (!intel_output->ddc_bus) {
+ dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration "
+ "failed.\n");
+ return 0;
+ }
+ intel_i2c_destroy(intel_output->ddc_bus);
+
+ ret = intel_ddc_get_modes(output);
+ if (ret)
+ return ret;
+#if 0
+ if (!output->monitor_info) {
+ edid_mon = kzalloc(sizeof(*output->monitor_info), GFP_KERNEL);
+ 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->monitor_info = edid_mon;
+ }
+ }
+#endif
+ if (dev_priv->panel_fixed_mode != NULL) {
+ struct drm_display_mode *mode =
+ drm_mode_duplicate(dev, dev_priv->panel_fixed_mode);
+ drm_mode_probed_add(output, mode);
+ return 1;
+ }
- printk(KERN_ERR "max backlight value: %d\n",
- lvds_get_max_backlight(dev));
- dvoa_enabled = I915_READ(DVOA);
- dvob_enabled = I915_READ(DVOB);
- dvoc_enabled = I915_READ(DVOC);
- lvds_enabled = I915_READ(LVDS);
-
- printk(KERN_ERR "dvoa_enabled: 0x%08lx\n", dvoa_enabled);
- printk(KERN_ERR "dvob_enabled: 0x%08lx\n", dvob_enabled);
- printk(KERN_ERR "dvoc_enabled: 0x%08lx\n", dvoc_enabled);
- printk(KERN_ERR "lvds_enabled: 0x%08lx\n", lvds_enabled);
- printk(KERN_ERR "BLC_PWM_CTL: 0x%08x\n", I915_READ(BLC_PWM_CTL));
-
return 0;
}
-static const struct drm_output_funcs intel_lvds_output_funcs;
+static void intel_lvds_destroy(struct drm_output *output)
+{
+ drm_output_destroy(output);
+}
+
+static const struct drm_output_funcs intel_lvds_output_funcs = {
+ .dpms = intel_lvds_dpms,
+ .save = intel_lvds_save,
+ .restore = intel_lvds_restore,
+ .mode_valid = intel_lvds_mode_valid,
+ .mode_fixup = intel_lvds_mode_fixup,
+ .prepare = intel_output_prepare,
+ .mode_set = intel_lvds_mode_set,
+ .commit = intel_output_commit,
+ .detect = intel_lvds_detect,
+ .get_modes = intel_lvds_get_modes,
+ .cleanup = intel_lvds_destroy
+};
+
+void
+intel_lvds_init(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct drm_output *output;
+ struct intel_output *intel_output;
+ struct drm_display_mode *modes, scan, bios_mode;
+
+ output = drm_output_create(dev, &intel_lvds_output_funcs, "LVDS");
+ if (!output)
+ return;
+
+ intel_output = kmalloc(sizeof(struct intel_output), GFP_KERNEL);
+ if (!intel_output) {
+ drm_output_destroy(output);
+ return;
+ }
+
+ intel_output->type = INTEL_OUTPUT_LVDS;
+ output->driver_private = intel_output;
+ output->subpixel_order = SubPixelHorizontalRGB;
+ output->interlace_allowed = FALSE;
+ output->doublescan_allowed = FALSE;
+
+ /*
+ * Attempt to get the fixed panel mode from DDC. Assume that the
+ * preferred mode is the right one.
+ */
+ intel_ddc_get_modes(output);
+ list_for_each_entry(scan, &output->probed_modes, head) {
+ if (scan->type & DRM_MODE_TYPE_PREFERRED)
+ break;
+ }
+
+ if (scan != NULL)
+ dev_priv->panel_fixed_mode = scan;
+
+#if 0
+ /*
+ * If we didn't get EDID, try checking if the panel is already turned
+ * on. If so, assume that whatever is currently programmed is the
+ * correct mode.
+ */
+ if (dev_priv->panel_fixed_mode == NULL) {
+ u32 lvds = I915_READ(LVDS);
+ int pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0;
+ struct drm_crtc_config *crtc_config = dev->crtc_config;
+ struct drm_crtc *crtc = crtc_config->crtc[pipe];
+
+ if (lvds & LVDS_PORT_EN) {
+ dev_priv->panel_fixed_mode = intel_crtc_mode_get(pScrn, crtc);
+ if (dev_priv->panel_fixed_mode != NULL)
+ dev_priv->panel_fixed_mode->type |= M_T_PREFERRED;
+ }
+ }
+
+ /* 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.
+ */
+ bios_mode = intel_bios_get_panel_mode(pScrn);
+ if (bios_mode != NULL) {
+ if (dev_priv->panel_fixed_mode != NULL) {
+ if (dev_priv->debug_modes &&
+ !xf86ModesEqual(dev_priv->panel_fixed_mode, bios_mode))
+ {
+ xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
+ "BIOS panel mode data doesn't match probed data, "
+ "continuing with probed.\n");
+ xf86DrvMsg(pScrn->scrnIndex, X_INFO, "BIOS mode:\n");
+ xf86PrintModeline(pScrn->scrnIndex, bios_mode);
+ xf86DrvMsg(pScrn->scrnIndex, X_INFO, "probed mode:\n");
+ xf86PrintModeline(pScrn->scrnIndex, dev_priv->panel_fixed_mode);
+ xfree(bios_mode->name);
+ xfree(bios_mode);
+ }
+ } else {
+ dev_priv->panel_fixed_mode = bios_mode;
+ }
+ } else {
+ xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
+ "Couldn't detect panel mode. Disabling panel\n");
+ goto disable_exit;
+ }
+
+ /* Blacklist machines with BIOSes that list an LVDS panel without actually
+ * having one.
+ */
+ if (dev_priv->PciInfo->chipType == PCI_CHIP_I945_GM) {
+ if (dev_priv->PciInfo->subsysVendor == 0xa0a0) /* aopen mini pc */
+ goto disable_exit;
+
+ if ((dev_priv->PciInfo->subsysVendor == 0x8086) &&
+ (dev_priv->PciInfo->subsysCard == 0x7270)) {
+ /* 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 (dev_priv->panel_fixed_mode != NULL &&
+ dev_priv->panel_fixed_mode->HDisplay == 800 &&
+ dev_priv->panel_fixed_mode->VDisplay == 600)
+ {
+ xf86DrvMsg(pScrn->scrnIndex, X_INFO,
+ "Suspected Mac Mini, ignoring the LVDS\n");
+ goto disable_exit;
+ }
+ }
+ }
+
+#endif
+ return;
+}
+
+#if 0
/**
* intel_lvds_init - setup LVDS outputs on this device
* @dev: drm device
@@ -115,16 +469,5 @@ void intel_lvds_init(drm_device_t *dev)
output->subpixel_order = SubPixelHorizontalRGB;
output->interlace_allowed = 0;
output->doublescan_allowed = 0;
-
- intel_output->ddc_bus = intel_i2c_create(dev, GPIOC, "LVDSDDC_C");
- if (!intel_output->ddc_bus) {
- dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration "
- "failed.\n");
- return;
- }
-
- modes = intel_ddc_get_modes(output);
- intel_i2c_destroy(intel_output->ddc_bus);
- drm_output_destroy(output);
}
-
+#endif