[Openchrome-devel] drm-openchrome: drivers/gpu/drm

James Simmons jsimmons at kemper.freedesktop.org
Sun Mar 24 09:26:00 PDT 2013


 drivers/gpu/drm/via/via_hdmi.c |  692 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 692 insertions(+)

New commits:
commit 2be4f83bedda4f62d6a9d838c5097b75615c0cf9
Author: James Simmons <jsimmons at infradead.org>
Date:   Sun Mar 24 12:25:51 2013 -0400

    Initial support for HDMI. Currently DVI monitor attached to a HDMI port is not supported nor is audio not implemented. A mode can be set but I see problems with setting the proper resolution. Will be addressed in a latter patch.

diff --git a/drivers/gpu/drm/via/via_hdmi.c b/drivers/gpu/drm/via/via_hdmi.c
new file mode 100644
index 0000000..77e648f
--- /dev/null
+++ b/drivers/gpu/drm/via/via_hdmi.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright © 2013 James Simmons
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *	James Simmons <jsimmons at infradead.org>
+ */
+#include "via_drv.h"
+
+#define HDMI_AUDIO_ENABLED	BIT(0)
+#define HDMI_SINK		BIT(1)
+#define HDMI_COLOR_RANGE	BIT(2)
+
+/*
+ * Routines for controlling stuff on the HDMI port
+ */
+static const struct drm_encoder_funcs via_hdmi_enc_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static void
+via_hdmi_enc_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct drm_via_private *dev_priv = encoder->dev->dev_private;
+
+	switch (mode) {
+	case DRM_MODE_DPMS_SUSPEND:
+	case DRM_MODE_DPMS_STANDBY:
+	case DRM_MODE_DPMS_OFF:
+		/* disable HDMI */
+		VIA_WRITE_MASK(0xC280, 0x0, 0x2);
+		break;
+
+	case DRM_MODE_DPMS_ON:
+	default:
+		/* enable band gap */
+		VIA_WRITE_MASK(0xC740, BIT(0), BIT(0));
+		/* enable video */
+		VIA_WRITE_MASK(0xC640, BIT(3), BIT(3));
+		/* enable HDMI */
+		VIA_WRITE_MASK(0xC280, BIT(1), BIT(1));
+		break;
+	}
+}
+
+static bool
+via_hdmi_enc_mode_fixup(struct drm_encoder *encoder,
+		 const struct drm_display_mode *mode,
+		 struct drm_display_mode *adjusted_mode)
+{
+	uint32_t panelHSyncTime = 0, panelHBlankStart = 0, newHBlankStart = 0;
+	uint32_t panelVSyncTime = 0, panelVBlankStart = 0, newVBlankStart = 0;
+	uint32_t left_border = 0, right_border = 0;
+	uint32_t top_border = 0, bottom_border = 0;
+
+	if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) {
+                /* when interlace mode,
+                 * we should consider halve vertical timings. */
+                panelHSyncTime = adjusted_mode->hsync_end -
+                        adjusted_mode->hsync_start;
+                panelVSyncTime = adjusted_mode->vsync_end / 2 -
+                        adjusted_mode->vsync_start / 2;
+                panelHBlankStart = adjusted_mode->hdisplay;
+                panelVBlankStart = adjusted_mode->vdisplay / 2;
+                newHBlankStart = adjusted_mode->hdisplay - left_border;
+                newVBlankStart = adjusted_mode->vdisplay / 2 - top_border;
+
+                adjusted_mode->hdisplay =
+                        adjusted_mode->hdisplay - left_border - right_border;
+                adjusted_mode->hsync_start =
+                        (adjusted_mode->hsync_start - panelHBlankStart) +
+                        newHBlankStart;
+                adjusted_mode->hsync_end =
+                        adjusted_mode->hsync_start + panelHSyncTime;
+
+                adjusted_mode->vdisplay = adjusted_mode->vdisplay / 2 -
+                        top_border - bottom_border;
+                adjusted_mode->vsync_start =
+                        (adjusted_mode->vsync_start / 2 - panelVBlankStart) +
+                        newVBlankStart;
+                adjusted_mode->vsync_end =
+                        adjusted_mode->vsync_start + panelVSyncTime;
+
+	} else {
+                panelHSyncTime =
+                        adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+                panelVSyncTime =
+                        adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+                panelHBlankStart = adjusted_mode->hdisplay;
+                panelVBlankStart = adjusted_mode->vdisplay;
+                newHBlankStart = adjusted_mode->hdisplay - left_border;
+                newVBlankStart = adjusted_mode->vdisplay - top_border;
+
+                adjusted_mode->hdisplay =
+                        adjusted_mode->hdisplay - left_border - right_border;
+                adjusted_mode->hsync_start =
+                        (adjusted_mode->hsync_start - panelHBlankStart) +
+                        newHBlankStart;
+                adjusted_mode->hsync_end =
+                        adjusted_mode->hsync_start + panelHSyncTime;
+
+                adjusted_mode->vdisplay =
+                        adjusted_mode->vdisplay - top_border - bottom_border;
+                adjusted_mode->vsync_start =
+                        (adjusted_mode->vsync_start - panelVBlankStart) +
+                        newVBlankStart;
+                adjusted_mode->vsync_end =
+                        adjusted_mode->vsync_start + panelVSyncTime;
+        }
+
+	/* Adjust crtc H and V */
+        adjusted_mode->crtc_hdisplay = adjusted_mode->hdisplay;
+        adjusted_mode->crtc_hblank_start = newHBlankStart;
+        adjusted_mode->crtc_hblank_end =
+                adjusted_mode->crtc_htotal - left_border;
+        adjusted_mode->crtc_hsync_start = adjusted_mode->hsync_start;
+        adjusted_mode->crtc_hsync_end = adjusted_mode->hsync_end;
+
+        adjusted_mode->crtc_vdisplay = adjusted_mode->vdisplay;
+        adjusted_mode->crtc_vblank_start = newVBlankStart;
+        adjusted_mode->crtc_vblank_end =
+                adjusted_mode->crtc_vtotal - top_border;
+        adjusted_mode->crtc_vsync_start = adjusted_mode->vsync_start;
+        adjusted_mode->crtc_vsync_end = adjusted_mode->vsync_end;
+
+	drm_mode_set_crtcinfo(adjusted_mode, 0);
+	return true;
+}
+
+static void
+via_hdmi_native_mode_set(struct via_crtc *iga, struct drm_display_mode *mode,
+			bool audio_off)
+{
+	struct drm_via_private *dev_priv = iga->base.dev->dev_private;
+	u32 reg_c280, reg_c284;
+	int max_packet, delay;
+	u8 value = BIT(0);
+
+	/* 135MHz ~ 270MHz */
+	if (mode->clock >= 135000)
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x00000000, 0xC0000000);
+	/* 67.5MHz ~ <135MHz */
+	else if (mode->clock >= 67500)
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x40000000, 0xC0000000);
+	/* 33.75MHz ~ <67.5MHz */
+	else if (mode->clock >= 33750)
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x80000000, 0xC0000000);
+	/* 25MHz ~ <33.75MHz */
+	else
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0xC0000000, 0xC0000000);
+
+	/* touch C282 when init HDMI by mode 720x576, 720x480,
+	 * or other modes */
+	if ((mode->hdisplay == 720) && (mode->vdisplay == 576))
+		VIA_WRITE(0xC280, 0x18232402);
+	else if ((mode->hdisplay == 720) && (mode->vdisplay == 480))
+		VIA_WRITE(0xC280, 0x181f2402);
+	else
+		VIA_WRITE(0xC280, 0x18330002);
+
+	/* init C280 */
+	reg_c280 = 0x18000002 | (VIA_READ(0xC280) & 0x40);
+	/* sync polarity */
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg_c280 |= BIT(10);
+
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg_c280 |= BIT(13);
+
+	/* calculate correct delay of regC280[22:16] */
+	if ((mode->crtc_hsync_start - mode->crtc_hdisplay) > (58 - 11))
+		delay = 0;
+	else
+		delay = 58 - (mode->crtc_hsync_start - mode->crtc_hdisplay) - 11;
+
+	/* calculate max_packet */
+	max_packet = (mode->crtc_hblank_end - mode->crtc_hsync_start - 16 - 11 - delay) / 32;
+	if (0 == delay)
+		delay = mode->crtc_hblank_end - mode->crtc_hsync_start - (32 * max_packet + 16 + 11);
+
+	reg_c280 |= (delay << 16);
+	VIA_WRITE(0xC280, reg_c280);
+	reg_c284 = 0x00000102 | (max_packet << 28);
+	/* power down to reset */
+	VIA_WRITE_MASK(0xC740, 0x00000000, 0x06000000);
+	/* enable DP data pass */
+	VIA_WRITE_MASK(DP_DATA_PASS_ENABLE_REG, 0x00000001, 0x00000001);
+	/* select HDMI mode */
+	VIA_WRITE_MASK(0xC748, 0, BIT(0));
+	if (audio_off) {
+		/* enable HDMI with DVI mode for disable audio. */
+		VIA_WRITE_MASK(0xC280, 0x40, 0x40);
+	} else {
+		/* enable HDMI with HDMI mode */
+		VIA_WRITE_MASK(0xC280, 0x0, 0x40);
+	}
+	/* select AC mode */
+	VIA_WRITE_MASK(0xC74C, 0x40, 0x40);
+	/* enable InfoFrame */
+	VIA_WRITE(0xC284, reg_c284);
+	/* set status of Lane0~3 */
+	VIA_WRITE_MASK(0xC744, 0x00FFFF82, 0x00FFFF82);
+	VIA_WRITE(0xC0B4, 0x12000000);
+	/* enable audio packet */
+	VIA_WRITE_MASK(0xC294, 0x10000000, 0x10000000);
+	/* enable InfoFrame */
+	VIA_WRITE(0xC284, reg_c284);
+	VIA_WRITE_MASK(0xC740, 0x1E4CBE7F, 0x3FFFFFFF);
+	VIA_WRITE_MASK(0xC748, 0x84509180, 0x001FFFFF);
+	/* Select PHY Function as HDMI */
+	/* Select HDTV0 source */
+	if (!iga->index)
+		value |= BIT(1);
+	svga_wcrt_mask(VGABASE, 0xFF, value, BIT(1) | BIT(0));
+}
+
+static void
+via_hdmi_enc_mode_set(struct drm_encoder *encoder,
+		struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	struct via_encoder *enc = container_of(encoder, struct via_encoder, base);
+	struct via_crtc *iga = container_of(encoder->crtc, struct via_crtc, base);
+	struct drm_via_private *dev_priv = encoder->dev->dev_private;
+	struct drm_connector *connector = NULL, *con;
+	struct drm_device *dev = encoder->dev;
+
+	list_for_each_entry(con, &dev->mode_config.connector_list, head) {
+		if (encoder ==  con->encoder) {
+			connector = con;
+			break;
+		}
+	}
+
+	if (!connector) {
+		DRM_INFO("HDMI encoder is not used by any connector\n");
+		return;
+	}
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) {
+		struct via_connector *con = container_of(connector, struct via_connector, base);
+		bool audio_off = (con->flags & HDMI_AUDIO_ENABLED);
+		u32 v_sync_adjust = 0;
+
+		if (enc->diPort == DISP_DI_NONE)
+			via_hdmi_native_mode_set(iga, adjusted_mode, audio_off);
+
+		if (!iga->index)
+			via_load_crtc_pixel_timing(encoder->crtc, adjusted_mode);
+
+		/* Set Hsync Offset, delay one clock (To meet 861-D spec.) */
+		svga_wcrt_mask(VGABASE, 0x8A, 0x01, 0x7);
+
+		/* If CR8A +1, HSyc must -1 */
+		vga_wcrt(VGABASE, 0x56, vga_rcrt(VGABASE, 0x56) - 1);
+		vga_wcrt(VGABASE, 0x57, vga_rcrt(VGABASE, 0x57) - 1);
+
+		if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) {
+			if (iga->index) {
+				switch (dev->pci_device) {
+				case PCI_DEVICE_ID_VIA_VX875:
+					svga_wcrt_mask(VGABASE, 0xFB,
+							v_sync_adjust & 0xFF, 0xFF);
+					svga_wcrt_mask(VGABASE, 0xFC,
+							(v_sync_adjust & 0x700) >> 8, 0x07);
+					break;
+
+				case PCI_DEVICE_ID_VIA_VX900:
+					svga_wcrt_mask(VGABASE, 0xAB, v_sync_adjust & 0xFF, 0xFF);
+					svga_wcrt_mask(VGABASE, 0xAC, (v_sync_adjust & 0x700) >> 8, 0x07);
+					break;
+
+				default:
+					svga_wcrt_mask(VGABASE, 0xFB, v_sync_adjust & 0xFF, 0xFF);
+					svga_wcrt_mask(VGABASE, 0xFC, (v_sync_adjust & 0x700) >> 8, 0x07);
+					break;
+				}
+			}
+		} else { /* non-interlace, clear interlace setting. */
+			if (iga->index) {
+				vga_wcrt(VGABASE, 0xFB, 0);
+				svga_wcrt_mask(VGABASE, 0xFC, 0, 0x07);
+			}
+		}
+	} else if (connector->connector_type == DRM_MODE_CONNECTOR_DVID) {
+		/* 135MHz ~ 270MHz */
+		if (mode->clock >= 135000)
+			VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x00000000, 0xC0000000);
+		/* 67.5MHz ~ < 135MHz */
+		else if (mode->clock >= 67500)
+			VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x40000000, 0xC0000000);
+		/* 33.75MHz ~ < 67.5MHz */
+		else if (mode->clock >= 33750)
+			VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x80000000, 0xC0000000);
+		/* 25MHz ~ < 33.75MHz */
+		else
+			VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0xC0000000, 0xC0000000);
+
+		/* Power down TPLL to reset */
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x00000000, 0x06000000);
+		/* Enable DP data pass */
+		VIA_WRITE_MASK(DP_DATA_PASS_ENABLE_REG, 0x00000001, 0x00000001);
+		/* Select EPHY as HDMI mode */
+		VIA_WRITE_MASK(DP_EPHY_MISC_PWR_REG, 0, BIT(0));
+		/* Enable HDMI with DVI mode */
+		VIA_WRITE_MASK(0xC280, 0x40, 0x40);
+		/* select AC mode */
+		VIA_WRITE_MASK(0xC74C, 0x40, 0x40);
+		/* Set status of Lane0~3 */
+		VIA_WRITE_MASK(0xC744, 0x00FFFF00, 0x00FFFF00);
+		/* Disable InfoFrame */
+		VIA_WRITE_MASK(0xC284, 0x00000000, 0x00000002);
+		/* EPHY Control Register */
+		VIA_WRITE_MASK(DP_EPHY_PLL_REG, 0x1EC46E6F, 0x3FFFFFFF);
+		/* Select PHY Function as HDMI */
+		svga_wcrt_mask(VGABASE, 0xFF, BIT(0), BIT(0));
+		/* Select HDTV0 source */
+		if (!iga->index)
+			svga_wcrt_mask(VGABASE, 0xFF, 0, BIT(1));
+		else
+			svga_wcrt_mask(VGABASE, 0xFF, BIT(1), BIT(1));
+
+		/* in 640x480 case, MPLL is different */
+		/* For VT3410 internal transmitter 640x480 issue */
+		if (mode->hdisplay == 640 && mode->vdisplay == 480) {
+			VIA_WRITE(DP_EPHY_PLL_REG, 0xD8C29E6F);
+			VIA_WRITE(DP_EPHY_PLL_REG, 0xDEC29E6F);
+		}
+	}
+
+	/* Patch for clock skew */
+	if (enc->diPort == DISP_DI_DVP1) {
+		switch (dev->pdev->device) {
+		case PCI_DEVICE_ID_VIA_VT3157:	/* CX700 */
+			svga_wcrt_mask(VGABASE, 0x65, 0x0B, 0x0F);
+			svga_wcrt_mask(VGABASE, 0x9B, 0x00, 0x0F);
+			break;
+
+		case PCI_DEVICE_ID_VIA_VT1122:	/* VX800 */
+		case PCI_DEVICE_ID_VIA_VX875:	/* VX855 */
+			svga_wcrt_mask(VGABASE, 0x65, 0x0B, 0x0F);
+			svga_wcrt_mask(VGABASE, 0x9B, 0x0F, 0x0F);
+			break;
+
+		case PCI_DEVICE_ID_VIA_VX900:	/* VX900 */
+			svga_wcrt_mask(VGABASE, 0x65, 0x09, 0x0F);
+			svga_wcrt_mask(VGABASE, 0x9B, 0x09, 0x0F);
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	via_set_sync_polarity(encoder, mode, adjusted_mode);
+}
+
+static const struct drm_encoder_helper_funcs via_hdmi_enc_helper_funcs = {
+	.dpms = via_hdmi_enc_dpms,
+	.mode_fixup = via_hdmi_enc_mode_fixup,
+	.mode_set = via_hdmi_enc_mode_set,
+	.prepare = via_encoder_prepare,
+	.commit = via_encoder_commit,
+	.disable = via_encoder_disable,
+};
+
+static unsigned int
+via_check_hdmi_i2c_status(struct drm_via_private *dev_priv,
+			unsigned int check_bits, unsigned int condition)
+{
+	unsigned int status = true, max = 50, loop = 0;
+
+	if (condition) {
+		while ((VIA_READ(0xC0B8) & check_bits) && loop < max) {
+			/* delay 20 us */
+			udelay(20);
+
+			if (++loop == max)
+				status = false;
+		}
+	} else {
+		while (!(VIA_READ(0xC0B8) & check_bits) && loop < max) {
+			/* delay 20 us */
+			udelay(20);
+
+			if (++loop == max)
+				status = false;
+		}
+	}
+	return status;
+}
+
+unsigned int
+via_ddc_read_bytes_by_hdmi(struct drm_via_private *dev_priv, unsigned char *block)
+{
+	unsigned int status = true, temp = 0, i;
+	unsigned char ckm = 0;
+
+	/* Enable DDC */
+	VIA_WRITE_MASK(0xC000, 0x00000001, 0x00000001);
+	VIA_WRITE(0xC0C4, (VIA_READ(0xC0C4) & 0xFC7FFFFF) | 0x00800000);
+	VIA_WRITE(0xC0B8, 0x00000001);
+
+	/* START */
+	VIA_WRITE(0xC0B8, 0x0011);
+	VIA_WRITE(0xC0B8, 0x0019);
+	if (status)
+		status = via_check_hdmi_i2c_status(dev_priv, 0x0018, true);
+
+	/* Slave Address */
+	temp = 0xA0;
+	temp <<= 16;
+	temp |= VIA_READ(0xC0B4) & 0xFF00FFFF;
+	VIA_WRITE(0xC0B4, temp);
+	VIA_WRITE(0xC0B8, 0x0009);
+	if (status)
+		status = via_check_hdmi_i2c_status(dev_priv, 0x0008, true);
+
+	/* Offset - always read at start of buffer */
+	temp = 0;
+	temp <<= 16;
+	temp |= VIA_READ(0xC0B4) & 0xFF00FFFF;
+	VIA_WRITE(0xC0B4, temp);
+	VIA_WRITE(0xC0B8, 0x0009);
+	if (status)
+		status = via_check_hdmi_i2c_status(dev_priv, 0x0008, true);
+
+	/* START */
+	VIA_WRITE(0xC0B8, 0x0011);
+	VIA_WRITE(0xC0B8, 0x0019);
+	if (status)
+		status = via_check_hdmi_i2c_status(dev_priv, 0x0018, true);
+
+	/* Slave Address + 1 */
+	temp = 0xA1;
+	temp <<= 16;
+	temp |= VIA_READ(0xC0B4) & 0xFF00FFFF;
+	VIA_WRITE(0xC0B4, temp);
+	VIA_WRITE(0xC0B8, 0x0009);
+	if (status)
+		status = via_check_hdmi_i2c_status(dev_priv, 0x0008, true);
+
+	for (i = 0; i < EDID_LENGTH; i++) {
+		/* Read Data */
+		VIA_WRITE(0xC0B8, 0x0009);
+		via_check_hdmi_i2c_status(dev_priv, 0x0008, true);
+		via_check_hdmi_i2c_status(dev_priv, 0x0080, false);
+		if (i == 127) {
+			*block++ = (0x100 - ckm);
+			ckm = 0;
+		} else {
+			*block++ = (unsigned char) ((VIA_READ(0xC0B4) & 0x0000FF00) >> 8);
+			ckm += (unsigned char) ((VIA_READ(0xC0B4) & 0x0000FF00) >> 8);
+		}
+		VIA_WRITE(0xC0B8, (VIA_READ(0xC0B8) & ~0x80));
+	}
+
+	/* STOP */
+	VIA_WRITE(0xC0B8, 0x0021);
+	VIA_WRITE(0xC0B8, 0x0029);
+
+	status = via_check_hdmi_i2c_status(dev_priv, 0x0828, true);
+	if (!status) {
+		/* Reset */
+		VIA_WRITE_MASK(0xC0C4, 0x00000080, 0x00000080);
+		VIA_WRITE_MASK(0xC0C4, 0x00000000, 0x00000080);
+	}
+	return status;
+}
+
+struct edid *
+via_hdmi_get_edid(struct drm_connector *connector)
+{
+	bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS);
+	struct drm_via_private *dev_priv = connector->dev->dev_private;
+	int i, j = 0, valid_extensions = 0;
+	struct edid *edid = NULL;
+	u8 *block;
+
+	/* Clear out old EDID block */
+	drm_mode_connector_update_edid_property(connector, edid);
+
+	block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+	if (!block)
+		return edid;
+
+	/* base block fetch */
+	for (i = 0; i < 4; i++) {
+		if (!via_ddc_read_bytes_by_hdmi(dev_priv, block))
+			goto out;
+
+		if (drm_edid_block_valid(block, 0, print_bad_edid))
+			break;
+
+		if (i == 0 && !memchr_inv(block, 0, EDID_LENGTH)) {
+			connector->null_edid_counter++;
+			goto carp;
+		}
+	}
+	if (i == 4)
+		goto carp;
+
+	/* parse the extensions if present */
+	if (block[0x7e]) {
+		u8 *new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
+
+		if (!new)
+			goto out;
+		block = new;
+
+		for (j = 1; j <= block[0x7e]; j++) {
+			for (i = 0; i < 4; i++) {
+				new = block + (valid_extensions + 1) * EDID_LENGTH;
+
+				if (!via_ddc_read_bytes_by_hdmi(dev_priv, new))
+					goto out;
+
+				if (drm_edid_block_valid(new, j, print_bad_edid)) {
+					valid_extensions++;
+					break;
+				}
+			}
+
+			if (i == 4 && print_bad_edid) {
+				dev_warn(connector->dev->dev,
+					"%s: Ignoring invalid EDID block %d.\n",
+					drm_get_connector_name(connector), j);
+
+				connector->bad_edid_counter++;
+			}
+		}
+	}
+
+	edid = (struct edid *) block;
+	drm_mode_connector_update_edid_property(connector, edid);
+	return edid;
+
+carp:
+	if (print_bad_edid) {
+		dev_warn(connector->dev->dev, "%s: EDID block %d invalid.\n",
+			drm_get_connector_name(connector), j);
+	}
+	connector->bad_edid_counter++;
+out:
+	kfree(block);
+	return edid;
+}
+
+static enum drm_connector_status
+via_hdmi_detect(struct drm_connector *connector, bool force)
+{
+	struct drm_via_private *dev_priv = connector->dev->dev_private;
+	enum drm_connector_status ret = connector_status_disconnected;
+	u32 mm_c730 = VIA_READ(0xc730) & 0xC0000000;
+
+	if (VIA_IRQ_DP_HOT_UNPLUG == mm_c730) {
+		drm_mode_connector_update_edid_property(connector, NULL);
+		return ret;
+	}
+
+	if (via_hdmi_get_edid(connector))
+		ret = connector_status_connected;
+	return ret;
+}
+
+static int
+via_hdmi_set_property(struct drm_connector *connector,
+		  struct drm_property *property,
+		  uint64_t value)
+{
+	struct drm_device *dev = connector->dev;
+
+	if (property == dev->mode_config.dpms_property && connector->encoder)
+		via_hdmi_enc_dpms(connector->encoder, (uint32_t)(value & 0xf));
+	return 0;
+}
+
+static const struct drm_connector_funcs via_hdmi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = via_hdmi_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.set_property = via_hdmi_set_property,
+	.destroy = via_connector_destroy,
+};
+
+static int
+via_hdmi_mode_valid(struct drm_connector *connector,
+			struct drm_display_mode *mode)
+{
+	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+	    !connector->interlace_allowed)
+		return MODE_NO_INTERLACE;
+
+	if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) &&
+	    !connector->doublescan_allowed)
+		return MODE_NO_DBLESCAN;
+
+	return MODE_OK;
+}
+
+int
+via_hdmi_get_modes(struct drm_connector *connector)
+{
+	struct edid *edid = via_hdmi_get_edid(connector);
+
+	if (edid) {
+		struct via_connector *con;
+
+		if (edid->input & DRM_EDID_INPUT_DIGITAL) {
+			con = container_of(connector, struct via_connector, base);
+
+			if (via_hdmi_audio)
+				con->flags |= drm_detect_monitor_audio(edid);
+
+			if (drm_detect_hdmi_monitor(edid))
+				con->flags |= HDMI_SINK;
+
+			if (drm_rgb_quant_range_selectable(edid))
+				con->flags |= HDMI_COLOR_RANGE;
+
+			drm_edid_to_eld(connector, edid);
+		}
+	}
+	return drm_add_edid_modes(connector, edid);
+}
+
+static const struct drm_connector_helper_funcs via_hdmi_connector_helper_funcs = {
+	.mode_valid = via_hdmi_mode_valid,
+	.get_modes = via_hdmi_get_modes,
+	.best_encoder = via_best_encoder,
+};
+
+void
+via_hdmi_init(struct drm_device *dev, int diport)
+{
+	struct via_connector *con;
+	struct via_encoder *enc;
+	void *par;
+
+	par = kzalloc(sizeof(*enc) + sizeof(*con), GFP_KERNEL);
+	if (!par) {
+		DRM_ERROR("Failed to allocate connector and encoder\n");
+		return;
+	}
+	con = par + sizeof(*enc);
+	enc = par;
+
+	/* Piece together our connector */
+	drm_connector_init(dev, &con->base, &via_hdmi_connector_funcs,
+				DRM_MODE_CONNECTOR_HDMIA);
+	drm_connector_helper_add(&con->base, &via_hdmi_connector_helper_funcs);
+	drm_sysfs_connector_add(&con->base);
+
+	con->base.doublescan_allowed = false;
+	switch (dev->pdev->device) {
+	case PCI_DEVICE_ID_VIA_VT3157:
+	case PCI_DEVICE_ID_VIA_VT3353:
+		con->base.interlace_allowed = false;
+		break;
+	default:
+		con->base.interlace_allowed = true;
+		break;
+	}
+
+	/* Setup the encoders and attach them */
+	drm_encoder_init(dev, &enc->base, &via_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS);
+	drm_encoder_helper_add(&enc->base, &via_hdmi_enc_helper_funcs);
+
+	enc->base.possible_crtcs = BIT(1) | BIT(0);
+	enc->base.possible_clones = 0;
+	enc->diPort = diport;
+
+	drm_mode_connector_attach_encoder(&con->base, &enc->base);
+}


More information about the Openchrome-devel mailing list