summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2015-07-04 09:29:05 +0200
committerHans de Goede <hdegoede@redhat.com>2015-07-04 09:29:05 +0200
commit4fe73acfb7aec35981b1782be4ce0028da109246 (patch)
treeae20e203ca18b5192ae3ab0f1dcf57e0befbc46e
Initial gm12u320 kms driver commit
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
-rw-r--r--Makefile18
-rw-r--r--gm12u320_connector.c150
-rw-r--r--gm12u320_dmabuf.c283
-rw-r--r--gm12u320_drv.c127
-rw-r--r--gm12u320_drv.h123
-rw-r--r--gm12u320_encoder.c83
-rw-r--r--gm12u320_fb.c374
-rw-r--r--gm12u320_gem.c241
-rw-r--r--gm12u320_main.c368
-rw-r--r--gm12u320_modeset.c150
10 files changed, 1917 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2e5f413
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+# Makefile for GM12U320-based devices driver
+
+ccflags-y := -Iinclude/drm
+
+gm12u320-y := gm12u320_drv.o gm12u320_modeset.o gm12u320_connector.o \
+ gm12u320_encoder.o gm12u320_main.o gm12u320_fb.o \
+ gm12u320_gem.o gm12u320_dmabuf.o
+
+obj-m += gm12u320.o
+
+all:
+ make -C /lib/modules/`uname -r`/build M=`pwd` modules
+
+modules_install:
+ make -C /lib/modules/`uname -r`/build M=`pwd` modules_install
+
+clean:
+ make -C /lib/modules/`uname -r`/build M=`pwd` clean
diff --git a/gm12u320_connector.c b/gm12u320_connector.c
new file mode 100644
index 0000000..48388d8
--- /dev/null
+++ b/gm12u320_connector.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_crtc_helper.h>
+#include "gm12u320_drv.h"
+
+/*
+ * Note this assumes this driver is only ever used with the Acer C120, if we
+ * add support for other devices the vendor and model should be parametrized.
+ */
+static struct edid gm12u320_edid = {
+ .header = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 },
+ .mfg_id = { 0x04, 0x72 }, /* "ACR" */
+ .prod_code = { 0x20, 0xc1 }, /* C120h */
+ .mfg_week = 1,
+ .mfg_year = 1,
+ .version = 1, /* EDID 1.3 */
+ .revision = 3, /* EDID 1.3 */
+ .input = 0x80, /* Digital input */
+ .features = 0x02, /* Pref timing in DTD 1 */
+ .standard_timings = { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 },
+ { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 } },
+ .detailed_timings = { {
+ .pixel_clock = 3383,
+ /* hactive = 852, hblank = 256 */
+ .data.pixel_data.hactive_lo = 0x54,
+ .data.pixel_data.hblank_lo = 0x00,
+ .data.pixel_data.hactive_hblank_hi = 0x31,
+ /* vactive = 480, vblank = 28 */
+ .data.pixel_data.vactive_lo = 0xe0,
+ .data.pixel_data.vblank_lo = 0x1c,
+ .data.pixel_data.vactive_vblank_hi = 0x10,
+ /* hsync offset 40 pw 128, vsync offset 1 pw 4 */
+ .data.pixel_data.hsync_offset_lo = 0x28,
+ .data.pixel_data.hsync_pulse_width_lo = 0x80,
+ .data.pixel_data.vsync_offset_pulse_width_lo = 0x14,
+ .data.pixel_data.hsync_vsync_offset_pulse_width_hi = 0x00,
+ /* Digital separate syncs, hsync+, vsync+ */
+ .data.pixel_data.misc = 0x1e,
+ }, {
+ .pixel_clock = 0,
+ .data.other_data.type = 0xfd, /* Monitor ranges */
+ .data.other_data.data.range.min_vfreq = 59,
+ .data.other_data.data.range.max_vfreq = 61,
+ .data.other_data.data.range.min_hfreq_khz = 29,
+ .data.other_data.data.range.max_hfreq_khz = 32,
+ .data.other_data.data.range.pixel_clock_mhz = 4, /* 40 MHz */
+ .data.other_data.data.range.flags = 0,
+ .data.other_data.data.range.formula.cvt =
+ { 0xa0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
+ }, {
+ .pixel_clock = 0,
+ .data.other_data.type = 0xfc, /* Model string */
+ .data.other_data.data.str.str =
+ { 'C', '1', '2', '0', 'P', 'r', 'o', 'j', 'e', 'c',
+ 't', 'o', 'r' },
+ }, {
+ .pixel_clock = 0,
+ .data.other_data.type = 0xfe, /* Unspecified text / padding */
+ .data.other_data.data.str.str =
+ { '\n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+ ' ', ' ', ' ' },
+ } },
+ .checksum = 0x40,
+};
+
+static int gm12u320_get_modes(struct drm_connector *connector)
+{
+ drm_mode_connector_update_edid_property(connector, &gm12u320_edid);
+ return drm_add_edid_modes(connector, &gm12u320_edid);
+}
+
+static enum drm_connector_status
+gm12u320_detect(struct drm_connector *connector, bool force)
+{
+ if (drm_device_is_unplugged(connector->dev))
+ return connector_status_disconnected;
+
+ return connector_status_connected;
+}
+
+static struct drm_encoder*
+gm12u320_best_single_encoder(struct drm_connector *connector)
+{
+ int enc_id = connector->encoder_ids[0];
+ return drm_encoder_find(connector->dev, enc_id);
+}
+
+static int gm12u320_connector_set_property(struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return 0;
+}
+
+static void gm12u320_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+ kfree(connector);
+}
+
+static struct drm_connector_helper_funcs gm12u320_connector_helper_funcs = {
+ .get_modes = gm12u320_get_modes,
+ .best_encoder = gm12u320_best_single_encoder,
+};
+
+static struct drm_connector_funcs gm12u320_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = gm12u320_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = gm12u320_connector_destroy,
+ .set_property = gm12u320_connector_set_property,
+};
+
+int gm12u320_connector_init(struct drm_device *dev,
+ struct drm_encoder *encoder)
+{
+ struct drm_connector *connector;
+
+ connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL);
+ if (!connector)
+ return -ENOMEM;
+
+ drm_connector_init(dev, connector, &gm12u320_connector_funcs,
+ DRM_MODE_CONNECTOR_Unknown);
+ drm_connector_helper_add(connector, &gm12u320_connector_helper_funcs);
+
+ drm_connector_register(connector);
+ drm_mode_connector_attach_encoder(connector, encoder);
+
+ drm_object_attach_property(&connector->base,
+ dev->mode_config.dirty_info_property, 1);
+
+ return 0;
+}
diff --git a/gm12u320_dmabuf.c b/gm12u320_dmabuf.c
new file mode 100644
index 0000000..788a817
--- /dev/null
+++ b/gm12u320_dmabuf.c
@@ -0,0 +1,283 @@
+/*
+ * Based on the udl dmabuf code:
+ *
+ * Copyright (c) 2014 The Chromium OS Authors
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <drm/drmP.h>
+#include "gm12u320_drv.h"
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+struct gm12u320_drm_dmabuf_attachment {
+ struct sg_table sgt;
+ enum dma_data_direction dir;
+ bool is_mapped;
+};
+
+static int gm12u320_attach_dma_buf(struct dma_buf *dmabuf,
+ struct device *dev,
+ struct dma_buf_attachment *attach)
+{
+ struct gm12u320_drm_dmabuf_attachment *gm12u320_attach;
+
+ DRM_DEBUG_PRIME("[DEV:%s] size:%zd\n", dev_name(attach->dev),
+ attach->dmabuf->size);
+
+ gm12u320_attach = kzalloc(sizeof(*gm12u320_attach), GFP_KERNEL);
+ if (!gm12u320_attach)
+ return -ENOMEM;
+
+ gm12u320_attach->dir = DMA_NONE;
+ attach->priv = gm12u320_attach;
+
+ return 0;
+}
+
+static void gm12u320_detach_dma_buf(struct dma_buf *dmabuf,
+ struct dma_buf_attachment *attach)
+{
+ struct gm12u320_drm_dmabuf_attachment *gm12u320_attach = attach->priv;
+ struct sg_table *sgt;
+
+ if (!gm12u320_attach)
+ return;
+
+ DRM_DEBUG_PRIME("[DEV:%s] size:%zd\n", dev_name(attach->dev),
+ attach->dmabuf->size);
+
+ sgt = &gm12u320_attach->sgt;
+
+ if (gm12u320_attach->dir != DMA_NONE)
+ dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents,
+ gm12u320_attach->dir);
+
+ sg_free_table(sgt);
+ kfree(gm12u320_attach);
+ attach->priv = NULL;
+}
+
+static struct sg_table *gm12u320_map_dma_buf(struct dma_buf_attachment *attach,
+ enum dma_data_direction dir)
+{
+ struct gm12u320_drm_dmabuf_attachment *gm12u320_attach = attach->priv;
+ struct gm12u320_gem_object *obj = to_gm12u320_bo(attach->dmabuf->priv);
+ struct drm_device *dev = obj->base.dev;
+ struct scatterlist *rd, *wr;
+ struct sg_table *sgt = NULL;
+ unsigned int i;
+ int page_count;
+ int nents, ret;
+
+ DRM_DEBUG_PRIME("[DEV:%s] size:%zd dir=%d\n", dev_name(attach->dev),
+ attach->dmabuf->size, dir);
+
+ /* just return current sgt if already requested. */
+ if (gm12u320_attach->dir == dir && gm12u320_attach->is_mapped)
+ return &gm12u320_attach->sgt;
+
+ if (!obj->pages) {
+ ret = gm12u320_gem_get_pages(obj);
+ if (ret) {
+ DRM_ERROR("failed to map pages.\n");
+ return ERR_PTR(ret);
+ }
+ }
+
+ page_count = obj->base.size / PAGE_SIZE;
+ obj->sg = drm_prime_pages_to_sg(obj->pages, page_count);
+ if (IS_ERR(obj->sg)) {
+ DRM_ERROR("failed to allocate sgt.\n");
+ return ERR_CAST(obj->sg);
+ }
+
+ sgt = &gm12u320_attach->sgt;
+
+ ret = sg_alloc_table(sgt, obj->sg->orig_nents, GFP_KERNEL);
+ if (ret) {
+ DRM_ERROR("failed to alloc sgt.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ mutex_lock(&dev->struct_mutex);
+
+ rd = obj->sg->sgl;
+ wr = sgt->sgl;
+ for (i = 0; i < sgt->orig_nents; ++i) {
+ sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
+ rd = sg_next(rd);
+ wr = sg_next(wr);
+ }
+
+ if (dir != DMA_NONE) {
+ nents = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir);
+ if (!nents) {
+ DRM_ERROR("failed to map sgl with iommu.\n");
+ sg_free_table(sgt);
+ sgt = ERR_PTR(-EIO);
+ goto err_unlock;
+ }
+ }
+
+ gm12u320_attach->is_mapped = true;
+ gm12u320_attach->dir = dir;
+ attach->priv = gm12u320_attach;
+
+err_unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return sgt;
+}
+
+static void gm12u320_unmap_dma_buf(struct dma_buf_attachment *attach,
+ struct sg_table *sgt,
+ enum dma_data_direction dir)
+{
+ /* Nothing to do. */
+ DRM_DEBUG_PRIME("[DEV:%s] size:%zd dir:%d\n", dev_name(attach->dev),
+ attach->dmabuf->size, dir);
+}
+
+static void *gm12u320_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num)
+{
+ /* TODO */
+
+ return NULL;
+}
+
+static void *gm12u320_dmabuf_kmap_atomic(struct dma_buf *dma_buf,
+ unsigned long page_num)
+{
+ /* TODO */
+
+ return NULL;
+}
+
+static void gm12u320_dmabuf_kunmap(struct dma_buf *dma_buf,
+ unsigned long page_num, void *addr)
+{
+ /* TODO */
+}
+
+static void gm12u320_dmabuf_kunmap_atomic(struct dma_buf *dma_buf,
+ unsigned long page_num,
+ void *addr)
+{
+ /* TODO */
+}
+
+static int gm12u320_dmabuf_mmap(struct dma_buf *dma_buf,
+ struct vm_area_struct *vma)
+{
+ /* TODO */
+
+ return -EINVAL;
+}
+
+static struct dma_buf_ops gm12u320_dmabuf_ops = {
+ .attach = gm12u320_attach_dma_buf,
+ .detach = gm12u320_detach_dma_buf,
+ .map_dma_buf = gm12u320_map_dma_buf,
+ .unmap_dma_buf = gm12u320_unmap_dma_buf,
+ .kmap = gm12u320_dmabuf_kmap,
+ .kmap_atomic = gm12u320_dmabuf_kmap_atomic,
+ .kunmap = gm12u320_dmabuf_kunmap,
+ .kunmap_atomic = gm12u320_dmabuf_kunmap_atomic,
+ .mmap = gm12u320_dmabuf_mmap,
+ .release = drm_gem_dmabuf_release,
+};
+
+struct dma_buf *gm12u320_gem_prime_export(struct drm_device *dev,
+ struct drm_gem_object *obj, int flags)
+{
+ DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+
+ exp_info.ops = &gm12u320_dmabuf_ops;
+ exp_info.size = obj->size;
+ exp_info.flags = flags;
+ exp_info.priv = obj;
+
+ return dma_buf_export(&exp_info);
+}
+
+static int gm12u320_prime_create(struct drm_device *dev,
+ size_t size,
+ struct sg_table *sg,
+ struct gm12u320_gem_object **obj_p)
+{
+ struct gm12u320_gem_object *obj;
+ int npages;
+
+ npages = size / PAGE_SIZE;
+
+ *obj_p = NULL;
+ obj = gm12u320_gem_alloc_object(dev, npages * PAGE_SIZE);
+ if (!obj)
+ return -ENOMEM;
+
+ obj->sg = sg;
+ obj->pages = drm_malloc_ab(npages, sizeof(struct page *));
+ if (obj->pages == NULL) {
+ DRM_ERROR("obj pages is NULL %d\n", npages);
+ return -ENOMEM;
+ }
+
+ drm_prime_sg_to_page_addr_arrays(sg, obj->pages, NULL, npages);
+
+ *obj_p = obj;
+ return 0;
+}
+
+struct drm_gem_object *gm12u320_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf)
+{
+ struct dma_buf_attachment *attach;
+ struct sg_table *sg;
+ struct gm12u320_gem_object *uobj;
+ int ret;
+
+ /* need to attach */
+ get_device(dev->dev);
+ attach = dma_buf_attach(dma_buf, dev->dev);
+ if (IS_ERR(attach)) {
+ put_device(dev->dev);
+ return ERR_CAST(attach);
+ }
+
+ get_dma_buf(dma_buf);
+
+ sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
+ if (IS_ERR(sg)) {
+ ret = PTR_ERR(sg);
+ goto fail_detach;
+ }
+
+ ret = gm12u320_prime_create(dev, dma_buf->size, sg, &uobj);
+ if (ret)
+ goto fail_unmap;
+
+ uobj->base.import_attach = attach;
+ uobj->flags = GM12U320_BO_WC;
+
+ return &uobj->base;
+
+fail_unmap:
+ dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL);
+fail_detach:
+ dma_buf_detach(dma_buf, attach);
+ dma_buf_put(dma_buf);
+ put_device(dev->dev);
+ return ERR_PTR(ret);
+}
diff --git a/gm12u320_drv.c b/gm12u320_drv.c
new file mode 100644
index 0000000..c2b3df0
--- /dev/null
+++ b/gm12u320_drv.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "gm12u320_drv.h"
+
+static int gm12u320_driver_set_busid(struct drm_device *d, struct drm_master *m)
+{
+ return 0;
+}
+
+static const struct vm_operations_struct gm12u320_gem_vm_ops = {
+ .fault = gm12u320_gem_fault,
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static const struct file_operations gm12u320_driver_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = gm12u320_drm_gem_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+ .release = drm_release,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .llseek = noop_llseek,
+};
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+ .load = gm12u320_driver_load,
+ .unload = gm12u320_driver_unload,
+ .set_busid = gm12u320_driver_set_busid,
+
+ /* gem hooks */
+ .gem_free_object = gm12u320_gem_free_object,
+ .gem_vm_ops = &gm12u320_gem_vm_ops,
+
+ .dumb_create = gm12u320_dumb_create,
+ .dumb_map_offset = gm12u320_gem_mmap,
+ .dumb_destroy = drm_gem_dumb_destroy,
+ .fops = &gm12u320_driver_fops,
+
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_export = gm12u320_gem_prime_export,
+ .gem_prime_import = gm12u320_gem_prime_import,
+
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+ .patchlevel = DRIVER_PATCHLEVEL,
+};
+
+static int gm12u320_usb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct drm_device *dev;
+ int r;
+
+ /*
+ * The gm12u320 presents itself to the system as 2 usb mass-storage
+ * interfaces, for the second one we proceed successully with binding,
+ * but otherwise ignore it.
+ */
+ if (interface->cur_altsetting->desc.bInterfaceNumber != 0)
+ return 0;
+
+ dev = drm_dev_alloc(&driver, &interface->dev);
+ if (!dev)
+ return -ENOMEM;
+
+ r = drm_dev_register(dev, (unsigned long)udev);
+ if (r)
+ goto err_free;
+
+ usb_set_intfdata(interface, dev);
+ DRM_INFO("Initialized gm12u320 on minor %d\n", dev->primary->index);
+
+ return 0;
+
+err_free:
+ drm_dev_unref(dev);
+ return r;
+}
+
+static void gm12u320_usb_disconnect(struct usb_interface *interface)
+{
+ struct drm_device *dev = usb_get_intfdata(interface);
+
+ if (!dev)
+ return;
+
+ drm_kms_helper_poll_disable(dev);
+ drm_connector_unplug_all(dev);
+ gm12u320_fbdev_unplug(dev);
+ drm_unplug_dev(dev);
+}
+
+static struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1de1, 0xc102) },
+ {},
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver gm12u320_driver = {
+ .name = "gm12u320",
+ .probe = gm12u320_usb_probe,
+ .disconnect = gm12u320_usb_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(gm12u320_driver);
+MODULE_LICENSE("GPL");
diff --git a/gm12u320_drv.h b/gm12u320_drv.h
new file mode 100644
index 0000000..f934e13
--- /dev/null
+++ b/gm12u320_drv.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#ifndef GM12U320_DRV_H
+#define GM12U320_DRV_H
+
+#include <linux/usb.h>
+#include <linux/spinlock.h>
+#include <drm/drm_gem.h>
+
+#define DRIVER_NAME "gm12u320"
+#define DRIVER_DESC "Grain Media GM12U320 USB projector display"
+#define DRIVER_DATE "20150107"
+
+#define DRIVER_MAJOR 0
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 1
+
+#define GM12U320_BO_CACHEABLE (1 << 0)
+#define GM12U320_BO_WC (1 << 1)
+
+/*
+ * The DLP has an actual width of 854 pixels, but that is not a multiple
+ * of 4, breaking things left and right, so we export a width of 852.
+ */
+#define GM12U320_USER_WIDTH 852
+#define GM12U320_REAL_WIDTH 854
+#define GM12U320_HEIGHT 480
+
+#define GM12U320_FRAME_COUNT 2
+#define GM12U320_BLOCK_COUNT 20
+
+struct gm12u320_device;
+
+struct gm12u320_fbdev;
+
+struct gm12u320_device {
+ struct device *dev;
+ struct usb_device *udev;
+ struct drm_device *ddev;
+ struct gm12u320_fbdev *fbdev;
+ unsigned char *cmd_buf;
+ unsigned char *data_buf[GM12U320_FRAME_COUNT][GM12U320_BLOCK_COUNT];
+ struct workqueue_struct *frame_workq;
+ struct work_struct frame_work;
+ wait_queue_head_t frame_waitq;
+ spinlock_t frame_lock;
+ int current_frame;
+ int next_frame;
+};
+
+struct gm12u320_gem_object {
+ struct drm_gem_object base;
+ struct page **pages;
+ void *vmapping;
+ struct sg_table *sg;
+ unsigned int flags;
+};
+
+#define to_gm12u320_bo(x) container_of(x, struct gm12u320_gem_object, base)
+
+struct gm12u320_framebuffer {
+ struct drm_framebuffer base;
+ struct gm12u320_gem_object *obj;
+};
+
+#define to_gm12u320_fb(x) container_of(x, struct gm12u320_framebuffer, base)
+
+/* modeset */
+int gm12u320_modeset_init(struct drm_device *dev);
+void gm12u320_modeset_cleanup(struct drm_device *dev);
+int gm12u320_connector_init(struct drm_device *dev, struct drm_encoder *encoder);
+
+struct drm_encoder *gm12u320_encoder_init(struct drm_device *dev);
+
+int gm12u320_driver_load(struct drm_device *dev, unsigned long flags);
+int gm12u320_driver_unload(struct drm_device *dev);
+
+int gm12u320_fbdev_init(struct drm_device *dev);
+void gm12u320_fbdev_cleanup(struct drm_device *dev);
+void gm12u320_fbdev_unplug(struct drm_device *dev);
+struct drm_framebuffer *
+gm12u320_fb_user_fb_create(struct drm_device *dev,
+ struct drm_file *file,
+ struct drm_mode_fb_cmd2 *mode_cmd);
+
+int gm12u320_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int gm12u320_gem_mmap(struct drm_file *file_priv, struct drm_device *dev,
+ uint32_t handle, uint64_t *offset);
+
+void gm12u320_gem_free_object(struct drm_gem_object *gem_obj);
+struct gm12u320_gem_object *gm12u320_gem_alloc_object(struct drm_device *dev,
+ size_t size);
+struct dma_buf *gm12u320_gem_prime_export(struct drm_device *dev,
+ struct drm_gem_object *obj, int flags);
+struct drm_gem_object *gm12u320_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf);
+
+int gm12u320_gem_get_pages(struct gm12u320_gem_object *obj);
+void gm12u320_gem_put_pages(struct gm12u320_gem_object *obj);
+int gm12u320_gem_vmap(struct gm12u320_gem_object *obj);
+void gm12u320_gem_vunmap(struct gm12u320_gem_object *obj);
+int gm12u320_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+int gm12u320_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
+
+void gm12u320_update_frame(struct gm12u320_framebuffer *fb);
+int gm12u320_handle_damage(struct gm12u320_framebuffer *fb, int x, int y,
+ int width, int height);
+
+#endif
diff --git a/gm12u320_encoder.c b/gm12u320_encoder.c
new file mode 100644
index 0000000..df1f8ea
--- /dev/null
+++ b/gm12u320_encoder.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include "gm12u320_drv.h"
+
+/* dummy encoder */
+static void gm12u320_enc_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+}
+
+static void gm12u320_encoder_disable(struct drm_encoder *encoder)
+{
+}
+
+static bool gm12u320_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void gm12u320_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void gm12u320_encoder_commit(struct drm_encoder *encoder)
+{
+}
+
+static void gm12u320_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void
+gm12u320_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static const struct drm_encoder_helper_funcs gm12u320_helper_funcs = {
+ .dpms = gm12u320_encoder_dpms,
+ .mode_fixup = gm12u320_mode_fixup,
+ .prepare = gm12u320_encoder_prepare,
+ .mode_set = gm12u320_encoder_mode_set,
+ .commit = gm12u320_encoder_commit,
+ .disable = gm12u320_encoder_disable,
+};
+
+static const struct drm_encoder_funcs gm12u320_enc_funcs = {
+ .destroy = gm12u320_enc_destroy,
+};
+
+struct drm_encoder *gm12u320_encoder_init(struct drm_device *dev)
+{
+ struct drm_encoder *encoder;
+
+ encoder = kzalloc(sizeof(struct drm_encoder), GFP_KERNEL);
+ if (!encoder)
+ return NULL;
+
+ drm_encoder_init(dev, encoder, &gm12u320_enc_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ drm_encoder_helper_add(encoder, &gm12u320_helper_funcs);
+ encoder->possible_crtcs = 1;
+ return encoder;
+}
diff --git a/gm12u320_fb.c b/gm12u320_fb.c
new file mode 100644
index 0000000..dd862d2
--- /dev/null
+++ b/gm12u320_fb.c
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include "gm12u320_drv.h"
+
+#include <drm/drm_fb_helper.h>
+
+struct gm12u320_fbdev {
+ struct drm_fb_helper helper;
+ struct gm12u320_framebuffer fb;
+ struct fb_deferred_io defio;
+ struct list_head fbdev_list;
+};
+
+static void gm12u320_defio_cb(struct fb_info *info, struct list_head *pl)
+{
+ struct gm12u320_fbdev *fbdev = info->par;
+
+ gm12u320_update_frame(&fbdev->fb);
+}
+
+static int gm12u320_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ unsigned long start = vma->vm_start;
+ unsigned long size = vma->vm_end - vma->vm_start;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ unsigned long page, pos;
+
+ if (offset + size > info->fix.smem_len)
+ return -EINVAL;
+
+ pos = (unsigned long)info->fix.smem_start + offset;
+
+ pr_notice("mmap() framebuffer addr:%lu size:%lu\n",
+ pos, size);
+
+ while (size > 0) {
+ page = vmalloc_to_pfn((void *)pos);
+ if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
+ return -EAGAIN;
+
+ start += PAGE_SIZE;
+ pos += PAGE_SIZE;
+ if (size > PAGE_SIZE)
+ size -= PAGE_SIZE;
+ else
+ size = 0;
+ }
+
+ /* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */
+ return 0;
+}
+
+static int gm12u320_fb_open(struct fb_info *info, int user)
+{
+ struct gm12u320_fbdev *fbdev = info->par;
+ struct drm_device *ddev = fbdev->fb.base.dev;
+
+ /* If the USB device is gone, we don't accept new opens */
+ if (drm_device_is_unplugged(ddev))
+ return -ENODEV;
+
+ return 0;
+}
+
+static int gm12u320_fb_release(struct fb_info *info, int user)
+{
+ return 0;
+}
+
+static struct fb_ops gm12u320fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_setcmap = drm_fb_helper_setcmap,
+ .fb_debug_enter = drm_fb_helper_debug_enter,
+ .fb_debug_leave = drm_fb_helper_debug_leave,
+ .fb_mmap = gm12u320_fb_mmap,
+ .fb_open = gm12u320_fb_open,
+ .fb_release = gm12u320_fb_release,
+};
+
+static int gm12u320_user_framebuffer_dirty(struct drm_framebuffer *drm_fb,
+ struct drm_file *file,
+ unsigned flags, unsigned color,
+ struct drm_clip_rect *clips,
+ unsigned num_clips)
+{
+ struct gm12u320_framebuffer *fb = to_gm12u320_fb(drm_fb);
+ int ret = 0;
+
+ drm_modeset_lock_all(drm_fb->dev);
+
+ if (fb->obj->base.import_attach) {
+ ret = dma_buf_begin_cpu_access(
+ fb->obj->base.import_attach->dmabuf, 0,
+ fb->obj->base.size, DMA_FROM_DEVICE);
+ if (ret)
+ goto unlock;
+ }
+
+ gm12u320_update_frame(fb);
+
+ if (fb->obj->base.import_attach) {
+ dma_buf_end_cpu_access(fb->obj->base.import_attach->dmabuf, 0,
+ fb->obj->base.size, DMA_FROM_DEVICE);
+ }
+
+ unlock:
+ drm_modeset_unlock_all(drm_fb->dev);
+
+ return ret;
+}
+
+static void gm12u320_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
+{
+ struct gm12u320_framebuffer *fb = to_gm12u320_fb(drm_fb);
+
+ if (fb->obj)
+ drm_gem_object_unreference_unlocked(&fb->obj->base);
+
+ drm_framebuffer_cleanup(drm_fb);
+ kfree(fb);
+}
+
+static const struct drm_framebuffer_funcs gm12u320fb_funcs = {
+ .destroy = gm12u320_user_framebuffer_destroy,
+ .dirty = gm12u320_user_framebuffer_dirty,
+};
+
+
+static int
+gm12u320_framebuffer_init(struct drm_device *dev,
+ struct gm12u320_framebuffer *fb,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct gm12u320_gem_object *obj)
+{
+ int ret;
+
+ fb->obj = obj;
+ drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd);
+ ret = drm_framebuffer_init(dev, &fb->base, &gm12u320fb_funcs);
+ return ret;
+}
+
+
+static int gm12u320fb_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct gm12u320_fbdev *fbdev =
+ container_of(helper, struct gm12u320_fbdev, helper);
+ struct drm_device *dev = fbdev->helper.dev;
+ struct fb_info *info;
+ struct device *device = dev->dev;
+ struct drm_framebuffer *drm_fb;
+ struct drm_mode_fb_cmd2 mode_cmd;
+ struct gm12u320_gem_object *obj;
+ uint32_t size;
+ int ret = 0;
+
+ if (sizes->surface_bpp == 24)
+ sizes->surface_bpp = 32;
+
+ 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;
+ size = ALIGN(size, PAGE_SIZE);
+
+ obj = gm12u320_gem_alloc_object(dev, size);
+ if (!obj)
+ goto out;
+
+ ret = gm12u320_gem_vmap(obj);
+ if (ret) {
+ DRM_ERROR("failed to vmap fb\n");
+ goto out_gfree;
+ }
+
+ info = framebuffer_alloc(0, device);
+ if (!info) {
+ ret = -ENOMEM;
+ goto out_gfree;
+ }
+ info->par = fbdev;
+
+ ret = gm12u320_framebuffer_init(dev, &fbdev->fb, &mode_cmd, obj);
+ if (ret)
+ goto out_gfree;
+
+ drm_fb = &fbdev->fb.base;
+
+ fbdev->helper.fb = drm_fb;
+ fbdev->helper.fbdev = info;
+
+ strcpy(info->fix.id, "gm12u320drmfb");
+
+ info->screen_base = fbdev->fb.obj->vmapping;
+ info->fix.smem_len = size;
+ info->fix.smem_start = (unsigned long)fbdev->fb.obj->vmapping;
+
+ info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+ info->fbops = &gm12u320fb_ops;
+ info->fbdefio = &fbdev->defio;
+ drm_fb_helper_fill_fix(info, drm_fb->pitches[0], drm_fb->depth);
+ drm_fb_helper_fill_var(info, &fbdev->helper, sizes->fb_width, sizes->fb_height);
+ fbdev->defio.delay = msecs_to_jiffies(20);
+ fbdev->defio.deferred_io = gm12u320_defio_cb;
+ fb_deferred_io_init(info);
+
+ ret = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (ret) {
+ ret = -ENOMEM;
+ goto out_gfree;
+ }
+
+ DRM_DEBUG_KMS("allocated %dx%d vmal %p\n",
+ drm_fb->width, drm_fb->height,
+ fbdev->fb.obj->vmapping);
+
+ return ret;
+out_gfree:
+ drm_gem_object_unreference(&fbdev->fb.obj->base);
+out:
+ return ret;
+}
+
+static const struct drm_fb_helper_funcs gm12u320_fb_helper_funcs = {
+ .fb_probe = gm12u320fb_create,
+};
+
+static void gm12u320_fbdev_destroy(struct drm_device *dev,
+ struct gm12u320_fbdev *fbdev)
+{
+ struct fb_info *info;
+ if (fbdev->helper.fbdev) {
+ info = fbdev->helper.fbdev;
+ fb_deferred_io_cleanup(info);
+ unregister_framebuffer(info);
+ if (info->cmap.len)
+ fb_dealloc_cmap(&info->cmap);
+ framebuffer_release(info);
+ }
+ drm_fb_helper_fini(&fbdev->helper);
+ drm_framebuffer_unregister_private(&fbdev->fb.base);
+ drm_framebuffer_cleanup(&fbdev->fb.base);
+ drm_gem_object_unreference_unlocked(&fbdev->fb.obj->base);
+}
+
+int gm12u320_fbdev_init(struct drm_device *dev)
+{
+ struct gm12u320_device *gm12u320 = dev->dev_private;
+ struct gm12u320_fbdev *fbdev;
+ int ret;
+
+ fbdev = kzalloc(sizeof(struct gm12u320_fbdev), GFP_KERNEL);
+ if (!fbdev)
+ return -ENOMEM;
+
+ gm12u320->fbdev = fbdev;
+
+ drm_fb_helper_prepare(dev, &fbdev->helper, &gm12u320_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, &fbdev->helper, 1, 1);
+ if (ret)
+ goto free;
+
+ ret = drm_fb_helper_single_add_all_connectors(&fbdev->helper);
+ if (ret)
+ goto fini;
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+ ret = drm_fb_helper_initial_config(&fbdev->helper, 32);
+ if (ret)
+ goto fini;
+
+ return 0;
+
+fini:
+ drm_fb_helper_fini(&fbdev->helper);
+free:
+ kfree(fbdev);
+ return ret;
+}
+
+void gm12u320_fbdev_cleanup(struct drm_device *dev)
+{
+ struct gm12u320_device *gm12u320 = dev->dev_private;
+ if (!gm12u320->fbdev)
+ return;
+
+ gm12u320_fbdev_destroy(dev, gm12u320->fbdev);
+ kfree(gm12u320->fbdev);
+ gm12u320->fbdev = NULL;
+}
+
+void gm12u320_fbdev_unplug(struct drm_device *dev)
+{
+ struct gm12u320_device *gm12u320 = dev->dev_private;
+ struct gm12u320_fbdev *fbdev;
+
+ if (!gm12u320->fbdev)
+ return;
+
+ fbdev = gm12u320->fbdev;
+ if (fbdev->helper.fbdev) {
+ struct fb_info *info;
+ info = fbdev->helper.fbdev;
+ unlink_framebuffer(info);
+ }
+}
+
+struct drm_framebuffer *
+gm12u320_fb_user_fb_create(struct drm_device *dev,
+ struct drm_file *file,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct drm_gem_object *obj;
+ struct gm12u320_framebuffer *fb;
+ int ret;
+ uint32_t size;
+
+ obj = drm_gem_object_lookup(dev, file, mode_cmd->handles[0]);
+ if (obj == NULL)
+ return ERR_PTR(-ENOENT);
+
+ size = mode_cmd->pitches[0] * mode_cmd->height;
+ size = ALIGN(size, PAGE_SIZE);
+
+ if (size > obj->size) {
+ DRM_ERROR("object size not sufficient for fb %d %zu %d %d\n", size, obj->size, mode_cmd->pitches[0], mode_cmd->height);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (fb == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ ret = gm12u320_framebuffer_init(dev, fb, mode_cmd, to_gm12u320_bo(obj));
+ if (ret) {
+ kfree(fb);
+ return ERR_PTR(-EINVAL);
+ }
+ return &fb->base;
+}
diff --git a/gm12u320_gem.c b/gm12u320_gem.c
new file mode 100644
index 0000000..7af30e2
--- /dev/null
+++ b/gm12u320_gem.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <drm/drmP.h>
+#include "gm12u320_drv.h"
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+struct gm12u320_gem_object *gm12u320_gem_alloc_object(struct drm_device *dev,
+ size_t size)
+{
+ struct gm12u320_gem_object *obj;
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (obj == NULL)
+ return NULL;
+
+ if (drm_gem_object_init(dev, &obj->base, size) != 0) {
+ kfree(obj);
+ return NULL;
+ }
+
+ obj->flags = GM12U320_BO_CACHEABLE;
+ return obj;
+}
+
+static int
+gm12u320_gem_create(struct drm_file *file,
+ struct drm_device *dev,
+ uint64_t size,
+ uint32_t *handle_p)
+{
+ struct gm12u320_gem_object *obj;
+ int ret;
+ u32 handle;
+
+ size = roundup(size, PAGE_SIZE);
+
+ obj = gm12u320_gem_alloc_object(dev, size);
+ if (obj == NULL)
+ return -ENOMEM;
+
+ ret = drm_gem_handle_create(file, &obj->base, &handle);
+ if (ret) {
+ drm_gem_object_release(&obj->base);
+ kfree(obj);
+ return ret;
+ }
+
+ drm_gem_object_unreference(&obj->base);
+ *handle_p = handle;
+ return 0;
+}
+
+static void update_vm_cache_attr(struct gm12u320_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ DRM_DEBUG_KMS("flags = 0x%x\n", obj->flags);
+
+ /* non-cacheable as default. */
+ if (obj->flags & GM12U320_BO_CACHEABLE) {
+ vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+ } else if (obj->flags & GM12U320_BO_WC) {
+ vma->vm_page_prot =
+ pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+ } else {
+ vma->vm_page_prot =
+ pgprot_noncached(vm_get_page_prot(vma->vm_flags));
+ }
+}
+
+int gm12u320_dumb_create(struct drm_file *file,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ args->pitch = args->width * DIV_ROUND_UP(args->bpp, 8);
+ args->size = args->pitch * args->height;
+ return gm12u320_gem_create(file, dev,
+ args->size, &args->handle);
+}
+
+int gm12u320_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_flags |= VM_MIXEDMAP;
+
+ update_vm_cache_attr(to_gm12u320_bo(vma->vm_private_data), vma);
+
+ return ret;
+}
+
+int gm12u320_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct gm12u320_gem_object *obj = to_gm12u320_bo(vma->vm_private_data);
+ struct page *page;
+ unsigned int page_offset;
+ int ret = 0;
+
+ page_offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
+ PAGE_SHIFT;
+
+ if (!obj->pages)
+ return VM_FAULT_SIGBUS;
+
+ page = obj->pages[page_offset];
+ ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address, page);
+ switch (ret) {
+ case -EAGAIN:
+ case 0:
+ case -ERESTARTSYS:
+ return VM_FAULT_NOPAGE;
+ case -ENOMEM:
+ return VM_FAULT_OOM;
+ default:
+ return VM_FAULT_SIGBUS;
+ }
+}
+
+int gm12u320_gem_get_pages(struct gm12u320_gem_object *obj)
+{
+ struct page **pages;
+
+ if (obj->pages)
+ return 0;
+
+ pages = drm_gem_get_pages(&obj->base);
+ if (IS_ERR(pages))
+ return PTR_ERR(pages);
+
+ obj->pages = pages;
+
+ return 0;
+}
+
+void gm12u320_gem_put_pages(struct gm12u320_gem_object *obj)
+{
+ if (obj->base.import_attach) {
+ drm_free_large(obj->pages);
+ obj->pages = NULL;
+ return;
+ }
+
+ drm_gem_put_pages(&obj->base, obj->pages, false, false);
+ obj->pages = NULL;
+}
+
+int gm12u320_gem_vmap(struct gm12u320_gem_object *obj)
+{
+ int page_count = obj->base.size / PAGE_SIZE;
+ int ret;
+
+ if (obj->base.import_attach) {
+ obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf);
+ if (!obj->vmapping)
+ return -ENOMEM;
+ return 0;
+ }
+
+ ret = gm12u320_gem_get_pages(obj);
+ if (ret)
+ return ret;
+
+ obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
+ if (!obj->vmapping)
+ return -ENOMEM;
+ return 0;
+}
+
+void gm12u320_gem_vunmap(struct gm12u320_gem_object *obj)
+{
+ if (obj->base.import_attach) {
+ dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping);
+ return;
+ }
+
+ vunmap(obj->vmapping);
+
+ gm12u320_gem_put_pages(obj);
+}
+
+void gm12u320_gem_free_object(struct drm_gem_object *gem_obj)
+{
+ struct gm12u320_gem_object *obj = to_gm12u320_bo(gem_obj);
+
+ if (obj->vmapping)
+ gm12u320_gem_vunmap(obj);
+
+ if (gem_obj->import_attach) {
+ drm_prime_gem_destroy(gem_obj, obj->sg);
+ put_device(gem_obj->dev->dev);
+ }
+
+ if (obj->pages)
+ gm12u320_gem_put_pages(obj);
+
+ drm_gem_free_mmap_offset(gem_obj);
+}
+
+/* the dumb interface doesn't work with the GEM straight MMAP
+ interface, it expects to do MMAP on the drm fd, like normal */
+int gm12u320_gem_mmap(struct drm_file *file, struct drm_device *dev,
+ uint32_t handle, uint64_t *offset)
+{
+ struct gm12u320_gem_object *gobj;
+ struct drm_gem_object *obj;
+ int ret = 0;
+
+ mutex_lock(&dev->struct_mutex);
+ obj = drm_gem_object_lookup(dev, file, handle);
+ if (obj == NULL) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+ gobj = to_gm12u320_bo(obj);
+
+ ret = gm12u320_gem_get_pages(gobj);
+ if (ret)
+ goto out;
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret)
+ goto out;
+
+ *offset = drm_vma_node_offset_addr(&gobj->base.vma_node);
+
+out:
+ drm_gem_object_unreference(&gobj->base);
+unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return ret;
+}
diff --git a/gm12u320_main.c b/gm12u320_main.c
new file mode 100644
index 0000000..804ca55
--- /dev/null
+++ b/gm12u320_main.c
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+#include <drm/drmP.h>
+#include "gm12u320_drv.h"
+
+#define DATA_RCV_EPT 2
+#define DATA_SND_EPT 3
+
+#define DATA_BLOCK_HEADER_SIZE 84
+#define DATA_BLOCK_CONTENT_SIZE 64512
+#define DATA_BLOCK_FOOTER_SIZE 20
+#define DATA_BLOCK_SIZE DATA_BLOCK_HEADER_SIZE + \
+ DATA_BLOCK_CONTENT_SIZE + \
+ DATA_BLOCK_FOOTER_SIZE
+#define DATA_LAST_BLOCK_CONTENT_SIZE 4032
+#define DATA_LAST_BLOCK_SIZE DATA_BLOCK_HEADER_SIZE + \
+ DATA_LAST_BLOCK_CONTENT_SIZE + \
+ DATA_BLOCK_FOOTER_SIZE
+
+#define CMD_SIZE 31
+#define READ_BLOCK_SIZE 13
+
+#define CMD_TIMEOUT msecs_to_jiffies(200)
+#define DATA_TIMEOUT msecs_to_jiffies(1000)
+#define IDLE_TIMEOUT msecs_to_jiffies(2000)
+#define FIRST_FRAME_TIMEOUT msecs_to_jiffies(2000)
+
+static const char cmd_data[CMD_SIZE] =
+ {0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x68, 0xfc, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0xff, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const char cmd_draw[CMD_SIZE] =
+ {0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0xd1, 0x05, 0x00, 0x40,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static const char data_block_header[DATA_BLOCK_HEADER_SIZE] =
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x15, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x01, 0x00, 0x00, 0xdb };
+
+static const char data_last_block_header[DATA_BLOCK_HEADER_SIZE] =
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x20, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x01, 0x00, 0x00, 0xd7 };
+
+static const char data_block_footer[DATA_BLOCK_FOOTER_SIZE] =
+ { 0xfb, 0x14, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x4f };
+
+static const char bl_get_set_brightness[CMD_SIZE] =
+ {0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x10, 0xfd, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x35, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static int gm12u320_usb_alloc(struct gm12u320_device *gm12u320)
+{
+ int i, k;
+ int block_size;
+ const char *header;
+
+ gm12u320->cmd_buf = kmalloc(CMD_SIZE, GFP_KERNEL);
+ if (!gm12u320->cmd_buf)
+ return -ENOMEM;
+
+ for (k = 0; k < GM12U320_FRAME_COUNT; k++) {
+ for (i = 0; i < GM12U320_BLOCK_COUNT; i++) {
+ if (i == GM12U320_BLOCK_COUNT - 1) {
+ block_size = DATA_LAST_BLOCK_SIZE;
+ header = data_last_block_header;
+ } else {
+ block_size = DATA_BLOCK_SIZE;
+ header = data_block_header;
+ }
+
+ gm12u320->data_buf[k][i] = kzalloc(block_size,
+ GFP_KERNEL);
+ if (!gm12u320->data_buf[k][i])
+ return -ENOMEM;
+
+ memcpy(gm12u320->data_buf[k][i], header,
+ DATA_BLOCK_HEADER_SIZE);
+ memcpy(gm12u320->data_buf[k][i] +
+ (block_size - DATA_BLOCK_FOOTER_SIZE),
+ data_block_footer, DATA_BLOCK_FOOTER_SIZE);
+ }
+ }
+ return 0;
+}
+
+static void gm12u320_usb_free(struct gm12u320_device *gm12u320)
+{
+ int i, k;
+
+ for (k = 0; k < GM12U320_FRAME_COUNT; k++)
+ for (i = 0; i < GM12U320_BLOCK_COUNT; i++)
+ kfree(gm12u320->data_buf[k][i]);
+
+ kfree(gm12u320->cmd_buf);
+}
+
+void gm12u320_32bpp_to_24bpp_packed(u8 *dst, u8 *src, int len)
+{
+ while (len--) {
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ src++;
+ }
+}
+
+void gm12u320_update_frame(struct gm12u320_framebuffer *fb)
+{
+ struct drm_device *dev = fb->base.dev;
+ struct gm12u320_device *gm12u320 = dev->dev_private;
+ int block, dst_offset, frame, len, remain, ret;
+ unsigned long flags;
+ u8 *src;
+ int x1 = 0;
+ int x2 = GM12U320_USER_WIDTH;
+ int y1 = 0;
+ int y2 = GM12U320_HEIGHT;
+
+ if (!fb->obj->vmapping) {
+ ret = gm12u320_gem_vmap(fb->obj);
+ if (ret == -ENOMEM) {
+ DRM_ERROR("failed to vmap fb\n");
+ return;
+ }
+ if (!fb->obj->vmapping) {
+ DRM_ERROR("failed to vmapping\n");
+ return;
+ }
+ }
+
+ spin_lock_irqsave(&gm12u320->frame_lock, flags);
+ frame = !gm12u320->current_frame;
+ spin_unlock_irqrestore(&gm12u320->frame_lock, flags);
+
+ x1 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
+ x2 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
+
+ src = fb->obj->vmapping;
+ for (; y1 < y2; y1++) {
+ remain = 0;
+ len = (x2 - x1) * 3;
+ dst_offset = (y1 * GM12U320_REAL_WIDTH + x1) * 3;
+ block = dst_offset / DATA_BLOCK_CONTENT_SIZE;
+ dst_offset %= DATA_BLOCK_CONTENT_SIZE;
+
+ if ((dst_offset + len) > DATA_BLOCK_CONTENT_SIZE) {
+ remain = dst_offset + len - DATA_BLOCK_CONTENT_SIZE;
+ len = DATA_BLOCK_CONTENT_SIZE - dst_offset;
+ }
+
+ dst_offset += DATA_BLOCK_HEADER_SIZE;
+ len /= 3;
+
+ gm12u320_32bpp_to_24bpp_packed(
+ gm12u320->data_buf[frame][block] + dst_offset,
+ src, len);
+
+ if (remain) {
+ block++;
+ dst_offset = DATA_BLOCK_HEADER_SIZE;
+ gm12u320_32bpp_to_24bpp_packed(
+ gm12u320->data_buf[frame][block] + dst_offset,
+ src + len * 4, remain / 3);
+ }
+ src += fb->base.pitches[0];
+ }
+
+ spin_lock_irqsave(&gm12u320->frame_lock, flags);
+ gm12u320->next_frame = frame;
+ spin_unlock_irqrestore(&gm12u320->frame_lock, flags);
+
+ wake_up(&gm12u320->frame_waitq);
+}
+
+static int gm12u320_frame_ready(struct gm12u320_device *gm12u320)
+{
+ int ret;
+
+ spin_lock(&gm12u320->frame_lock);
+ ret = gm12u320->next_frame != gm12u320->current_frame;
+ spin_unlock(&gm12u320->frame_lock);
+
+ return ret;
+}
+
+static void gm12u320_frame_work(struct work_struct *work)
+{
+ struct gm12u320_device *gm12u320 =
+ container_of(work, struct gm12u320_device, frame_work);
+ int draw_status_timeout = FIRST_FRAME_TIMEOUT;
+ int block, block_size, frame, len, ret;
+
+ while (1) {
+ /*
+ * We must draw a frame every 2s otherwise the projector
+ * switches back to showing its logo.
+ */
+ wait_event_timeout(gm12u320->frame_waitq,
+ gm12u320_frame_ready(gm12u320),
+ IDLE_TIMEOUT);
+
+ spin_lock(&gm12u320->frame_lock);
+ frame = gm12u320->current_frame = gm12u320->next_frame;
+ spin_unlock(&gm12u320->frame_lock);
+
+ for (block = 0; block < GM12U320_BLOCK_COUNT; block++) {
+ if (block == GM12U320_BLOCK_COUNT - 1)
+ block_size = DATA_LAST_BLOCK_SIZE;
+ else
+ block_size = DATA_BLOCK_SIZE;
+
+ /* Send data command to device */
+ memcpy(gm12u320->cmd_buf, cmd_data, CMD_SIZE);
+ gm12u320->cmd_buf[8] = block_size & 0xff;
+ gm12u320->cmd_buf[9] = block_size >> 8;
+ gm12u320->cmd_buf[20] = 0xfc - block * 4;
+ gm12u320->cmd_buf[21] = block | (frame << 7);
+
+ ret = usb_bulk_msg(gm12u320->udev,
+ usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+ gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
+ if (ret || len != CMD_SIZE)
+ goto err;
+
+ /* Send data block to device */
+ ret = usb_bulk_msg(gm12u320->udev,
+ usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+ gm12u320->data_buf[frame][block], block_size,
+ &len, DATA_TIMEOUT);
+ if (ret || len != block_size)
+ goto err;
+
+ /* Read status */
+ ret = usb_bulk_msg(gm12u320->udev,
+ usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
+ gm12u320->cmd_buf, READ_BLOCK_SIZE, &len,
+ CMD_TIMEOUT);
+ if (ret || len != READ_BLOCK_SIZE)
+ goto err;
+ }
+
+ /* Send draw command to device */
+ memcpy(gm12u320->cmd_buf, cmd_draw, CMD_SIZE);
+ ret = usb_bulk_msg(gm12u320->udev,
+ usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+ gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
+ if (ret || len != CMD_SIZE)
+ goto err;
+
+ /* Read status */
+ ret = usb_bulk_msg(gm12u320->udev,
+ usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
+ gm12u320->cmd_buf, READ_BLOCK_SIZE, &len,
+ draw_status_timeout);
+ if (ret || len != READ_BLOCK_SIZE)
+ goto err;
+
+ draw_status_timeout = CMD_TIMEOUT;
+ }
+err:
+ /* Do not log errors caused by module unload or device unplug */
+ if (ret != -ENOENT && ret != -ECONNRESET && ret != -ESHUTDOWN &&
+ ret != -ENODEV)
+ dev_err(&gm12u320->udev->dev, "Frame update error: %d\n", ret);
+}
+
+int gm12u320_driver_load(struct drm_device *dev, unsigned long flags)
+{
+ struct usb_device *udev = (void*)flags;
+ struct gm12u320_device *gm12u320;
+ int ret = -ENOMEM;
+
+ DRM_DEBUG("\n");
+ gm12u320 = kzalloc(sizeof(struct gm12u320_device), GFP_KERNEL);
+ if (!gm12u320)
+ return -ENOMEM;
+
+ gm12u320->udev = udev;
+ gm12u320->ddev = dev;
+ dev->dev_private = gm12u320;
+
+ INIT_WORK(&gm12u320->frame_work, gm12u320_frame_work);
+ spin_lock_init(&gm12u320->frame_lock);
+ init_waitqueue_head(&gm12u320->frame_waitq);
+
+ /*
+ * These are deliberately different so that we send out an empty
+ * screen to replace the projector logo immediately.
+ */
+ gm12u320->current_frame = 1;
+ gm12u320->next_frame = 0;
+
+ ret = gm12u320_usb_alloc(gm12u320);
+ if (ret)
+ goto err;
+
+ gm12u320->frame_workq = create_singlethread_workqueue(DRIVER_NAME);
+ if (!gm12u320->frame_workq) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ DRM_DEBUG("\n");
+ ret = gm12u320_modeset_init(dev);
+ if (ret)
+ goto err;
+
+ ret = gm12u320_fbdev_init(dev);
+ if (ret)
+ goto err;
+
+ ret = drm_vblank_init(dev, 1);
+ if (ret)
+ goto err_fb;
+
+ queue_work(gm12u320->frame_workq, &gm12u320->frame_work);
+
+ return 0;
+err_fb:
+ gm12u320_fbdev_cleanup(dev);
+err:
+ gm12u320_usb_free(gm12u320);
+ kfree(gm12u320);
+ DRM_ERROR("%d\n", ret);
+ return ret;
+}
+
+int gm12u320_driver_unload(struct drm_device *dev)
+{
+ struct gm12u320_device *gm12u320 = dev->dev_private;
+
+ /* Wake up frame_work, so that it sees the disconnect and exits */
+ wake_up(&gm12u320->frame_waitq);
+ cancel_work_sync(&gm12u320->frame_work);
+ destroy_workqueue(gm12u320->frame_workq);
+ gm12u320_usb_free(gm12u320);
+
+ drm_vblank_cleanup(dev);
+ gm12u320_fbdev_cleanup(dev);
+ gm12u320_modeset_cleanup(dev);
+ kfree(gm12u320);
+ return 0;
+}
diff --git a/gm12u320_modeset.c b/gm12u320_modeset.c
new file mode 100644
index 0000000..6fbd6e2
--- /dev/null
+++ b/gm12u320_modeset.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012-2015 Red Hat Inc.
+ *
+ * Based in parts on the udl code. Based in parts on the gm12u320 fb driver:
+ * Copyright (C) 2013 Viacheslav Nurmekhamitov <slavrn@yandex.ru>
+ * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
+ * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane_helper.h>
+#include "gm12u320_drv.h"
+
+static void gm12u320_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+}
+
+static bool gm12u320_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int gm12u320_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *old_fb)
+
+{
+ struct gm12u320_framebuffer *fb = to_gm12u320_fb(crtc->primary->fb);
+
+ gm12u320_update_frame(fb);
+ return 0;
+}
+
+
+static void gm12u320_crtc_disable(struct drm_crtc *crtc)
+{
+}
+
+static void gm12u320_crtc_destroy(struct drm_crtc *crtc)
+{
+ drm_crtc_cleanup(crtc);
+ kfree(crtc);
+}
+
+static int gm12u320_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *drm_fb,
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags)
+{
+ struct gm12u320_framebuffer *fb = to_gm12u320_fb(drm_fb);
+ struct drm_device *dev = crtc->dev;
+ unsigned long flags;
+
+ gm12u320_update_frame(fb);
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ if (event)
+ drm_send_vblank_event(dev, 0, event);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ crtc->primary->fb = drm_fb;
+
+ return 0;
+}
+
+static void gm12u320_crtc_prepare(struct drm_crtc *crtc)
+{
+}
+
+static void gm12u320_crtc_commit(struct drm_crtc *crtc)
+{
+ gm12u320_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static struct drm_crtc_helper_funcs gm12u320_helper_funcs = {
+ .dpms = gm12u320_crtc_dpms,
+ .mode_fixup = gm12u320_crtc_mode_fixup,
+ .mode_set = gm12u320_crtc_mode_set,
+ .prepare = gm12u320_crtc_prepare,
+ .commit = gm12u320_crtc_commit,
+ .disable = gm12u320_crtc_disable,
+};
+
+static const struct drm_crtc_funcs gm12u320_crtc_funcs = {
+ .set_config = drm_crtc_helper_set_config,
+ .destroy = gm12u320_crtc_destroy,
+ .page_flip = gm12u320_crtc_page_flip,
+};
+
+static int gm12u320_crtc_init(struct drm_device *dev)
+{
+ struct drm_crtc *crtc;
+
+ crtc = kzalloc(sizeof(struct drm_crtc) + sizeof(struct drm_connector *), GFP_KERNEL);
+ if (crtc == NULL)
+ return -ENOMEM;
+
+ drm_crtc_init(dev, crtc, &gm12u320_crtc_funcs);
+ drm_crtc_helper_add(crtc, &gm12u320_helper_funcs);
+
+ return 0;
+}
+
+static const struct drm_mode_config_funcs gm12u320_mode_funcs = {
+ .fb_create = gm12u320_fb_user_fb_create,
+ .output_poll_changed = NULL,
+};
+
+int gm12u320_modeset_init(struct drm_device *dev)
+{
+ struct drm_encoder *encoder;
+ drm_mode_config_init(dev);
+
+ dev->mode_config.min_width = GM12U320_USER_WIDTH;
+ dev->mode_config.min_height = GM12U320_HEIGHT;
+
+ dev->mode_config.max_width = 2048;
+ dev->mode_config.max_height = 2048;
+
+ dev->mode_config.prefer_shadow = 0;
+ dev->mode_config.preferred_depth = 24;
+
+ dev->mode_config.funcs = &gm12u320_mode_funcs;
+
+ drm_mode_create_dirty_info_property(dev);
+
+ gm12u320_crtc_init(dev);
+
+ encoder = gm12u320_encoder_init(dev);
+
+ gm12u320_connector_init(dev, encoder);
+
+ return 0;
+}
+
+void gm12u320_modeset_cleanup(struct drm_device *dev)
+{
+ drm_mode_config_cleanup(dev);
+}