commit 467eefb4a02972c5f9747ddaa7d8d582fb15a759
Author: James Simmons <jsimm...@infradead.org>
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 <jsimm...@infradead.org>

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 <jsimm...@infradead.org>
+ */
+#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);
+}
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to