@@ -149,6 +149,11 @@ struct drm_i915_error_state {
struct timeval time;
};
+struct blc_funcs {
+ int (*get)(struct drm_device *dev);
+ void (*set)(struct drm_device *dev, int level);
+};
+
typedef struct drm_i915_private {
struct drm_device *dev;
@@ -212,6 +217,9 @@ typedef struct drm_i915_private {
struct drm_display_mode *panel_fixed_mode;
struct drm_display_mode *lfp_lvds_vbt_mode; /* if any */
struct drm_display_mode *sdvo_lvds_vbt_mode; /* if any */
+ struct blc_struct blc_info;
+ struct blc_funcs backlight;
+ struct backlight_device *backlight_device; /* for sysfs */
/* Feature bits from the VBIOS */
unsigned int int_tv_support:1;
@@ -895,6 +895,7 @@
/* Backlight control */
#define BLC_PWM_CTL 0x61254
#define BACKLIGHT_MODULATION_FREQ_SHIFT (17)
+#define BACKLIGHT_MODULATION_FREQ_SHIFT2 (16)
#define BLC_PWM_CTL2 0x61250 /* 965+ only */
#define BLM_COMBINATION_MODE (1 << 30)
/*
@@ -904,6 +905,7 @@
* The actual value is this field multiplied by two.
*/
#define BACKLIGHT_MODULATION_FREQ_MASK (0x7fff << 17)
+#define BACKLIGHT_MODULATION_FREQ_MASK2 (0xffff << 16)
#define BLM_LEGACY_MODE (1 << 16)
/*
* This is the number of cycles out of the backlight modulation cycle for which
@@ -315,6 +315,27 @@ parse_driver_features(struct drm_i915_private *dev_priv,
dev_priv->edp_support = 1;
}
+static void
+parse_lvds_backlight(struct drm_i915_private *dev_priv, struct bdb_header *bdb)
+{
+ struct bdb_lvds_options *lvds_options;
+ struct bdb_lvds_backlight *backlight;
+ int entry;
+
+ lvds_options = find_section(bdb, BDB_LVDS_OPTIONS);
+ backlight = find_section(bdb, BDB_LVDS_BACKLIGHT);
+ if (!lvds_options || !backlight)
+ return;
+
+ /* Make sure we have a compatible layout */
+ if (backlight->blcstruct_size != sizeof(struct blc_struct))
+ return;
+
+ entry = lvds_options->panel_type;
+ memcpy(&dev_priv->blc_info, &backlight->panels[entry],
+ sizeof(struct blc_struct));
+}
+
/**
* intel_init_bios - initialize VBIOS settings & find VBT
* @dev: DRM device
@@ -366,6 +387,7 @@ intel_init_bios(struct drm_device *dev)
parse_sdvo_panel_data(dev_priv, bdb);
parse_sdvo_device_mapping(dev_priv, bdb);
parse_driver_features(dev_priv, bdb);
+ parse_lvds_backlight(dev_priv, bdb);
pci_unmap_rom(pdev, bios);
@@ -380,6 +380,48 @@ struct bdb_sdvo_lvds_options {
u8 panel_misc_bits_4;
} __attribute__((packed));
+#define BLC_INVERTER_TYPE_NONE 0
+#define BLC_INVERTER_TYPE_I2C 1
+#define BLC_INVERTER_TYPE_PWM 2
+
+#define BLC_GPIO_NONE 0
+#define BLC_GPIO_I2C 1
+#define BLC_GPIO_CRT_DDC 2
+#define BLC_GPIO_DVI_DDC 3
+#define BLC_GPIO_SDVO_I2C 5
+
+#define BLC_GMBUS_100KHZ 0
+#define BLC_GMBUS_50KHZ 1
+#define BLC_GMBUS_400KHZ 2
+#define BLC_GMBUS_1MHZ 3
+
+struct blc_struct {
+ u8 inverter_type:2;
+ u8 inverter_polarity:1; /* 1 means inverted (0 = max brightness) */
+ u8 gpio_pins:3;
+ u8 gmbus_speed:2;
+ u16 pwm_freq; /* in Hz */
+ u8 min_brightness; /* (0-255) */
+ u8 i2c_slave_addr;
+ u8 i2c_cmd;
+} __attribute__((packed));
+
+struct bdb_lvds_backlight {
+ u8 blcstruct_size;
+ struct blc_struct panels[16];
+} __attribute__((packed));
+
+struct bdb_lvds_power {
+ u8 dpst_enabled:1;
+ u8 pwr_prefs:3;
+ u8 rsvd1:3;
+ u8 als_enabled:1;
+ u16 als_backlight1;
+ u16 als_backlight2;
+ u16 als_backlight3;
+ u16 als_backlight4;
+ u16 als_backlight5;
+} __attribute__((packed));
#define BDB_DRIVER_FEATURE_NO_LVDS 0
#define BDB_DRIVER_FEATURE_INT_LVDS 1
@@ -27,6 +27,7 @@
* Jesse Barnes <jesse.barnes@intel.com>
*/
+#include <linux/backlight.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
#include "drmP.h"
@@ -56,11 +57,14 @@ struct intel_lvds_priv {
};
/**
- * Sets the backlight level.
+ * blc_pwm_set - set PWM backlight reg
+ * @dev: DRM device
+ * @level: brightness level (from 0-max)
*
- * \param level backlight level, from 0 to intel_lvds_get_max_backlight().
+ * Set the backlight to @level, scaling as necessary based on hw maximum
+ * and inversion.
*/
-static void intel_lvds_set_backlight(struct drm_device *dev, int level)
+static void blc_pwm_set(struct drm_device *dev, int level)
{
struct drm_i915_private *dev_priv = dev->dev_private;
u32 blc_pwm_ctl, reg;
@@ -70,9 +74,47 @@ static void intel_lvds_set_backlight(struct drm_device *dev, int level)
else
reg = BLC_PWM_CTL;
+ /* Adjust for min and invert if necessary */
+ level += dev_priv->blc_info.min_brightness;
+ if (dev_priv->blc_info.inverter_polarity)
+ level = dev_priv->backlight_device->props.max_brightness - level;
+ /* Adjust for combo mode?
+ * - LBB (if not 0xff) * pwm is brightness
+ * - so divide incoming value by 255 and feed into pwm
+ * - if it overflows, put remainder in lbb
+ * - if lbb is already 0, we need to increase it
+ */
blc_pwm_ctl = I915_READ(reg) & ~BACKLIGHT_DUTY_CYCLE_MASK;
- I915_WRITE(reg, (blc_pwm_ctl |
- (level << BACKLIGHT_DUTY_CYCLE_SHIFT)));
+ I915_WRITE(reg, (blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT)));
+}
+
+static int blc_pwm_get(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ int level, reg;
+
+ if (IS_IGDNG(dev))
+ reg = BLC_PWM_CPU_CTL;
+ else
+ reg = BLC_PWM_CTL;
+
+ level = I915_READ(reg) & ~BACKLIGHT_DUTY_CYCLE_MASK;
+
+ /* Adjust for min and invert if necessary */
+ if (dev_priv->blc_info.inverter_polarity)
+ level = dev_priv->backlight_device->props.max_brightness - level;
+ level -= dev_priv->blc_info.min_brightness;
+
+ return level;
+}
+
+static void blc_i2c_set(struct drm_device *dev, int level)
+{
+}
+
+static int blc_i2c_get(struct drm_device *dev)
+{
+ return 0;
}
/**
@@ -114,11 +156,7 @@ static void intel_lvds_set_power(struct drm_device *dev, bool on)
do {
pp_status = I915_READ(status_reg);
} 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(ctl_reg, I915_READ(ctl_reg) &
~POWER_TARGET_ON);
do {
@@ -652,7 +690,11 @@ static int intel_lvds_get_modes(struct drm_connector *connector)
static void intel_lvds_destroy(struct drm_connector *connector)
{
struct intel_output *intel_output = to_intel_output(connector);
+ struct drm_device *dev = connector->dev;
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ if (dev_priv->backlight_device)
+ backlight_device_unregister(dev_priv->backlight_device);
if (intel_output->ddc_bus)
intel_i2c_destroy(intel_output->ddc_bus);
drm_sysfs_connector_remove(connector);
@@ -856,6 +898,104 @@ static int intel_lid_present(void)
}
#endif
+static int intel_get_backlight_brightness(struct backlight_device *bd)
+{
+ struct drm_device *dev = bl_get_data(bd);
+ struct drm_i915_private *dev_priv = dev->dev_private;
+
+ return dev_priv->backlight.get(dev);
+}
+
+static int intel_update_backlight_status(struct backlight_device *bd)
+{
+ struct drm_device *dev = bl_get_data(bd);
+ struct drm_i915_private *dev_priv = dev->dev_private;
+
+ /* FIXME: check for FB blanking */
+ dev_priv->backlight.set(dev, bd->props.brightness);
+ return 0;
+}
+
+static struct backlight_ops intel_backlight_ops = {
+ .get_brightness = intel_get_backlight_brightness,
+ .update_status = intel_update_backlight_status,
+};
+
+/**
+ * intel_lvds_backlight_init - find & initialize backlight devices
+ * @dev: DRM device
+ *
+ * If we find a backlight controller (i.e. the backlight controller type
+ * isn't BLC_INVERTER_TYPE_NONE) we set it up here. This either means
+ * a PWM attached controller or an I2C attached controller. Control of the
+ * backlight is exposed through sysfs, unless an ACPI or other platform
+ * device has already been registered.
+ */
+static void intel_lvds_backlight_init(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ struct backlight_device *backlight_device;
+ u32 pwm_ctl, pwm_ctl2;
+ int max;
+
+ /* FIXME: check whether /sys/class/backlight has a platform device */
+
+ /* ACPI already has one? */
+ if (acpi_video_backlight_support())
+ return;
+
+ switch (dev_priv->blc_info.inverter_type) {
+ case BLC_INVERTER_TYPE_I2C:
+ dev_priv->backlight.get = blc_i2c_get;
+ dev_priv->backlight.set = blc_i2c_set;
+ max = 255;
+ break;
+ case BLC_INVERTER_TYPE_PWM:
+ dev_priv->backlight.get = blc_pwm_get;
+ dev_priv->backlight.set = blc_pwm_set;
+ pwm_ctl = I915_READ(BLC_PWM_CTL);
+ if (IS_I965GM(dev) || IS_GM45(dev)) {
+ max = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK2) >>
+ BACKLIGHT_MODULATION_FREQ_SHIFT2);
+ pwm_ctl2 = I915_READ(BLC_PWM_CTL2);
+ if (pwm_ctl2 & BLM_COMBINATION_MODE)
+ max *= 255;
+ } else {
+ max = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK) >>
+ BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
+ if (pwm_ctl & BLM_LEGACY_MODE)
+ max *= 255;
+ }
+ break;
+ default:
+ DRM_DEBUG("backlight inverter not available\n");
+ goto out;
+ }
+
+ backlight_device = backlight_device_register("i915-backlight",
+ &dev->pdev->dev, dev,
+ &intel_backlight_ops);
+ if (IS_ERR(backlight_device)) {
+ DRM_ERROR("failed to register backlight device\n");
+ goto out;
+ }
+
+ dev_priv->backlight_device = backlight_device;
+
+ /* User exposed value always goes from 0-(max - min) */
+ if (dev_priv->blc_info.inverter_polarity)
+ backlight_device->props.max_brightness =
+ dev_priv->blc_info.min_brightness - max;
+ else
+ backlight_device->props.max_brightness =
+ max - dev_priv->blc_info.min_brightness;
+ backlight_device->props.brightness = dev_priv->backlight.get(dev);
+ backlight_device->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(backlight_device);
+out:
+ return;
+}
+
/**
* intel_lvds_init - setup LVDS connectors on this device
* @dev: drm device
@@ -1010,6 +1150,8 @@ void intel_lvds_init(struct drm_device *dev)
goto failed;
out:
+ intel_lvds_backlight_init(dev);
+
if (IS_IGDNG(dev)) {
u32 pwm;
/* make sure PWM is enabled */