From 9d064966d8495038921d0e731c0bfca0cd58d244 Mon Sep 17 00:00:00 2001
From: Jerome Glisse <glisse@freedesktop.org>
Date: Sat, 8 Dec 2007 00:45:33 +0100
Subject: radeon_ms: fix pll computation to follow hw constraint

---
 shared-core/radeon_ms_crtc.c   | 127 +++++++++++++++++++++++++----------------
 shared-core/radeon_ms_family.c |   6 +-
 2 files changed, 82 insertions(+), 51 deletions(-)

diff --git a/shared-core/radeon_ms_crtc.c b/shared-core/radeon_ms_crtc.c
index a9387f96..fe89e5e4 100644
--- a/shared-core/radeon_ms_crtc.c
+++ b/shared-core/radeon_ms_crtc.c
@@ -379,13 +379,26 @@ static void radeon_ms_crtc_mode_prepare(struct drm_crtc *crtc)
 	crtc->funcs->dpms(crtc, DPMSModeOff);
 }
 
-/* Compute n/d with rounding */
-static int radeon_div(int n, int d)
+/* compute PLL registers values for requested video mode */
+static int radeon_pll1_constraint(int clock, int rdiv,
+                                  int fdiv, int pdiv,
+                                  int rfrq, int pfrq)
 {
-	return (n + (d / 2)) / d;
+    int dfrq;
+
+    if (rdiv < 2 || fdiv < 4) {
+        return 0;
+    }
+    dfrq = rfrq / rdiv;
+    if (dfrq < 2000 || dfrq > 3300) {
+        return 0;
+    }
+    if (pfrq < 125000 || pfrq > 250000) {
+        return 0;
+    }
+    return 1;
 }
 
-/* compute PLL registers values for requested video mode */
 static void radeon_pll1_compute(struct drm_crtc *crtc,
 				struct drm_display_mode *mode)
 {
@@ -410,65 +423,73 @@ static void radeon_pll1_compute(struct drm_crtc *crtc,
 	};
 	struct drm_radeon_private *dev_priv = crtc->dev->dev_private;
 	struct radeon_state *state = &dev_priv->driver_state;
-	unsigned long freq = mode->clock / 10;
-	int pll_output_freq;
-	int min_rcenter_dist;
-	int rcenter_dist;
-	int post_divider;
-	int post_divider_id;
-	int ref_div;
-	int fb_div;
+	int clock = mode->clock;
+	int rfrq = dev_priv->properties->pll_reference_freq;
+	int pdiv = 1;
+	int pdiv_id = 0;
+	int rdiv_best = 2;
+	int fdiv_best = 4;
+	int tfrq_best = 0;
+	int pfrq_best = 0;
+	int diff_cpfrq_best = 350000;
 	int vco_freq;
 	int vco_gain;
+	int rdiv = 0;
+	int fdiv = 0;
+	int tfrq = 35000;
+	int pfrq = 35000;
+	int diff_cpfrq = 350000;
 
 	/* clamp frequency into pll [min; max] frequency range */
-	if (freq > dev_priv->properties->pll_max_pll_freq) {
-		freq = dev_priv->properties->pll_max_pll_freq;
+	if (clock > dev_priv->properties->pll_max_pll_freq) {
+		clock = dev_priv->properties->pll_max_pll_freq;
 	}
-	if (freq < dev_priv->properties->pll_min_pll_freq) {
-		freq = dev_priv->properties->pll_min_pll_freq;
+	if ((clock * 12) < dev_priv->properties->pll_min_pll_freq) {
+		clock = dev_priv->properties->pll_min_pll_freq / 12;
 	}
-	/* select divider so that pll output frequency is the nearest to
-	 * the center of [350; 125]Mhz range */
-	min_rcenter_dist = 350 * 100;
-	post_divider = post_divs[0].divider;
-	post_divider_id = post_divs[0].divider_id;
+
+	/* maximize pll_ref_div while staying in boundary and minimizing
+	 * the difference btw target frequency and programmed frequency */
 	for (post_div = &post_divs[0]; post_div->divider; ++post_div) {
-		if (post_div->divider == 0)
+		if (post_div->divider == 0) {
 			break;
-		/* pll output frequency (before post divider) */
-		pll_output_freq = post_div->divider * freq;
-		/* compute distance to [350; 125] range center*/
-		rcenter_dist = abs(pll_output_freq - 11250);
-		if (rcenter_dist < min_rcenter_dist) {
-			min_rcenter_dist = rcenter_dist;
-			post_divider = post_div->divider;
-			post_divider_id = post_div->divider_id;
+		}
+		tfrq = clock * post_div->divider;
+		for (fdiv = 1023; fdiv >= 4; fdiv--) {
+			rdiv = (fdiv * rfrq) / tfrq;
+			if (radeon_pll1_constraint(clock, rdiv, fdiv,
+						pdiv, rfrq, tfrq)) {
+				pfrq = (fdiv * rfrq) / rdiv;
+				diff_cpfrq = pfrq - tfrq;
+				if ((diff_cpfrq >= 0 &&
+				     diff_cpfrq < diff_cpfrq_best) ||
+				    (diff_cpfrq == diff_cpfrq_best &&
+				     rdiv > rdiv_best)) {
+					rdiv_best = rdiv;
+					fdiv_best = fdiv;
+					tfrq_best = tfrq;
+					pfrq_best = pfrq;
+					pdiv = post_div->divider;
+					pdiv_id = post_div->divider_id;
+					diff_cpfrq_best = diff_cpfrq;
+				}
+			}
 		}
 	}
-	pll_output_freq = post_divider * freq;
-	/* select first feedback */
-	state->clock_cntl_index = REG_S(CLOCK_CNTL_INDEX, PPLL_DIV_SEL, 0);
-	/* set ref div so that ref_freq/ref_div is in middle of [2; 3.3]Mhz */
-	ref_div = dev_priv->properties->pll_reference_freq / 265;
-	state->ppll_ref_div = REG_S(PPLL_REF_DIV, PPLL_REF_DIV, ref_div) |
-		REG_S(PPLL_REF_DIV, PPLL_REF_DIV_ACC, ref_div);
-	fb_div = radeon_div(pll_output_freq * ref_div,
-			dev_priv->properties->pll_reference_freq);
-	state->ppll_div_0 = REG_S(PPLL_DIV_0, PPLL_FB0_DIV, fb_div) |
-		REG_S(PPLL_DIV_0, PPLL_POST0_DIV, post_divider_id);
-	/* configure vco gain */
-	state->ppll_cntl = PPLL_R(PPLL_CNTL);
-	vco_gain = REG_G(PPLL_CNTL, PPLL_PVG, state->ppll_cntl); 
-	vco_freq = (fb_div * dev_priv->properties->pll_reference_freq) /
-		ref_div;
+	state->ppll_ref_div =
+		REG_S(PPLL_REF_DIV, PPLL_REF_DIV, rdiv_best) |
+		REG_S(PPLL_REF_DIV, PPLL_REF_DIV_ACC, rdiv_best);
+	state->ppll_div_0 = REG_S(PPLL_DIV_0, PPLL_FB0_DIV, fdiv_best) |
+		REG_S(PPLL_DIV_0, PPLL_POST0_DIV, pdiv_id);
+
+	vco_freq = (fdiv_best * rfrq) / rdiv_best;
 	/* This is horribly crude: the VCO frequency range is divided into
 	 * 3 parts, each part having a fixed PLL gain value.
 	 */
-        if (vco_freq >= 30000) {
+        if (vco_freq >= 300000) {
 		/* [300..max] MHz : 7 */
 		vco_gain = 7;
-	} else if (vco_freq >= 18000) {
+	} else if (vco_freq >= 180000) {
 		/* [180..300) MHz : 4 */
 		vco_gain = 4;
 	} else {
@@ -479,6 +500,16 @@ static void radeon_pll1_compute(struct drm_crtc *crtc,
 	state->vclk_ecp_cntl |= REG_S(VCLK_ECP_CNTL, VCLK_SRC_SEL,
 			VCLK_SRC_SEL__PPLLCLK);
 	state->htotal_cntl = 0;
+	DRM_INFO("rdiv: %d\n", rdiv_best);
+	DRM_INFO("fdiv: %d\n", fdiv_best);
+	DRM_INFO("pdiv: %d\n", pdiv);
+	DRM_INFO("pdiv: %d\n", pdiv_id);
+	DRM_INFO("tfrq: %d\n", tfrq_best);
+	DRM_INFO("pfrq: %d\n", pfrq_best);
+	DRM_INFO("PPLL_REF_DIV:  0x%08X\n", state->ppll_ref_div);
+	DRM_INFO("PPLL_DIV_0:    0x%08X\n", state->ppll_div_0);
+	DRM_INFO("PPLL_CNTL:     0x%08X\n", state->ppll_cntl);
+	DRM_INFO("VCLK_ECP_CNTL: 0x%08X\n", state->vclk_ecp_cntl);
 }
 
 static void radeon_ms_crtc1_mode_set(struct drm_crtc *crtc,
diff --git a/shared-core/radeon_ms_family.c b/shared-core/radeon_ms_family.c
index ea5f6ca3..779595d6 100644
--- a/shared-core/radeon_ms_family.c
+++ b/shared-core/radeon_ms_family.c
@@ -76,7 +76,7 @@ static struct radeon_ms_connector radeon_ms_dvi_i_2 = {
 static struct radeon_ms_properties properties[] = {
 	/* default only one VGA connector */
 	{
-		0, 0, 2700, 2500, 20000, 1, 1, 1, 1,
+		0, 0, 27000, 25000, 200000, 1, 1, 1, 1,
 		{
 			&radeon_ms_dac1, NULL, NULL, NULL, NULL, NULL, NULL,
 			NULL
@@ -87,7 +87,7 @@ static struct radeon_ms_properties properties[] = {
 		}
 	},
 	{
-		0x1043, 0x176, 2700, 2500, 20000, 1, 1, 1, 1,
+		0x1043, 0x176, 27000, 25000, 200000, 1, 1, 1, 1,
 		{
 			&radeon_ms_dac1, &radeon_ms_dac2, NULL, NULL, NULL,
 			NULL, NULL, NULL
@@ -98,7 +98,7 @@ static struct radeon_ms_properties properties[] = {
 		}
 	},
 	{
-		0x1002, 0x4150, 2700, 2500, 20000, 1, 1, 1, 1,
+		0x1002, 0x4150, 27000, 25000, 200000, 1, 1, 1, 1,
 		{
 			&radeon_ms_dac1, &radeon_ms_dac2, NULL, NULL, NULL,
 			NULL, NULL, NULL
-- 
cgit v1.2.3