From patchwork Sat Jun 8 16:53:40 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Simmons X-Patchwork-Id: 2692861 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 6A771DF24C for ; Sat, 8 Jun 2013 17:00:20 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 62F1EE6022 for ; Sat, 8 Jun 2013 10:00:20 -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 64537E614A; Sat, 8 Jun 2013 09:53:44 -0700 (PDT) Received: from jsimmons (helo=localhost) by casper.infradead.org with local-esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UlMOa-000669-TT; Sat, 08 Jun 2013 16:53:43 +0000 Date: Sat, 8 Jun 2013 17:53:40 +0100 (BST) From: James Simmons To: DRI development list Subject: [RFC 15/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_175340_986570_50FD0418 X-CRM114-Status: GOOD ( 18.48 ) 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 1c886b4f35ec239f7787a6a4db10ecc80b3c9824 Author: James Simmons Date: Sat Jun 8 12:04:31 2013 -0400 via: LVDS support Implement the encoder and connector for LVDS lcd type displays. Signed-Off-by: James Simmons diff --git a/drivers/gpu/drm/via/via_lvds.c b/drivers/gpu/drm/via/via_lvds.c new file mode 100644 index 0000000..d8ed81c --- /dev/null +++ b/drivers/gpu/drm/via/via_lvds.c @@ -0,0 +1,778 @@ +/* + * 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, sub license, + * 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHOR(S) OR COPYRIGHT HOLDER(S) 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. + */ +#include +#include + +#include "via_drv.h" + +/* Encoder flags for LVDS */ +#define LVDS_DUAL_CHANNEL 1 + +/* caculate the cetering timing using mode and adjusted_mode */ +static void +via_centering_timing(const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int panel_hsync_time = adjusted_mode->hsync_end - + adjusted_mode->hsync_start; + int panel_vsync_time = adjusted_mode->vsync_end - + adjusted_mode->vsync_start; + int panel_hblank_start = adjusted_mode->hdisplay; + int panel_vbank_start = adjusted_mode->vdisplay; + int hborder = (adjusted_mode->hdisplay - mode->hdisplay) / 2; + int vborder = (adjusted_mode->vdisplay - mode->vdisplay) / 2; + int new_hblank_start = hborder + mode->hdisplay; + int new_vblank_start = vborder + mode->vdisplay; + + adjusted_mode->hdisplay = mode->hdisplay; + adjusted_mode->hsync_start = (adjusted_mode->hsync_start - + panel_hblank_start) + new_hblank_start; + adjusted_mode->hsync_end = adjusted_mode->hsync_start + + panel_hsync_time; + adjusted_mode->vdisplay = mode->vdisplay; + adjusted_mode->vsync_start = (adjusted_mode->vsync_start - + panel_vbank_start) + new_vblank_start; + adjusted_mode->vsync_end = adjusted_mode->vsync_start + + panel_vsync_time; + /* Adjust Crtc H and V */ + adjusted_mode->crtc_hdisplay = adjusted_mode->hdisplay; + adjusted_mode->crtc_hblank_start = new_hblank_start; + adjusted_mode->crtc_hblank_end = adjusted_mode->crtc_htotal - hborder; + 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 = new_vblank_start; + adjusted_mode->crtc_vblank_end = adjusted_mode->crtc_vtotal - vborder; + adjusted_mode->crtc_vsync_start = adjusted_mode->vsync_start; + adjusted_mode->crtc_vsync_end = adjusted_mode->vsync_end; +} + +static void +via_enable_internal_lvds(struct drm_encoder *encoder) +{ + struct via_encoder *enc = container_of(encoder, struct via_encoder, base); + struct drm_via_private *dev_priv = encoder->dev->dev_private; + struct drm_device *dev = encoder->dev; + + /* Turn on LCD panel */ + if ((enc->diPort & DISP_DI_DFPL) || (enc->diPort == DISP_DI_DVP1)) { + if ((dev->pci_device == PCI_DEVICE_ID_VIA_VT1122) || + (dev->pci_device == PCI_DEVICE_ID_VIA_CLE266)) { + /* Software control power sequence ON */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(7)); + svga_wcrt_mask(VGABASE, 0x91, BIT(0), BIT(0)); + /* Delay td0 msec. */ + mdelay(200); + /* VDD ON */ + svga_wcrt_mask(VGABASE, 0x91, BIT(4), BIT(4)); + /* Delay td1 msec. */ + mdelay(25); + /* DATA ON */ + svga_wcrt_mask(VGABASE, 0x91, BIT(3), BIT(3)); + /* VEE ON (unused on vt3353) */ + svga_wcrt_mask(VGABASE, 0x91, BIT(2), BIT(2)); + /* Delay td3 msec. */ + mdelay(250); + /* Back-Light ON */ + svga_wcrt_mask(VGABASE, 0x91, BIT(1), BIT(1)); + } else { + /* Use first power sequence control: * + * Use hardware control power sequence. */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(0)); + /* Turn on back light and panel path. */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(7) | BIT(6)); + /* Turn on hardware power sequence. */ + svga_wcrt_mask(VGABASE, 0x6A, BIT(3), BIT(3)); + } + } + + if (enc->diPort & DISP_DI_DFPH) { + if ((dev->pci_device == PCI_DEVICE_ID_VIA_VT1122) || + (dev->pci_device == PCI_DEVICE_ID_VIA_CLE266)) { + /* Software control power sequence ON */ + svga_wcrt_mask(VGABASE, 0xD4, 0x00, BIT(1)); + svga_wcrt_mask(VGABASE, 0xD3, BIT(0), BIT(0)); + /* Delay td0 msec. */ + mdelay(200); + /* VDD ON */ + svga_wcrt_mask(VGABASE, 0xD3, BIT(4), BIT(4)); + /* Delay td1 msec. */ + mdelay(25); + /* DATA ON */ + svga_wcrt_mask(VGABASE, 0xD3, BIT(3), BIT(3)); + /* VEE ON (unused on vt3353) */ + svga_wcrt_mask(VGABASE, 0xD3, BIT(2), BIT(2)); + /* Delay td3 msec. */ + mdelay(250); + /* Back-Light ON */ + svga_wcrt_mask(VGABASE, 0xD3, BIT(1), BIT(1)); + } else { + /* Use hardware control power sequence. */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(0)); + /* Turn on back light and panel path. */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(7) | BIT(6)); + /* Turn on hardware power sequence. */ + svga_wcrt_mask(VGABASE, 0xD4, BIT(1), BIT(1)); + } + } + + /* Power on LVDS channel. */ + if (enc->flags & LVDS_DUAL_CHANNEL) { + /* For high resolution LCD (internal), + * power on both LVDS0 and LVDS1 */ + svga_wcrt_mask(VGABASE, 0xD2, 0x00, BIT(7) | BIT(6)); + } else { + if (enc->diPort & DISP_DI_DFPL) + svga_wcrt_mask(VGABASE, 0xD2, 0x00, BIT(7)); + else if (enc->diPort & DISP_DI_DFPH) + svga_wcrt_mask(VGABASE, 0xD2, 0x00, BIT(6)); + } +} + +static void +via_disable_internal_lvds(struct drm_encoder *encoder) +{ + struct via_encoder *enc = container_of(encoder, struct via_encoder, base); + struct drm_via_private *dev_priv = encoder->dev->dev_private; + struct drm_device *dev = encoder->dev; + + /* Turn off LCD panel */ + if ((enc->diPort & DISP_DI_DFPL) || (enc->diPort == DISP_DI_DVP1)) { + /* Set LCD software power sequence off */ + if ((dev->pci_device == PCI_DEVICE_ID_VIA_VT1122) || + (dev->pci_device == PCI_DEVICE_ID_VIA_CLE266)) { + /* Back-Light OFF */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(1)); + /* Delay td3 msec. */ + mdelay(250); + /* VEE OFF (unused on vt3353) */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(2)); + /* DATA OFF */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(3)); + /* Delay td1 msec. */ + mdelay(25); + /* VDD OFF */ + svga_wcrt_mask(VGABASE, 0x91, 0x00, BIT(4)); + } else { + /* Use first power sequence control: * + * Turn off power sequence. */ + svga_wcrt_mask(VGABASE, 0x6A, 0x00, BIT(3)); + + /* Turn off back light and panel path. */ + svga_wcrt_mask(VGABASE, 0x91, 0xC0, BIT(7) | BIT(6)); + } + } + + if (enc->diPort & DISP_DI_DFPH) { + /* Set LCD software power sequence off */ + if ((dev->pci_device == PCI_DEVICE_ID_VIA_VT1122) || + (dev->pci_device == PCI_DEVICE_ID_VIA_CLE266)) { + /* Back-Light OFF */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(1)); + /* Delay td3 msec. */ + mdelay(250); + /* VEE OFF */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(2)); + /* DATA OFF */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(3)); + /* Delay td1 msec. */ + mdelay(25); + /* VDD OFF */ + svga_wcrt_mask(VGABASE, 0xD3, 0x00, BIT(4)); + } else { + /* Use second power sequence control: * + * Turn off power sequence. */ + svga_wcrt_mask(VGABASE, 0xD4, 0x00, BIT(1)); + /* Turn off back light and panel path. */ + svga_wcrt_mask(VGABASE, 0xD3, 0xC0, BIT(7) | BIT(6)); + } + } + + /* Power off LVDS channel. */ + if (enc->flags & LVDS_DUAL_CHANNEL) { + /* For high resolution LCD (internal) we + * power off both LVDS0 and LVDS1 */ + svga_wcrt_mask(VGABASE, 0xD2, 0xC0, BIT(7) | BIT(6)); + } else { + if (enc->diPort & DISP_DI_DFPL) + svga_wcrt_mask(VGABASE, 0xD2, BIT(7), BIT(7)); + else if (enc->diPort & DISP_DI_DFPH) + svga_wcrt_mask(VGABASE, 0xD2, BIT(6), BIT(6)); + } +} + +static void +via_lvds_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_via_private *dev_priv = encoder->dev->dev_private; + struct drm_device *dev = encoder->dev; + struct via_crtc *iga = NULL; + + switch (mode) { + case DRM_MODE_DPMS_ON: + if (encoder->crtc == NULL) + return; + iga = container_of(encoder->crtc, struct via_crtc, base); + + /* when using the EPIA-EX board, if we do not set this bit, + * light LCD will failed in nonRandR structure, + * So, when light LCD this bit is always setted */ + svga_wcrt_mask(VGABASE, 0x6A, BIT(3), BIT(3)); + + if (dev_priv->spread_spectrum) { + if ((dev->pci_device == PCI_DEVICE_ID_VIA_VT1122) || + (dev->pci_device == PCI_DEVICE_ID_VIA_VX875) || + (dev->pci_device == PCI_DEVICE_ID_VIA_VX900)) { + /* GPIO-4/5 are used for spread spectrum, + * we must clear SR3D[7:6] to disable + * GPIO-4/5 output */ + svga_wseq_mask(VGABASE, 0x3D, BIT(0), 0xC1); + } else { + svga_wseq_mask(VGABASE, 0x2C, BIT(0), BIT(0)); + } + svga_wseq_mask(VGABASE, 0x1E, BIT(3), BIT(3)); + } + via_enable_internal_lvds(encoder); + break; + + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + via_disable_internal_lvds(encoder); + break; + } +} + +static bool +via_lvds_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_property *prop = encoder->dev->mode_config.scaling_mode_property; + struct via_crtc *iga = container_of(encoder->crtc, struct via_crtc, base); + struct drm_display_mode *native_mode = NULL, *tmp, *t; + struct drm_connector *connector = NULL, *con; + u64 scale_mode = DRM_MODE_SCALE_CENTER; + 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("LVDS encoder is not used by any connector\n"); + return false; + } + + list_for_each_entry_safe(tmp, t, &connector->modes, head) { + if (tmp->type & (DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER)) { + native_mode = tmp; + break; + } + } + + if (!native_mode) { + DRM_INFO("No native mode for LVDS\n"); + return false; + } + + drm_object_property_get_value(&connector->base, prop, &scale_mode); + + if ((mode->hdisplay != native_mode->hdisplay) || + (mode->vdisplay != native_mode->vdisplay)) { + if (scale_mode == DRM_MODE_SCALE_NONE) + return false; + drm_mode_copy(adjusted_mode, native_mode); + } + drm_mode_set_crtcinfo(adjusted_mode, 0); + + iga->scaling_mode = VIA_NO_SCALING; + /* Take care of 410 downscaling */ + if ((mode->hdisplay > native_mode->hdisplay) || + (mode->vdisplay > native_mode->vdisplay)) { + iga->scaling_mode = VIA_SHRINK; + } else { + if (!iga->index || scale_mode == DRM_MODE_SCALE_CENTER) { + /* Do centering according to mode and adjusted_mode */ + via_centering_timing(mode, adjusted_mode); + } else { + if (mode->hdisplay < native_mode->hdisplay) + iga->scaling_mode |= VIA_HOR_EXPAND; + if (mode->vdisplay < native_mode->vdisplay) + iga->scaling_mode |= VIA_VER_EXPAND; + } + } + return true; +} + +const struct drm_encoder_helper_funcs via_lvds_helper_funcs = { + .dpms = via_lvds_dpms, + .mode_fixup = via_lvds_mode_fixup, + .mode_set = via_set_sync_polarity, + .prepare = via_encoder_prepare, + .commit = via_encoder_commit, + .disable = via_encoder_disable, +}; + +const struct drm_encoder_funcs via_lvds_enc_funcs = { + .destroy = via_encoder_cleanup, +}; + +/* detect this connector connect status */ +static enum drm_connector_status +via_lcd_detect(struct drm_connector *connector, bool force) +{ + struct via_connector *con = container_of(connector, struct via_connector, base); + enum drm_connector_status ret = connector_status_disconnected; + struct edid *edid = drm_get_edid(&con->base, con->ddc_bus); + + if (edid) { + drm_mode_connector_update_edid_property(&con->base, edid); + kfree(edid); + ret = connector_status_connected; + } else { + struct drm_via_private *dev_priv = connector->dev->dev_private; + u8 mask = BIT(1); + + if (connector->dev->pci_device == PCI_DEVICE_ID_VIA_CLE266) + mask = BIT(3); + + if (vga_rcrt(VGABASE, 0x3B) & mask) + ret = connector_status_connected; + + if (machine_is_olpc()) + ret = connector_status_connected; + } + return ret; +} + +static int +via_lcd_set_property(struct drm_connector *connector, + struct drm_property *property, uint64_t value) +{ + struct drm_via_private *dev_priv = connector->dev->dev_private; + struct drm_device *dev = connector->dev; + uint64_t orig; + int ret; + + ret = drm_object_property_get_value(&connector->base, property, &orig); + if (!ret && (orig != value)) { + if (property == dev->mode_config.dithering_mode_property) { + u8 reg_value; + + switch (value) { + case DRM_MODE_DITHERING_AUTO: + case DRM_MODE_DITHERING_ON: + reg_value = BIT(0); + break; + + case DRM_MODE_DITHERING_OFF: + reg_value = 0x00; + break; + + default: + return -EINVAL; + } + svga_wcrt_mask(VGABASE, 0x88, reg_value, BIT(0)); + + } else if (property == dev->mode_config.scaling_mode_property) { + switch (value) { + case DRM_MODE_SCALE_NONE: + break; + + case DRM_MODE_SCALE_CENTER: + break; + + case DRM_MODE_SCALE_ASPECT: + break; + + case DRM_MODE_SCALE_FULLSCREEN: + break; + + default: + return -EINVAL; + } + } + } + return 0; +} + +struct drm_connector_funcs via_lcd_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = via_lcd_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = via_lcd_set_property, + .destroy = via_connector_destroy, +}; + +static int +via_lcd_get_modes(struct drm_connector *connector) +{ + int count = via_get_edid_modes(connector); + + /* If no edid then we detect the mode using + * the scratch pad registers. */ + if (!count) { + struct drm_display_mode *native_mode = NULL; + struct drm_device *dev = connector->dev; + + /* OLPC is very special */ + if (machine_is_olpc()) { + native_mode = drm_mode_create(dev); + + native_mode->clock = 56519; + native_mode->hdisplay = 1200; + native_mode->hsync_start = 1211; + native_mode->hsync_end = 1243; + native_mode->htotal = 1264; + native_mode->hskew = 0; + native_mode->vdisplay = 900; + native_mode->vsync_start = 901; + native_mode->vsync_end = 911; + native_mode->vtotal = 912; + native_mode->vscan = 0; + native_mode->vrefresh = 50; + native_mode->hsync = 0; + } else { + struct drm_via_private *dev_priv = dev->dev_private; + u8 reg_value = (vga_rcrt(VGABASE, 0x3F) & 0x0F); + int hdisplay = 0, vdisplay = 0; + + switch (reg_value) { + case 0x00: + hdisplay = 640; + vdisplay = 480; + break; + + case 0x01: + hdisplay = 800; + vdisplay = 600; + break; + + case 0x02: + hdisplay = 1024; + vdisplay = 768; + break; + + case 0x03: + hdisplay = 1280; + vdisplay = 768; + break; + + case 0x04: + hdisplay = 1280; + vdisplay = 1024; + break; + + case 0x05: + hdisplay = 1400; + vdisplay = 1050; + break; + + case 0x06: + hdisplay = 1440; + vdisplay = 900; + break; + + case 0x07: + hdisplay = 1280; + vdisplay = 800; + break; + + case 0x08: + hdisplay = 800; + vdisplay = 480; + break; + + case 0x09: + hdisplay = 1024; + vdisplay = 600; + break; + + case 0x0A: + hdisplay = 1366; + vdisplay = 768; + break; + + case 0x0B: + hdisplay = 1600; + vdisplay = 1200; + break; + + case 0x0C: + hdisplay = 1280; + vdisplay = 768; + break; + + case 0x0D: + hdisplay = 1280; + vdisplay = 1024; + break; + + case 0x0E: + hdisplay = 1600; + vdisplay = 1200; + break; + + case 0x0F: + hdisplay = 480; + vdisplay = 640; + break; + + default: + break; + } + + if (hdisplay && vdisplay) + native_mode = drm_cvt_mode(dev, hdisplay, vdisplay, + 60, false, false, false); + } + + if (native_mode) { + native_mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + drm_mode_set_name(native_mode); + drm_mode_probed_add(connector, native_mode); + count = 1; + } + } + return count; +} + +static int +via_lcd_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_property *prop = connector->dev->mode_config.scaling_mode_property; + struct drm_display_mode *native_mode = NULL, *tmp, *t; + struct drm_device *dev = connector->dev; + u64 scale_mode = DRM_MODE_SCALE_CENTER; + + list_for_each_entry_safe(tmp, t, &connector->modes, head) { + if (tmp->type & (DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER)) { + native_mode = tmp; + break; + } + } + + drm_object_property_get_value(&connector->base, prop, &scale_mode); + + if ((scale_mode == DRM_MODE_SCALE_NONE) && + ((mode->hdisplay != native_mode->hdisplay) || + (mode->vdisplay != native_mode->vdisplay))) + return MODE_PANEL; + + /* Don't support mode larger than physical size */ + if (dev->pci_device != PCI_DEVICE_ID_VIA_VX900) { + if (mode->hdisplay > native_mode->hdisplay) + return MODE_PANEL; + if (mode->vdisplay > native_mode->vdisplay) + return MODE_PANEL; + } else { + /* HW limitation 410 only can + * do <= 1.33 scaling */ + if (mode->hdisplay * 100 > native_mode->hdisplay * 133) + return MODE_PANEL; + if (mode->vdisplay * 100 > native_mode->vdisplay * 133) + return MODE_PANEL; + + /* Now we can not support H V different scale */ + if ((mode->hdisplay > native_mode->hdisplay) && + (mode->vdisplay < native_mode->vdisplay)) + return MODE_PANEL; + + if ((mode->hdisplay < native_mode->hdisplay) && + (mode->vdisplay > native_mode->vdisplay)) + return MODE_PANEL; + } + return MODE_OK; +} + +struct drm_connector_helper_funcs via_lcd_connector_helper_funcs = { + .get_modes = via_lcd_get_modes, + .mode_valid = via_lcd_mode_valid, + .best_encoder = via_best_encoder, +}; + +static int __init via_ttl_lvds_dmi_callback(const struct dmi_system_id *id) +{ + DRM_INFO("LVDS is TTL type for %s\n", id->ident); + return 1; +} + +static const struct dmi_system_id via_ttl_lvds[] = { + { + .callback = via_ttl_lvds_dmi_callback, + .ident = "VIA Quanta Netbook", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "QCI"), + DMI_MATCH(DMI_PRODUCT_VERSION, "VT6413A"), + }, + }, + + { } +}; + +void +via_lvds_init(struct drm_device *dev) +{ + struct drm_via_private *dev_priv = dev->dev_private; + bool dual_channel = false, is_msb = false; + uint64_t dither = DRM_MODE_DITHERING_OFF; + struct via_connector *con; + struct via_encoder *enc; + struct edid *edid; + u8 reg_value; + + enc = kzalloc(sizeof(*enc) + sizeof(*con), GFP_KERNEL); + if (!enc) { + DRM_INFO("Failed to allocate LVDS output\n"); + return; + } + con = &enc->cons[0]; + + drm_connector_init(dev, &con->base, &via_lcd_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(&con->base, &via_lcd_connector_helper_funcs); + drm_sysfs_connector_add(&con->base); + + switch (dev->pci_device) { + case PCI_DEVICE_ID_VIA_VX875: + case PCI_DEVICE_ID_VIA_VX900: + con->ddc_bus = via_find_ddc_bus(0x2C); + break; + default: + con->ddc_bus = via_find_ddc_bus(0x31); + break; + } + + edid = drm_get_edid(&con->base, con->ddc_bus); + if (!edid) { + if (!machine_is_olpc()) { + u8 mask = BIT(1); + + if (dev->pci_device == PCI_DEVICE_ID_VIA_CLE266) + mask = BIT(3); + + /* First we have to make sure a LVDS is present */ + reg_value = (vga_rcrt(VGABASE, 0x3B) & mask); + if (!reg_value) + goto no_device; + + /* If no edid then we detect the mode using + * the scratch pad registers. */ + reg_value = (vga_rcrt(VGABASE, 0x3F) & 0x0F); + + switch (reg_value) { + case 0x04: + case 0x05: + case 0x06: + case 0x09: + case 0x0B: + case 0x0D: + case 0x0E: + case 0x0F: + dual_channel = true; + break; + + default: + break; + } + + DRM_DEBUG("panel index %x detected\n", reg_value); + + if (reg_value < 0x0A) + dither = DRM_MODE_DITHERING_ON; + } + } else { + /* 00 LVDS1 + LVDS2 10 = Dual channel. Other are reserved */ + if ((vga_rseq(VGABASE, 0x13) >> 6) == 2) + dual_channel = true; + + kfree(edid); + } + con->base.doublescan_allowed = false; + con->base.interlace_allowed = false; + + drm_mode_create_scaling_mode_property(dev); + drm_object_attach_property(&con->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_CENTER); + + drm_mode_create_dithering_property(dev); + drm_object_attach_property(&con->base.base, + dev->mode_config.dithering_mode_property, + dither); + via_lcd_set_property(&con->base, dev->mode_config.dithering_mode_property, + dither); + + /* Now setup the encoder */ + drm_encoder_init(dev, &enc->base, &via_lvds_enc_funcs, + DRM_MODE_ENCODER_LVDS); + drm_encoder_helper_add(&enc->base, &via_lvds_helper_funcs); + + enc->base.possible_crtcs = BIT(1) | BIT(0); + + switch (dev->pci_device) { + case PCI_DEVICE_ID_VIA_CLE266: + enc->diPort = DISP_DI_DVP1; + break; + + case PCI_DEVICE_ID_VIA_VX875: + case PCI_DEVICE_ID_VIA_VX900: + enc->diPort = DISP_DI_DFPL; + break; + + default: + enc->diPort = DISP_DI_DFPH; + break; + } + + /* There has to be a way to detect TTL LVDS + * For now we use the DMI to handle this */ + if (dmi_check_system(via_ttl_lvds)) + enc->diPort = DISP_DI_DFPL | DISP_DI_DVP1; + + reg_value = 0x00; + if (enc->diPort == DISP_DI_DFPH) { + if (!is_msb) + reg_value = BIT(0); + svga_wcrt_mask(VGABASE, 0xD2, reg_value, BIT(0)); + } else if (enc->diPort == DISP_DI_DFPL) { + if (!is_msb) + reg_value = BIT(1); + svga_wcrt_mask(VGABASE, 0xD2, reg_value, BIT(1)); + } + + if (dual_channel) + enc->flags |= LVDS_DUAL_CHANNEL; + + /* Put it all together */ + drm_mode_connector_attach_encoder(&con->base, &enc->base); + return; + +no_device: + drm_sysfs_connector_remove(&con->base); + drm_connector_cleanup(&con->base); + kfree(enc); +}