From 8cca1f0487a6ccadf969dc7f3a9baae2f5cbafa8 Mon Sep 17 00:00:00 2001
From: Thomas Zimmermann <tdz@users.sourceforge.net>
Date: Fri, 26 Feb 2016 18:01:17 +0100
Subject: [PATCH] drm/sisvga: Add KMS driver for SiS 6326

Signed-off-by: Thomas Zimmermann <tdz@users.sourceforge.net>
---
 drivers/gpu/drm/Kconfig                     |   2 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/sisvga/Kconfig              |  13 +
 drivers/gpu/drm/sisvga/Makefile             |  17 +
 drivers/gpu/drm/sisvga/sisvga_bo.c          | 225 +++++
 drivers/gpu/drm/sisvga/sisvga_connector.c   | 244 +++++
 drivers/gpu/drm/sisvga/sisvga_crtc.c        | 979 ++++++++++++++++++++
 drivers/gpu/drm/sisvga/sisvga_ddc.c         | 411 ++++++++
 drivers/gpu/drm/sisvga/sisvga_debug.c       | 303 ++++++
 drivers/gpu/drm/sisvga/sisvga_debug.h       |  17 +
 drivers/gpu/drm/sisvga/sisvga_device.c      | 592 ++++++++++++
 drivers/gpu/drm/sisvga/sisvga_device.h      | 268 ++++++
 drivers/gpu/drm/sisvga/sisvga_drv.c         | 505 ++++++++++
 drivers/gpu/drm/sisvga/sisvga_drv.h         |  24 +
 drivers/gpu/drm/sisvga/sisvga_encoder.c     | 213 +++++
 drivers/gpu/drm/sisvga/sisvga_fbdev.c       | 288 ++++++
 drivers/gpu/drm/sisvga/sisvga_framebuffer.c |  93 ++
 drivers/gpu/drm/sisvga/sisvga_modes.c       |  42 +
 drivers/gpu/drm/sisvga/sisvga_modes.h       |  46 +
 drivers/gpu/drm/sisvga/sisvga_plane.c       | 127 +++
 drivers/gpu/drm/sisvga/sisvga_ttm.c         | 275 ++++++
 drivers/gpu/drm/sisvga/sisvga_vclk.c        | 167 ++++
 drivers/gpu/drm/sisvga/sisvga_vclk.h        |  57 ++
 include/linux/pci_ids.h                     |   2 +
 24 files changed, 4911 insertions(+)
 create mode 100644 drivers/gpu/drm/sisvga/Kconfig
 create mode 100644 drivers/gpu/drm/sisvga/Makefile
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_bo.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_connector.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_crtc.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_ddc.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_debug.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_debug.h
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_device.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_device.h
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_drv.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_drv.h
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_encoder.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_fbdev.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_framebuffer.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_modes.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_modes.h
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_plane.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_ttm.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_vclk.c
 create mode 100644 drivers/gpu/drm/sisvga/sisvga_vclk.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 10f9f01123ead..2c73d5f7abca8 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -304,6 +304,8 @@ source "drivers/gpu/drm/tve200/Kconfig"
 
 source "drivers/gpu/drm/xen/Kconfig"
 
+source "drivers/gpu/drm/sisvga/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 8873d47691163..414163bdbea3f 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_DRM_V3D)  += v3d/
 obj-$(CONFIG_DRM_VC4)  += vc4/
 obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus/
 obj-$(CONFIG_DRM_SIS)   += sis/
+obj-$(CONFIG_DRM_SISVGA)+= sisvga/
 obj-$(CONFIG_DRM_SAVAGE)+= savage/
 obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
 obj-$(CONFIG_DRM_VIA)	+=via/
diff --git a/drivers/gpu/drm/sisvga/Kconfig b/drivers/gpu/drm/sisvga/Kconfig
new file mode 100644
index 0000000000000..ce039f1d91be4
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/Kconfig
@@ -0,0 +1,13 @@
+config DRM_SISVGA
+	tristate "SiS video cards (KMS driver)"
+	depends on DRM && PCI && AGP
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select DRM_KMS_HELPER
+	select DRM_KMS_FB_HELPER
+	select DRM_TTM
+	help
+	  Choose this option if you have a SiS 305 or compatible video
+          chipset. If M is selected the module will be called sisvga. AGP
+          support is required for this driver to work.
diff --git a/drivers/gpu/drm/sisvga/Makefile b/drivers/gpu/drm/sisvga/Makefile
new file mode 100644
index 0000000000000..2e9ec45489190
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/Makefile
@@ -0,0 +1,17 @@
+ccflags-y = -Iinclude/drm
+sisvga-y := sisvga_bo.o \
+	    sisvga_connector.o \
+	    sisvga_crtc.o \
+	    sisvga_ddc.o \
+	    sisvga_debug.o \
+	    sisvga_device.o \
+	    sisvga_drv.o \
+	    sisvga_encoder.o \
+	    sisvga_fbdev.o \
+	    sisvga_framebuffer.o \
+	    sisvga_modes.o \
+	    sisvga_plane.o \
+	    sisvga_ttm.o \
+	    sisvga_vclk.o
+
+obj-$(CONFIG_DRM_SISVGA)   += sisvga.o
diff --git a/drivers/gpu/drm/sisvga/sisvga_bo.c b/drivers/gpu/drm/sisvga/sisvga_bo.c
new file mode 100644
index 0000000000000..29a50a144e283
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_bo.c
@@ -0,0 +1,225 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+
+/*
+ * Buffer objects
+ */
+
+void sisvga_bo_ttm_placement(struct sisvga_bo *bo, int domain)
+{
+	u32 c = 0;
+	unsigned i;
+
+	bo->placement.placement = bo->placements;
+	bo->placement.busy_placement = bo->placements;
+
+	if (domain & TTM_PL_FLAG_VRAM)
+		bo->placements[c++].flags = TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM;
+
+	if (domain & TTM_PL_FLAG_SYSTEM)
+		bo->placements[c++].flags = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+
+	if (!c)
+		bo->placements[c++].flags = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+
+	bo->placement.num_placement = c;
+	bo->placement.num_busy_placement = c;
+
+	for (i = 0; i < c; ++i) {
+		bo->placements[i].fpfn = 0;
+		bo->placements[i].lpfn = 0;
+	}
+}
+
+static void sisvga_bo_ttm_destroy(struct ttm_buffer_object *tbo)
+{
+	struct sisvga_bo *sis_bo;
+
+	sis_bo = container_of(tbo, struct sisvga_bo, bo);
+
+	drm_gem_object_release(&sis_bo->gem);
+	kfree(sis_bo);
+}
+
+int sisvga_bo_create(struct drm_device *dev, int size, int align,
+		     uint32_t flags, struct sisvga_bo **sis_bo_out)
+{
+	struct sisvga_device *sis_dev = dev->dev_private;
+	struct sisvga_bo *sis_bo;
+	size_t acc_size;
+	int ret;
+
+	sis_bo = kzalloc(sizeof(struct sisvga_bo), GFP_KERNEL);
+	if (!sis_bo)
+		return -ENOMEM;
+
+	ret = drm_gem_object_init(dev, &sis_bo->gem, size);
+	if (ret) {
+		kfree(sis_bo);
+		return ret;
+	}
+
+	sis_bo->bo.bdev = &sis_dev->ttm.bdev;
+
+	sisvga_bo_ttm_placement(sis_bo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
+
+	acc_size = ttm_bo_dma_acc_size(&sis_dev->ttm.bdev, size,
+				       sizeof(struct sisvga_bo));
+
+	ret = ttm_bo_init(&sis_dev->ttm.bdev, &sis_bo->bo, size,
+			  ttm_bo_type_device, &sis_bo->placement,
+			  align >> PAGE_SHIFT, false, acc_size,
+			  NULL, NULL, sisvga_bo_ttm_destroy);
+	if (ret)
+		return ret;
+
+	*sis_bo_out = sis_bo;
+	return 0;
+}
+
+void sisvga_bo_unref(struct sisvga_bo **sis_bo)
+{
+	struct ttm_buffer_object *tbo;
+
+	if ((*sis_bo) == NULL)
+		return;
+
+	tbo = &((*sis_bo)->bo);
+	ttm_bo_unref(&tbo);
+	*sis_bo = NULL;
+}
+
+int sisvga_bo_reserve(struct sisvga_bo *bo, bool no_wait)
+{
+	int ret;
+
+	ret = ttm_bo_reserve(&bo->bo, true, no_wait, NULL);
+	if (ret) {
+		if (ret != -ERESTARTSYS && ret != -EBUSY)
+			DRM_ERROR("sisvga: ttm_bo_reserve(%p) failed,"
+				  " error %d\n", bo, -ret);
+		return ret;
+	}
+	return 0;
+}
+
+void sisvga_bo_unreserve(struct sisvga_bo *bo)
+{
+	ttm_bo_unreserve(&bo->bo);
+}
+
+u64 sisvga_bo_mmap_offset(struct sisvga_bo *sis_bo)
+{
+	return drm_vma_node_offset_addr(&sis_bo->bo.vma_node);
+}
+
+int sisvga_bo_pin(struct sisvga_bo *bo, u32 pl_flag, u64 *gpu_addr)
+{
+	int i, ret;
+	struct ttm_operation_ctx ctx = { false, false };
+
+	if (bo->pin_count) {
+		bo->pin_count++;
+		goto out;
+	}
+
+	sisvga_bo_ttm_placement(bo, pl_flag);
+	for (i = 0; i < bo->placement.num_placement; i++)
+		bo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
+
+	ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+	if (ret) {
+		DRM_ERROR("sisvga: ttm_bo_validate failed, error %d\n", -ret);
+		return ret;
+	}
+
+	bo->pin_count = 1;
+
+out:
+	if (gpu_addr)
+		*gpu_addr = bo->bo.offset;
+
+	return 0;
+}
+
+int sisvga_bo_unpin(struct sisvga_bo *bo)
+{
+	int i;
+	struct ttm_operation_ctx ctx = { false, false };
+
+	if (!bo->pin_count) {
+		DRM_ERROR("sisvga: BO %p is not pinned \n", bo);
+		return 0;
+	}
+	bo->pin_count--;
+	if (bo->pin_count)
+		return 0;
+
+	for (i = 0; i < bo->placement.num_placement ; i++)
+		bo->placements[i].flags &= ~TTM_PL_FLAG_NO_EVICT;
+
+	return ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+}
+
+int sisvga_bo_push_to_system(struct sisvga_bo *bo)
+{
+	int i, ret;
+	struct ttm_operation_ctx ctx = { false, false };
+
+	if (!bo->pin_count) {
+		DRM_ERROR("sisvga: BO %p is not pinned \n", bo);
+		return 0;
+	}
+	bo->pin_count--;
+	if (bo->pin_count)
+		return 0;
+
+	if (bo->kmap.virtual)
+		ttm_bo_kunmap(&bo->kmap);
+
+	sisvga_bo_ttm_placement(bo, TTM_PL_FLAG_SYSTEM);
+	for (i = 0; i < bo->placement.num_placement ; i++)
+		bo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
+
+	ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+	if (ret) {
+		DRM_ERROR("sisvga: ttm_bo_validate failed, error %d\n", -ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * GEM objects
+ */
+
+int sisvga_gem_create(struct drm_device *dev, u32 size, bool iskernel,
+		      struct drm_gem_object **obj)
+{
+	struct sisvga_bo *sis_bo;
+	int ret;
+
+	*obj = NULL;
+
+	size = roundup(size, PAGE_SIZE);
+	if (size == 0)
+		return -EINVAL;
+
+	ret = sisvga_bo_create(dev, size, 0, 0, &sis_bo);
+	if (ret) {
+		if (ret != -ERESTARTSYS)
+			DRM_ERROR("sisvga: sisvga_bo_create() failed,"
+				  " error %d\n", -ret);
+		return ret;
+	}
+	*obj = &sis_bo->gem;
+	return 0;
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_connector.c b/drivers/gpu/drm/sisvga/sisvga_connector.c
new file mode 100644
index 0000000000000..18ca4d6d680f9
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_connector.c
@@ -0,0 +1,244 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include "sisvga_vclk.h"
+
+static struct sisvga_connector* sisvga_connector_of(
+	struct drm_connector* connector)
+{
+	return container_of(connector, struct sisvga_connector, base);
+}
+
+/*
+ * Connector helper funcs
+ */
+
+static int sisvga_connector_helper_get_modes_vga(
+	struct drm_connector *connector)
+{
+	struct sisvga_connector *sis_connector;
+	struct edid *edid;
+	int ret;
+
+	sis_connector = sisvga_connector_of(connector);
+
+	edid = drm_get_edid(connector, &sis_connector->ddc.adapter);
+	if (!edid)
+		return 0;
+
+	ret = drm_mode_connector_update_edid_property(connector, edid);
+	if (ret < 0)
+		goto err;
+
+	ret = drm_add_edid_modes(connector, edid);
+	if (ret < 0)
+		goto err;
+
+	kfree(edid);
+
+	return 0;
+
+err:
+	kfree(edid);
+	return ret;
+}
+
+static int sisvga_connector_helper_mode_valid_vga(
+	struct drm_connector *connector,
+	struct drm_display_mode *mode)
+{
+	enum sisvga_vclk vclk;
+	int ret;
+	struct sisvga_device *sdev = connector->dev->dev_private;
+	const struct sisvga_device_info *info = sdev->info;
+	const struct sisvga_mode* smode;
+	int bpp = info->max_bpp;
+
+	/* validate dotclock */
+
+	if (mode->clock > info->max_clock)
+		return MODE_CLOCK_HIGH;
+	ret = sisvga_vclk_of_clock(mode->clock, &vclk);
+	if (ret < 0)
+		return MODE_CLOCK_RANGE;
+	if (!(info->supported_vclks & SISVGA_VCLK_BIT(vclk)))
+		return MODE_CLOCK_RANGE;
+
+	/* validate display size */
+
+	if (mode->hdisplay > info->max_hdisplay)
+		return MODE_VIRTUAL_X;
+	if (mode->vdisplay > info->max_vdisplay)
+		return MODE_VIRTUAL_Y;
+
+	if (((mode->hdisplay % 8) != 0 ||
+	     (mode->hsync_start % 8) != 0 ||
+	     (mode->hsync_end % 8) != 0 ||
+	     (mode->htotal % 8) != 0) &&
+	    ((mode->hdisplay % 9) != 0 ||
+	     (mode->hsync_start % 9) != 0 ||
+	     (mode->hsync_end % 9) != 0 ||
+	     (mode->htotal % 9) != 0)) {
+		return MODE_H_ILLEGAL;
+	}
+
+	if (mode->hdisplay > info->max_hdisplay ||
+	    mode->hsync_start > info->max_hsync_start ||
+	    mode->hsync_end > info->max_hsync_end ||
+	    mode->htotal > info->max_htotal ||
+	    mode->vdisplay > info->max_vdisplay ||
+	    mode->vsync_start > info->max_vsync_start ||
+	    mode->vsync_end > info->max_vsync_end ||
+	    mode->vtotal > info->max_vtotal) {
+		return MODE_BAD;
+	}
+
+	/* validate memory requirements */
+
+	if (connector->cmdline_mode.specified) {
+		if (connector->cmdline_mode.bpp_specified)
+			bpp = connector->cmdline_mode.bpp;
+	}
+
+	if ((mode->hdisplay * mode->vdisplay * (bpp / 8)) > sdev->vram.size) {
+		if (connector->cmdline_mode.specified)
+			connector->cmdline_mode.specified = false;
+		return MODE_BAD;
+	}
+
+	/* see if the mode is supported by the device */
+
+	smode = sisvga_find_compatible_mode(info->vga_modes,
+					    info->vga_modes +
+					    info->vga_modes_len,
+					    mode, bpp);
+	if (!smode)
+		return MODE_BAD;
+
+	return MODE_OK;
+}
+
+static struct drm_encoder* sisvga_connector_helper_best_encoder_vga(
+	struct drm_connector *connector)
+{
+	struct drm_encoder *encoder;
+	size_t i;
+	size_t len = ARRAY_SIZE(connector->encoder_ids);
+
+	for (i = 0; (i < len) && connector->encoder_ids[i]; ++i) {
+
+		encoder = drm_encoder_find(connector->dev, NULL,
+					   connector->encoder_ids[i]);
+		if (!encoder) {
+			DRM_ERROR("encoder %d not found",
+				connector->encoder_ids[i]);
+			continue;
+		}
+		if (encoder->encoder_type != DRM_MODE_ENCODER_DAC)
+			continue;
+
+		return encoder;
+	}
+
+	return NULL;
+}
+
+static const struct drm_connector_helper_funcs sisvga_connector_helper_funcs = {
+	.get_modes = sisvga_connector_helper_get_modes_vga,
+	.mode_valid = sisvga_connector_helper_mode_valid_vga,
+	.best_encoder = sisvga_connector_helper_best_encoder_vga,
+};
+
+/*
+ * Connector funcs
+ */
+
+static enum drm_connector_status sisvga_connector_detect_vga(
+	struct drm_connector *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static void sisvga_connector_destroy(struct drm_connector *connector)
+{
+	struct sisvga_connector *sis_connector = sisvga_connector_of(connector);
+	struct drm_device *dev = connector->dev;
+
+	sisvga_ddc_fini(&sis_connector->ddc);
+
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+	devm_kfree(dev->dev, sis_connector);
+}
+
+static const struct drm_connector_funcs sisvga_connector_funcs_vga = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = sisvga_connector_detect_vga,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = sisvga_connector_destroy,
+};
+
+/*
+ * struct sisvga_connector
+ */
+
+static int sisvga_connector_init_vga(struct sisvga_connector *sis_connector,
+				     struct drm_device *dev)
+{
+	int ret;
+	struct drm_connector *connector = &sis_connector->base;
+
+	ret = drm_connector_init(dev, connector, &sisvga_connector_funcs_vga,
+				 DRM_MODE_CONNECTOR_VGA);
+	if (ret < 0)
+		return ret;
+
+	drm_connector_helper_add(connector, &sisvga_connector_helper_funcs);
+
+	ret = sisvga_ddc_init(&sis_connector->ddc, dev);
+	if (ret < 0)
+		goto err_sisvga_ddc_init;
+
+	ret = drm_connector_register(connector);
+	if (ret < 0)
+		goto err_drm_connector_register;
+
+	return 0;
+
+err_drm_connector_register:
+	sisvga_ddc_fini(&sis_connector->ddc);
+err_sisvga_ddc_init:
+	drm_connector_cleanup(connector);
+	return ret;
+}
+
+struct sisvga_connector* sisvga_connector_create_vga(struct drm_device *dev)
+{
+	struct sisvga_connector *sis_connector;
+	int ret;
+
+	sis_connector = devm_kzalloc(dev->dev, sizeof(*sis_connector),
+				     GFP_KERNEL);
+	if (!sis_connector)
+		return ERR_PTR(-ENOMEM);
+
+	ret = sisvga_connector_init_vga(sis_connector, dev);
+	if (ret < 0)
+		goto err_sisvga_connector_init_vga;
+
+	return sis_connector;
+
+err_sisvga_connector_init_vga:
+	devm_kfree(dev->dev, sis_connector);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_crtc.c b/drivers/gpu/drm/sisvga/sisvga_crtc.c
new file mode 100644
index 0000000000000..228c3c0a7f334
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_crtc.c
@@ -0,0 +1,979 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane_helper.h>
+#include "sisvga_reg.h"
+
+static struct sisvga_crtc* sisvga_crtc_of(struct drm_crtc* crtc)
+{
+	return container_of(crtc, struct sisvga_crtc, base);
+}
+
+/*
+ * LUT helpers
+ */
+
+static void load_lut_18(struct sisvga_device *sdev, const u8 (*lut)[3],
+			size_t len)
+{
+	size_t i, j;
+	u8 rgb[3];
+
+	for (i = 0; i < len; ++i) {
+		RREG_DAC(i, rgb[0], rgb[1], rgb[2]);
+		for (j = 0; j < ARRAY_SIZE(rgb); ++j) {
+			rgb[j] = (rgb[j] & 0xc0) | (lut[i][0] & 0x3f);
+		}
+		WREG_DAC(i, rgb[0], rgb[1], rgb[2]);
+	}
+}
+
+/* SiS graphics cards allow for 8-bit values in the DAC. */
+static void load_lut_24(struct sisvga_device *sdev, const u8 (*lut)[3],
+			size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; ++i) {
+		WREG_DAC(i, lut[i][0], lut[i][1], lut[i][2]);
+	}
+}
+
+/*
+ * DPMS helpers
+ */
+
+static void set_crtc_dpms_mode(struct sisvga_device* sdev, int mode)
+{
+	u8 sr01;
+
+	RREG_SR(0x01, sr01);
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		sr01 &= 0xdf; /* screen enabled */
+		break;
+	case DRM_MODE_DPMS_STANDBY:
+	case DRM_MODE_DPMS_SUSPEND:
+	case DRM_MODE_DPMS_OFF:
+		sr01 |= 0x20; /* screen disabled */
+		break;
+	default:
+		DRM_ERROR("sisvga: invalid DPMS mode %d\n", mode);
+		return;
+	}
+
+	WREG_SR(0x01, sr01);
+}
+
+/*
+ * Mode-setting helpers
+ */
+
+static u32 bytes_per_pixel(const struct drm_format_info* format)
+{
+	u32 bpp;
+	u8 i;
+
+	for (bpp = 0, i = 0; i < format->num_planes; ++i) {
+		bpp += format->cpp[i];
+	}
+	return bpp;
+}
+
+static int compute_offset(unsigned int width,
+			  const struct drm_format_info* format,
+			  u8 addr_incr, bool interlaced)
+{
+	int offset = width * bytes_per_pixel(format);
+
+	if (interlaced)
+		offset *= 2;
+
+	return offset / (addr_incr * 2);
+
+}
+
+static int set_framebuffer(struct sisvga_device *sdev,
+			   struct sisvga_framebuffer *new_sis_fb,
+			   struct sisvga_framebuffer *old_sis_fb,
+			   bool atomic, bool interlaced)
+{
+	struct sisvga_bo *bo;
+	int ret, offset;
+	u64 gpu_addr;
+	u32 start_address;
+	u8 cr13, cr0c, cr0d, sr02, sr03, sr04, sr06, sr0a, sr0c, sr26, sr27, sr3e;
+
+	/* push the previous fb to system ram */
+	if (!atomic && old_sis_fb) {
+		bo = gem_to_sisvga_bo(old_sis_fb->gem_obj);
+		ret = sisvga_bo_reserve(bo, false);
+		if (ret)
+			return ret;
+		sisvga_bo_push_to_system(bo);
+		sisvga_bo_unreserve(bo);
+	}
+
+	bo = gem_to_sisvga_bo(new_sis_fb->gem_obj);
+
+	ret = sisvga_bo_reserve(bo, false);
+	if (ret)
+		return ret;
+
+	ret = sisvga_bo_pin(bo, TTM_PL_FLAG_VRAM, &gpu_addr);
+	if (ret) {
+		sisvga_bo_unreserve(bo);
+		return ret;
+	}
+
+	sisvga_bo_unreserve(bo);
+
+	ret = compute_offset(new_sis_fb->base.width, new_sis_fb->base.format,
+			     4, interlaced);
+	if (ret < 0)
+		return ret;
+	offset = ret;
+
+	start_address = gpu_addr >> 2;
+
+	RREG_SR(0x02, sr02);
+	RREG_SR(0x03, sr03);
+	RREG_SR(0x04, sr04);
+	RREG_SR(0x06, sr06);
+	RREG_SR(0x0a, sr0a);
+	RREG_SR(0x27, sr27);
+	RREG_SR(0x3e, sr3e);
+
+	cr13 = offset & 0xff;
+	cr0c = (start_address & 0x0000ff00) >> 8;
+	cr0d = start_address & 0x000000ff;
+	sr02 &= 0xf0; /* preserve reserved bits */
+	sr02 |= 0x0f; /* writes to all planes enabled */
+	sr03 &= 0xc0; /* preserve reserved bits */
+	sr04 &= 0xf1; /* preserve reserved bits */
+	sr04 |= 0x08 | /* Chain-4 mode enabled */
+		0x04 | /* Odd/Even disable */
+		0x02; /* Extended memory enabled */
+	sr06 |= 0x80; /* linear addressing enabled */
+	sr0a &= 0x0f; /* preserve bits */
+	sr0a |= (offset & 0xf00) >> 4;
+	sr0c = 0x80; /* 32-bit graphics-memory access enabled */
+	sr26 = 0x10; /* continuous memory access enabled */
+	sr27 &= 0xf0; /* preserve reserved bits */
+	sr27 |= (start_address & 0x000f0000) >> 16;
+
+	WREG_CR(0x13, cr13);
+	WREG_CR(0x0c, cr0c);
+	WREG_CR(0x0d, cr0d);
+
+	WREG_SR(0x02, sr02);
+	WREG_SR(0x03, sr03);
+	WREG_SR(0x04, sr04);
+	WREG_SR(0x06, sr06);
+	WREG_SR(0x0a, sr0a);
+	WREG_SR(0x0c, sr0c);
+	WREG_SR(0x26, sr26);
+	WREG_SR(0x27, sr27);
+	WREG_SR(0x3e, sr3e);
+
+	return 0;
+}
+
+static int set_color_mode(const struct sisvga_device *sdev,
+			  const struct drm_framebuffer *fb)
+{
+	struct drm_format_name_buf buf;
+	u8 cr14, cr17, ar10, ar11, ar12, ar13, ar14, gr00, gr01,
+	   gr02, gr03, gr04, gr05, gr06, gr07, gr08, sr06, sr0b;
+
+	RREG_CR(0x14, cr14);
+	RREG_CR(0x17, cr17);
+	RREG_GR(0x00, gr00);
+	RREG_GR(0x01, gr01);
+	RREG_GR(0x02, gr02);
+	RREG_GR(0x03, gr03);
+	RREG_GR(0x04, gr04);
+	RREG_GR(0x05, gr05);
+	RREG_GR(0x06, gr06);
+	RREG_GR(0x07, gr07);
+	RREG_AR(0x10, ar10);
+	RREG_AR(0x12, ar12);
+	RREG_AR(0x13, ar13);
+	RREG_AR(0x14, ar14);
+	RREG_SR(0x06, sr06);
+	RREG_SR(0x0b, sr0b);
+
+	cr14 |= 0x40; /* set double-word addressing */
+	//cr14 &= 0x9f; /* double-word addressing disabled */
+	cr17 &= 0xbf; /* word-mode addressing enabled */
+	//if (fb->format->depth < 8)
+	//	cr17 |= 0x20;
+	cr17 |= /*0x20 |*/ /* set MA 15 bit in word-address mode */
+                0x80; /* word-based refresh enabled */
+
+	gr00 &= 0xf0; /* preserve reserved bits */
+	gr01 &= 0xf0; /* preserve reserved bits */
+	gr02 &= 0xf0; /* preserve reserved bits */
+	gr03 &= 0xe0; /* preserve reserved bits */
+	gr04 &= 0xfa; /* preserve reserved bits */
+	gr05 &= 0x84; /* preserve reserved bits */
+	gr06 &= 0xf0; /* preserve reserved bits */
+	gr06 |= /*0x04 |*/
+		0x01; /* graphics mode */
+	gr07 &= 0xf0; /* preserve reserved bits */
+	gr07 |= 0x0f; /* color-don't-care for Read Mode 1 */
+	gr08 = 0xff; /* bitmask for Write Modes 0,2,3 */
+
+	ar10 &= 0x10; /* preserve reserved bits */
+	ar10 |= 0x01; /* graphics mode enabled */
+	ar11 = 0x00; /* clear overscan palette index */
+	ar12 &= 0xf0; /* preserve reserved bits */
+	//ar12 |= 0x0f; /* enable color planes */
+	ar13 &= 0xf0; /* preserve reserved bits */
+	ar14 &= 0xf0; /* preserve reserved bits */
+
+	/* In TrueColor mode, the hardware expects RGB buffers in
+	 * big-endian byte order. If the framebuffer is in little
+	 * endian, we invert the RGB/BGR mode. */
+	switch (fb->format->format) {
+	case DRM_FORMAT_RGB888:
+		sr06 &= 0xe0; /* clear graphics/text mode */
+		sr06 |= 0x10; /* TrueColor enabled */
+		sr06 |= 0x02; /* enhanced graphcs mode enabled */
+		if (fb->format->format & DRM_FORMAT_BIG_ENDIAN)
+			sr0b &= 0x7f; /* RGB byte order */
+		else
+			sr0b |= 0x80; /* RGB byte order (little endian) */
+		break;
+	case DRM_FORMAT_BGR888:
+		sr06 &= 0xe0; /* clear graphics/text mode */
+		sr06 |= 0x10; /* TrueColor enabled */
+		sr06 |= 0x02; /* enhanced graphcs mode enabled */
+		if (fb->format->format & DRM_FORMAT_BIG_ENDIAN)
+			sr0b |= 0x80; /* BGR byte order */
+		else
+			sr0b &= 0x7f; /* BGR byte order (little endian) */
+		break;
+	case DRM_FORMAT_RGB565:
+		sr06 &= 0xe0; /* clear graphics/text mode */
+		sr06 |= 0x08; /* 64KColor enabled */;
+		sr06 |= 0x02; /* enhanced graphcs mode enabled */
+		sr0b &= 0x7f; /* clear RGB byte order */
+		break;
+	case DRM_FORMAT_XRGB1555:
+		sr06 &= 0xe0; /* clear graphics/text mode */
+		sr06 |= 0x04; /* 32KColor enabled */
+		sr06 |= 0x02; /* enhanced graphcs mode enabled */
+		sr0b &= 0x7f; /* clear RGB byte order */
+		break;
+	case DRM_FORMAT_C8:
+		gr05 = 0x40; /* 256-color palette enabled */
+		ar10 |= 0x40; /* 8-bit palette enabled */
+		sr06 &= 0xe3; /* clear enhanced color modes */
+		sr0b &= 0x7f; /* clear RGB byte-order bit */
+		break;
+	default: 	/* unsupported VGA color configuration */
+		/* BUG: We should have detected this in mode_fixup(). */
+		DRM_ERROR("sisvga: %s color format is not supported\n",
+			  drm_get_format_name(fb->format->format, &buf));
+		return -EINVAL;
+	}
+
+	WREG_CR(0x14, cr14);
+	WREG_CR(0x17, cr17);
+
+	WREG_GR(0x00, gr00);
+	WREG_GR(0x01, gr01);
+	WREG_GR(0x02, gr02);
+	WREG_GR(0x03, gr03);
+	WREG_GR(0x04, gr04);
+	WREG_GR(0x05, gr05);
+	WREG_GR(0x06, gr06);
+	WREG_GR(0x07, gr07);
+	WREG_GR(0x08, gr08);
+
+	WREG_AR(0x10, ar10);
+	WREG_AR(0x11, ar11);
+	WREG_AR(0x12, ar12);
+	WREG_AR(0x13, ar13);
+	WREG_AR(0x14, ar14);
+
+	WREG_SR(0x06, sr06);
+	WREG_SR(0x0b, sr0b);
+
+	return 0;
+}
+
+static bool is_9_dot_mode(const struct drm_display_mode* mode)
+{
+	int hdisplay = mode->hdisplay;
+	if (mode->flags & DRM_MODE_FLAG_CLKDIV2)
+		hdisplay *= 2;
+
+	/* VGA 9-dot modes have a ratio of 9:5 */
+	return !(hdisplay % 9) && (((mode->vdisplay * 9) / 5) == hdisplay);
+}
+
+static int set_display_mode(struct sisvga_device *sdev,
+			    const struct drm_display_mode *mode)
+{
+	static const enum sisvga_freq freq_list[] = {
+		SISVGA_FREQ_14318,
+		SISVGA_FREQ_25175,
+		SISVGA_FREQ_28322
+	};
+	static const u8 freq_bits_list[] = {
+		[SISVGA_FREQ_14318] = 0x03,
+		[SISVGA_FREQ_25175] = 0x00,
+		[SISVGA_FREQ_28322] = 0x01
+	};
+	static const u8 freqi_bits_list[] = {
+		[SISVGA_FREQ_14318] = 0x00,
+		[SISVGA_FREQ_25175] = 0x01,
+		[SISVGA_FREQ_28322] = 0x02
+	};
+
+	size_t i;
+	long ret;
+	enum sisvga_vclk vclk;
+	enum sisvga_freq freq;
+	unsigned long num, denum, div, postscal, f;
+	u8 freq_bits, freqi_bits, num_bits, denum_bits, div_bits, postscal_bits;
+
+	int dots, htotal, hsync_start, hsync_end, hdisplay, hskew, hblank_start,
+	    hblank_end, vtotal, vsync_start, vsync_end, vdisplay, vscan,
+	    vblank_start, vblank_end, line_compare;
+	u8 misc;
+	u8 cr00, cr01, cr02, cr03, cr04, cr05, cr06, cr07, cr08, cr09, cr0a,
+	   cr0b, cr0c, cr0d, cr0e, cr0f, cr10, cr11, cr12, cr14, cr15,
+	   cr16, cr17, cr18;
+	u8 sr01, sr06, sr07, sr0a, sr12, sr13, sr2a, sr2b, sr38;
+
+	/* The CRTC values are the same as for regular VGA adapters.
+	 * Some of the bits for higher resolutions will be copied to
+	 * SiS' extended registers. */
+
+	if (is_9_dot_mode(mode))
+		dots = 9;
+	else
+		dots = 8;
+
+	htotal = (mode->crtc_htotal / dots) - 5;
+	hsync_start = (mode->crtc_hsync_start / dots) - 1;
+	hsync_end = (mode->crtc_hsync_end / dots) - 1;
+	hdisplay = (mode->crtc_hdisplay / dots) - 1;
+	if (mode->flags & DRM_MODE_FLAG_HSKEW)
+		hskew = mode->hskew / dots;
+	else
+		hskew = 0;
+	hblank_start = (mode->crtc_hblank_start / dots) - 1;
+	hblank_end = (mode->crtc_hblank_end / dots) - 1;
+
+	vtotal = mode->crtc_vtotal - 2;
+	vsync_start = mode->crtc_vsync_start - 1;
+	vsync_end = mode->crtc_vsync_end - 1;
+	vdisplay = mode->crtc_vdisplay - 1;
+	if (mode->vscan)
+		vscan = mode->vscan - 1;
+	else
+		vscan = 0;
+	vblank_start = mode->crtc_vblank_start - 1;
+	vblank_end = mode->crtc_vblank_end - 1;
+	line_compare = mode->crtc_vtotal + 1; /* beyond end of display; disabled */
+
+	/* We have to compute the PLL's configuration for the given
+	 * dot clock. With the computed parameters, we can also select
+	 * the correct registers. sr38 serves as index register for
+	 * sr13, sr2a, and sr2b. */
+
+	ret = sisvga_vclk_of_clock(mode->clock, &vclk);
+	if (ret < 0) {
+		/* BUG: We should have detected this in mode_valid(). */
+		DRM_ERROR("sisvga: unsupported dot clock of %d KHz, error %ld",
+			  mode->clock, -ret);
+		return ret;
+	}
+	sisvga_vclk_regs(vclk, &freq, &num, &denum, &div, &postscal, &f);
+
+	freq_bits = freq_bits_list[freq];
+	freqi_bits = freqi_bits_list[freq];
+	num_bits = num - 1;
+	denum_bits = denum - 1;
+	div_bits = div - 1;
+	postscal_bits = postscal - 1;
+
+	misc = RREG8(REG_MISC_IN);
+
+	RREG_CR(0x08, cr08);
+	RREG_CR(0x09, cr09);
+	RREG_CR(0x11, cr11);
+	RREG_CR(0x14, cr14);
+	RREG_CR(0x17, cr17);
+
+	/* Below is the register setup code. sr38 contains the index
+	 * register for sr13, sr2a and sr2b. We set it up before all
+	 * other sequencer registers. With the correct registers
+	 * selected we can later configure the dot clock generator. */
+	/* TODO: NOT on 6202 ??? */
+	RREG_SR(0x38, sr38);
+	sr38 &= 0xfc; /* clear clock-register selector */
+	sr38 |= freqi_bits;
+	WREG_SR(0x38, sr38);
+	WAIT_SR_MASKED(0x38, freqi_bits, 0x03);
+
+	RREG_SR(0x01, sr01);
+	RREG_SR(0x06, sr06);
+	RREG_SR(0x07, sr07);
+	if (MODEL_IS_GE(6326))
+		RREG_SR(0x13, sr13);
+	else
+		sr13 = 0;
+	RREG_SR(0x0a, sr0a);
+	RREG_SR(0x2a, sr2a);
+	RREG_SR(0x2b, sr2b);
+	if (MODEL_IS_GE(6326))
+		RREG_SR(0x38, sr38);
+	else
+		sr38 = 0;
+
+	cr00 = htotal & 0xff;
+	cr01 = hdisplay & 0xff;
+	cr02 = hblank_start & 0xff;
+	cr03 = 0x80 | /* preserve reserved bit */
+	       ((hskew & 0x03) << 5) |
+	       (hblank_end & 0x1f);
+	cr04 = hsync_start & 0xff;
+	cr05 = ((hblank_end & 0x20) << 2) |
+	       (hsync_end & 0x1f);
+	cr06 = vtotal & 0xff;
+	cr07 = ((vsync_start & 0x200) >> 2) |
+	       ((vdisplay & 0x200) >> 3) |
+	       ((vtotal & 0x200) >> 4) |
+	       ((line_compare & 0x100) >> 4) |
+	       ((vblank_start & 0x100) >> 5) |
+	       ((vsync_start & 0x100) >> 6) |
+	       ((vdisplay & 0x100) >> 7) |
+	       ((vtotal & 0x100) >> 8);
+	cr08 &= 0x80; /* preserve bit */
+	cr09 = ((line_compare & 0x200) >> 3) |
+	       ((vblank_start & 0x200) >> 4) |
+	       (vscan & 0x1f);
+	if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+		cr09 |= 0x80;
+	cr0a = 0;
+	cr0b = 0;
+	cr0c = 0;
+	cr0d = 0;
+	cr0e = 0;
+	cr0f = 0;
+	cr10 = vsync_start & 0xff;
+	cr11 = vsync_end & 0x0f;
+	cr12 = vdisplay & 0xff;
+	cr14 &= 0x80; /* preserve reserved bit */
+	cr15 = vblank_start & 0xff;
+	cr16 = vblank_end & 0xff; /* SiS uses all 8 bits */
+	cr17 |= 0x40 | /* byte-address mode enabled */
+		0x03;
+	cr18 = line_compare & 0xff;
+
+	if (dots == 9)
+		sr01 &= 0xfe; /* 9-dot mode */
+	else
+		sr01 |= 0x01; /* 8-dot mode */
+	sr0a &= 0xf0; /* preserve bits */
+	sr0a |= ((vsync_start & 0x400) >> 7) |
+		((vblank_start & 0x400) >> 8) |
+		((vdisplay & 0x400) >> 9) |
+		((vtotal & 0x400) >> 10);
+	if (MODEL_IS_GE(6326))
+		sr12 = ((hblank_end & 0x40) >> 2) |
+		       ((hsync_start & 0x100) >> 5) |
+		       ((hblank_start & 0x100) >> 6) |
+		       ((hdisplay & 0x100) >> 7) |
+		       ((htotal & 0x100) >> 8);
+	else
+		sr12 = 0;
+
+	misc &= 0x3f; /* clear sync bits */
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		misc |= 0x80;
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		misc |= 0x40;
+
+	misc &= 0xf3; /* clear clock selector */
+	misc |= freq_bits << 2;
+
+	if (mode->flags & DRM_MODE_FLAG_CLKDIV2)
+		sr01 |= 0x08;
+	else
+		sr01 &= 0xf7; /* don't divide dot-clock rate by 2 */
+
+	/* TODO: Force 9/8 dot mode to 0 when switching to
+	 * 25.175 MHz in rev 0b and earlier. */
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		sr06 |= 0x20;
+	else
+		sr06 &= 0xdf;
+
+	if (mode->clock > MHZ_TO_KHZ(135))
+		sr07 |= 0x02; /* high-frequency DAC enabled */
+	else
+		sr07 &= 0xfd;
+
+	if (freq == SISVGA_FREQ_14318) {
+
+		/* On all SiS adapters we have to configure the internal dot
+		 * clock generator. In theory (aka 'the manual') on SiS 6326
+		 * we can configure VGA clock generators in the same way. In
+		 * practice hardware doesn't support it. So we only use VGA
+		 * registers for VGA dot clocks, and extended registers for
+		 * the internal clock generator. */
+
+		if (MODEL_IS_GE(6326)) {
+			if (postscal_bits & 0x04)
+				sr13 |= 0x40;
+			else
+				sr13 &= 0xbf; /* clear post-scaler bit */
+		}
+
+		sr2a = ((div_bits & 0x01) << 7) |
+		       (num_bits & 0x7f);
+		sr2b = ((postscal_bits & 0x03) << 5) |
+		       (denum_bits & 0x1f);
+		if (mode->clock > MHZ_TO_KHZ(135))
+			sr2b |= 0x80; /* high-frequency gain enabled */
+
+		sr06 |= 0x03; /* enable enhanced modes */
+	} else {
+		sr06 &= 0xfc; /* disable enhanced modes */
+	}
+
+	if (MODEL_IS_GE(6326))
+		sr38 |= 0x04; /* Disable line compare */
+
+	WREG8(REG_MISC_OUT, misc);
+	WAIT8_MASKED(REG_MISC_IN, misc, 0xef);
+
+	WREG_SR(0x01, sr01);
+
+	/* VCLK setup */
+	if (freq == SISVGA_FREQ_14318) {
+		if (MODEL_IS_GE(6326))
+			WREG_SR(0x13, sr13);
+		WREG_SR(0x2a, sr2a);
+		WREG_SR(0x2b, sr2b);
+	}
+
+	WREG_SR(0x0a, sr0a);
+	WREG_SR(0x06, sr06);
+	WREG_SR(0x07, sr07);
+	if (MODEL_IS_GE(6326)) {
+		WREG_SR(0x12, sr12);
+		WREG_SR(0x38, sr38);
+	}
+
+	WREG_CR(0x00, cr00);
+	WREG_CR(0x01, cr01);
+	WREG_CR(0x02, cr02);
+	WREG_CR(0x03, cr03);
+	WREG_CR(0x04, cr04);
+	WREG_CR(0x05, cr05);
+	WREG_CR(0x06, cr06);
+	WREG_CR(0x07, cr07);
+	WREG_CR(0x08, cr08);
+	WREG_CR(0x09, cr09);
+	WREG_CR(0x0a, cr0a);
+	WREG_CR(0x0b, cr0b);
+	WREG_CR(0x0c, cr0c);
+	WREG_CR(0x0d, cr0d);
+	WREG_CR(0x0e, cr0e);
+	WREG_CR(0x0f, cr0f);
+	WREG_CR(0x10, cr10);
+	WREG_CR(0x11, cr11);
+	WREG_CR(0x12, cr12);
+	WREG_CR(0x14, cr14);
+	WREG_CR(0x15, cr15);
+	WREG_CR(0x16, cr16);
+	WREG_CR(0x17, cr17);
+	WREG_CR(0x18, cr18);
+
+	return 0;
+}
+
+/*
+ * CRTC helper funcs
+ */
+
+static void sisvga_crtc_helper_disable(struct drm_crtc *crtc)
+{
+	set_crtc_dpms_mode(crtc->dev->dev_private, DRM_MODE_DPMS_OFF);
+}
+
+static void sisvga_crtc_helper_dpms(struct drm_crtc *crtc, int mode)
+{
+	struct sisvga_crtc *sis_crtc = sisvga_crtc_of(crtc);
+
+	set_crtc_dpms_mode(crtc->dev->dev_private, mode);
+
+	if (sis_crtc->lut_24) {
+		load_lut_24(crtc->dev->dev_private, sis_crtc->lut,
+			    ARRAY_SIZE(sis_crtc->lut));
+	} else {
+		load_lut_18(crtc->dev->dev_private, sis_crtc->lut,
+			    ARRAY_SIZE(sis_crtc->lut));
+	}
+}
+
+static bool sisvga_crtc_helper_mode_fixup(struct drm_crtc *crtc,
+					  const struct drm_display_mode *mode,
+					  struct drm_display_mode *adj_mode)
+{
+	u8 bpp;
+	int framebuffer_size;
+	struct sisvga_device *sdev = crtc->dev->dev_private;
+	const struct sisvga_device_info *info = sdev->info;
+
+	if (adj_mode->hdisplay > info->max_hdisplay)
+		return false;
+
+	if (adj_mode->vdisplay > info->max_vdisplay)
+		return false;
+
+	if (((adj_mode->crtc_hdisplay % 8) != 0 ||
+	     (adj_mode->crtc_hsync_start % 8) != 0 ||
+	     (adj_mode->crtc_hsync_end % 8) != 0 ||
+	     (adj_mode->crtc_htotal % 8) != 0) &&
+	    ((adj_mode->crtc_hdisplay % 9) != 0 ||
+	     (adj_mode->crtc_hsync_start % 9) != 0 ||
+	     (adj_mode->crtc_hsync_end % 9) != 0 ||
+	     (adj_mode->crtc_htotal % 9) != 0)) {
+		return false;
+	}
+
+	if (adj_mode->crtc_hdisplay > info->max_hdisplay ||
+	    adj_mode->crtc_hsync_start > info->max_hsync_start ||
+	    adj_mode->crtc_hsync_end > info->max_hsync_end ||
+	    adj_mode->crtc_htotal > info->max_htotal ||
+	    adj_mode->crtc_vdisplay > info->max_vdisplay ||
+	    adj_mode->crtc_vsync_start > info->max_vsync_start ||
+	    adj_mode->crtc_vsync_end > info->max_vsync_end ||
+	    adj_mode->crtc_vtotal > info->max_vtotal) {
+		return false;
+	}
+
+	bpp = bytes_per_pixel(crtc->primary->fb->format);
+	framebuffer_size = adj_mode->crtc_hdisplay * adj_mode->crtc_vdisplay *
+			   bpp;
+
+	if (framebuffer_size > sdev->vram.size)
+		return false;
+
+	return true;
+}
+
+static int sisvga_crtc_helper_mode_set(struct drm_crtc *crtc,
+				       struct drm_display_mode *mode,
+				       struct drm_display_mode *adj_mode,
+				       int x, int y,
+				       struct drm_framebuffer *old_fb)
+{
+	int ret;
+
+	ret = set_display_mode(crtc->dev->dev_private, adj_mode);
+	if (ret < 0)
+		return ret;
+
+	ret = set_color_mode(crtc->dev->dev_private, crtc->primary->fb);
+	if (ret < 0)
+		return ret;
+
+	ret = set_framebuffer(crtc->dev->dev_private,
+			      sisvga_framebuffer_of(crtc->primary->fb),
+			      sisvga_framebuffer_of(old_fb),
+			      false,
+			      !!(mode->flags & DRM_MODE_FLAG_INTERLACE));
+	if (ret < 0)
+		return ret;
+
+	{
+		int len = 3 * crtc->primary->fb->width * crtc->primary->fb->height;
+		struct sisvga_device* sdev = crtc->dev->dev_private;
+		for (ret = 0; ret < len; ++ret)
+			((u8*)sdev->vram.mem)[ret] = 0xff;
+	}
+
+	return 0;
+}
+
+static int sisvga_crtc_helper_mode_set_base(struct drm_crtc *crtc,
+					    int x, int y,
+					    struct drm_framebuffer *old_fb)
+{
+	int ret;
+
+	ret = set_framebuffer(crtc->dev->dev_private,
+			      sisvga_framebuffer_of(crtc->primary->fb),
+			      sisvga_framebuffer_of(old_fb),
+			      false, false);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void sisvga_crtc_helper_prepare(struct drm_crtc *crtc)
+{
+	struct sisvga_device* sdev = crtc->dev->dev_private;
+	u8 sr00, sr01;
+
+	/* We disable the screen to allow for flicker-free
+	 * mode switching. */
+	set_crtc_dpms_mode(crtc->dev->dev_private, DRM_MODE_DPMS_OFF);
+
+	RREG_SR(0x00, sr00);
+	RREG_SR(0x01, sr01);
+
+	sr00 &= 0xfc; /* sequencer reset */
+	sr01 |= 0x20; /* screen disabled */
+
+	WREG_SR(0x01, sr01);
+	WREG_SR(0x00, sr00);
+}
+
+static void sisvga_crtc_helper_commit(struct drm_crtc *crtc)
+{
+	struct sisvga_device* sdev = crtc->dev->dev_private;
+	u8 sr00, sr01;
+
+	RREG_SR(0x00, sr00);
+	RREG_SR(0x01, sr01);
+
+	sr00 |= 0x03; /* no reset; start sequencer */
+	sr01 &= 0xdf; /* screen enabled */
+
+	WREG_SR(0x00, sr00);
+	WREG_SR(0x01, sr01);
+
+	set_crtc_dpms_mode(crtc->dev->dev_private, DRM_MODE_DPMS_ON);
+
+}
+
+static const struct drm_crtc_helper_funcs sisvga_crtc_helper_funcs = {
+	.disable = sisvga_crtc_helper_disable,
+	.dpms = sisvga_crtc_helper_dpms,
+	.mode_fixup = sisvga_crtc_helper_mode_fixup,
+	.mode_set = sisvga_crtc_helper_mode_set,
+	.mode_set_base = sisvga_crtc_helper_mode_set_base,
+	.prepare = sisvga_crtc_helper_prepare,
+	.commit = sisvga_crtc_helper_commit,
+};
+
+/*
+ * CRTC funcs
+ */
+
+static int sisvga_crtc_cursor_set(struct drm_crtc *crtc,
+				  struct drm_file *file_priv,
+				  uint32_t handle,
+				  uint32_t width,
+				  uint32_t height)
+{
+	return 0;
+}
+
+static int sisvga_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+	return 0;
+}
+
+static int sisvga_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
+				 u16 *blue, uint32_t size,
+				 struct drm_modeset_acquire_ctx *ctx)
+{
+	uint32_t i;
+	struct sisvga_crtc *sis_crtc = sisvga_crtc_of(crtc);
+
+	for (i = 0; i < size; i++) {
+		sis_crtc->lut[i][0] = red[i] >> 8;
+		sis_crtc->lut[i][1] = green[i] >> 8;
+		sis_crtc->lut[i][2] = blue[i] >> 8;
+	}
+	sis_crtc->lut_len = size;
+	sis_crtc->lut_24 = true;
+	load_lut_24(crtc->dev->dev_private, sis_crtc->lut, sis_crtc->lut_len);
+
+	return 0;
+}
+
+static void sisvga_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct sisvga_crtc *sis_crtc = sisvga_crtc_of(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	drm_crtc_cleanup(&sis_crtc->base);
+	devm_kfree(dev->dev, sis_crtc);
+}
+
+static int sisvga_crtc_page_flip(struct drm_crtc *crtc,
+				 struct drm_framebuffer *fb,
+				 struct drm_pending_vblank_event *event,
+				 uint32_t flags,
+				 struct drm_modeset_acquire_ctx *ctx)
+{
+	struct sisvga_device *sdev = crtc->dev->dev_private;
+	struct drm_framebuffer *old_fb = crtc->primary->fb;
+	unsigned long irqflags;
+	struct drm_format_name_buf buf;
+
+	crtc->primary->fb = fb;
+	sisvga_crtc_helper_mode_set_base(crtc, 0, 0, old_fb);
+
+	if (event) {
+		spin_lock_irqsave(&sdev->base.event_lock, irqflags);
+		drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irqrestore(&sdev->base.event_lock, irqflags);
+	}
+
+	return 0;
+}
+
+static const struct drm_crtc_funcs sisvga_crtc_funcs = {
+	.cursor_set = sisvga_crtc_cursor_set,
+	.cursor_move = sisvga_crtc_cursor_move,
+	.gamma_set = sisvga_crtc_gamma_set,
+	.set_config = drm_crtc_helper_set_config,
+	.destroy = sisvga_crtc_destroy,
+	.page_flip = sisvga_crtc_page_flip,
+};
+
+/*
+ * struct sisvga_crtc
+ */
+
+static int map_crtc_registers(struct sisvga_device *sdev)
+{
+	static const u8 mask = 0x01;
+	u8 newmisc, misc;
+	int i = 0;
+
+	/* CRTC registers can be mapped in two separate locations: 0x3b? for
+	 * compatibility with monochrome adapters and 0x3d? for compatibility
+	 * with color adapters. We always use the latter. The loop waits
+	 * several microseconds for success if neccessary. */
+
+	misc = RREG8(REG_MISC_IN);
+
+	newmisc = misc | 0x01;
+
+	do {
+		if ((misc & mask) == (newmisc & mask))
+			return 0;
+		if (i)
+			udelay(10);
+		else
+			WREG8(REG_MISC_OUT, newmisc);
+		misc = RREG8(REG_MISC_IN);
+	} while (i++ < 5);
+
+	DRM_ERROR("sisvga: failed to map CRTC registers\n");
+
+	return -ETIMEDOUT;
+}
+
+static int unlock_crtc_registers(struct sisvga_device *sdev)
+{
+	u8 cr11;
+	int i = 0;
+
+	/* Some of the CRTC registers might be write protected. We unprotect
+	 * them here, or assume the device is not compatible. The loop waits
+	 * several microseconds for success if necessary.*/
+
+	RREG_CR(0x11, cr11);
+
+	do {
+		if (!(cr11 & 0x80))
+			return 0;
+		if (i)
+			udelay(10);
+		else
+			WREG_CR(0x11, cr11 & 0x7f);
+		RREG_CR(0x11, cr11);
+	} while (i++ < 5);
+
+	DRM_ERROR("sisvga: failed to disable CRTC write protection\n");
+
+	return -ETIMEDOUT;
+}
+
+static int sisvga_crtc_init(struct sisvga_crtc *sis_crtc,
+			    struct drm_device *dev,
+			    struct drm_plane *primary_plane,
+			    struct drm_plane *cursor_plane)
+{
+	struct sisvga_device *sdev = dev->dev_private;
+	int ret;
+	size_t i, j;
+
+	ret = map_crtc_registers(sdev);
+	if (ret < 0)
+		return ret;
+	ret = unlock_crtc_registers(sdev);
+	if (ret < 0)
+		return ret;
+
+	ret = drm_crtc_init_with_planes(dev, &sis_crtc->base,
+					primary_plane, cursor_plane,
+					&sisvga_crtc_funcs, NULL);
+	if (ret < 0) {
+		DRM_ERROR("sisvga: drmcrtc_init_with_planes() failed,"
+			  " error %d\n", -ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(sis_crtc->lut); ++i) {
+		for (j = 0; j < ARRAY_SIZE(sis_crtc->lut[0]); ++j) {
+			sis_crtc->lut[i][j] = i;
+		}
+	}
+	sis_crtc->lut_len = ARRAY_SIZE(sis_crtc->lut);
+	sis_crtc->lut_24 = true;
+	drm_mode_crtc_set_gamma_size(&sis_crtc->base, sis_crtc->lut_len);
+
+	drm_crtc_helper_add(&sis_crtc->base, &sisvga_crtc_helper_funcs);
+
+	return 0;
+}
+
+struct sisvga_crtc* sisvga_crtc_create(struct drm_device *dev,
+				       struct drm_plane *primary_plane,
+				       struct drm_plane *cursor_plane)
+{
+	struct sisvga_crtc* sis_crtc;
+	int ret;
+
+	sis_crtc = devm_kzalloc(dev->dev, sizeof(*sis_crtc), GFP_KERNEL);
+	if (!sis_crtc)
+		return ERR_PTR(-ENOMEM);
+
+	ret = sisvga_crtc_init(sis_crtc, dev, primary_plane, cursor_plane);
+	if (ret)
+		goto err_sisvga_crtc_init;
+
+	return sis_crtc;
+
+err_sisvga_crtc_init:
+	devm_kfree(dev->dev, sis_crtc);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_ddc.c b/drivers/gpu/drm/sisvga/sisvga_ddc.c
new file mode 100644
index 0000000000000..f41f12a075efb
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_ddc.c
@@ -0,0 +1,411 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ *
+ * Based on regular I2C algorithm as implemented in
+ *
+ * 	drivers/i2c/algos/i2c-algo-bit.c
+ *
+ * DO NOT COPY-AND-PASTE THIS FILE INTO YOUR DRM DRIVER! SiS graphics
+ * cards use their own variant of the i2c bit-shift algorithm for DDC.
+ * Using the SiS algorithm can damage your hardware.
+ */
+
+#include "sisvga_device.h"
+#include <drm/drm_device.h>
+#include <linux/timex.h>
+#include "sisvga_reg.h"
+
+/*
+ * I2C helpers
+ */
+
+static int i2c_get(struct sisvga_device *sdev, u8 mask)
+{
+	u8 sr11;
+
+	BUG_ON(!sdev);
+	RREG_SR(0x11, sr11);
+
+	return (sr11 & mask) != 0;
+}
+
+static void i2c_set(struct sisvga_device *sdev, u8 mask, int state)
+{
+	u8 sr11;
+
+	BUG_ON(!sdev);
+	RREG_SR(0x11, sr11);
+
+	if (state)
+		sr11 |= mask;
+	else
+		sr11 &= ~mask;
+
+	WREG_SR(0x11, sr11);
+}
+
+/*
+ * SiS DDC I2C algorithm
+ */
+
+static int getsda(struct sisvga_ddc *sis_ddc)
+{
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	return i2c_get(sdev, sis_ddc->sda_mask);
+}
+
+static void setsda(struct sisvga_ddc *sis_ddc, int state)
+{
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	i2c_set(sdev, sis_ddc->sda_mask, state);
+}
+
+static int getscl(struct sisvga_ddc *sis_ddc)
+{
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	return i2c_get(sdev, sis_ddc->scl_mask);
+}
+
+static void setscl(struct sisvga_ddc *sis_ddc, int state)
+{
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	i2c_set(sdev, sis_ddc->scl_mask, state);
+}
+
+static int setscl_validate(struct sisvga_ddc *sis_ddc, int state)
+{
+	unsigned long timeout;
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	i2c_set(sdev, sis_ddc->scl_mask, state);
+
+	timeout = jiffies + sis_ddc->timeout;
+	while (getscl(sis_ddc) != state) {
+		if (time_after(jiffies, timeout)) {
+			if (getscl(sis_ddc) == state)
+				break;
+			DRM_ERROR("SCL state validation failed with timeout\n");
+			return -ETIMEDOUT;
+		}
+		cpu_relax();
+	}
+	udelay(sis_ddc->udelay);
+
+	return 0;
+}
+
+static int pre_xfer(struct sisvga_ddc *sis_ddc)
+{
+	int ret;
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	/* enable display while probing EDID */
+	RREG_SR(0x01, sis_ddc->sr01);
+	if (!(sis_ddc->sr01 & 0x20))
+		WREG_SR(0x01, sis_ddc->sr01 & ~0x20);
+
+	/* raise SDA, SCL before transfer */
+	setsda(sis_ddc, 1);
+	udelay((sis_ddc->udelay + 1) / 2);
+	ret = setscl_validate(sis_ddc, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void post_xfer(struct sisvga_ddc *sis_ddc)
+{
+	u8 sr01;
+	struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+
+	/* restore register state */
+
+	RREG_SR(0x01, sr01);
+	if (sr01 != sis_ddc->sr01)
+		WREG_SR(0x01, sis_ddc->sr01);
+}
+
+static int tx_start(struct sisvga_ddc *sis_ddc)
+{
+	/* expect SDA, SCL high */
+
+	setsda(sis_ddc, 0);
+	udelay(sis_ddc->udelay);
+	setscl(sis_ddc, 0);
+
+	return 0;
+}
+
+static int tx_repstart(struct sisvga_ddc *sis_ddc)
+{
+	int ret;
+
+	setsda(sis_ddc, 1);
+	ret = setscl_validate(sis_ddc, 1);
+	if (ret < 0)
+		return ret;
+
+	return tx_start(sis_ddc);
+}
+
+static int tx_stop(struct sisvga_ddc *sis_ddc)
+{
+	int ret;
+
+	/* expect SCL low */
+
+	setsda(sis_ddc, 0);
+	ret = setscl_validate(sis_ddc, 1);
+	if (ret < 0)
+		return ret;
+	setsda(sis_ddc, 1);
+	udelay(sis_ddc->udelay);
+
+	return 0;
+}
+
+static int tx_ack(struct sisvga_ddc *sis_ddc, u8 ack)
+{
+	int ret;
+
+	/* expect SCL low */
+
+	setsda(sis_ddc, !ack); /* bit is invert to ack */
+	udelay((sis_ddc->udelay + 1) / 2);
+	ret = setscl_validate(sis_ddc, 1);
+	if (ret < 0)
+			return ret;
+	setscl(sis_ddc, 0);
+
+	return 0;
+}
+
+static int rx_ack(struct sisvga_ddc *sis_ddc)
+{
+	int ret, sda;
+
+	/* expect SCL low */
+
+	setsda(sis_ddc, 1);
+	ret = setscl_validate(sis_ddc, 1);
+	if (ret < 0)
+		return ret;
+	sda = getsda(sis_ddc);
+	setscl(sis_ddc, 0);
+	if (sda)
+		return -EPROTO;
+
+	return 0;
+}
+
+static int rx_byte(struct sisvga_ddc *sis_ddc, bool ack)
+{
+	int i, ret, sda;
+	u8 byte = 0;
+
+	/* expect SCL low */
+
+	for (i = 0; i < 8; ++i) {
+		byte <<= 1;
+		/* In contrast to the regular I2C bit-transfer algorithm, we
+		 * raise SDA before receiving each bit. Usually this would be
+		 * done by the sender side.
+		 *
+		 * DO NOT COPY-AND-PASTE THIS CODE INTO YOUR DRM DRIVER!
+		 */
+		setsda(sis_ddc, 1);
+		ret = setscl_validate(sis_ddc, 1);
+		if (ret < 0)
+			goto err;
+		sda = getsda(sis_ddc);
+		if (sda)
+			byte |= 0x01;
+		if (1) {
+			setscl(sis_ddc, 0);
+			udelay(i == 7 ? sis_ddc->udelay / 2 : sis_ddc->udelay);
+		} else {
+			struct sisvga_device *sdev = sis_ddc->dev->dev_private;
+			i2c_set(sdev, sis_ddc->scl_mask, 0);
+			udelay(i == 7 ? sis_ddc->udelay / 2 : sis_ddc->udelay);
+		}
+	}
+	ret = tx_ack(sis_ddc, ack);
+	if (ret < 0)
+		return ret;
+
+	return byte;
+
+err:
+	setscl(sis_ddc, 0);
+	tx_ack(sis_ddc, !ack);
+	return ret;
+}
+
+static int rx_buf(struct sisvga_ddc *sis_ddc, u8 *buf, u16 len)
+{
+	int l, ret;
+
+	for (l = len; l--; ++buf) {
+		ret = rx_byte(sis_ddc, l); /* no ack on final byte */
+		if (ret < 0)
+			return ret;
+		*buf = ret;
+	}
+
+	return len;
+}
+
+static int tx_byte(struct sisvga_ddc *sis_ddc, u8 byte)
+{
+	int i, ret;
+
+	/* expect SCL low */
+
+	for (i = 0; i < 8; ++i) {
+		setsda(sis_ddc, byte & 0x80);
+		udelay((sis_ddc->udelay + 1) / 2);
+		ret = setscl_validate(sis_ddc, 1);
+		if (ret < 0)
+			return ret;
+		byte <<= 1;
+		setscl(sis_ddc, 0);
+	}
+	ret = rx_ack(sis_ddc);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tx_buf(struct sisvga_ddc *sis_ddc, const u8 *buf, u16 len)
+{
+	int l, ret;
+
+	for (l = len; l--; ++buf) {
+		ret = tx_byte(sis_ddc, *buf);
+		if (ret < 0)
+			return ret;
+	}
+
+	return len;
+}
+
+static int tx_addr(struct sisvga_ddc *sis_ddc, u8 addr, bool rx)
+{
+	return tx_byte(sis_ddc, (addr << 1) | !!rx);
+}
+
+/*
+ * I2C adapter funcs
+ */
+
+static int master_xfer(struct i2c_adapter *adapter,
+		       struct i2c_msg *msgs, int num)
+{
+	int ret, i;
+	struct sisvga_ddc *sis_ddc = i2c_get_adapdata(adapter);
+
+	ret = pre_xfer(sis_ddc);
+	if (ret < 0)
+		return ret;
+
+	ret = tx_start(sis_ddc);
+	if (ret < 0)
+		goto err_tx_start;
+
+	for (i = 0; i < num; ++i) {
+		if (i) {
+			ret = tx_repstart(sis_ddc);
+			if (ret < 0)
+				goto err_msg;
+		}
+		if (msgs[i].flags & I2C_M_RD) {
+			ret = tx_addr(sis_ddc, msgs[i].addr, true);
+			if (ret < 0)
+				goto err_msg;
+
+			ret = rx_buf(sis_ddc, msgs[i].buf, msgs[i].len);
+			if (ret < 0)
+				goto err_msg;
+		} else {
+			ret = tx_addr(sis_ddc, msgs[i].addr, false);
+			if (ret < 0)
+				goto err_msg;
+
+			ret = tx_buf(sis_ddc, msgs[i].buf, msgs[i].len);
+			if (ret < 0)
+				goto err_msg;
+		}
+	}
+
+	tx_stop(sis_ddc);
+
+	post_xfer(sis_ddc);
+
+	return i; /* number of messages signals success */
+
+err_msg:
+	setscl(sis_ddc, 0);
+	tx_stop(sis_ddc);
+err_tx_start:
+	post_xfer(sis_ddc);
+	return ret;
+
+}
+
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm sisvga_i2c_algorithm = {
+	.master_xfer = master_xfer,
+	.functionality = functionality,
+};
+
+/*
+ * struct sisvga_ddc
+ */
+
+int sisvga_ddc_init(struct sisvga_ddc *sis_ddc, struct drm_device *dev)
+{
+	int ret;
+
+	sis_ddc->dev = dev;
+
+	sis_ddc->sda_mask = 0x02;
+	sis_ddc->scl_mask = 0x01;
+
+	sis_ddc->udelay = 10;
+	sis_ddc->timeout = usecs_to_jiffies(2200);
+
+	sis_ddc->adapter.owner = THIS_MODULE;
+	sis_ddc->adapter.class = I2C_CLASS_DDC;
+	sis_ddc->adapter.dev.parent = &dev->pdev->dev;
+	sis_ddc->adapter.algo = &sisvga_i2c_algorithm;
+	sis_ddc->adapter.algo_data = sis_ddc;
+	sis_ddc->adapter.retries = 3;
+	i2c_set_adapdata(&sis_ddc->adapter, sis_ddc);
+	snprintf(sis_ddc->adapter.name, sizeof(sis_ddc->adapter.name),
+		 "sisvga DDC");
+
+	ret = i2c_add_adapter(&sis_ddc->adapter);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+void sisvga_ddc_fini(struct sisvga_ddc *sis_ddc)
+{
+	i2c_del_adapter(&sis_ddc->adapter);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_debug.c b/drivers/gpu/drm/sisvga/sisvga_debug.c
new file mode 100644
index 0000000000000..1d11459d54e79
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_debug.c
@@ -0,0 +1,303 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_debug.h"
+#include "sisvga_device.h"
+#include "sisvga_reg.h"
+
+void sisvga_debug_print_mode(struct sisvga_device* sdev)
+{
+	struct drm_display_mode mode;
+
+	int dots, htotal, hsync_start, hsync_end, hdisplay, hskew, hblank_start,
+	    hblank_end, vtotal, vsync_start, vsync_end, vdisplay, vscan,
+	    vblank_start, vblank_end;
+	int fr, fd, num, denum, div, postscal;
+
+	u8 misc;
+	u8 sr01, sr05, sr12, sr13, sr0a, sr2a, sr2b, sr38;
+	u8 cr00, cr01, cr02, cr03, cr04, cr05, cr06, cr07, cr09,
+	   cr10, cr11, cr12, cr15, cr16;
+	u8 saved_sr38, freqi;
+
+	misc = RREG8(REG_MISC_IN);
+
+	RREG_SR(0x01, sr01);
+	RREG_SR(0x05, sr05);
+	RREG_SR(0x12, sr12);
+	RREG_SR(0x0a, sr0a);
+
+	RREG_CR(0x00, cr00);
+	RREG_CR(0x01, cr01);
+	RREG_CR(0x02, cr02);
+	RREG_CR(0x03, cr03);
+	RREG_CR(0x04, cr04);
+	RREG_CR(0x05, cr05);
+	RREG_CR(0x06, cr06);
+	RREG_CR(0x07, cr07);
+	RREG_CR(0x09, cr09);
+	RREG_CR(0x10, cr10);
+	RREG_CR(0x11, cr11);
+	RREG_CR(0x12, cr12);
+	RREG_CR(0x15, cr15);
+	RREG_CR(0x16, cr16);
+
+	if (sr01 & 0x01)
+		dots = 8;
+	else
+		dots = 9;
+
+	htotal = (((u32)sr12 & 0x01) << 8) | cr00;
+	hblank_start = (((u32)sr12 & 0x04) << 6) | cr02;
+	hblank_end = (((u32)sr12 & 0x10) << 4) |
+		     (((u32)sr05 & 0x80) >> 2) | (cr03 & 0x1f);
+	hdisplay = (((u32)sr12 & 0x02) << 7) | cr01;
+	hsync_start = (((u32)sr12 & 0x08) << 5) | cr04;
+	hsync_end = (((u32)sr12 & 0x08) << 5) | (cr04 & 0xe0) | (cr05 & 0x1f);
+	hskew = ((cr05 & 0x60) >> 5);
+
+	vtotal = (((u32)sr0a & 0x01) << 9) |
+		 (((u32)cr07 & 0x20) << 4) |
+		 (((u32)cr07 & 0x01) << 8) | cr06;
+	vblank_start = (((u32)sr0a & 0x04) << 7) |
+		       (((u32)cr09 & 0x20) << 4) |
+		       (((u32)cr07 & 0x08) << 5) | cr15;
+	vblank_end = vblank_start + (cr16 & 0x1f);
+	vdisplay = (((u32)sr0a & 0x02) << 8) |
+		   (((u32)cr07 & 0x02) << 7) |
+		   (((u32)cr07 & 0x40) << 3) | cr12;
+	vsync_start = (((u32)sr0a & 0x08) << 6) |
+		      (((u32)cr07 & 0x80) << 2) |
+		      (((u32)cr07 & 0x04) << 6) | cr10;
+	vsync_end = (((u32)sr0a & 0x08) << 6) |
+		    (((u32)cr07 & 0x80) << 2) |
+		    (((u32)cr07 & 0x04) << 6) | (cr10 & 0xf0) | (cr11 & 0x0f);
+	vscan = cr09 & 0x1f;
+
+	memset(&mode, 0, sizeof(mode));
+	mode.type = DRM_MODE_TYPE_DRIVER;
+	mode.htotal = (htotal + 5) * dots;
+	/*mode.hblank_start = (hblank_start + 1) * dots;
+	mode.hblank_end = (hblank_end + 1) * dots;*/
+	mode.hdisplay = (hdisplay + 1) * dots;
+	mode.hsync_start = (hsync_start + 1) * dots;
+	mode.hsync_end = (hsync_end + 1) * dots;
+	mode.hskew = hskew * dots;
+
+	mode.vtotal = vtotal + 2; // 2?
+	/*mode.vblank_start = vblank_start;
+	mode.vblank_end = vblank_end;*/
+	mode.vdisplay = vdisplay + 1;
+	mode.vsync_start = vsync_start + 1;
+	mode.vsync_end = vsync_end + 1;
+	if (vscan)
+		mode.vscan = vscan + 1;
+	else
+		mode.vscan = 0;
+
+
+	if (misc & 0x80)
+		mode.flags |= DRM_MODE_FLAG_NVSYNC;
+	else
+		mode.flags |= DRM_MODE_FLAG_PVSYNC;
+	if (misc & 0x40)
+		mode.flags |= DRM_MODE_FLAG_NHSYNC;
+	else
+		mode.flags |= DRM_MODE_FLAG_PHSYNC;
+	if (cr09 & 0x80)
+		mode.flags |= DRM_MODE_FLAG_DBLSCAN;
+	if (hskew)
+		mode.flags |= DRM_MODE_FLAG_HSKEW;
+	if (sr01 & 0x08)
+		mode.flags |= DRM_MODE_FLAG_CLKDIV2;
+
+	RREG_SR(0x38, sr38);
+
+	saved_sr38 = sr38;
+
+	switch (misc & 0x0c) {
+	case 0x00:
+		fr = 25175;
+		freqi = 0x01;
+		break;
+	case 0x04:
+		fr = 28322;
+		freqi = 0x02;
+		break;
+	case 0x0c:
+		fr = 14813;
+		freqi = 0x00;
+		break;
+	default:
+		BUG();
+		break;
+	}
+
+	sr38 &= 0xfc;
+	sr38 |= freqi;
+
+	WREG_SR(0x38, sr38);
+	WAIT_SR_MASKED(0x38, freqi, 0x03);
+
+	RREG_SR(0x13, sr13);
+	RREG_SR(0x2a, sr2a);
+	RREG_SR(0x2b, sr2b);
+
+	/* Restore previous sr38 */
+	WREG_SR(0x38, saved_sr38);
+	WAIT_SR(0x38, saved_sr38);
+
+	div = ((sr2a & 0x80) >> 7) + 1;
+	num = (sr2a & 0x7f) + 1;
+	denum = ((sr2b & 0x1f) + 1);
+	postscal = ((sr2b & 0x60) >> 5) + 1;
+	if (sr13 & 0x80)
+		postscal *= 2;
+
+	fd = (fr * num * div) / (denum * postscal);
+	mode.clock = fd;
+
+	DRM_INFO("fd=%d fr=%d num=%d denum=%d div=%d postscal=%d\n", fd, fr, num, denum, div, postscal);
+
+	DRM_INFO("mode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&mode));
+}
+
+void sisvga_debug_print_regs(struct sisvga_device* sdev)
+{
+	u8 ar10, ar11, ar12, ar13, ar14;
+	u8 misc;
+	u8 sr00, sr01, sr03, sr04, sr06, sr0a, sr0b, sr0c, sr0d,
+	   sr0e, sr0f, sr13, sr21, sr26, sr2a, sr2b, sr2d, sr38;
+	u8 gr00, gr01, gr02, gr03, gr04, gr05, gr06;
+	u8 cr00, cr01, cr02, cr03, cr04, cr05, cr06, cr07,
+	   cr08, cr09, cr0a, cr0b, cr0c, cr0d, cr0e, cr0f,
+	   cr10, cr11, cr12, cr13, cr14, cr15, cr16, cr17,
+	   cr18;
+
+	RREG_AR(0x10, ar10);
+	RREG_AR(0x11, ar11);
+	RREG_AR(0x12, ar12);
+	RREG_AR(0x13, ar13);
+	RREG_AR(0x14, ar14);
+
+	misc = RREG8(REG_MISC_IN);
+
+	RREG_SR(0x00, sr00);
+	RREG_SR(0x01, sr01);
+	RREG_SR(0x03, sr03);
+	RREG_SR(0x04, sr04);
+	RREG_SR(0x06, sr06);
+	RREG_SR(0x0a, sr0a);
+	RREG_SR(0x0b, sr0b);
+	RREG_SR(0x0c, sr0c);
+	RREG_SR(0x0d, sr0d);
+	RREG_SR(0x0e, sr0e);
+	RREG_SR(0x0f, sr0f);
+	RREG_SR(0x13, sr13);
+	RREG_SR(0x21, sr21);
+	RREG_SR(0x26, sr26);
+	RREG_SR(0x2a, sr2a);
+	RREG_SR(0x2b, sr2b);
+	RREG_SR(0x2d, sr2d);
+	RREG_SR(0x38, sr38);
+
+	RREG_GR(0x00, gr00);
+	RREG_GR(0x01, gr01);
+	RREG_GR(0x02, gr02);
+	RREG_GR(0x03, gr03);
+	RREG_GR(0x04, gr04);
+	RREG_GR(0x05, gr05);
+	RREG_GR(0x06, gr06);
+
+	RREG_CR(0x00, cr00);
+	RREG_CR(0x01, cr01);
+	RREG_CR(0x02, cr02);
+	RREG_CR(0x03, cr03);
+	RREG_CR(0x04, cr04);
+	RREG_CR(0x05, cr05);
+	RREG_CR(0x06, cr06);
+	RREG_CR(0x07, cr07);
+	RREG_CR(0x08, cr08);
+	RREG_CR(0x09, cr09);
+	RREG_CR(0x0a, cr0a);
+	RREG_CR(0x0b, cr0b);
+	RREG_CR(0x0c, cr0c);
+	RREG_CR(0x0d, cr0d);
+	RREG_CR(0x0e, cr0e);
+	RREG_CR(0x0f, cr0f);
+	RREG_CR(0x10, cr10);
+	RREG_CR(0x11, cr11);
+	RREG_CR(0x12, cr12);
+	RREG_CR(0x13, cr13);
+	RREG_CR(0x14, cr14);
+	RREG_CR(0x15, cr15);
+	RREG_CR(0x16, cr16);
+	RREG_CR(0x17, cr17);
+	RREG_CR(0x18, cr18);
+
+	DRM_INFO("ar10=0x%x\n", ar10);
+	DRM_INFO("ar11=0x%x\n", ar11);
+	DRM_INFO("ar12=0x%x\n", ar12);
+	DRM_INFO("ar13=0x%x\n", ar13);
+	DRM_INFO("ar14=0x%x\n", ar14);
+
+	DRM_INFO("misc=0x%x\n", misc);
+
+	DRM_INFO("sr00=0x%x\n", sr00);
+	DRM_INFO("sr01=0x%x\n", sr01);
+	DRM_INFO("sr03=0x%x\n", sr03);
+	DRM_INFO("sr04=0x%x\n", sr04);
+	DRM_INFO("sr06=0x%x\n", sr06);
+	DRM_INFO("sr0a=0x%x\n", sr0a);
+	DRM_INFO("sr0b=0x%x\n", sr0b);
+	DRM_INFO("sr0c=0x%x\n", sr0c);
+	DRM_INFO("sr0d=0x%x\n", sr0d);
+	DRM_INFO("sr0e=0x%x\n", sr0e);
+	DRM_INFO("sr0f=0x%x\n", sr0f);
+	DRM_INFO("sr13=0x%x\n", sr13);
+	DRM_INFO("sr21=0x%x\n", sr21);
+	DRM_INFO("sr26=0x%x\n", sr26);
+	DRM_INFO("sr2a=0x%x\n", sr2a);
+	DRM_INFO("sr2b=0x%x\n", sr2b);
+	DRM_INFO("sr2d=0x%x\n", sr2d);
+	DRM_INFO("sr38=0x%x\n", sr38);
+
+	DRM_INFO("gr00=0x%x\n", gr00);
+	DRM_INFO("gr01=0x%x\n", gr01);
+	DRM_INFO("gr02=0x%x\n", gr02);
+	DRM_INFO("gr03=0x%x\n", gr03);
+	DRM_INFO("gr04=0x%x\n", gr04);
+	DRM_INFO("gr05=0x%x\n", gr05);
+	DRM_INFO("gr06=0x%x\n", gr06);
+
+	DRM_INFO("cr00=0x%x\n", cr00);
+	DRM_INFO("cr01=0x%x\n", cr01);
+	DRM_INFO("cr02=0x%x\n", cr02);
+	DRM_INFO("cr03=0x%x\n", cr03);
+	DRM_INFO("cr04=0x%x\n", cr04);
+	DRM_INFO("cr05=0x%x\n", cr05);
+	DRM_INFO("cr06=0x%x\n", cr06);
+	DRM_INFO("cr07=0x%x\n", cr07);
+	DRM_INFO("cr08=0x%x\n", cr08);
+	DRM_INFO("cr09=0x%x\n", cr09);
+	DRM_INFO("cr0a=0x%x\n", cr0a);
+	DRM_INFO("cr0b=0x%x\n", cr0b);
+	DRM_INFO("cr0c=0x%x\n", cr0c);
+	DRM_INFO("cr0d=0x%x\n", cr0d);
+	DRM_INFO("cr0e=0x%x\n", cr0e);
+	DRM_INFO("cr0f=0x%x\n", cr0f);
+	DRM_INFO("cr10=0x%x\n", cr10);
+	DRM_INFO("cr11=0x%x\n", cr11);
+	DRM_INFO("cr12=0x%x\n", cr12);
+	DRM_INFO("cr13=0x%x\n", cr13);
+	DRM_INFO("cr14=0x%x\n", cr14);
+	DRM_INFO("cr15=0x%x\n", cr15);
+	DRM_INFO("cr16=0x%x\n", cr16);
+	DRM_INFO("cr17=0x%x\n", cr17);
+	DRM_INFO("cr18=0x%x\n", cr18);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_debug.h b/drivers/gpu/drm/sisvga/sisvga_debug.h
new file mode 100644
index 0000000000000..155f6db67e94a
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_debug.h
@@ -0,0 +1,17 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#ifndef SISVGA_DEBUG_H
+#define SISVGA_DEBUG_H
+
+struct sisvga_device;
+
+void sisvga_debug_print_mode(struct sisvga_device* sdev);
+void sisvga_debug_print_regs(struct sisvga_device* sdev);
+
+#endif
diff --git a/drivers/gpu/drm/sisvga/sisvga_device.c b/drivers/gpu/drm/sisvga/sisvga_device.c
new file mode 100644
index 0000000000000..f78f81138cd3b
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_device.c
@@ -0,0 +1,592 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h>
+#include <drm/drm_pci.h>
+#include <linux/device.h>
+#include <linux/ioport.h>
+#include "sisvga_reg.h"
+
+static int modify_vga_status(struct sisvga_device *sdev, bool enable)
+{
+	static const u8 mask = 0x01;
+	u8 newvga, vga;
+	int i = 0;
+
+	/* Tests and modifies the VGA enable bit. The loop waits
+	 * several microseconds for success if necessary. */
+
+	vga = RREG8(REG_VGA);
+
+	if (enable)
+		newvga = vga | 0x01;
+	else
+		newvga = vga & 0xfe;
+
+	do {
+		if ((vga & mask) == (newvga & mask))
+			return 0;
+		if (i)
+			udelay(10);
+		else
+			WREG8(REG_VGA, newvga);
+		vga = RREG8(REG_VGA);
+	} while (i++ < 50);
+
+	return -ETIMEDOUT;
+}
+
+static int enable_vga(struct sisvga_device *sdev)
+{
+	int ret = modify_vga_status(sdev, true);
+	if (ret < 0)
+		DRM_ERROR("sisvga: could not enable VGA card");
+	return ret;
+}
+
+static int disable_vga(struct sisvga_device *sdev)
+{
+	int ret = modify_vga_status(sdev, false);
+	if (ret < 0)
+		DRM_ERROR("sisvga: could not disable VGA card");
+	return ret;
+}
+
+static int modify_device_lock(struct sisvga_device *sdev, u8 wr, u8 rd)
+{
+	u8 sr05;
+	int i = 0;
+
+	/* Tests and modifies the device's lock. The loop waits
+	 * several microseconds for success if necessary. */
+
+	RREG_SR(0x05, sr05);
+
+	do {
+		if (sr05 == rd)
+			return 0;
+		if (i)
+			udelay(10);
+		else
+			WREG_SR(0x05, wr);
+		RREG_SR(0x05, sr05);
+	} while (i++ < 50);
+
+	return -ETIMEDOUT;
+}
+
+static int unlock_device(struct sisvga_device *sdev)
+{
+	int ret = modify_device_lock(sdev, 0x86, 0xa1);
+	if (ret < 0)
+		DRM_ERROR("sisvga: could not unlock SiS extended registers");
+	return ret;
+}
+
+static int lock_device(struct sisvga_device *sdev)
+{
+	int ret = modify_device_lock(sdev, 0x00, 0x21);
+	if (ret < 0)
+		DRM_ERROR("sisvga: could not lock SiS extended registers");
+	return ret;
+}
+
+/*
+ * sisvga_device::regs
+ */
+
+static int sisvga_regs_init(struct sisvga_ioports* regs,
+			    struct pci_dev *pdev)
+{
+	resource_size_t base, size;
+	int err;
+
+	base = pci_resource_start(pdev, SISVGA_PCI_BAR_REGS);
+	size = pci_resource_len(pdev, SISVGA_PCI_BAR_REGS);
+
+	regs->res = request_region(base, size, "sisvga");
+	if (!regs->res) {
+		DRM_ERROR("sisvga: can't reserve I/O ports\n");
+		return -ENXIO;
+	}
+
+	regs->mem = pci_iomap(pdev, SISVGA_PCI_BAR_REGS, 0);
+	if (!regs->mem) {
+		DRM_ERROR("sisvga: can't map I/O ports\n");
+		err = -ENOMEM;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	release_resource(regs->res);
+	return err;
+}
+
+static void sisvga_regs_fini(struct sisvga_ioports *regs,
+			     struct pci_dev *pdev)
+{
+	pci_iounmap(pdev, regs->mem);
+	release_resource(regs->res);
+}
+
+/*
+ * sisvga_device::mmio
+ */
+
+static int sisvga_mmio_init(struct sisvga_devmem *mmio,
+			    struct sisvga_device *sdev,
+			    struct pci_dev *pdev)
+{
+	u8 sr0b;
+	resource_size_t base, size;
+
+	RREG_SR(0x0b, sr0b);
+
+	sr0b |= 0x60; /* map MMIO range */
+
+	WREG_SR(0x0b, sr0b);
+
+	base = pci_resource_start(pdev, SISVGA_PCI_BAR_MMIO);
+	size = pci_resource_len(pdev, SISVGA_PCI_BAR_MMIO);
+
+	mmio->base = base;
+	mmio->size = size;
+	mmio->mtrr = 0;
+
+	mmio->res = request_mem_region(base, size, "sisvga-mmio");
+	if (!mmio->res) {
+		DRM_ERROR("sisvga: can't reserve MMIO memory\n");
+		return -ENXIO;
+	}
+
+	mmio->mem = ioremap(base, size);
+	if (!mmio->mem) {
+		DRM_ERROR("sisvga: can't map MMIO memory\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void sisvga_mmio_fini(struct sisvga_devmem *mmio,
+			     struct pci_dev *pdev)
+{
+	iounmap(mmio->mem);
+	release_region(mmio->base, mmio->size);
+}
+
+/*
+ * sisvga_device::vram
+ */
+
+static int sisvga_vram_init(struct sisvga_devmem *vram,
+			    struct sisvga_device *sdev,
+			    struct pci_dev *pdev)
+{
+	resource_size_t base, size;
+	u8 misc, gr06, sr06, sr0b, sr0c, sr20, sr21, sr2d;
+	unsigned long sizeexp;
+
+	base = pci_resource_start(pdev, SISVGA_PCI_BAR_VRAM);
+	size = pci_resource_len(pdev, SISVGA_PCI_BAR_VRAM);
+
+	misc = RREG8(REG_MISC_IN);
+	RREG_GR(0x06, gr06);
+	RREG_SR(0x06, sr06);
+	RREG_SR(0x0b, sr0b);
+	RREG_SR(0x0c, sr0c);
+	RREG_SR(0x20, sr20);
+	RREG_SR(0x21, sr21);
+	RREG_SR(0x2d, sr2d);
+
+	misc |= 0x02 | /* allow CPU access to vram */
+		0x01; /* Color-graphics memory range enabled */
+	gr06 &= 0xf3; /* select memory map 0 */
+	sr06 |= 0x80; /* Linear addressing enabled */
+	sr0b |= 0x20 | /* map video memory */
+	        0x01; /* CPU-driven bitblt enabled */
+	sr0c |= 0x80; /* Graphics-mode 32-bit memory access enabled */
+	sr20 = (base & 0x07f80000) >> 19; /* Linear addressing base */
+	/* We're mapping the VRAM size to a 3-bit field in sr21, such
+	 * that
+	 *
+	 *     size = 2 ^ sr21[7:5] * 512 * 1024
+	 *
+	 * 512 KiB maps to 0x00, 1 MiB maps to 0x20, 2 maps to 0x04,
+	 * and so on. The manual only specifies bits [6:5], but bit
+	 * 7 works as well. This way, cards with more than 4 MiB of
+	 * VRAM, such as the Diamond Speedstar A70, are supported.
+	 *
+	 * If the device fits the spec, (i.e., has at most 4 MiB of
+	 * VRAM) we do the safe thing and preserve the reserved bit
+	 * no 7.
+	 */
+	sizeexp = ((size >> 20) - 1);
+	if (size > KiB_TO_BYTE(4096)) {
+		sr21 = sizeexp << 5;
+	} else {
+		sr21 &= 0x80; /* preserve reserved bits */
+		sr21 |= (sizeexp & 0x03) << 5;
+	}
+	sr21 |= (base & 0xf8000000) >> 27; /* Linear addressing base */
+	sr2d &= 0xf0; /* preserve reserved bits */
+	sr2d |= 0x00; /* TODO: page size depends on bus type! */
+
+	WREG8(REG_MISC_OUT, misc);
+	WREG_GR(0x06, gr06);
+	WREG_SR(0x06, sr06);
+
+	//WREG_SR(0x0b, sr0b);
+	//WREG_SR(0x0c, sr0c);
+	WREG_SR(0x20, sr20);
+	WREG_SR(0x21, sr21);
+	//WREG_SR(0x2d, sr2d);
+
+        arch_io_reserve_memtype_wc(base, size);
+
+	vram->base = base;
+	vram->size = size;
+	vram->mtrr = arch_phys_wc_add(base, size);
+
+	vram->res = request_mem_region(base, size, "sisvga-vram");
+	if (!vram->res) {
+		DRM_ERROR("sisvga: can't reserve vram registers\n");
+		return -ENXIO;
+	}
+
+	vram->mem = ioremap_wc(base, size);
+	if (!vram->mem) {
+		DRM_ERROR("sisvga: can't map video ram\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void sisvga_vram_fini(struct sisvga_devmem *vram,
+			     struct pci_dev *pdev)
+{
+        arch_io_free_memtype_wc(vram->base, vram->size);
+	arch_phys_wc_del(vram->mtrr);
+
+	iounmap(vram->mem);
+	release_region(vram->base, vram->size);
+}
+
+/*
+ * Mode-config funcs
+ */
+
+static struct drm_framebuffer* sisvga_mode_config_funcs_fb_create(
+	struct drm_device *dev, struct drm_file *filp,
+	const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct drm_gem_object *gem_obj;
+	struct sisvga_framebuffer *sis_fb;
+	int ret;
+
+	gem_obj = drm_gem_object_lookup(filp, mode_cmd->handles[0]);
+	if (!gem_obj)
+		return ERR_PTR(-ENOENT);
+
+	sis_fb = sisvga_framebuffer_create(dev, gem_obj, mode_cmd);
+	if (IS_ERR(sis_fb)) {
+		ret = PTR_ERR(sis_fb);
+		goto err_sisvga_framebuffer_create;
+	}
+
+	drm_gem_object_put_unlocked(gem_obj);
+
+	return &sis_fb->base;
+
+err_sisvga_framebuffer_create:
+	drm_gem_object_put_unlocked(gem_obj);
+	return ERR_PTR(ret);
+}
+
+static const struct drm_mode_config_funcs sisvga_mode_config_funcs = {
+	.fb_create = sisvga_mode_config_funcs_fb_create,
+};
+
+static int sisvga_mode_config_init(struct sisvga_device *sdev)
+{
+	static const uint32_t primary_formats[] = {
+		DRM_FORMAT_RGB888,
+		DRM_FORMAT_BGR888,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_C8
+	};
+	static const uint32_t cursor_formats[] = {
+		DRM_FORMAT_XRGB8888
+	};
+	static const uint64_t format_modifiers[] = {
+		DRM_FORMAT_MOD_INVALID
+	};
+
+	int ret;
+	struct sisvga_plane *sis_primary;
+	struct sisvga_plane *sis_cursor;
+	struct sisvga_crtc *sis_crtc;
+	struct sisvga_encoder *sis_encoder;
+	struct sisvga_connector *sis_connector;
+
+	drm_mode_config_init(&sdev->base);
+	sdev->base.mode_config.max_width = sdev->info->max_htotal;
+	sdev->base.mode_config.max_height = sdev->info->max_vtotal;
+	sdev->base.mode_config.funcs = &sisvga_mode_config_funcs;
+	sdev->base.mode_config.fb_base = sdev->vram.base;
+	sdev->base.mode_config.preferred_depth = sdev->info->preferred_bpp;
+
+	/* Planes */
+
+	sis_primary = sisvga_plane_create(&sdev->base, primary_formats,
+					  ARRAY_SIZE(primary_formats),
+					  format_modifiers,
+					  DRM_PLANE_TYPE_PRIMARY);
+	if (IS_ERR(sis_primary)) {
+		ret = PTR_ERR(sis_primary);
+		goto err_sisvga_plane_create_primary;
+	}
+
+	sis_cursor = sisvga_plane_create(&sdev->base, cursor_formats,
+					 ARRAY_SIZE(cursor_formats),
+					 format_modifiers,
+					 DRM_PLANE_TYPE_CURSOR);
+	if (IS_ERR(sis_cursor)) {
+		ret = PTR_ERR(sis_cursor);
+		goto err_sisvga_plane_create_cursor;
+	}
+
+	/* CRTCs */
+
+	sis_crtc = sisvga_crtc_create(&sdev->base, &sis_primary->base,
+				      &sis_cursor->base);
+	if (IS_ERR(sis_crtc)) {
+		ret = PTR_ERR(sis_crtc);
+		goto err_sisvga_crtc_create;
+	}
+
+	/* Encoders */
+
+	sis_encoder = sisvga_encoder_create(DRM_MODE_ENCODER_DAC,
+					    &sdev->base);
+	if (IS_ERR(sis_encoder)) {
+		ret = PTR_ERR(sis_encoder);
+		goto err_sisvga_encoder_create;
+	}
+
+	sis_encoder->base.possible_crtcs = (1ul << sis_crtc->base.index);
+
+	/* Connectors */
+
+	sis_connector = sisvga_connector_create_vga(&sdev->base);
+	if (IS_ERR(sis_connector)) {
+		ret = PTR_ERR(sis_connector);
+		goto err_sisvga_connector_create_vga;
+	}
+
+	ret = drm_mode_connector_attach_encoder(&sis_connector->base,
+						&sis_encoder->base);
+	if (ret < 0)
+		goto err_drm_mode_connector_attach_encoder;
+
+	return 0;
+
+err_drm_mode_connector_attach_encoder:
+	/* fall through */
+err_sisvga_connector_create_vga:
+	/* fall through */
+err_sisvga_encoder_create:
+	/* fall through */
+err_sisvga_crtc_create:
+	/* fall through */
+err_sisvga_plane_create_cursor:
+	/* fall through */
+err_sisvga_plane_create_primary:
+	drm_mode_config_cleanup(&sdev->base);
+	return ret;
+}
+
+/*
+ * struct sisvga_device
+ */
+
+int sisvga_device_init(struct sisvga_device *sdev,
+		       struct drm_driver *driver, struct pci_dev *pdev,
+		       const struct sisvga_device_info *info)
+{
+	int ret;
+
+	/* DRM initialization
+	 */
+
+	ret = drm_dev_init(&sdev->base, driver, &pdev->dev);
+	if (ret)
+		return ret;
+	sdev->base.dev_private = sdev;
+	sdev->base.pdev = pdev;
+
+        if (drm_core_check_feature(&sdev->base, DRIVER_USE_AGP)) {
+                if (pci_find_capability(sdev->base.pdev, PCI_CAP_ID_AGP))
+                        sdev->base.agp = drm_agp_init(&sdev->base);
+                if (sdev->base.agp) {
+                        sdev->base.agp->agp_mtrr = arch_phys_wc_add(
+                                sdev->base.agp->agp_info.aper_base,
+                                sdev->base.agp->agp_info.aper_size *
+                                1024 * 1024);
+                }
+        }
+
+	/* Make VGA and extended registers available. Later initialization
+	 * requires registers, so this has to be done first.
+	 */
+
+	sdev->info = info;
+
+	ret = sisvga_regs_init(&sdev->regs, sdev->base.pdev);
+	if (ret < 0)
+		goto err_sisvga_regs_init;
+
+	ret = enable_vga(sdev);
+	if (ret < 0)
+		goto err_enable_vga;
+
+	ret = unlock_device(sdev);
+	if (ret < 0)
+		goto err_unlock_device;
+
+	/* The MMIO region can contain VGA memory or command buffer. We
+	 * always map the latter.
+	 */
+
+	ret = sisvga_mmio_init(&sdev->mmio, sdev, sdev->base.pdev);
+	if (ret < 0)
+		goto err_sisvga_mmio_init;
+
+	/* Next is memory management. We set-up the framebuffer memory and
+	 * memory manager for the card.
+	 */
+
+	ret = sisvga_vram_init(&sdev->vram, sdev, sdev->base.pdev);
+	if (ret < 0)
+		goto err_sisvga_vram_init;
+
+	ret = sisvga_ttm_init(&sdev->ttm, &sdev->base,
+			      sdev->vram.size >> PAGE_SHIFT);
+	if (ret < 0)
+		goto err_sisvga_ttm_init;
+
+	/* One by one, we enable all stages of the display pipeline and
+	 * connect them with each other.
+	 */
+
+	ret = sisvga_mode_config_init(sdev);
+	if (ret < 0)
+		goto err_sisvga_mode_config_init;
+
+	/* With the display pipeline running, we can now start fbdev
+	 * emulation. This will also enable a framebuffer console, if
+	 * configured.
+	 */
+
+	ret = sisvga_fbdev_init(&sdev->fbdev, &sdev->base,
+				info->preferred_bpp);
+	if (ret < 0)
+		goto err_sisvga_fbdev_init;
+
+	return 0;
+
+err_sisvga_fbdev_init:
+	drm_mode_config_cleanup(&sdev->base);
+err_sisvga_mode_config_init:
+	sisvga_ttm_fini(&sdev->ttm);
+err_sisvga_ttm_init:
+	sisvga_vram_fini(&sdev->vram, sdev->base.pdev);
+err_sisvga_vram_init:
+	sisvga_mmio_fini(&sdev->mmio, sdev->base.pdev);
+err_sisvga_mmio_init:
+	lock_device(sdev);
+err_unlock_device:
+	disable_vga(sdev);
+err_enable_vga:
+	sisvga_regs_fini(&sdev->regs, sdev->base.pdev);
+err_sisvga_regs_init:
+	drm_dev_fini(&sdev->base);
+	return ret;
+}
+
+void sisvga_device_fini(struct sisvga_device *sdev)
+{
+	struct drm_device *dev = &sdev->base;
+
+	sisvga_fbdev_fini(&sdev->fbdev);
+
+	drm_mode_config_cleanup(dev);
+
+	sisvga_ttm_fini(&sdev->ttm);
+	sisvga_vram_fini(&sdev->vram, dev->pdev);
+	sisvga_mmio_fini(&sdev->mmio, dev->pdev);
+
+	lock_device(sdev);
+	disable_vga(sdev);
+
+	sisvga_regs_fini(&sdev->regs, dev->pdev);
+
+	dev->dev_private = NULL;
+}
+
+int sisvga_device_mmap(struct sisvga_device* sdev, struct file *filp,
+		       struct vm_area_struct *vma)
+{
+	return ttm_bo_mmap(filp, vma, &sdev->ttm.bdev);
+}
+
+int sisvga_device_create_dumb(struct sisvga_device *sdev,
+			      struct drm_file *file,
+			      struct drm_mode_create_dumb *args)
+{
+	int ret;
+	struct drm_gem_object *gobj;
+	u32 handle;
+
+	args->pitch = args->width * ((args->bpp + 7) / 8);
+	args->size = args->pitch * args->height;
+
+	ret = sisvga_gem_create(&sdev->base, args->size, false, &gobj);
+	if (ret)
+		return ret;
+
+	ret = drm_gem_handle_create(file, gobj, &handle);
+	drm_gem_object_put_unlocked(gobj);
+	if (ret)
+		return ret;
+
+	args->handle = handle;
+	return 0;
+}
+
+int sisvga_device_mmap_dumb_offset(struct sisvga_device *sdev,
+				   struct drm_file *file, uint32_t handle,
+				   uint64_t *offset)
+{
+	struct drm_gem_object *obj;
+	struct sisvga_bo *sis_bo;
+
+	obj = drm_gem_object_lookup(file, handle);
+	if (obj == NULL)
+		return -ENOENT;
+
+	sis_bo = gem_to_sisvga_bo(obj);
+	*offset = sisvga_bo_mmap_offset(sis_bo);
+
+	drm_gem_object_put_unlocked(obj);
+	return 0;
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_device.h b/drivers/gpu/drm/sisvga/sisvga_device.h
new file mode 100644
index 0000000000000..5e2b34bd652e7
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_device.h
@@ -0,0 +1,268 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#ifndef SISVGA_DEVICE_H
+#define SISVGA_DEVICE_H
+
+/* Set SISVGA_REGS to the standard location for
+ * memory-mapped I/O ports. */
+#define	SISVGA_REGS \
+	((void __iomem *)((sdev)->regs.mem))
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drmP.h> // required by drm/drm_gem.h
+#include <drm/drm_gem.h>
+#include <drm/ttm/ttm_bo_driver.h>
+#include <linux/compiler.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include "sisvga_drv.h"
+#include "sisvga_modes.h"
+#include "sisvga_vclk.h"
+
+#define DRM_FILE_PAGE_OFFSET (0x100000000ULL >> PAGE_SHIFT)
+
+#define SISVGA_PCI_BAR_VRAM	0	/* BAR 0 contains VRAM */
+#define SISVGA_PCI_BAR_MMIO	1	/* BAR 1 contains MMIO region */
+#define SISVGA_PCI_BAR_REGS	2	/* BAR 2 contains VGA registers */
+
+#define SISVGA_LUT_SIZE		256
+
+struct drm_device;
+struct drm_file;
+struct drm_mode_fb_cmd2;
+struct resource;
+
+enum sisvga_model {
+	SISVGA_MODEL_6202,
+	SISVGA_MODEL_6215,
+	SISVGA_MODEL_6326
+};
+
+struct sisvga_device_info {
+
+	enum sisvga_model model;
+
+	/* Video RAM */
+	resource_size_t max_size; /* Byte */
+
+	/* RAMDAC speed */
+	int max_clock; /* KHz */
+
+	/* Bitmask of supported VCLK frequencies */
+	unsigned long supported_vclks;
+
+	/* CRTC */
+	int max_htotal;
+	int max_hsync_start;
+	int max_hsync_end;
+	int max_hdisplay;
+	int max_vtotal;
+	int max_vsync_start;
+	int max_vsync_end;
+	int max_vdisplay;
+	int max_bpp;
+	int preferred_bpp;
+
+	/* List of all supported VGA modes */
+	const struct sisvga_mode* vga_modes;
+	size_t vga_modes_len;
+};
+
+#define SISVGA_DEVICE_INFO(model_, size_, clock_, supported_vclks_, htotal_,\
+			   hsync_start_, hsync_end_, hdisplay_, vtotal_,\
+			   vsync_start_, vsync_end_, vdisplay_,\
+			   bpp_, preferred_bpp_,\
+			   vga_modes_, vga_modes_len_)\
+	.model = (model_),\
+	.max_size = (size_),\
+	.max_clock = (clock_),\
+	.supported_vclks = (supported_vclks_),\
+	.max_htotal = (htotal_),\
+	.max_hsync_start = (hsync_start_),\
+	.max_hsync_end = (hsync_end_),\
+	.max_hdisplay = (hdisplay_),\
+	.max_vtotal = (vtotal_),\
+	.max_vsync_start = (vsync_start_),\
+	.max_vsync_end = (vsync_end_),\
+	.max_vdisplay = (vdisplay_),\
+	.max_bpp = (bpp_),\
+	.preferred_bpp = (preferred_bpp_),\
+	.vga_modes = (vga_modes_),\
+	.vga_modes_len = (vga_modes_len_)
+
+#define SISVGA_VCLK_BIT(vclk_) \
+    (1ul << (unsigned long)(vclk_))
+
+struct sisvga_ddc {
+	struct drm_device *dev;
+	u8 scl_mask;
+	u8 sda_mask;
+
+	struct i2c_adapter adapter;
+
+	int udelay; /* I2C delay, in usec */
+	int timeout; /* I2C timeout, in jiffies */
+
+	/* saved register state */
+	u8 sr01;
+};
+
+struct sisvga_connector {
+	struct drm_connector base;
+	struct sisvga_ddc ddc;
+};
+
+struct sisvga_plane {
+	struct drm_plane base;
+};
+
+struct sisvga_crtc {
+	struct drm_crtc	base;
+	u8		lut[SISVGA_LUT_SIZE][3];
+	size_t		lut_len;
+	bool		lut_24;
+};
+
+struct sisvga_encoder {
+	struct drm_encoder base;
+};
+
+struct sisvga_framebuffer {
+	struct drm_framebuffer base;
+	struct drm_gem_object *gem_obj;
+};
+
+struct sisvga_fbdev {
+	struct drm_fb_helper helper;
+	struct sisvga_framebuffer *fb;
+
+	int x1, y1, x2, y2; /* dirty rect */
+	spinlock_t dirty_lock;
+};
+
+struct sisvga_ioports {
+	struct resource	*res;
+	void __iomem	*mem;
+};
+
+struct sisvga_devmem {
+	resource_size_t	base;
+	resource_size_t	size;
+	int mtrr;
+	struct resource	*res;
+	void __iomem	*mem;
+};
+
+struct sisvga_ttm {
+	struct drm_global_reference mem_global_ref;
+	struct ttm_bo_global_ref bo_global_ref;
+	struct ttm_bo_device bdev;
+};
+
+struct sisvga_bo {
+	struct ttm_buffer_object bo;
+	struct ttm_placement placement;
+	struct ttm_bo_kmap_obj kmap;
+	struct drm_gem_object gem;
+
+	/* Supported placements are VRAM and SYSTEM */
+	struct ttm_place placements[3];
+	int pin_count;
+};
+#define gem_to_sisvga_bo(gobj) container_of((gobj), struct sisvga_bo, gem)
+
+struct sisvga_device {
+	struct drm_device	base;
+
+	const struct sisvga_device_info	*info;
+
+	struct sisvga_ioports		regs;
+	struct sisvga_devmem		mmio;
+	struct sisvga_devmem		vram;
+
+	struct sisvga_ttm		ttm;
+
+	struct sisvga_fbdev	fbdev;
+};
+
+#define MODEL_IS_EQ(num_) \
+	((sdev)->info->model == SISVGA_MODEL_ ## num_)
+
+#define MODEL_IS_LE(num_) \
+	((sdev)->info->model <= SISVGA_MODEL_ ## num_)
+
+#define MODEL_IS_GE(num_) \
+	((sdev)->info->model >= SISVGA_MODEL_ ## num_)
+
+#define MODEL_IS_LT(num_) \
+	(!MODEL_IS_GE(num_))
+
+#define MODEL_IS_GT(num_) \
+	(!MODEL_IS_LE(num_))
+
+int sisvga_device_init(struct sisvga_device *sdev,
+		       struct drm_driver *driver, struct pci_dev *pdev,
+		       const struct sisvga_device_info *info);
+void sisvga_device_fini(struct sisvga_device *sdev);
+int sisvga_device_mmap(struct sisvga_device *sdev, struct file *filp,
+		       struct vm_area_struct *vma);
+int sisvga_device_create_dumb(struct sisvga_device *sdev,
+			      struct drm_file *file,
+			      struct drm_mode_create_dumb *args);
+
+struct sisvga_connector* sisvga_connector_create_vga(struct drm_device *dev);
+
+struct sisvga_plane* sisvga_plane_create(struct drm_device *dev,
+					 const uint32_t *formats,
+					 unsigned int format_count,
+		      			 const uint64_t *format_modifiers,
+		      			 enum drm_plane_type type);
+
+struct sisvga_crtc* sisvga_crtc_create(struct drm_device *dev,
+				       struct drm_plane *primary_plane,
+				       struct drm_plane *cursor_plane);
+
+int sisvga_ddc_init(struct sisvga_ddc *sis_ddc, struct drm_device *dev);
+void sisvga_ddc_fini(struct sisvga_ddc *sis_ddc);
+
+struct sisvga_encoder* sisvga_encoder_create(int encoder_type,
+					     struct drm_device *dev);
+
+int sisvga_fbdev_init(struct sisvga_fbdev *sis_fbdev,
+		      struct drm_device *dev, int bpp);
+void sisvga_fbdev_fini(struct sisvga_fbdev *sis_fbdev);
+
+struct sisvga_framebuffer* sisvga_framebuffer_create(
+	struct drm_device *dev,
+	struct drm_gem_object *gem_obj,
+	const struct drm_mode_fb_cmd2 *mode_cmd);
+
+struct sisvga_framebuffer* sisvga_framebuffer_of(struct drm_framebuffer *fb);
+
+int sisvga_ttm_init(struct sisvga_ttm *ttm,
+		    struct drm_device *dev,
+		    unsigned long p_size);
+void sisvga_ttm_fini(struct sisvga_ttm *ttm);
+
+int sisvga_bo_create(struct drm_device *dev, int size, int align,
+		     uint32_t flags, struct sisvga_bo **sis_bo_out);
+void sisvga_bo_unref(struct sisvga_bo **sis_bo);
+int sisvga_bo_reserve(struct sisvga_bo *bo, bool no_wait);
+void sisvga_bo_unreserve(struct sisvga_bo *bo);
+u64 sisvga_bo_mmap_offset(struct sisvga_bo *sis_bo);
+int sisvga_bo_pin(struct sisvga_bo *bo, u32 pl_flag, u64 *gpu_addr);
+int sisvga_bo_unpin(struct sisvga_bo *bo);
+int sisvga_bo_push_to_system(struct sisvga_bo *bo);
+
+int sisvga_gem_create(struct drm_device *dev, u32 size, bool iskernel,
+		      struct drm_gem_object **obj);
+
+#endif /* SISVGA_DEVICE_H */
diff --git a/drivers/gpu/drm/sisvga/sisvga_drv.c b/drivers/gpu/drm/sisvga/sisvga_drv.c
new file mode 100644
index 0000000000000..c06a08f377d70
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_drv.c
@@ -0,0 +1,505 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_drv.h"
+#include <drm/drm_pciids.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include "sisvga_device.h"
+
+static const struct sisvga_mode sisvga_6202_mode_list[] = {
+	/* VGA modes */
+	{ SISVGA_MODE("13",    320,  200, 70,  8,  25175,    0, 0) },
+	/* Enhanced video modes */
+	{ SISVGA_MODE("2D",    640,  350, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E",    640,  480, 60,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E*",   640,  480, 72,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2E+",   640,  480, 75,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2F",    640,  400, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("30",    800,  600, 56,  8,  36000,    0, 0) },
+	{ SISVGA_MODE("30*",   800,  600, 60,  8,  40000,    0, 0) },
+	{ SISVGA_MODE("30+",   800,  600, 72,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("30#",   800,  600, 75,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("38i",  1024,  768, 87,  8,  44900,    0,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("38n",  1024,  768, 60,  8,  65000,    0, 0) },
+	{ SISVGA_MODE("38n+", 1024,  768, 70,  8,  75000,    0, 0) },
+	{ SISVGA_MODE("38n#", 1024,  768, 75,  8,  80000,    0, 0) },
+	{ SISVGA_MODE("3Ai",  1280, 1024, 89,  8,  80000, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("3An",  1280, 1024, 60,  8, 110000, 2048, 0) },
+	{ SISVGA_MODE("40",    320,  200, 70, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("41",    320,  200, 70, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("42",    320,  200, 70, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("43",    640,  480, 60, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("43*",   640,  480, 72, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("43+",   640,  480, 75, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("44",    640,  480, 60, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("44*",   640,  480, 72, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("44+",   640,  480, 75, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("45",    640,  480, 60, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("45*",   640,  480, 72, 24,  31500,    0, 0) },
+	{ SISVGA_MODE("45+",   640,  480, 75, 24,  31500,    0, 0) },
+	{ SISVGA_MODE("46",    800,  600, 56, 15,  36000,    0, 0) },
+	{ SISVGA_MODE("46*",   800,  600, 60, 15,  40000,    0, 0) },
+	{ SISVGA_MODE("46+",   800,  600, 72, 15,  50000,    0, 0) },
+	{ SISVGA_MODE("46#",   800,  600, 75, 15,  50000,    0, 0) },
+	{ SISVGA_MODE("47",    800,  600, 56, 16,  36000,    0, 0) },
+	{ SISVGA_MODE("47*",   800,  600, 60, 16,  40000,    0, 0) },
+	{ SISVGA_MODE("47+",   800,  600, 72, 16,  50000,    0, 0) },
+	{ SISVGA_MODE("47#",   800,  600, 75, 16,  50000,    0, 0) },
+	{ SISVGA_MODE("48",    800,  600, 56, 24,  36000, 2048, 0) },
+	{ SISVGA_MODE("48*",   800,  600, 60, 24,  40000, 2048, 0) },
+	{ SISVGA_MODE("48+",   800,  600, 72, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("48#",   800,  600, 75, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("49i",  1024,  768, 87, 15,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("49n",  1024,  768, 60, 15,  65000, 2048, 0) },
+	{ SISVGA_MODE("49n+", 1024,  768, 70, 15,  75000, 2048, 0) },
+	{ SISVGA_MODE("49n#", 1024,  768, 75, 15,  80000, 2048, 0) },
+	{ SISVGA_MODE("4Ai",  1024,  768, 87, 16,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4An",  1024,  768, 60, 16,  65000, 2048, 0) },
+	{ SISVGA_MODE("4An+", 1024,  768, 70, 16,  75000, 2048, 0) },
+	{ SISVGA_MODE("4An#", 1024,  768, 75, 16,  80000, 2048, 0) },
+	{ SISVGA_MODE("4Bi",  1024,  768, 87, 24,  44900, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4Ci",  1280, 1024, 89, 15,  80000, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4Di",  1280, 1024, 89, 16,  80000, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+};
+
+static const struct sisvga_mode sisvga_6215_mode_list[] = {
+	/* VGA modes */
+	{ SISVGA_MODE("13",     320,  200, 70,  8,  25175,    0, 0) },
+	/* Enhanced video modes */
+	{ SISVGA_MODE("2D",     640,  350, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E",     640,  480, 60,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E*",    640,  480, 72,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2E+",    640,  480, 75,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2E++",   640,  480, 85,  8,  36000,    0, 0) },
+	{ SISVGA_MODE("2F",     640,  400, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("30",     800,  600, 56,  8,  36000,    0, 0) },
+	{ SISVGA_MODE("30*",    800,  600, 60,  8,  40000,    0, 0) },
+	{ SISVGA_MODE("30+",    800,  600, 72,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("30#",    800,  600, 75,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("30##",   800,  600, 85,  8,  56300,    0, 0) },
+	{ SISVGA_MODE("38i",   1024,  768, 87,  8,  44900,    0,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("38n",   1024,  768, 60,  8,  65000,    0, 0) },
+	{ SISVGA_MODE("38n+",  1024,  768, 70,  8,  75000,    0, 0) },
+	{ SISVGA_MODE("38n#",  1024,  768, 75,  8,  80000,    0, 0) },
+	{ SISVGA_MODE("38n##", 1024,  768, 85,  8,  94500,    0, 0) },
+	{ SISVGA_MODE("3Ai",   1280, 1024, 87,  8,  80000, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("3An",   1280, 1024, 60,  8, 110000, 2048, 0) },
+	{ SISVGA_MODE("3An+",  1280, 1024, 75,  8, 135000, 2048, 0) },
+	{ SISVGA_MODE("40",     320,  200, 70, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("41",     320,  200, 70, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("42",     320,  200, 70, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("43",     640,  480, 60, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("43*",    640,  480, 72, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("43+",    640,  480, 75, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("43++",   640,  480, 85, 15,  36000,    0, 0) },
+	{ SISVGA_MODE("44",     640,  480, 60, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("44*",    640,  480, 72, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("44+",    640,  480, 75, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("44++",   640,  480, 85, 16,  36000,    0, 0) },
+	{ SISVGA_MODE("45",     640,  480, 60, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("45*",    640,  480, 72, 24,  31500, 2048, 0) },
+	{ SISVGA_MODE("45+",    640,  480, 75, 24,  31500, 2048, 0) },
+	{ SISVGA_MODE("45++",   640,  480, 85, 24,  36000,    0, 0) },
+	{ SISVGA_MODE("46",     800,  600, 56, 15,  36000,    0, 0) },
+	{ SISVGA_MODE("46*",    800,  600, 60, 15,  40000,    0, 0) },
+	{ SISVGA_MODE("46+",    800,  600, 72, 15,  50000, 2048, 0) },
+	{ SISVGA_MODE("46#",    800,  600, 75, 15,  50000, 2048, 0) },
+	{ SISVGA_MODE("46##",   800,  600, 85, 15,  56300,    0, 0) },
+	{ SISVGA_MODE("47",     800,  600, 56, 16,  36000,    0, 0) },
+	{ SISVGA_MODE("47*",    800,  600, 60, 16,  40000,    0, 0) },
+	{ SISVGA_MODE("47+",    800,  600, 72, 16,  50000, 2048, 0) },
+	{ SISVGA_MODE("47#",    800,  600, 75, 16,  50000, 2048, 0) },
+	{ SISVGA_MODE("47##",   800,  600, 85, 16,  56300,    0, 0) },
+	{ SISVGA_MODE("48",     800,  600, 56, 24,  36000, 2048, 0) },
+	{ SISVGA_MODE("48*",    800,  600, 60, 24,  40000, 2048, 0) },
+	{ SISVGA_MODE("48+",    800,  600, 72, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("48#",    800,  600, 75, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("48##",   800,  600, 85, 24,  56300, 2048, 0) },
+	{ SISVGA_MODE("49i",   1024,  768, 87, 15,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("49n",   1024,  768, 60, 15,  65000, 2048, 0) },
+	{ SISVGA_MODE("49n+",  1024,  768, 70, 15,  75000, 2048, 0) },
+	{ SISVGA_MODE("49n#",  1024,  768, 75, 15,  80000, 2048, 0) },
+	{ SISVGA_MODE("49n##", 1024,  768, 85, 15,  94500, 2048, 0) },
+	{ SISVGA_MODE("4Ai",   1024,  768, 87, 16,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4An",   1024,  768, 60, 16,  65000, 2048, 0) },
+	{ SISVGA_MODE("4An+",  1024,  768, 70, 16,  75000, 2048, 0) },
+	{ SISVGA_MODE("4An#",  1024,  768, 75, 16,  80000, 2048, 0) },
+	{ SISVGA_MODE("4An##", 1024,  768, 85, 16,  94500, 2048, 0) },
+};
+
+static const struct sisvga_mode sisvga_6326_mode_list[] = {
+	/* VGA modes */
+	{ SISVGA_MODE("13",     320,  200, 70,  8,  25175,    0, 0) },
+	/* Enhanced video modes */
+	{ SISVGA_MODE("2D",     640,  350, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E",     640,  480, 60,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("2E*",    640,  480, 72,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2E+",    640,  480, 75,  8,  31500,    0, 0) },
+	{ SISVGA_MODE("2E++",   640,  480, 85,  8,  36000,    0, 0) },
+	{ SISVGA_MODE("2F",     640,  400, 70,  8,  25175,    0, 0) },
+	{ SISVGA_MODE("30",     800,  600, 56,  8,  36000,    0, 0) },
+	{ SISVGA_MODE("30*",    800,  600, 60,  8,  40000,    0, 0) },
+	{ SISVGA_MODE("30+",    800,  600, 72,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("30#",    800,  600, 75,  8,  50000,    0, 0) },
+	{ SISVGA_MODE("30##",   800,  600, 85,  8,  56300,    0, 0) },
+	{ SISVGA_MODE("38i",   1024,  768, 87,  8,  44900,    0,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("38n",   1024,  768, 60,  8,  65000,    0, 0) },
+	{ SISVGA_MODE("38n+",  1024,  768, 70,  8,  75000,    0, 0) },
+	{ SISVGA_MODE("38n#",  1024,  768, 75,  8,  80000,    0, 0) },
+	{ SISVGA_MODE("38n##", 1024,  768, 85,  8,  94500,    0, 0) },
+	{ SISVGA_MODE("3Ai",   1280, 1024, 87,  8,  80000, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("3An",   1280, 1024, 60,  8, 110000, 2048, 0) },
+	{ SISVGA_MODE("3An+",  1280, 1024, 75,  8, 135000, 2048, 0) },
+	{ SISVGA_MODE("3Ci",   1600, 1200, 87,  8, 135000, 2048, 0) },
+	{ SISVGA_MODE("3C",    1600, 1200, 60,  8, 162000, 2048, 0) },
+	{ SISVGA_MODE("3C*",   1600, 1200, 65,  8, 175500, 2048, 0) },
+	{ SISVGA_MODE("40",     320,  200, 70, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("41",     320,  200, 70, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("42",     320,  200, 70, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("43",     640,  480, 60, 15,  25175,    0, 0) },
+	{ SISVGA_MODE("43*",    640,  480, 72, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("43+",    640,  480, 75, 15,  31500,    0, 0) },
+	{ SISVGA_MODE("43++",   640,  480, 85, 15,  36000,    0, 0) },
+	{ SISVGA_MODE("44",     640,  480, 60, 16,  25175,    0, 0) },
+	{ SISVGA_MODE("44*",    640,  480, 72, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("44+",    640,  480, 75, 16,  31500,    0, 0) },
+	{ SISVGA_MODE("44++",   640,  480, 85, 16,  36000,    0, 0) },
+	{ SISVGA_MODE("45",     640,  480, 60, 24,  25175,    0, 0) },
+	{ SISVGA_MODE("45*",    640,  480, 72, 24,  31500, 2048, 0) },
+	{ SISVGA_MODE("45+",    640,  480, 75, 24,  31500, 2048, 0) },
+	{ SISVGA_MODE("45++",   640,  480, 85, 24,  36000,    0, 0) },
+	{ SISVGA_MODE("46",     800,  600, 56, 15,  36000,    0, 0) },
+	{ SISVGA_MODE("46*",    800,  600, 60, 15,  40000,    0, 0) },
+	{ SISVGA_MODE("46+",    800,  600, 72, 15,  50000, 2048, 0) },
+	{ SISVGA_MODE("46#",    800,  600, 75, 15,  50000, 2048, 0) },
+	{ SISVGA_MODE("46##",   800,  600, 85, 15,  56300,    0, 0) },
+	{ SISVGA_MODE("47",     800,  600, 56, 16,  36000,    0, 0) },
+	{ SISVGA_MODE("47*",    800,  600, 60, 16,  40000,    0, 0) },
+	{ SISVGA_MODE("47+",    800,  600, 72, 16,  50000, 2048, 0) },
+	{ SISVGA_MODE("47#",    800,  600, 75, 16,  50000, 2048, 0) },
+	{ SISVGA_MODE("47##",   800,  600, 85, 16,  56300,    0, 0) },
+	{ SISVGA_MODE("48",     800,  600, 56, 24,  36000, 2048, 0) },
+	{ SISVGA_MODE("48*",    800,  600, 60, 24,  40000, 2048, 0) },
+	{ SISVGA_MODE("48+",    800,  600, 72, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("48#",    800,  600, 75, 24,  50000, 2048, 0) },
+	{ SISVGA_MODE("48##",   800,  600, 85, 24,  56300, 2048, 0) },
+	{ SISVGA_MODE("49i",   1024,  768, 87, 15,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("49n",   1024,  768, 60, 15,  65000, 2048, 0) },
+	{ SISVGA_MODE("49n+",  1024,  768, 70, 15,  75000, 2048, 0) },
+	{ SISVGA_MODE("49n#",  1024,  768, 75, 15,  80000, 2048, 0) },
+	{ SISVGA_MODE("49n##", 1024,  768, 85, 15,  94500, 2048, 0) },
+	{ SISVGA_MODE("4Ai",   1024,  768, 87, 16,  44900, 2048,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4An",   1024,  768, 60, 16,  65000, 2048, 0) },
+	{ SISVGA_MODE("4An+",  1024,  768, 70, 16,  75000, 2048, 0) },
+	{ SISVGA_MODE("4An#",  1024,  768, 75, 16,  80000, 2048, 0) },
+	{ SISVGA_MODE("4An##", 1024,  768, 85, 16,  94500, 2048, 0) },
+	{ SISVGA_MODE("4Bi",   1024,  768, 87, 24,  44900, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4Bn",   1024,  768, 60, 24,  65000, 4096, 0) },
+	{ SISVGA_MODE("4Bn+",  1024,  768, 70, 24,  75000, 4096, 0) },
+	{ SISVGA_MODE("4Bn#",  1024,  768, 75, 24,  80000, 4096, 0) },
+	{ SISVGA_MODE("4Bn##", 1024,  768, 85, 24,  94500, 4096, 0) },
+	{ SISVGA_MODE("4Ci",   1280, 1024, 89, 15,  80000, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+	{ SISVGA_MODE("4Ci",   1280, 1024, 89, 16,  80000, 4096,
+			DRM_MODE_FLAG_INTERLACE) },
+};
+
+static const struct sisvga_device_info sisvga_device_info_list[] = {
+	[SISVGA_MODEL_6202] = {
+		SISVGA_DEVICE_INFO(SISVGA_MODEL_6202, 2 * 1024 * 1024, 130000,
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_25175) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_28322) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_31500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_36000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_40000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44889) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_50000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_65000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_75000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_77000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_80000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_94500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_110000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_120000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_130000) |
+				   /* Only in modes list; not in VCKL spec */
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_30000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44900),
+				   2048, 2048, 2048, 1280,
+				   1024, 1024, 1024, 1024,
+				   24, 24,
+				   sisvga_6202_mode_list,
+				   ARRAY_SIZE(sisvga_6202_mode_list)) },
+	[SISVGA_MODEL_6215] = {
+		SISVGA_DEVICE_INFO(SISVGA_MODEL_6215, 2 * 1024 * 1024, 135000,
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_25175) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_28322) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_31500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_36000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_40000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44889) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_50000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_65000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_75000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_77000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_80000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_94500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_110000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_120000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_135000) |
+				   /* Only in modes list; not in VCKL spec */
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_30000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44900),
+				   2048, 2048, 2048, 1280,
+				   1024, 1024, 1024, 1024,
+				   24, 24,
+				   sisvga_6215_mode_list,
+				   ARRAY_SIZE(sisvga_6215_mode_list)) },
+	[SISVGA_MODEL_6326] = {
+		SISVGA_DEVICE_INFO(SISVGA_MODEL_6326, 8 * 1024 * 1024, 175500,
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_25175) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_28322) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_30000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_31500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_36000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_40000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44889) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_50000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_65000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_77000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_80000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_94500) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_110000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_110000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_120000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_135000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_162000) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_175500) |
+				   /* Only in modes list; not in VCKL spec */
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_44900) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_56300) |
+				   SISVGA_VCLK_BIT(SISVGA_VCLK_75000),
+				   4096, 4096, 4096, 1600,
+				   2048, 2048, 2048, 1200,
+				   24, 24,
+				   sisvga_6326_mode_list,
+				   ARRAY_SIZE(sisvga_6326_mode_list)) },
+};
+
+/*
+ * DRM entry points
+ */
+
+static void sisvga_driver_unload(struct drm_device *dev)
+{
+	struct sisvga_device *sdev = dev->dev_private;
+
+	if (!sdev)
+		return;
+
+	sisvga_device_fini(sdev);
+}
+
+int sisvga_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct drm_file *file_priv = filp->private_data;
+	struct sisvga_device *sdev = file_priv->minor->dev->dev_private;
+
+	if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET))
+		return -EINVAL;
+
+	return sisvga_device_mmap(sdev, filp, vma);
+}
+
+static const struct file_operations sisvga_driver_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.release = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+	.mmap = sisvga_mmap,
+	.poll = drm_poll,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.read = drm_read,
+};
+
+static void sisvga_driver_gem_free_object(struct drm_gem_object *obj)
+{
+	struct sisvga_bo *sis_bo = gem_to_sisvga_bo(obj);
+	sisvga_bo_unref(&sis_bo);
+}
+
+static int sisvga_driver_dumb_create(struct drm_file *file_priv,
+                		     struct drm_device *dev,
+        			     struct drm_mode_create_dumb *args)
+{
+	struct sisvga_device *sdev = dev->dev_private;
+	return sisvga_device_create_dumb(sdev, file_priv, args);
+}
+
+static int sisvga_driver_dumb_mmap_offset(struct drm_file *file_priv,
+                        		  struct drm_device *dev,
+					  uint32_t handle, uint64_t *offset)
+{
+	struct drm_gem_object *obj;
+	struct sisvga_bo *sis_bo;
+
+	obj = drm_gem_object_lookup(file_priv, handle);
+	if (obj == NULL)
+		return -ENOENT;
+
+	sis_bo = gem_to_sisvga_bo(obj);
+	*offset = sisvga_bo_mmap_offset(sis_bo);
+
+	drm_gem_object_put_unlocked(obj);
+	return 0;
+}
+
+static struct drm_driver sisvga_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET,
+	.unload = sisvga_driver_unload,
+	.fops = &sisvga_driver_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+	.patchlevel = DRIVER_PATCHLEVEL,
+	/* GEM interfaces */
+	.gem_free_object = sisvga_driver_gem_free_object,
+	/* Dumb interfaces */
+	.dumb_create = sisvga_driver_dumb_create,
+	.dumb_map_offset = sisvga_driver_dumb_mmap_offset,
+};
+
+/*
+ * PCI driver entry points
+ */
+
+static const struct pci_device_id pciidlist[] = {
+	{ PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_6202, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SISVGA_MODEL_6202 },
+	{ PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_6215, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SISVGA_MODEL_6215 },
+	{ PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_6326, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SISVGA_MODEL_6326 },
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, pciidlist);
+
+static void sisvga_kick_out_firmware_fb(struct pci_dev *pdev)
+{
+	struct apertures_struct *ap;
+	bool primary = false;
+
+	ap = alloc_apertures(1);
+	if (!ap)
+		return;
+
+	ap->ranges[0].base = pci_resource_start(pdev, SISVGA_PCI_BAR_VRAM);
+	ap->ranges[0].size = pci_resource_len(pdev, SISVGA_PCI_BAR_VRAM);
+
+#ifdef CONFIG_X86
+	primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
+#endif
+	drm_fb_helper_remove_conflicting_framebuffers(ap, "sisfb", primary);
+	kfree(ap);
+}
+
+static int sisvga_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	int ret;
+	enum sisvga_model model;
+	struct sisvga_device* sdev;
+
+	sisvga_kick_out_firmware_fb(pdev);
+
+	ret = pcim_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	model = ent->driver_data & 0xf;
+	if (model >= ARRAY_SIZE(sisvga_device_info_list)) {
+		/* BUG: There should be a device info for every model. */
+		DRM_ERROR("sisvga: unknown device model %d\n", model);
+		return -EINVAL;
+	}
+
+	sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+	if (!sdev)
+		return -ENOMEM;
+
+	ret = sisvga_device_init(sdev, &sisvga_drm_driver, pdev,
+				 sisvga_device_info_list + model);
+	if (ret)
+		return ret;
+
+	ret = drm_dev_register(&sdev->base, 0);
+	if (ret)
+		goto err_drm_dev_register;
+
+	pci_set_drvdata(pdev, &sdev->base);
+
+	return 0;
+
+err_drm_dev_register:
+	sisvga_device_fini(sdev);
+	return ret;
+}
+
+static void sisvga_remove(struct pci_dev *pdev)
+{
+	struct drm_device *dev = pci_get_drvdata(pdev);
+
+	drm_put_dev(dev);
+}
+
+static struct pci_driver sisvga_pci_driver = {
+	.name = DRIVER_NAME,
+	.id_table = pciidlist,
+	.probe = sisvga_probe,
+	.remove = sisvga_remove,
+};
+
+static int __init sisvga_init(void)
+{
+#ifdef CONFIG_VGA_CONSOLE
+	if (vgacon_text_force())
+		return -EINVAL;
+#endif
+
+	return pci_register_driver(&sisvga_pci_driver);
+}
+
+static void __exit sisvga_exit(void)
+{
+	pci_unregister_driver(&sisvga_pci_driver);
+}
+
+module_init(sisvga_init);
+module_exit(sisvga_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sisvga/sisvga_drv.h b/drivers/gpu/drm/sisvga/sisvga_drv.h
new file mode 100644
index 0000000000000..96e265396fc0c
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_drv.h
@@ -0,0 +1,24 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#ifndef SISVGA_DRV_H
+#define SISVGA_DRV_H
+
+#define DRIVER_AUTHOR		"Thomas Zimmermann"
+#define DRIVER_NAME		"sisvga"
+#define DRIVER_DESC		"SiS graphics driver"
+#define DRIVER_DATE		"20160301"
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		0
+#define DRIVER_PATCHLEVEL	0
+
+#define KiB_TO_BYTE(_kib)	((_kib) * 1024)
+#define MiB_TO_KiB(_mib)	((_mib) * 1024)
+#define MiB_TO_BYTE(_mib)	KiB_TO_MiB((MiB_TO_KiB(_mib)))
+
+#endif
diff --git a/drivers/gpu/drm/sisvga/sisvga_encoder.c b/drivers/gpu/drm/sisvga/sisvga_encoder.c
new file mode 100644
index 0000000000000..297dd350bed2a
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_encoder.c
@@ -0,0 +1,213 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include "sisvga_debug.h"
+#include "sisvga_reg.h"
+
+static struct sisvga_encoder* sisvga_encoder_of(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct sisvga_encoder, base);
+}
+
+/*
+ * DPMS helpers
+ */
+
+static void set_encoder_dpms_mode(struct sisvga_device *sdev, int mode)
+{
+	u8 sr11, cr17;
+
+	RREG_SR(0x11, sr11);
+	RREG_CR(0x17, cr17);
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		cr17 |= 0x80; /* sync pulses enabled */
+		sr11 &= 0x3f; /* clear power-management mode */
+		break;
+	case DRM_MODE_DPMS_STANDBY:
+		cr17 |= 0x80; /* sync pulses enabled */
+		sr11 &= 0x3f; /* clear power-management mode and...*/
+		sr11 |= 0x40; /* ...force suspend mode */
+		break;
+	case DRM_MODE_DPMS_SUSPEND:
+		cr17 |= 0x80; /* sync pulses enabled */
+		sr11 &= 0x3f; /* clear power-management mode and...*/
+		sr11 |= 0x80; /* ...force stand-by mode */
+		break;
+	case DRM_MODE_DPMS_OFF:
+		cr17 &= 0x7f; /* sync pulses disabled */
+		sr11 |= 0xc0; /* force off */
+		break;
+	default:
+		DRM_ERROR("sisvga: invalid DPMS mode %d\n", mode);
+		return;
+	}
+
+	WREG_CR(0x17, cr17);
+	WREG_SR(0x11, sr11);
+}
+
+/*
+ * Encoder helper funcs
+ */
+
+static void sisvga_encoder_helper_dpms(struct drm_encoder *encoder, int mode)
+{
+	set_encoder_dpms_mode(encoder->dev->dev_private, mode);
+}
+
+static bool sisvga_encoder_mode_fixup(struct drm_encoder *encoder,
+				      const struct drm_display_mode *mode,
+				      struct drm_display_mode *adj_mode)
+{
+	long ret;
+	enum sisvga_vclk vclk;
+	enum sisvga_freq freq;
+	unsigned long num, denum, div, postscal, f;
+	struct sisvga_device *sdev = encoder->dev->dev_private;
+	const struct sisvga_device_info *info = sdev->info;
+
+	if (mode->clock > info->max_clock)
+		return false; /* not enough bandwidth */
+
+	ret = sisvga_vclk_of_clock(adj_mode->clock, &vclk);
+	if (ret < 0) {
+		/* BUG: We should have detected this in mode_valid(). */
+		DRM_INFO("sisvga: unsupported dot clock of %d KHz, error %ld",
+			  mode->clock, -ret);
+		return false;
+	}
+	sisvga_vclk_regs(vclk, &freq, &num, &denum, &div, &postscal, &f);
+
+	if (freq == SISVGA_FREQ_14318) {
+
+		/* For modes that use the internal clock generator, we
+		 * fixup the display size to better match the requested dot
+		 * clock. */
+
+		long f_diff;
+		int dots;
+
+		if ((mode->htotal % 9) == 0)
+			dots = 9;
+		else
+			dots = 8;
+
+		f_diff = KHZ_TO_HZ(adj_mode->crtc_clock) - f;
+	}
+
+	return true;
+}
+
+static void sisvga_encoder_helper_prepare(struct drm_encoder *encoder)
+{
+	/* We disable the screen to allow for flicker-free
+	 * mode switching. */
+	set_encoder_dpms_mode(encoder->dev->dev_private, DRM_MODE_DPMS_OFF);
+
+	sisvga_debug_print_regs(encoder->dev->dev_private);
+	sisvga_debug_print_mode(encoder->dev->dev_private);
+}
+
+static void sisvga_encoder_helper_commit(struct drm_encoder *encoder)
+{
+	set_encoder_dpms_mode(encoder->dev->dev_private, DRM_MODE_DPMS_ON);
+
+	sisvga_debug_print_regs(encoder->dev->dev_private);
+	sisvga_debug_print_mode(encoder->dev->dev_private);
+}
+
+static void sisvga_encoder_helper_mode_set(struct drm_encoder *encoder,
+					   struct drm_display_mode *mode,
+					   struct drm_display_mode *adj_mode)
+{
+}
+
+static void sisvga_encoder_helper_disable(struct drm_encoder *encoder)
+{
+	set_encoder_dpms_mode(encoder->dev->dev_private, DRM_MODE_DPMS_OFF);
+}
+
+static void sisvga_encoder_helper_enable(struct drm_encoder *encoder)
+{
+	set_encoder_dpms_mode(encoder->dev->dev_private, DRM_MODE_DPMS_ON);
+}
+
+static const struct drm_encoder_helper_funcs sisvga_encoder_helper_funcs = {
+	.dpms = sisvga_encoder_helper_dpms,
+	.mode_fixup = sisvga_encoder_mode_fixup,
+	.prepare = sisvga_encoder_helper_prepare,
+	.commit = sisvga_encoder_helper_commit,
+	.mode_set = sisvga_encoder_helper_mode_set,
+	.disable = sisvga_encoder_helper_disable,
+	.enable = sisvga_encoder_helper_enable,
+};
+
+/*
+ * Encoder funcs
+ */
+
+static void sisvga_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct sisvga_encoder *sis_encoder = sisvga_encoder_of(encoder);
+	struct drm_device *dev = encoder->dev;
+
+	drm_encoder_cleanup(&sis_encoder->base);
+	devm_kfree(dev->dev, sis_encoder);
+}
+
+static const struct drm_encoder_funcs sisvga_encoder_funcs = {
+	.destroy = sisvga_encoder_destroy,
+};
+
+/*
+ * struct sis_encoder
+ */
+
+static int sisvga_encoder_init(struct sisvga_encoder *sis_encoder,
+			int encoder_type,
+			struct drm_device *dev)
+{
+	int ret;
+	struct drm_encoder *encoder = &sis_encoder->base;
+
+	ret = drm_encoder_init(dev, encoder, &sisvga_encoder_funcs,
+			       encoder_type, NULL);
+	if (ret < 0)
+		return ret;
+
+	drm_encoder_helper_add(encoder, &sisvga_encoder_helper_funcs);
+
+	return 0;
+}
+
+struct sisvga_encoder* sisvga_encoder_create(int encoder_type,
+					     struct drm_device *dev)
+{
+	struct sisvga_encoder *sis_encoder;
+	int ret;
+
+	sis_encoder = devm_kzalloc(dev->dev, sizeof(*sis_encoder),
+				   GFP_KERNEL);
+	if (!sis_encoder)
+		return ERR_PTR(-ENOMEM);
+
+	ret = sisvga_encoder_init(sis_encoder, DRM_MODE_ENCODER_DAC, dev);
+	if (ret)
+		goto err_sisvga_encoder_init;
+
+	return sis_encoder;
+
+err_sisvga_encoder_init:
+	devm_kfree(dev->dev, sis_encoder);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_fbdev.c b/drivers/gpu/drm/sisvga/sisvga_fbdev.c
new file mode 100644
index 0000000000000..47acae7a47779
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_fbdev.c
@@ -0,0 +1,288 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drm_crtc_helper.h>
+#include <drm/drmP.h>
+
+/*
+ * FB ops
+ */
+
+static void memcpy_to(void* dst, const void* src, size_t size, bool is_iomem)
+{
+	if (is_iomem)
+		memcpy_toio(dst, src, size);
+	else
+		memcpy(dst, src, size);
+}
+
+static void sisvga_fbdev_dirty_update(struct sisvga_fbdev *sis_fbdev,
+				      int x, int y, int width, int height)
+{
+	int i;
+	struct sisvga_bo *bo;
+	int src_offset, dst_offset;
+	int bpp = sis_fbdev->fb->base.format->cpp[0];
+	int ret = -EBUSY;
+	bool store_for_later = false;
+	int x2, y2;
+	unsigned long flags;
+	const u8 *src;
+	bool is_iomem;
+	u8 *dst;
+
+	bo = gem_to_sisvga_bo(sis_fbdev->fb->gem_obj);
+
+	/*
+	 * try and reserve the BO, if we fail with busy
+	 * then the BO is being moved and we should
+	 * store up the damage until later.
+	 */
+	if (drm_can_sleep())
+		ret = sisvga_bo_reserve(bo, true);
+	if (ret) {
+		if (ret != -EBUSY)
+			return;
+
+		store_for_later = true;
+	}
+
+	x2 = x + width - 1;
+	y2 = y + height - 1;
+	spin_lock_irqsave(&sis_fbdev->dirty_lock, flags);
+
+	if (sis_fbdev->y1 < y)
+		y = sis_fbdev->y1;
+	if (sis_fbdev->y2 > y2)
+		y2 = sis_fbdev->y2;
+	if (sis_fbdev->x1 < x)
+		x = sis_fbdev->x1;
+	if (sis_fbdev->x2 > x2)
+		x2 = sis_fbdev->x2;
+
+	if (store_for_later) {
+		sis_fbdev->x1 = x;
+		sis_fbdev->x2 = x2;
+		sis_fbdev->y1 = y;
+		sis_fbdev->y2 = y2;
+		spin_unlock_irqrestore(&sis_fbdev->dirty_lock, flags);
+		return;
+	}
+
+	sis_fbdev->x1 = sis_fbdev->y1 = INT_MAX;
+	sis_fbdev->x2 = sis_fbdev->y2 = 0;
+	spin_unlock_irqrestore(&sis_fbdev->dirty_lock, flags);
+
+	if (!bo->kmap.virtual) {
+		ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap);
+		if (ret) {
+			DRM_ERROR("failed to kmap fb updates\n");
+			sisvga_bo_unreserve(bo);
+			return;
+		}
+	}
+
+	dst = ttm_kmap_obj_virtual(&bo->kmap, &is_iomem);
+	src = sis_fbdev->helper.fbdev->screen_base;
+
+	for (i = y; i <= y2; i++) {
+		/* assume equal stride for now */
+		src_offset = i * sis_fbdev->fb->base.pitches[0] + (x * bpp);
+		dst_offset = src_offset;
+		memcpy_to(dst + dst_offset, src + src_offset,
+			  (x2 - x + 1) * bpp, is_iomem);
+	}
+
+	sisvga_bo_unreserve(bo);
+}
+
+static void sisvga_fb_ops_fillrect(struct fb_info *info,
+				   const struct fb_fillrect *rect)
+{
+	struct sisvga_fbdev *sis_fbdev = info->par;
+
+	drm_fb_helper_sys_fillrect(info, rect);
+
+	sisvga_fbdev_dirty_update(sis_fbdev, rect->dx, rect->dy, rect->width,
+				  rect->height);
+}
+
+static void sisvga_fb_ops_copyarea(struct fb_info *info,
+				   const struct fb_copyarea *area)
+{
+	struct sisvga_fbdev *sis_fbdev = info->par;
+
+	drm_fb_helper_sys_copyarea(info, area);
+
+	sisvga_fbdev_dirty_update(sis_fbdev, area->dx, area->dy, area->width,
+				  area->height);
+}
+
+static void sisvga_fb_ops_imageblit(struct fb_info *info,
+				    const struct fb_image *image)
+{
+	struct sisvga_fbdev *sis_fbdev = info->par;
+
+	drm_fb_helper_sys_imageblit(info, image);
+
+	sisvga_fbdev_dirty_update(sis_fbdev, image->dx, image->dy, image->width,
+			          image->height);
+}
+
+static struct fb_ops sisvga_fb_ops = {
+	.owner = THIS_MODULE,
+	.fb_check_var = drm_fb_helper_check_var,
+	.fb_set_par = drm_fb_helper_set_par,
+	.fb_fillrect = sisvga_fb_ops_fillrect,
+	.fb_copyarea = sisvga_fb_ops_copyarea,
+	.fb_imageblit = sisvga_fb_ops_imageblit,
+	.fb_pan_display = drm_fb_helper_pan_display,
+	.fb_blank = drm_fb_helper_blank,
+	.fb_setcmap = drm_fb_helper_setcmap,
+};
+
+/*
+ * Fbdev helpers
+ */
+
+static int sisvga_fb_helper_fb_probe(struct drm_fb_helper *helper,
+		    		     struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_mode_fb_cmd2 mode_cmd;
+	void *sysram;
+	u32 size;
+	int ret;
+	struct drm_gem_object *gem_obj;
+	struct sisvga_framebuffer *sis_fb;
+	struct fb_info *info;
+	struct sisvga_fbdev *sis_fbdev =
+		container_of(helper, struct sisvga_fbdev, helper);
+	struct drm_device *dev = sis_fbdev->helper.dev;
+	struct sisvga_device *sis_dev = dev->dev_private;
+
+	mode_cmd.width = sizes->surface_width;
+	mode_cmd.height = sizes->surface_height;
+	mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8);
+	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+							  sizes->surface_depth);
+
+	size = mode_cmd.pitches[0] * mode_cmd.height;
+
+	sysram = vmalloc(size);
+	if (!sysram)
+		return -ENOMEM;
+
+	ret = sisvga_gem_create(dev, size, true, &gem_obj);
+	if (ret)
+		goto err_sisvga_gem_create;
+
+	sis_fb = sisvga_framebuffer_create(dev, gem_obj, &mode_cmd);
+	if (IS_ERR(sis_fb)) {
+		ret = PTR_ERR(sis_fb);
+		goto err_sisvga_framebuffer_create;
+	}
+	sis_fbdev->fb = sis_fb;
+
+	/* setup helper */
+	sis_fbdev->helper.fb = &sis_fb->base;
+
+	info = drm_fb_helper_alloc_fbi(helper);
+	if (IS_ERR(info)) {
+		ret = PTR_ERR(info);
+		goto err_drm_fb_helper_alloc_fbi;
+	}
+
+	strcpy(info->fix.id, "sisvga-fb");
+	info->par = sis_fbdev;
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &sisvga_fb_ops;
+	/* setup aperture base/size for vesafb takeover */
+	info->apertures->ranges[0].base = dev->mode_config.fb_base;
+	info->apertures->ranges[0].size = sis_dev->vram.size;
+	drm_fb_helper_fill_fix(info, sis_fb->base.pitches[0],
+			       sis_fb->base.format->depth);
+	drm_fb_helper_fill_var(info, &sis_fbdev->helper, sizes->fb_width,
+			       sizes->fb_height);
+	info->screen_base = sysram;
+	info->screen_size = size;
+	info->pixmap.flags = FB_PIXMAP_SYSTEM;
+
+	drm_gem_object_put_unlocked(gem_obj);
+
+	return 0;
+
+err_drm_fb_helper_alloc_fbi:
+	drm_framebuffer_put(&sis_fb->base);
+err_sisvga_framebuffer_create:
+	drm_gem_object_put_unlocked(gem_obj);
+err_sisvga_gem_create:
+	vfree(sysram);
+	return ret;
+}
+
+static const struct drm_fb_helper_funcs sisvga_fb_helper_funcs = {
+	.fb_probe = sisvga_fb_helper_fb_probe,
+};
+
+/*
+ * struct sisvga_fbdev
+ */
+
+int sisvga_fbdev_init(struct sisvga_fbdev *sis_fbdev,
+		      struct drm_device *dev, int bpp)
+{
+	int ret;
+
+	sis_fbdev->x1 = sis_fbdev->y1 = INT_MAX;
+	sis_fbdev->x2 = sis_fbdev->y2 = 0;
+	spin_lock_init(&sis_fbdev->dirty_lock);
+
+	drm_fb_helper_prepare(dev, &sis_fbdev->helper,
+			      &sisvga_fb_helper_funcs);
+
+	ret = drm_fb_helper_init(dev, &sis_fbdev->helper, 1);
+	if (ret < 0) {
+		DRM_ERROR("sisvga: drm_fb_helper_init() failed,"
+			  " error %d\n", -ret);
+		return ret;
+	}
+
+	ret = drm_fb_helper_single_add_all_connectors(&sis_fbdev->helper);
+	if (ret < 0) {
+		DRM_ERROR("drm_fb_helper_single_add_all_connectors failed: %d", -ret);
+		return ret;
+	}
+
+	/* disable all the possible outputs/crtcs before entering KMS mode */
+	drm_helper_disable_unused_functions(dev);
+
+	ret = drm_fb_helper_initial_config(&sis_fbdev->helper, bpp);
+	if (ret < 3) {
+		DRM_ERROR("drm_fb_helper_initial_config failed: %d", -ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+void sisvga_fbdev_fini(struct sisvga_fbdev *sis_fbdev)
+{
+	void *sysram;
+	struct sisvga_framebuffer *sis_fb = sis_fbdev->fb;
+
+	sysram = sis_fbdev->helper.fbdev->screen_base;
+
+        drm_fb_helper_unregister_fbi(&sis_fbdev->helper);
+        drm_fb_helper_fini(&sis_fbdev->helper);
+
+	drm_framebuffer_put(&sis_fb->base);
+
+	if (sysram)
+		vfree(sysram);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_framebuffer.c b/drivers/gpu/drm/sisvga/sisvga_framebuffer.c
new file mode 100644
index 0000000000000..3349158b7aad9
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_framebuffer.c
@@ -0,0 +1,93 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h> /* include before drm/drm_gem.h */
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem.h>
+
+/*
+ * Framebuffer helpers
+ */
+
+static void sisvga_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+	struct sisvga_framebuffer *sis_fb = sisvga_framebuffer_of(fb);
+	struct drm_device *dev = fb->dev;
+	struct drm_gem_object *gem_obj = sis_fb->gem_obj;
+
+	drm_framebuffer_cleanup(&sis_fb->base);
+	devm_kfree(dev->dev, sis_fb);
+
+	if (gem_obj)
+		drm_gem_object_put_unlocked(gem_obj);
+}
+
+static const struct drm_framebuffer_funcs sisvga_framebuffer_funcs = {
+	.destroy = sisvga_framebuffer_destroy,
+};
+
+/*
+ * struct sisvga_framebuffer
+ */
+
+static int sisvga_framebuffer_init(struct sisvga_framebuffer *sis_fb,
+				   struct drm_device *dev,
+				   struct drm_gem_object *gem_obj,
+				   const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	int ret;
+
+	if (gem_obj)
+		drm_gem_object_get(gem_obj);
+	sis_fb->gem_obj = gem_obj;
+
+	drm_helper_mode_fill_fb_struct(dev, &sis_fb->base, mode_cmd);
+
+	ret = drm_framebuffer_init(dev, &sis_fb->base,
+				   &sisvga_framebuffer_funcs);
+	if (ret < 0) {
+		DRM_ERROR("drm_framebuffer_init failed: %d\n", ret);
+		goto err_drm_framebuffer_init;
+	}
+
+	return 0;
+
+err_drm_framebuffer_init:
+	if (gem_obj)
+		drm_gem_object_put_unlocked(gem_obj);
+	return ret;
+}
+
+struct sisvga_framebuffer* sisvga_framebuffer_create(
+	struct drm_device *dev,
+	struct drm_gem_object *gem_obj,
+	const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct sisvga_framebuffer *sis_fb;
+	int ret;
+
+	sis_fb = devm_kzalloc(dev->dev, sizeof(*sis_fb), GFP_KERNEL);
+	if (!sis_fb)
+		return ERR_PTR(-ENOMEM);
+
+	ret = sisvga_framebuffer_init(sis_fb, dev, gem_obj, mode_cmd);
+	if (ret)
+		goto err_sisvga_framebuffer_init;
+
+	return sis_fb;
+
+err_sisvga_framebuffer_init:
+	devm_kfree(dev->dev, sis_fb);
+	return ERR_PTR(ret);
+}
+
+struct sisvga_framebuffer* sisvga_framebuffer_of(struct drm_framebuffer *fb)
+{
+	return container_of(fb, struct sisvga_framebuffer, base);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_modes.c b/drivers/gpu/drm/sisvga/sisvga_modes.c
new file mode 100644
index 0000000000000..2c129bd656711
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_modes.c
@@ -0,0 +1,42 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_modes.h"
+
+static bool
+depth_is_compatible(const struct sisvga_mode* smode, int bpp)
+{
+	return (smode->depth == bpp) || ((smode->depth == 24) && (bpp == 32));
+}
+
+bool
+sisvga_mode_is_compatible(const struct sisvga_mode* smode,
+			  const struct drm_display_mode* mode,
+			  int bpp)
+{
+	return (smode->hdisplay == mode->hdisplay) &&
+	       (smode->vdisplay == mode->vdisplay) &&
+	       (smode->clock == mode->clock) &&
+	       (depth_is_compatible(smode, bpp));
+
+}
+
+const struct sisvga_mode*
+sisvga_find_compatible_mode(const struct sisvga_mode* beg,
+			    const struct sisvga_mode* end,
+			    const struct drm_display_mode* mode,
+			    int bpp)
+{
+	while (beg < end) {
+		if (sisvga_mode_is_compatible(beg, mode, bpp)) {
+			return beg;
+		}
+		++beg;
+	}
+	return NULL;
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_modes.h b/drivers/gpu/drm/sisvga/sisvga_modes.h
new file mode 100644
index 0000000000000..91f55e4f99e31
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_modes.h
@@ -0,0 +1,46 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#ifndef SISVGA_MODE_H
+#define SISVGA_MODE_H
+
+#include "sisvga_vclk.h"
+#include <drm/drm_modes.h>
+
+struct sisvga_mode {
+	int hdisplay;
+	int vdisplay;
+	int depth;
+	int clock; /* Pixel clock in KHz */
+	int min_vram; /* Minimally required VRAM in KiB */
+	int flags;
+	enum sisvga_vclk pixel_clock;
+};
+
+#define SISVGA_MODE(name_, hdisplay_, vdisplay_, frame_rate_, depth_,\
+		    clock_, min_vram_, flags_)\
+	.hdisplay = (hdisplay_),\
+	.vdisplay = (vdisplay_),\
+	.depth = (depth_),\
+	.clock = (clock_),\
+	.min_vram = (min_vram_),\
+	.flags = (flags_),\
+	.pixel_clock = (SISVGA_VCLK_ ## clock_)
+
+bool
+sisvga_mode_is_compatible(const struct sisvga_mode* smode,
+			  const struct drm_display_mode* mode,
+			  int bpp);
+
+const struct sisvga_mode*
+sisvga_find_compatible_mode(const struct sisvga_mode* beg,
+			    const struct sisvga_mode* end,
+			    const struct drm_display_mode* mode,
+			    int bpp);
+
+#endif
diff --git a/drivers/gpu/drm/sisvga/sisvga_plane.c b/drivers/gpu/drm/sisvga/sisvga_plane.c
new file mode 100644
index 0000000000000..86d7cbf6417a3
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_plane.c
@@ -0,0 +1,127 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+
+static struct sisvga_plane* sisvga_plane_of(struct drm_plane* plane)
+{
+	return container_of(plane, struct sisvga_plane, base);
+}
+
+/*
+ * Plane funcs
+ */
+
+static int sisvga_plane_funcs_update_plane(struct drm_plane *plane,
+				struct drm_crtc *crtc,
+                  		struct drm_framebuffer *fb,
+				int crtc_x, int crtc_y,
+                  		unsigned int crtc_w, unsigned int crtc_h,
+                  		uint32_t src_x, uint32_t src_y,
+                  		uint32_t src_w, uint32_t src_h,
+                  		struct drm_modeset_acquire_ctx *ctx)
+{
+	return 0;
+}
+
+static int sisvga_plane_funcs_disable_plane(
+	struct drm_plane *plane, struct drm_modeset_acquire_ctx *ctx)
+{
+        return 0;
+}
+
+static void sisvga_plane_funcs_destroy(struct drm_plane *plane)
+{
+	struct sisvga_plane* sis_plane = sisvga_plane_of(plane);
+	struct drm_device *dev = plane->dev;
+
+	drm_plane_cleanup(&sis_plane->base);
+	devm_kfree(dev->dev, sis_plane);
+}
+
+static int sisvga_plane_funcs_set_property(struct drm_plane *plane,
+        				   struct drm_property *property,
+                			   uint64_t value)
+{
+	DRM_INFO("property: %s\n", property->name);
+
+        return 0;
+}
+
+static const struct drm_plane_funcs sisvga_plane_funcs = {
+        .update_plane = sisvga_plane_funcs_update_plane,
+        .disable_plane = sisvga_plane_funcs_disable_plane,
+        .destroy = sisvga_plane_funcs_destroy,
+        .set_property = sisvga_plane_funcs_set_property,
+};
+
+/*
+ * struct sisvga_plane
+ */
+
+static int sisvga_plane_init(struct sisvga_plane* sis_plane,
+			     struct drm_device *dev,
+		             const uint32_t *formats,
+			     unsigned int format_count,
+		             const uint64_t *format_modifiers,
+		             enum drm_plane_type type)
+{
+	int ret;
+
+	ret = drm_universal_plane_init(dev, &sis_plane->base, 0,
+				       &sisvga_plane_funcs, formats,
+				       format_count, format_modifiers, type,
+				       NULL);
+	if (ret)
+		return ret;
+
+	ret = drm_plane_create_zpos_immutable_property(&sis_plane->base, 0);
+	if (ret) {
+		DRM_ERROR("%s:%d %d\n", __func__, __LINE__, -ret);
+		goto err;
+	}
+
+	ret = drm_plane_create_rotation_property(&sis_plane->base,
+						 DRM_MODE_ROTATE_0,
+						 DRM_MODE_ROTATE_0);
+	if (ret) {
+		DRM_ERROR("%s:%d %d\n", __func__, __LINE__, -ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	drm_plane_cleanup(&sis_plane->base);
+	return ret;
+}
+
+struct sisvga_plane* sisvga_plane_create(struct drm_device *dev,
+				         const uint32_t *formats,
+					 unsigned int format_count,
+				         const uint64_t *format_modifiers,
+				         enum drm_plane_type type)
+{
+	struct sisvga_plane* sis_plane;
+	int ret;
+
+	sis_plane = devm_kzalloc(dev->dev, sizeof(*sis_plane), GFP_KERNEL);
+	if (!sis_plane)
+		return ERR_PTR(-ENOMEM);
+
+	ret = sisvga_plane_init(sis_plane, dev, formats, format_count,
+				format_modifiers, type);
+	if (ret)
+		goto err_sisvga_plane_init;
+
+	return sis_plane;
+
+err_sisvga_plane_init:
+	devm_kfree(dev->dev, sis_plane);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_ttm.c b/drivers/gpu/drm/sisvga/sisvga_ttm.c
new file mode 100644
index 0000000000000..2dd50ff326d68
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_ttm.c
@@ -0,0 +1,275 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * Authors: Thomas Zimmermann
+ */
+
+#include "sisvga_device.h"
+#include <drm/drmP.h>
+#include <drm/ttm/ttm_page_alloc.h>
+#include "sisvga_reg.h"
+
+#undef VRAM_TTM_MEM
+#define VRAM_TTM_MEM(__ttm)	((__ttm)->mem_global_ref)
+
+#undef VRAM_TTM_BO
+#define VRAM_TTM_BO(__ttm)	((__ttm)->bo_global_ref)
+
+#undef VRAM_TTM_BO_DEV
+#define VRAM_TTM_BO_DEV(__ttm)	((__ttm)->bdev)
+
+static struct sisvga_device* sisvga_device_of_bo_device(
+	struct ttm_bo_device *bdev)
+{
+	return container_of(bdev, struct sisvga_device, ttm.bdev);
+}
+
+static struct sisvga_bo* sisvga_bo_of_ttm_buffer_object(
+	struct ttm_buffer_object *bo)
+{
+	return container_of(bo, struct sisvga_bo, bo);
+}
+
+/*
+ * TTM global memory
+ */
+
+static int sisvga_global_ttm_mem_init(struct drm_global_reference *ref)
+{
+	return ttm_mem_global_init(ref->object);
+}
+
+static void sisvga_global_ttm_mem_release(struct drm_global_reference *ref)
+{
+	ttm_mem_global_release(ref->object);
+}
+
+static int sisvga_init_ttm_mem(struct sisvga_ttm *ttm)
+{
+	int ret;
+
+	VRAM_TTM_MEM(ttm).global_type = DRM_GLOBAL_TTM_MEM;
+	VRAM_TTM_MEM(ttm).size = sizeof(struct ttm_mem_global);
+	VRAM_TTM_MEM(ttm).init = &sisvga_global_ttm_mem_init;
+	VRAM_TTM_MEM(ttm).release = &sisvga_global_ttm_mem_release;
+
+	ret = drm_global_item_ref(&VRAM_TTM_MEM(ttm));
+	if (ret < 0) {
+		DRM_ERROR("sisvga: setup of TTM memory accounting failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * TTM global BO
+ */
+
+static int sisvga_init_ttm_bo(struct sisvga_ttm* ttm)
+{
+	int ret;
+
+	VRAM_TTM_BO(ttm).mem_glob = VRAM_TTM_MEM(ttm).object;
+	VRAM_TTM_BO(ttm).ref.global_type = DRM_GLOBAL_TTM_BO;
+	VRAM_TTM_BO(ttm).ref.size = sizeof(struct ttm_bo_global);
+	VRAM_TTM_BO(ttm).ref.init = &ttm_bo_global_init;
+	VRAM_TTM_BO(ttm).ref.release = &ttm_bo_global_release;
+
+	ret = drm_global_item_ref(&VRAM_TTM_BO(ttm).ref);
+	if (ret < 0) {
+		DRM_ERROR("sisvga: setup of TTM BO failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * TTM BO device
+ */
+
+static void sisvga_ttm_backend_destroy(struct ttm_tt *tt)
+{
+	ttm_tt_fini(tt);
+	kfree(tt);
+}
+
+static struct ttm_backend_func sisvga_ttm_backend_func = {
+	.destroy = sisvga_ttm_backend_destroy
+};
+
+static struct ttm_tt *sisvga_ttm_tt_create(struct ttm_buffer_object *bo,
+					   uint32_t page_flags)
+{
+	struct ttm_tt *tt;
+	int ret;
+
+	tt = kzalloc(sizeof(*tt), GFP_KERNEL);
+	if (tt == NULL)
+		return NULL;
+
+	tt->func = &sisvga_ttm_backend_func;
+
+	ret = ttm_tt_init(tt, bo, page_flags);
+	if (ret < 0) {
+		kfree(tt);
+		return NULL;
+	}
+
+	return tt;
+}
+
+static int sisvga_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
+		 		   struct ttm_mem_type_manager *man)
+{
+	switch (type) {
+	case TTM_PL_SYSTEM:
+		man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+		man->available_caching = TTM_PL_MASK_CACHING;
+		man->default_caching = TTM_PL_FLAG_CACHED;
+		break;
+	case TTM_PL_VRAM:
+		man->func = &ttm_bo_manager_func;
+		man->flags = TTM_MEMTYPE_FLAG_FIXED |
+			     TTM_MEMTYPE_FLAG_MAPPABLE;
+		man->available_caching = TTM_PL_FLAG_UNCACHED |
+					 TTM_PL_FLAG_WC;
+		man->default_caching = TTM_PL_FLAG_WC;
+		break;
+	default:
+		DRM_ERROR("sisvga: Unsupported memory type %u\n", (unsigned)type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void sisvga_bo_evict_flags(struct ttm_buffer_object *bo,
+				  struct ttm_placement *pl)
+{
+	/* TODO */
+	DRM_INFO("%s:%d TODO\n", __func__, __LINE__);
+}
+
+static int sisvga_bo_verify_access(struct ttm_buffer_object *bo,
+				   struct file *filp)
+{
+	struct sisvga_bo *sis_bo = sisvga_bo_of_ttm_buffer_object(bo);
+
+	return drm_vma_node_verify_access(&sis_bo->gem.vma_node,
+					  filp->private_data);
+}
+
+static int sisvga_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+				     struct ttm_mem_reg *mem)
+{
+	struct ttm_mem_type_manager *man = bdev->man + mem->mem_type;
+	struct sisvga_device *sdev = sisvga_device_of_bo_device(bdev);
+
+	if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
+		return -EINVAL;
+
+	mem->bus.addr = NULL;
+	mem->bus.size = mem->num_pages << PAGE_SHIFT;
+
+	switch (mem->mem_type) {
+	case TTM_PL_SYSTEM:	/* nothing to do */
+		mem->bus.offset = 0;
+		mem->bus.base = 0;
+		mem->bus.is_iomem = false;
+		break;
+	case TTM_PL_VRAM:
+		mem->bus.offset = mem->start << PAGE_SHIFT;
+		mem->bus.base = sdev->vram.base; // pci_resource_start(mdev->dev->pdev, 0);
+		mem->bus.is_iomem = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void sisvga_ttm_io_mem_free(struct ttm_bo_device *bdev,
+				   struct ttm_mem_reg *mem)
+{
+	/* TODO */
+	DRM_INFO("%s:%d TODO\n", __func__, __LINE__);
+}
+
+static struct ttm_bo_driver sisvga_bo_driver = {
+	.ttm_tt_create = sisvga_ttm_tt_create,
+	.ttm_tt_populate = ttm_pool_populate,
+	.ttm_tt_unpopulate = ttm_pool_unpopulate,
+	.init_mem_type = sisvga_bo_init_mem_type,
+	.evict_flags = sisvga_bo_evict_flags,
+	.verify_access = sisvga_bo_verify_access,
+	.io_mem_reserve = sisvga_ttm_io_mem_reserve,
+	.io_mem_free = sisvga_ttm_io_mem_free,
+};
+
+static int sisvga_init_ttm_bo_device(struct sisvga_ttm *ttm,
+				     struct drm_device *dev,
+				     unsigned long p_size)
+{
+	int ret;
+
+	ret = ttm_bo_device_init(&VRAM_TTM_BO_DEV(ttm),
+				 VRAM_TTM_BO(ttm).ref.object,
+				 &sisvga_bo_driver,
+				 dev->anon_inode->i_mapping,
+				 DRM_FILE_PAGE_OFFSET,
+				 true);
+	if (ret) {
+		DRM_ERROR("sisvga: ttm_bo_device_init failed; %d\n", ret);
+		return ret;
+	}
+
+	ret = ttm_bo_init_mm(&VRAM_TTM_BO_DEV(ttm), TTM_PL_VRAM, p_size);
+	if (ret) {
+		DRM_ERROR("sisvga: ttm_bo_init_mm failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * struct sisvga_ttm
+ */
+
+int sisvga_ttm_init(struct sisvga_ttm *ttm,
+		    struct drm_device *dev,
+		    unsigned long p_size)
+{
+	int ret;
+
+	ret = sisvga_init_ttm_mem(ttm);
+	if (ret < 0)
+		return ret;
+
+	ret = sisvga_init_ttm_bo(ttm);
+	if (ret < 0)
+		goto err_init_ttm_bo;
+
+	ret = sisvga_init_ttm_bo_device(ttm, dev, p_size);
+	if (ret < 0)
+		goto err_init_ttm_bo_device;
+
+	return 0;
+
+err_init_ttm_bo_device:
+	ttm_mem_global_release(VRAM_TTM_BO(ttm).ref.object);
+err_init_ttm_bo:
+	ttm_mem_global_release(VRAM_TTM_MEM(ttm).object);
+	return ret;
+}
+
+void sisvga_ttm_fini(struct sisvga_ttm *ttm)
+{
+	ttm_bo_device_release(&VRAM_TTM_BO_DEV(ttm));
+	ttm_mem_global_release(VRAM_TTM_BO(ttm).ref.object);
+	ttm_mem_global_release(VRAM_TTM_MEM(ttm).object);
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_vclk.c b/drivers/gpu/drm/sisvga/sisvga_vclk.c
new file mode 100644
index 0000000000000..fd1fd5b9b5c69
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_vclk.c
@@ -0,0 +1,167 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ */
+
+#include "sisvga_vclk.h"
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+
+struct sisvga_clk_config {
+	enum sisvga_freq freq;
+	unsigned char num;
+	unsigned char denum;
+	unsigned char div;
+	unsigned char postscal;
+	int clock_hz;
+};
+
+#define SISVGA_CLK_CONFIG(description, f, n, dn, d, p, c) \
+	.freq = (SISVGA_FREQ_ ## f), \
+	.num = (n), \
+	.denum = (dn), \
+	.div = (d), \
+	.postscal = (p), \
+	.clock_hz = (c)
+
+/* On all SiS adapters we have to configure the internal dot-clock
+ * generator. According to its manual on the SiS 6326 we can configure
+ * VGA clock generators in the same way. In practice hardware doesn't
+ * support this. So we only use VGA registers for VGA dot clocks, and
+ * extended registers for the internal clock generator.
+ *
+ * The config table for the SiS 6326 replaces VGA clock generators,
+ * with the internal one, even if the VGA clock generator would produce
+ * better results.
+ */
+static const struct sisvga_clk_config sisvga_vclk_configs[] = {
+	/* Any VGA */
+	{ SISVGA_CLK_CONFIG( "25.175 MHz", 25175,   1,  1, 1, 1,  25175000) },
+	{ SISVGA_CLK_CONFIG( "28.322 MHz", 28322,   1,  1, 1, 1,  28322000) },
+	/* SiS 6202 and later */
+	{ SISVGA_CLK_CONFIG( "30.000 MHz", 14318,  22, 21, 2, 1,  29999996) },
+	{ SISVGA_CLK_CONFIG( "31.500 MHz", 14318,  11,  5, 1, 1,  31499996) },
+	//{ SISVGA_CLK_CONFIG( "36.000 MHz", 25175,   5,  7, 2, 1,  35964285) },
+	{ SISVGA_CLK_CONFIG( "36.000 MHz", 14318,  83, 11, 1, 3,  36012392) },
+	{ SISVGA_CLK_CONFIG( "40.000 MHz", 14318,  88, 21, 2, 3,  39999994) },
+	//{ SISVGA_CLK_CONFIG( "44.889 MHz", 25175,  41, 23, 1, 1,  44877173) },
+	{ SISVGA_CLK_CONFIG( "44.889 MHz", 14318,  47, 15, 1, 1,  44863630) },
+	{ SISVGA_CLK_CONFIG( "44.900 MHz", 14318, 127, 27, 2, 3,  44898984) },
+	{ SISVGA_CLK_CONFIG( "50.000 MHz", 14318, 110, 21, 2, 3,  49999993) },
+	{ SISVGA_CLK_CONFIG( "56.300 MHz", 14318,  59, 15, 1, 1,  56318174) },
+	{ SISVGA_CLK_CONFIG( "65.000 MHz", 14318,  59, 13, 1, 1,  64982509) },
+	{ SISVGA_CLK_CONFIG( "75.000 MHz", 14318,  55, 21, 2, 1,  74999990) },
+	{ SISVGA_CLK_CONFIG( "77.000 MHz", 14318, 121, 15, 2, 3,  76999990) },
+	{ SISVGA_CLK_CONFIG( "80.000 MHz", 14318,  95, 17, 1, 1,  80013358) },
+	{ SISVGA_CLK_CONFIG( "94.500 MHz", 14318,  33,  5, 1, 1,  94499988) },
+	{ SISVGA_CLK_CONFIG("110.000 MHz", 14318,  73, 19, 2, 1, 110023909) },
+	{ SISVGA_CLK_CONFIG("120.000 MHz", 14318,  88, 21, 2, 1, 119999984) },
+	{ SISVGA_CLK_CONFIG("130.000 MHz", 14318,  59, 13, 2, 1, 129965018) },
+	/* SiS 6215 and later */
+	{ SISVGA_CLK_CONFIG("135.000 MHz", 14318,  33,  7, 2, 1, 134999982) },
+	/* SiS 6326 and later */
+	//{ SISVGA_CLK_CONFIG("162.000 MHz", 25175,  74, 23, 2, 1, 161995652) },
+	{ SISVGA_CLK_CONFIG("162.000 MHz", 14318,  17,  3, 2, 1, 162272706) },
+	{ SISVGA_CLK_CONFIG("175.500 MHz", 14318,  49,  4, 1, 1, 175397705) },
+};
+
+int sisvga_vclk_of_clock(unsigned int clock_khz, enum sisvga_vclk *vclk_out)
+{
+	switch (clock_khz) {
+	case 25000:	/* fall through */
+	case 25175:
+		*vclk_out = SISVGA_VCLK_25175;
+		return 0;
+	case 28000:	/* fall through */
+	case 28322:
+		*vclk_out = SISVGA_VCLK_28322;
+		return 0;
+	case 30000:
+		*vclk_out = SISVGA_VCLK_30000;
+		return 0;
+	case 31500:
+		*vclk_out = SISVGA_VCLK_31500;
+		return 0;
+	case 36000:
+		*vclk_out = SISVGA_VCLK_36000;
+		return 0;
+	case 40000:
+		*vclk_out = SISVGA_VCLK_40000;
+		return 0;
+	case 44889:
+		*vclk_out = SISVGA_VCLK_44889;
+		return 0;
+	case 44900:
+		*vclk_out = SISVGA_VCLK_44900;
+		return 0;
+	case 50000:
+		*vclk_out = SISVGA_VCLK_50000;
+		return 0;
+	case 56300:
+		*vclk_out = SISVGA_VCLK_56300;
+		return 0;
+	case 65000:
+		*vclk_out = SISVGA_VCLK_65000;
+		return 0;
+	case 75000:
+		*vclk_out = SISVGA_VCLK_75000;
+		return 0;
+	case 77000:
+		*vclk_out = SISVGA_VCLK_77000;
+		return 0;
+	case 80000:
+		*vclk_out = SISVGA_VCLK_80000;
+		return 0;
+	case 94500:
+		*vclk_out = SISVGA_VCLK_94500;
+		return 0;
+	case 110000:
+		*vclk_out = SISVGA_VCLK_110000;
+		return 0;
+	case 120000:
+		*vclk_out = SISVGA_VCLK_120000;
+		return 0;
+	case 130000:
+		*vclk_out = SISVGA_VCLK_130000;
+		return 0;
+	case 135000:
+		*vclk_out = SISVGA_VCLK_135000;
+		return 0;
+	case 162000:
+		*vclk_out = SISVGA_VCLK_162000;
+		return 0;
+	case 175500:
+		*vclk_out = SISVGA_VCLK_175500;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+void sisvga_vclk_regs(enum sisvga_vclk vclk, enum sisvga_freq *freq_out,
+		      unsigned long *num_out,
+		      unsigned long *denum_out,
+		      unsigned long *div_out,
+		      unsigned long *postscal_out,
+	              unsigned long *f_out)
+{
+	BUG_ON(!(vclk < ARRAY_SIZE(sisvga_vclk_configs)));
+	BUG_ON(sisvga_vclk_configs[vclk].freq > 2);
+	BUG_ON(sisvga_vclk_configs[vclk].num < 1);
+	BUG_ON(sisvga_vclk_configs[vclk].num > 128);
+	BUG_ON(sisvga_vclk_configs[vclk].denum < 1);
+	BUG_ON(sisvga_vclk_configs[vclk].denum > 32);
+	BUG_ON(sisvga_vclk_configs[vclk].div < 1);
+	BUG_ON(sisvga_vclk_configs[vclk].div > 2);
+	BUG_ON(sisvga_vclk_configs[vclk].postscal < 1);
+	BUG_ON(sisvga_vclk_configs[vclk].postscal > 8);
+
+	*freq_out = sisvga_vclk_configs[vclk].freq;
+	*num_out = sisvga_vclk_configs[vclk].num;
+	*denum_out = sisvga_vclk_configs[vclk].denum;
+	*div_out = sisvga_vclk_configs[vclk].div;
+	*postscal_out = sisvga_vclk_configs[vclk].postscal;
+	*f_out = sisvga_vclk_configs[vclk].clock_hz;
+}
diff --git a/drivers/gpu/drm/sisvga/sisvga_vclk.h b/drivers/gpu/drm/sisvga/sisvga_vclk.h
new file mode 100644
index 0000000000000..b44fb9518e234
--- /dev/null
+++ b/drivers/gpu/drm/sisvga/sisvga_vclk.h
@@ -0,0 +1,57 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License version 2. See the file COPYING in the main
+ * directory of this archive for more details.
+ */
+
+#ifndef SISVGA_VCLK_H
+#define SISVGA_VCLK_H
+
+enum sisvga_freq {
+	SISVGA_FREQ_14318 = 0, /* from SiS internal clock generator */
+        SISVGA_FREQ_25175,
+        SISVGA_FREQ_28322
+};
+
+enum sisvga_vclk {
+	/* Any VGA */
+	SISVGA_VCLK_25175,
+	SISVGA_VCLK_28322,
+	/* SiS 6202 and later */
+	SISVGA_VCLK_30000,
+	SISVGA_VCLK_31500,
+	SISVGA_VCLK_36000,
+	SISVGA_VCLK_40000,
+	SISVGA_VCLK_44889,
+	SISVGA_VCLK_44900,
+	SISVGA_VCLK_50000,
+	SISVGA_VCLK_56300,
+	SISVGA_VCLK_65000,
+	SISVGA_VCLK_75000,
+	SISVGA_VCLK_77000,
+	SISVGA_VCLK_80000,
+	SISVGA_VCLK_94500,
+	SISVGA_VCLK_110000,
+	SISVGA_VCLK_120000,
+	SISVGA_VCLK_130000,
+	/* SiS 6215 and later */
+	SISVGA_VCLK_135000,
+	/* SiS 6326 and later */
+	SISVGA_VCLK_162000,
+	SISVGA_VCLK_175500
+};
+
+#define KHZ_TO_HZ(_khz)		((_khz) * 1000)
+#define MHZ_TO_KHZ(_mhz)	((_mhz) * 1000)
+#define MHZ_TO_HZ(_mhz)		KHZ_TO_HZ(MHZ_TO_KHZ(_mhz))
+
+int sisvga_vclk_of_clock(unsigned int clock_khz, enum sisvga_vclk *vclk_out);
+
+void sisvga_vclk_regs(enum sisvga_vclk vclk, enum sisvga_freq *freq_out,
+		      unsigned long *num_out,
+		      unsigned long *denum_out,
+		      unsigned long *div_out,
+		      unsigned long *postscal_out,
+	              unsigned long *f_out);
+
+#endif
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 29502238e5107..d474288b8e17b 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -684,6 +684,8 @@
 #define PCI_DEVICE_ID_SI_LPC		0x0018
 #define PCI_DEVICE_ID_SI_5597_VGA	0x0200
 #define PCI_DEVICE_ID_SI_6205		0x0205
+#define PCI_DEVICE_ID_SI_6215		0x0204
+#define PCI_DEVICE_ID_SI_6326		0x6326
 #define PCI_DEVICE_ID_SI_501		0x0406
 #define PCI_DEVICE_ID_SI_496		0x0496
 #define PCI_DEVICE_ID_SI_300		0x0300
-- 
GitLab