drm/i915: add some framework for backlight bl_power support
[deliverable/linux.git] / drivers / gpu / drm / i915 / intel_panel.c
index 12b02fe1d0aed7349662932bad8c38b53d914ac3..af54356349291ab5f4f7a15a6a468bbe85c529ba 100644 (file)
@@ -398,6 +398,69 @@ intel_panel_detect(struct drm_device *dev)
        }
 }
 
+/**
+ * scale - scale values from one range to another
+ *
+ * @source_val: value in range [@source_min..@source_max]
+ *
+ * Return @source_val in range [@source_min..@source_max] scaled to range
+ * [@target_min..@target_max].
+ */
+static uint32_t scale(uint32_t source_val,
+                     uint32_t source_min, uint32_t source_max,
+                     uint32_t target_min, uint32_t target_max)
+{
+       uint64_t target_val;
+
+       WARN_ON(source_min > source_max);
+       WARN_ON(target_min > target_max);
+
+       /* defensive */
+       source_val = clamp(source_val, source_min, source_max);
+
+       /* avoid overflows */
+       target_val = (uint64_t)(source_val - source_min) *
+               (target_max - target_min);
+       do_div(target_val, source_max - source_min);
+       target_val += target_min;
+
+       return target_val;
+}
+
+/* Scale user_level in range [0..user_max] to [hw_min..hw_max]. */
+static inline u32 scale_user_to_hw(struct intel_connector *connector,
+                                  u32 user_level, u32 user_max)
+{
+       struct intel_panel *panel = &connector->panel;
+
+       return scale(user_level, 0, user_max,
+                    panel->backlight.min, panel->backlight.max);
+}
+
+/* Scale user_level in range [0..user_max] to [0..hw_max], clamping the result
+ * to [hw_min..hw_max]. */
+static inline u32 clamp_user_to_hw(struct intel_connector *connector,
+                                  u32 user_level, u32 user_max)
+{
+       struct intel_panel *panel = &connector->panel;
+       u32 hw_level;
+
+       hw_level = scale(user_level, 0, user_max, 0, panel->backlight.max);
+       hw_level = clamp(hw_level, panel->backlight.min, panel->backlight.max);
+
+       return hw_level;
+}
+
+/* Scale hw_level in range [hw_min..hw_max] to [0..user_max]. */
+static inline u32 scale_hw_to_user(struct intel_connector *connector,
+                                  u32 hw_level, u32 user_max)
+{
+       struct intel_panel *panel = &connector->panel;
+
+       return scale(hw_level, panel->backlight.min, panel->backlight.max,
+                    0, user_max);
+}
+
 static u32 intel_panel_compute_brightness(struct intel_connector *connector,
                                          u32 val)
 {
@@ -557,17 +620,16 @@ intel_panel_actually_set_backlight(struct intel_connector *connector, u32 level)
        dev_priv->display.set_backlight(connector, level);
 }
 
-/* set backlight brightness to level in range [0..max] */
-void intel_panel_set_backlight(struct intel_connector *connector, u32 level,
-                              u32 max)
+/* set backlight brightness to level in range [0..max], scaling wrt hw min */
+static void intel_panel_set_backlight(struct intel_connector *connector,
+                                     u32 user_level, u32 user_max)
 {
        struct drm_device *dev = connector->base.dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
        struct intel_panel *panel = &connector->panel;
        enum pipe pipe = intel_get_pipe_from_connector(connector);
-       u32 freq;
+       u32 hw_level;
        unsigned long flags;
-       u64 n;
 
        if (!panel->backlight.present || pipe == INVALID_PIPE)
                return;
@@ -576,18 +638,46 @@ void intel_panel_set_backlight(struct intel_connector *connector, u32 level,
 
        WARN_ON(panel->backlight.max == 0);
 
-       /* scale to hardware max, but be careful to not overflow */
-       freq = panel->backlight.max;
-       n = (u64)level * freq;
-       do_div(n, max);
-       level = n;
+       hw_level = scale_user_to_hw(connector, user_level, user_max);
+       panel->backlight.level = hw_level;
+
+       if (panel->backlight.enabled)
+               intel_panel_actually_set_backlight(connector, hw_level);
+
+       spin_unlock_irqrestore(&dev_priv->backlight_lock, flags);
+}
+
+/* set backlight brightness to level in range [0..max], assuming hw min is
+ * respected.
+ */
+void intel_panel_set_backlight_acpi(struct intel_connector *connector,
+                                   u32 user_level, u32 user_max)
+{
+       struct drm_device *dev = connector->base.dev;
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_panel *panel = &connector->panel;
+       enum pipe pipe = intel_get_pipe_from_connector(connector);
+       u32 hw_level;
+       unsigned long flags;
+
+       if (!panel->backlight.present || pipe == INVALID_PIPE)
+               return;
+
+       spin_lock_irqsave(&dev_priv->backlight_lock, flags);
+
+       WARN_ON(panel->backlight.max == 0);
+
+       hw_level = clamp_user_to_hw(connector, user_level, user_max);
+       panel->backlight.level = hw_level;
 
-       panel->backlight.level = level;
        if (panel->backlight.device)
-               panel->backlight.device->props.brightness = level;
+               panel->backlight.device->props.brightness =
+                       scale_hw_to_user(connector,
+                                        panel->backlight.level,
+                                        panel->backlight.device->props.max_brightness);
 
        if (panel->backlight.enabled)
-               intel_panel_actually_set_backlight(connector, level);
+               intel_panel_actually_set_backlight(connector, hw_level);
 
        spin_unlock_irqrestore(&dev_priv->backlight_lock, flags);
 }
@@ -661,6 +751,8 @@ void intel_panel_disable_backlight(struct intel_connector *connector)
 
        spin_lock_irqsave(&dev_priv->backlight_lock, flags);
 
+       if (panel->backlight.device)
+               panel->backlight.device->props.power = FB_BLANK_POWERDOWN;
        panel->backlight.enabled = false;
        dev_priv->display.disable_backlight(connector);
 
@@ -860,11 +952,15 @@ void intel_panel_enable_backlight(struct intel_connector *connector)
                panel->backlight.level = panel->backlight.max;
                if (panel->backlight.device)
                        panel->backlight.device->props.brightness =
-                               panel->backlight.level;
+                               scale_hw_to_user(connector,
+                                                panel->backlight.level,
+                                                panel->backlight.device->props.max_brightness);
        }
 
        dev_priv->display.enable_backlight(connector);
        panel->backlight.enabled = true;
+       if (panel->backlight.device)
+               panel->backlight.device->props.power = FB_BLANK_UNBLANK;
 
        spin_unlock_irqrestore(&dev_priv->backlight_lock, flags);
 }
@@ -873,6 +969,7 @@ void intel_panel_enable_backlight(struct intel_connector *connector)
 static int intel_backlight_device_update_status(struct backlight_device *bd)
 {
        struct intel_connector *connector = bl_get_data(bd);
+       struct intel_panel *panel = &connector->panel;
        struct drm_device *dev = connector->base.dev;
 
        drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
@@ -880,6 +977,22 @@ static int intel_backlight_device_update_status(struct backlight_device *bd)
                      bd->props.brightness, bd->props.max_brightness);
        intel_panel_set_backlight(connector, bd->props.brightness,
                                  bd->props.max_brightness);
+
+       /*
+        * Allow flipping bl_power as a sub-state of enabled. Sadly the
+        * backlight class device does not make it easy to to differentiate
+        * between callbacks for brightness and bl_power, so our backlight_power
+        * callback needs to take this into account.
+        */
+       if (panel->backlight.enabled) {
+               if (panel->backlight_power) {
+                       bool enable = bd->props.power == FB_BLANK_UNBLANK;
+                       panel->backlight_power(connector, enable);
+               }
+       } else {
+               bd->props.power = FB_BLANK_POWERDOWN;
+       }
+
        drm_modeset_unlock(&dev->mode_config.connection_mutex);
        return 0;
 }
@@ -889,11 +1002,15 @@ static int intel_backlight_device_get_brightness(struct backlight_device *bd)
        struct intel_connector *connector = bl_get_data(bd);
        struct drm_device *dev = connector->base.dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
+       u32 hw_level;
        int ret;
 
        intel_runtime_pm_get(dev_priv);
        drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
-       ret = intel_panel_get_backlight(connector);
+
+       hw_level = intel_panel_get_backlight(connector);
+       ret = scale_hw_to_user(connector, hw_level, bd->props.max_brightness);
+
        drm_modeset_unlock(&dev->mode_config.connection_mutex);
        intel_runtime_pm_put(dev_priv);
 
@@ -913,12 +1030,24 @@ static int intel_backlight_device_register(struct intel_connector *connector)
        if (WARN_ON(panel->backlight.device))
                return -ENODEV;
 
-       BUG_ON(panel->backlight.max == 0);
+       WARN_ON(panel->backlight.max == 0);
 
        memset(&props, 0, sizeof(props));
        props.type = BACKLIGHT_RAW;
-       props.brightness = panel->backlight.level;
+
+       /*
+        * Note: Everything should work even if the backlight device max
+        * presented to the userspace is arbitrarily chosen.
+        */
        props.max_brightness = panel->backlight.max;
+       props.brightness = scale_hw_to_user(connector,
+                                           panel->backlight.level,
+                                           props.max_brightness);
+
+       if (panel->backlight.enabled)
+               props.power = FB_BLANK_UNBLANK;
+       else
+               props.power = FB_BLANK_POWERDOWN;
 
        /*
         * Note: using the same name independent of the connector prevents
@@ -964,6 +1093,19 @@ static void intel_backlight_device_unregister(struct intel_connector *connector)
  * XXX: Query mode clock or hardware clock and program PWM modulation frequency
  * appropriately when it's 0. Use VBT and/or sane defaults.
  */
+static u32 get_backlight_min_vbt(struct intel_connector *connector)
+{
+       struct drm_device *dev = connector->base.dev;
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_panel *panel = &connector->panel;
+
+       WARN_ON(panel->backlight.max == 0);
+
+       /* vbt value is a coefficient in range [0..255] */
+       return scale(dev_priv->vbt.backlight.min_brightness, 0, 255,
+                    0, panel->backlight.max);
+}
+
 static int bdw_setup_backlight(struct intel_connector *connector)
 {
        struct drm_device *dev = connector->base.dev;
@@ -979,6 +1121,8 @@ static int bdw_setup_backlight(struct intel_connector *connector)
        if (!panel->backlight.max)
                return -ENODEV;
 
+       panel->backlight.min = get_backlight_min_vbt(connector);
+
        val = bdw_get_backlight(connector);
        panel->backlight.level = intel_panel_compute_brightness(connector, val);
 
@@ -1003,6 +1147,8 @@ static int pch_setup_backlight(struct intel_connector *connector)
        if (!panel->backlight.max)
                return -ENODEV;
 
+       panel->backlight.min = get_backlight_min_vbt(connector);
+
        val = pch_get_backlight(connector);
        panel->backlight.level = intel_panel_compute_brightness(connector, val);
 
@@ -1035,6 +1181,8 @@ static int i9xx_setup_backlight(struct intel_connector *connector)
        if (!panel->backlight.max)
                return -ENODEV;
 
+       panel->backlight.min = get_backlight_min_vbt(connector);
+
        val = i9xx_get_backlight(connector);
        panel->backlight.level = intel_panel_compute_brightness(connector, val);
 
@@ -1062,6 +1210,8 @@ static int i965_setup_backlight(struct intel_connector *connector)
        if (!panel->backlight.max)
                return -ENODEV;
 
+       panel->backlight.min = get_backlight_min_vbt(connector);
+
        val = i9xx_get_backlight(connector);
        panel->backlight.level = intel_panel_compute_brightness(connector, val);
 
@@ -1099,6 +1249,8 @@ static int vlv_setup_backlight(struct intel_connector *connector)
        if (!panel->backlight.max)
                return -ENODEV;
 
+       panel->backlight.min = get_backlight_min_vbt(connector);
+
        val = _vlv_get_backlight(dev, PIPE_A);
        panel->backlight.level = intel_panel_compute_brightness(connector, val);
 
This page took 0.028335 seconds and 5 git commands to generate.