drm/i915: initialize backlight max from VBT

Normally we determine the backlight PWM modulation frequency (which we
also use as backlight max value) from the backlight registers at module
load time, expecting the registers have been initialized by the BIOS. If
this is not the case, we fail.

The VBT contains the backlight modulation frequency in Hz. Add platform
specific functions to convert the frequency in Hz to backlight PWM
modulation frequency, and use them to initialize the backlight when the
registers are not initialized by the BIOS.

v2: Fix SPT and VLV. Thanks to Clint for the VLV code.

Cc: Clint Taylor <clinton.a.taylor@intel.com>
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
Reviewed-by: Clint Taylor <Clinton.A.Taylor@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 482ddb7..3e46187 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -671,6 +671,8 @@
 			      uint32_t level);
 	void (*disable_backlight)(struct intel_connector *connector);
 	void (*enable_backlight)(struct intel_connector *connector);
+	uint32_t (*backlight_hz_to_pwm)(struct intel_connector *connector,
+					uint32_t hz);
 };
 
 enum forcewake_domain_id {
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 5811eeb..65b5682 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -4648,6 +4648,7 @@
 
 #define CBR1_VLV			(VLV_DISPLAY_BASE + 0x70400)
 #define  CBR_PND_DEADLINE_DISABLE	(1<<31)
+#define  CBR_PWM_CLOCK_MUX_SELECT	(1<<30)
 
 /* FIFO watermark sizes etc */
 #define G4X_FIFO_LINE_SIZE	64
@@ -6325,9 +6326,11 @@
 #define  FDI_PHASE_SYNC_OVR(pipe) (1<<(FDIA_PHASE_SYNC_SHIFT_OVR - ((pipe) * 2)))
 #define  FDI_PHASE_SYNC_EN(pipe) (1<<(FDIA_PHASE_SYNC_SHIFT_EN - ((pipe) * 2)))
 #define  FDI_BC_BIFURCATION_SELECT	(1 << 12)
+#define  SPT_PWM_GRANULARITY		(1<<0)
 #define SOUTH_CHICKEN2		0xc2004
 #define  FDI_MPHY_IOSFSB_RESET_STATUS	(1<<13)
 #define  FDI_MPHY_IOSFSB_RESET_CTL	(1<<12)
+#define  LPT_PWM_GRANULARITY		(1<<5)
 #define  DPLS_EDP_PPS_FIX_DIS		(1<<0)
 
 #define _FDI_RXA_CHICKEN         0xc200c
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index 681686e..d3a312c 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -1224,10 +1224,149 @@
 #endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */
 
 /*
- * Note: The setup hooks can't assume pipe is set!
+ * SPT: This value represents the period of the PWM stream in clock periods
+ * multiplied by 16 (default increment) or 128 (alternate increment selected in
+ * SCHICKEN_1 bit 0). PWM clock is 24 MHz.
+ */
+static u32 spt_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 mul, clock;
+
+	if (I915_READ(SOUTH_CHICKEN1) & SPT_PWM_GRANULARITY)
+		mul = 128;
+	else
+		mul = 16;
+
+	clock = MHz(24);
+
+	return clock / (pwm_freq_hz * mul);
+}
+
+/*
+ * LPT: This value represents the period of the PWM stream in clock periods
+ * multiplied by 128 (default increment) or 16 (alternate increment, selected in
+ * LPT SOUTH_CHICKEN2 register bit 5).
+ */
+static u32 lpt_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 mul, clock;
+
+	if (I915_READ(SOUTH_CHICKEN2) & LPT_PWM_GRANULARITY)
+		mul = 16;
+	else
+		mul = 128;
+
+	if (dev_priv->pch_id == INTEL_PCH_LPT_DEVICE_ID_TYPE)
+		clock = MHz(135); /* LPT:H */
+	else
+		clock = MHz(24); /* LPT:LP */
+
+	return clock / (pwm_freq_hz * mul);
+}
+
+/*
+ * ILK/SNB/IVB: This value represents the period of the PWM stream in PCH
+ * display raw clocks multiplied by 128.
+ */
+static u32 pch_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	int clock = MHz(intel_pch_rawclk(dev));
+
+	return clock / (pwm_freq_hz * 128);
+}
+
+/*
+ * Gen2: This field determines the number of time base events (display core
+ * clock frequency/32) in total for a complete cycle of modulated backlight
+ * control.
  *
- * XXX: Query mode clock or hardware clock and program PWM modulation frequency
- * appropriately when it's 0. Use VBT and/or sane defaults.
+ * Gen3: A time base event equals the display core clock ([DevPNV] HRAW clock)
+ * divided by 32.
+ */
+static u32 i9xx_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	int clock;
+
+	if (IS_PINEVIEW(dev))
+		clock = intel_hrawclk(dev);
+	else
+		clock = 1000 * dev_priv->display.get_display_clock_speed(dev);
+
+	return clock / (pwm_freq_hz * 32);
+}
+
+/*
+ * Gen4: This value represents the period of the PWM stream in display core
+ * clocks multiplied by 128.
+ */
+static u32 i965_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	int clock = 1000 * dev_priv->display.get_display_clock_speed(dev);
+
+	return clock / (pwm_freq_hz * 128);
+}
+
+/*
+ * VLV: This value represents the period of the PWM stream in display core
+ * clocks ([DevCTG] 200MHz HRAW clocks) multiplied by 128 or 25MHz S0IX clocks
+ * multiplied by 16. CHV uses a 19.2MHz S0IX clock.
+ */
+static u32 vlv_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	int clock;
+
+	if ((I915_READ(CBR1_VLV) & CBR_PWM_CLOCK_MUX_SELECT) == 0) {
+		if (IS_CHERRYVIEW(dev))
+			return KHz(19200) / (pwm_freq_hz * 16);
+		else
+			return MHz(25) / (pwm_freq_hz * 16);
+	} else {
+		clock = intel_hrawclk(dev);
+		return MHz(clock) / (pwm_freq_hz * 128);
+	}
+}
+
+static u32 get_backlight_max_vbt(struct intel_connector *connector)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u16 pwm_freq_hz = dev_priv->vbt.backlight.pwm_freq_hz;
+	u32 pwm;
+
+	if (!pwm_freq_hz) {
+		DRM_DEBUG_KMS("backlight frequency not specified in VBT\n");
+		return 0;
+	}
+
+	if (!dev_priv->display.backlight_hz_to_pwm) {
+		DRM_DEBUG_KMS("backlight frequency setting from VBT currently not supported on this platform\n");
+		return 0;
+	}
+
+	pwm = dev_priv->display.backlight_hz_to_pwm(connector, pwm_freq_hz);
+	if (!pwm) {
+		DRM_DEBUG_KMS("backlight frequency conversion failed\n");
+		return 0;
+	}
+
+	DRM_DEBUG_KMS("backlight frequency %u Hz from VBT\n", pwm_freq_hz);
+
+	return pwm;
+}
+
+/*
+ * Note: The setup hooks can't assume pipe is set!
  */
 static u32 get_backlight_min_vbt(struct intel_connector *connector)
 {
@@ -1267,6 +1406,10 @@
 
 	pch_ctl2 = I915_READ(BLC_PWM_PCH_CTL2);
 	panel->backlight.max = pch_ctl2 >> 16;
+
+	if (!panel->backlight.max)
+		panel->backlight.max = get_backlight_max_vbt(connector);
+
 	if (!panel->backlight.max)
 		return -ENODEV;
 
@@ -1293,6 +1436,10 @@
 
 	pch_ctl2 = I915_READ(BLC_PWM_PCH_CTL2);
 	panel->backlight.max = pch_ctl2 >> 16;
+
+	if (!panel->backlight.max)
+		panel->backlight.max = get_backlight_max_vbt(connector);
+
 	if (!panel->backlight.max)
 		return -ENODEV;
 
@@ -1324,12 +1471,18 @@
 		panel->backlight.active_low_pwm = ctl & BLM_POLARITY_PNV;
 
 	panel->backlight.max = ctl >> 17;
-	if (panel->backlight.combination_mode)
-		panel->backlight.max *= 0xff;
+
+	if (!panel->backlight.max) {
+		panel->backlight.max = get_backlight_max_vbt(connector);
+		panel->backlight.max >>= 1;
+	}
 
 	if (!panel->backlight.max)
 		return -ENODEV;
 
+	if (panel->backlight.combination_mode)
+		panel->backlight.max *= 0xff;
+
 	panel->backlight.min = get_backlight_min_vbt(connector);
 
 	val = i9xx_get_backlight(connector);
@@ -1353,12 +1506,16 @@
 
 	ctl = I915_READ(BLC_PWM_CTL);
 	panel->backlight.max = ctl >> 16;
-	if (panel->backlight.combination_mode)
-		panel->backlight.max *= 0xff;
+
+	if (!panel->backlight.max)
+		panel->backlight.max = get_backlight_max_vbt(connector);
 
 	if (!panel->backlight.max)
 		return -ENODEV;
 
+	if (panel->backlight.combination_mode)
+		panel->backlight.max *= 0xff;
+
 	panel->backlight.min = get_backlight_min_vbt(connector);
 
 	val = i9xx_get_backlight(connector);
@@ -1398,6 +1555,10 @@
 
 	ctl = I915_READ(VLV_BLC_PWM_CTL(pipe));
 	panel->backlight.max = ctl >> 16;
+
+	if (!panel->backlight.max)
+		panel->backlight.max = get_backlight_max_vbt(connector);
+
 	if (!panel->backlight.max)
 		return -ENODEV;
 
@@ -1424,6 +1585,10 @@
 	panel->backlight.active_low_pwm = pwm_ctl & BXT_BLC_PWM_POLARITY;
 
 	panel->backlight.max = I915_READ(BXT_BLC_PWM_FREQ1);
+
+	if (!panel->backlight.max)
+		panel->backlight.max = get_backlight_max_vbt(connector);
+
 	if (!panel->backlight.max)
 		return -ENODEV;
 
@@ -1537,12 +1702,17 @@
 		dev_priv->display.disable_backlight = lpt_disable_backlight;
 		dev_priv->display.set_backlight = lpt_set_backlight;
 		dev_priv->display.get_backlight = lpt_get_backlight;
+		if (HAS_PCH_LPT(dev))
+			dev_priv->display.backlight_hz_to_pwm = lpt_hz_to_pwm;
+		else
+			dev_priv->display.backlight_hz_to_pwm = spt_hz_to_pwm;
 	} else if (HAS_PCH_SPLIT(dev)) {
 		dev_priv->display.setup_backlight = pch_setup_backlight;
 		dev_priv->display.enable_backlight = pch_enable_backlight;
 		dev_priv->display.disable_backlight = pch_disable_backlight;
 		dev_priv->display.set_backlight = pch_set_backlight;
 		dev_priv->display.get_backlight = pch_get_backlight;
+		dev_priv->display.backlight_hz_to_pwm = pch_hz_to_pwm;
 	} else if (IS_VALLEYVIEW(dev)) {
 		if (dev_priv->vbt.has_mipi) {
 			dev_priv->display.setup_backlight = pwm_setup_backlight;
@@ -1556,6 +1726,7 @@
 			dev_priv->display.disable_backlight = vlv_disable_backlight;
 			dev_priv->display.set_backlight = vlv_set_backlight;
 			dev_priv->display.get_backlight = vlv_get_backlight;
+			dev_priv->display.backlight_hz_to_pwm = vlv_hz_to_pwm;
 		}
 	} else if (IS_GEN4(dev)) {
 		dev_priv->display.setup_backlight = i965_setup_backlight;
@@ -1563,12 +1734,14 @@
 		dev_priv->display.disable_backlight = i965_disable_backlight;
 		dev_priv->display.set_backlight = i9xx_set_backlight;
 		dev_priv->display.get_backlight = i9xx_get_backlight;
+		dev_priv->display.backlight_hz_to_pwm = i965_hz_to_pwm;
 	} else {
 		dev_priv->display.setup_backlight = i9xx_setup_backlight;
 		dev_priv->display.enable_backlight = i9xx_enable_backlight;
 		dev_priv->display.disable_backlight = i9xx_disable_backlight;
 		dev_priv->display.set_backlight = i9xx_set_backlight;
 		dev_priv->display.get_backlight = i9xx_get_backlight;
+		dev_priv->display.backlight_hz_to_pwm = i9xx_hz_to_pwm;
 	}
 }