From patchwork Sat Jun 8 16:54:52 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: James Simmons X-Patchwork-Id: 2692881 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by patchwork2.kernel.org (Postfix) with ESMTP id E18EFDF24C for ; Sat, 8 Jun 2013 17:02:41 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E48E0E616A for ; Sat, 8 Jun 2013 10:02:41 -0700 (PDT) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) by gabe.freedesktop.org (Postfix) with ESMTP id 61266E613B; Sat, 8 Jun 2013 09:54:56 -0700 (PDT) Received: from jsimmons (helo=localhost) by casper.infradead.org with local-esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UlMPk-00067m-O6; Sat, 08 Jun 2013 16:54:55 +0000 Date: Sat, 8 Jun 2013 17:54:52 +0100 (BST) From: James Simmons To: DRI development list Subject: [RFC 17/21] DRM: Add VIA DRM driver Message-ID: User-Agent: Alpine 2.03 (LFD 1266 2009-07-14) MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130608_175452_891666_497172CF X-CRM114-Status: GOOD ( 20.08 ) X-Spam-Score: -1.9 (-) X-Spam-Report: SpamAssassin version 3.3.2 on casper.infradead.org summary: Content analysis details: (-1.9 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 NO_RELAYS Informational: message was not relayed via SMTP -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: OpenChrome Development X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org Errors-To: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org commit 467eefb4a02972c5f9747ddaa7d8d582fb15a759 Author: James Simmons Date: Sat Jun 8 12:13:25 2013 -0400 via: HDMI/DVI-D support Implement the encoder and connector for HDMI/DVI-D displays. Signed-Off-by: James Simmons diff --git a/drivers/gpu/drm/via/via_hdmi.c b/drivers/gpu/drm/via/via_hdmi.c new file mode 100644 index 0000000..b37405a --- /dev/null +++ b/drivers/gpu/drm/via/via_hdmi.c @@ -0,0 +1,716 @@ +/* + * 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 + */ +#include "via_drv.h" + +#define HDMI_AUDIO_ENABLED BIT(0) +#define HDMI_COLOR_RANGE BIT(1) + +/* + * Routines for controlling stuff on the HDMI port + */ +static const struct drm_encoder_funcs via_hdmi_enc_funcs = { + .destroy = via_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 int offset, + unsigned char *block) +{ + unsigned int status = true, temp = 0, i; + + /* 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 */ + temp = offset; + 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); + *block++ = (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; + struct edid *edid = NULL; + int i, j = 0; + 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, 0, 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); + int valid_extensions = 0, offset = 0; + + if (!new) + goto out; + block = new; + + for (j = 1; j <= block[0x7e]; j++) { + for (i = 0; i < 4; i++) { + offset = (valid_extensions + 1) * EDID_LENGTH; + new = block + offset; + + if (!via_ddc_read_bytes_by_hdmi(dev_priv, offset, 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++; + } + } + + if (valid_extensions != block[0x7e]) { + block[EDID_LENGTH - 1] += block[0x7e] - valid_extensions; + block[0x7e] = valid_extensions; + + new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL); + if (!new) + goto out; + block = new; + } + } + 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; + struct edid *edid = NULL; + + if (VIA_IRQ_DP_HOT_UNPLUG == mm_c730) { + drm_mode_connector_update_edid_property(connector, NULL); + return ret; + } + + edid = via_hdmi_get_edid(connector); + if (edid) { + if ((connector->connector_type != DRM_MODE_CONNECTOR_HDMIA) ^ + (drm_detect_hdmi_monitor(edid))) + 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_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 *dvi, *hdmi; + struct via_encoder *enc; + + enc = kzalloc(sizeof(*enc) + 2 * sizeof(*hdmi), GFP_KERNEL); + if (!enc) { + DRM_ERROR("Failed to allocate connector and encoder\n"); + return; + } + hdmi = &enc->cons[0]; + dvi = &enc->cons[1]; + + /* 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; + + /* Setup the HDMI connector */ + drm_connector_init(dev, &hdmi->base, &via_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(&hdmi->base, &via_hdmi_connector_helper_funcs); + drm_sysfs_connector_add(&hdmi->base); + + hdmi->base.polled = DRM_CONNECTOR_POLL_HPD; + hdmi->base.doublescan_allowed = false; + switch (dev->pdev->device) { + case PCI_DEVICE_ID_VIA_VT3157: + case PCI_DEVICE_ID_VIA_VT3353: + hdmi->base.interlace_allowed = false; + break; + default: + hdmi->base.interlace_allowed = true; + break; + } + drm_mode_connector_attach_encoder(&hdmi->base, &enc->base); + + /* Setup the DVI connector */ + drm_connector_init(dev, &dvi->base, &via_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + drm_connector_helper_add(&dvi->base, &via_hdmi_connector_helper_funcs); + drm_sysfs_connector_add(&dvi->base); + + dvi->base.polled = DRM_CONNECTOR_POLL_HPD; + dvi->base.doublescan_allowed = false; + switch (dev->pdev->device) { + case PCI_DEVICE_ID_VIA_VT3157: + case PCI_DEVICE_ID_VIA_VT3353: + dvi->base.interlace_allowed = false; + break; + default: + dvi->base.interlace_allowed = true; + break; + } + drm_mode_connector_attach_encoder(&dvi->base, &enc->base); +}