[PATCH 13/28] drm/via: Add via_hdmi.c
Kevin Brace
kevinbrace at gmx.com
Fri Jun 24 20:26:18 UTC 2022
From: Kevin Brace <kevinbrace at bracecomputerlab.com>
Signed-off-by: Kevin Brace <kevinbrace at bracecomputerlab.com>
---
drivers/gpu/drm/via/via_hdmi.c | 719 +++++++++++++++++++++++++++++++++
1 file changed, 719 insertions(+)
create mode 100644 drivers/gpu/drm/via/via_hdmi.c
diff --git a/drivers/gpu/drm/via/via_hdmi.c b/drivers/gpu/drm/via/via_hdmi.c
new file mode 100644
index 000000000000..9412054a24df
--- /dev/null
+++ b/drivers/gpu/drm/via/via_hdmi.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright 2013 James Simmons. All Rights Reserved.
+ * Copyright 1998-2012 VIA Technologies, Inc. All Rights Reserved.
+ * Copyright 2001-2012 S3 Graphics, Inc. All Rights Reserved.
+ *
+ * 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.
+ *
+ * Author(s):
+ * James Simmons <jsimmons at infradead.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "via_drv.h"
+
+
+#define HDMI_AUDIO_ENABLED BIT(0)
+
+int via_hdmi_audio = 0;
+
+MODULE_PARM_DESC(audio, "HDMI Audio enable (1 = enable)");
+module_param_named(audio, via_hdmi_audio, int, 0444);
+
+/*
+ * 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_device *dev = encoder->dev;
+ struct via_drm_priv *dev_priv = to_via_drm_priv(dev);
+
+ 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_device *dev = iga->base.dev;
+ struct via_drm_priv *dev_priv = to_via_drm_priv(dev);
+ 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));
+ /* enable HDMI with HDMI mode */
+ VIA_WRITE_MASK(0xC280, 0x00, 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, 0x92000000);
+ /* enable audio packet */
+ if (!audio_off)
+ 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_connector *connector = NULL, *con;
+ struct drm_device *dev = encoder->dev;
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct via_drm_priv *dev_priv = to_via_drm_priv(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);
+
+ if (enc->di_port == VIA_DI_PORT_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) {
+ /* FIXME VIA where do you get this value from ??? */
+ u32 v_sync_adjust = 0;
+
+ switch (pdev->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_VGA:
+ 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->di_port == VIA_DI_PORT_DVP1) {
+ switch (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_VGA: /* VX900 */
+ svga_wcrt_mask(VGABASE, 0x65, 0x09, 0x0F);
+ svga_wcrt_mask(VGABASE, 0x9B, 0x09, 0x0F);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+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,
+};
+
+static unsigned int via_check_hdmi_i2c_status(struct via_drm_priv *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 via_drm_priv *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_enabled(DRM_UT_KMS));
+ struct drm_device *dev = connector->dev;
+ struct via_drm_priv *dev_priv = to_via_drm_priv(dev);
+ struct edid *edid = NULL;
+ int i, j = 0;
+ u8 *block;
+
+ /* Clear out old EDID block */
+ drm_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, NULL))
+ 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, NULL)) {
+ valid_extensions++;
+ break;
+ }
+ }
+
+ if (i == 4 && print_bad_edid) {
+ dev_warn(connector->dev->dev,
+ "%s: Ignoring invalid EDID block %d.\n",
+ connector->name, 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_connector_update_edid_property(connector, edid);
+ return edid;
+
+carp:
+ if (print_bad_edid) {
+ dev_warn(connector->dev->dev, "%s: EDID block %d invalid.\n",
+ connector->name, 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_device *dev = connector->dev;
+ struct via_drm_priv *dev_priv = to_via_drm_priv(dev);
+ 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_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,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state =
+ drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state =
+ drm_atomic_helper_connector_destroy_state,
+};
+
+static int via_hdmi_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ 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);
+ }
+ }
+ 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,
+};
+
+void via_hdmi_init(struct drm_device *dev, u32 di_port)
+{
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+ 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, NULL);
+ 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->di_port = di_port;
+
+ /* 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_connector_register(&hdmi->base);
+
+ hdmi->base.polled = DRM_CONNECTOR_POLL_HPD;
+ hdmi->base.doublescan_allowed = false;
+ INIT_LIST_HEAD(&hdmi->props);
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_VIA_VT3157:
+ case PCI_DEVICE_ID_VIA_VT1122:
+ hdmi->base.interlace_allowed = false;
+ break;
+ default:
+ hdmi->base.interlace_allowed = true;
+ break;
+ }
+ drm_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_connector_register(&dvi->base);
+
+ dvi->base.polled = DRM_CONNECTOR_POLL_HPD;
+ dvi->base.doublescan_allowed = false;
+ INIT_LIST_HEAD(&dvi->props);
+
+ switch (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_connector_attach_encoder(&dvi->base, &enc->base);
+}
--
2.35.1
More information about the dri-devel
mailing list