drm/radeon/kms/atom: add support for spread spectrum (v2)

Spread spectrum is a periodic disturbance added
to the feedback divider to change the pixel clock
periodically to reduce interference.

Only enabled on LVDS.

v2: add support for r4xx and fix DCE 3

Signed-off-by: Alex Deucher <alexdeucher@gmail.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
diff --git a/drivers/gpu/drm/radeon/radeon_atombios.c b/drivers/gpu/drm/radeon/radeon_atombios.c
index cec6791..1872925 100644
--- a/drivers/gpu/drm/radeon/radeon_atombios.c
+++ b/drivers/gpu/drm/radeon/radeon_atombios.c
@@ -771,6 +771,46 @@
 	return false;
 }
 
+static struct radeon_atom_ss *radeon_atombios_get_ss_info(struct
+							  radeon_encoder
+							  *encoder,
+							  int id)
+{
+	struct drm_device *dev = encoder->base.dev;
+	struct radeon_device *rdev = dev->dev_private;
+	struct radeon_mode_info *mode_info = &rdev->mode_info;
+	int index = GetIndexIntoMasterTable(DATA, PPLL_SS_Info);
+	uint16_t data_offset;
+	struct _ATOM_SPREAD_SPECTRUM_INFO *ss_info;
+	uint8_t frev, crev;
+	struct radeon_atom_ss *ss = NULL;
+
+	if (id > ATOM_MAX_SS_ENTRY)
+		return NULL;
+
+	atom_parse_data_header(mode_info->atom_context, index, NULL, &frev,
+			       &crev, &data_offset);
+
+	ss_info =
+	    (struct _ATOM_SPREAD_SPECTRUM_INFO *)(mode_info->atom_context->bios + data_offset);
+
+	if (ss_info) {
+		ss =
+		    kzalloc(sizeof(struct radeon_atom_ss), GFP_KERNEL);
+
+		if (!ss)
+			return NULL;
+
+		ss->percentage = le16_to_cpu(ss_info->asSS_Info[id].usSpreadSpectrumPercentage);
+		ss->type = ss_info->asSS_Info[id].ucSpreadSpectrumType;
+		ss->step = ss_info->asSS_Info[id].ucSS_Step;
+		ss->delay = ss_info->asSS_Info[id].ucSS_Delay;
+		ss->range = ss_info->asSS_Info[id].ucSS_Range;
+		ss->refdiv = ss_info->asSS_Info[id].ucRecommendedRef_Div;
+	}
+	return ss;
+}
+
 union lvds_info {
 	struct _ATOM_LVDS_INFO info;
 	struct _ATOM_LVDS_INFO_V12 info_12;
@@ -826,6 +866,8 @@
 		/* set crtc values */
 		drm_mode_set_crtcinfo(&lvds->native_mode, CRTC_INTERLACE_HALVE_V);
 
+		lvds->ss = radeon_atombios_get_ss_info(encoder, lvds_info->info.ucSS_Id);
+
 		encoder->native_mode = lvds->native_mode;
 	}
 	return lvds;